Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 0 additions & 44 deletions .github/workflows/blank.yml

This file was deleted.

115 changes: 115 additions & 0 deletions .github/workflows/build-test-scan-push-images.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
name: Build, test, scan and push images

on:
push:
branches:
- develop

pull_request:
branches:
- develop
- main

workflow_dispatch:
env:
REGISTRY: ghcr.io
NAMESPACE: austriandatalab
SUB_NAMESPACE: indiegamestream
jobs:
build-push-image:
strategy:
fail-fast: false
matrix:
image:
# Add your images here (name of the component + directory that contains Dockerfile)
- name: frontend
directory: frontend
runs-on: ubuntu-latest
permissions:
contents: write
packages: write
security-events: write # for github/codeql-action/upload-sarif to upload SARIF results

name: ${{ matrix.image.name }}

steps:
- uses: actions/checkout@v4

- name: Check if test stage exists
run: |
if grep -q -i -E '^\s*FROM\s+[^\s]+\s+AS\s+test\s*$' ${{ matrix.image.directory }}/Dockerfile ; then
echo "Test stage exists, continuing with tests."
echo "test_stage_exists=true" >> "$GITHUB_OUTPUT"
else
echo "Test stage does not exist, skipping tests."
echo "test_stage_exists=false" >> "$GITHUB_OUTPUT"
fi
id: check_test_stage

- name: Build Docker image to test stage
if: steps.check_test_stage.outputs.test_stage_exists == 'true'
uses: docker/build-push-action@3b5e8027fcad23fda98b2e3ac259d8d67585f671
with:
context: ${{ matrix.image.directory }}
file: ${{ matrix.image.directory }}/Dockerfile
push: false
tags: ${{ env.REGISTRY }}/${{ env.NAMESPACE }}/${{ env.SUB_NAMESPACE }}/${{ matrix.image.name }}-test:${{ github.sha }}
target: test

- name: Run test stage
if: steps.check_test_stage.outputs.test_stage_exists == 'true'
run: |
docker run --rm ${{ env.REGISTRY }}/${{ env.NAMESPACE }}/${{ env.SUB_NAMESPACE }}/${{ matrix.image.name }}-test:${{ github.sha }}

- name: Login to the Container registry
uses: docker/login-action@f054a8b539a109f9f41c372932f1ae047eff08c9
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@98669ae865ea3cffbcbaa878cf57c20bbf1c6c38
with:
images: ${{ env.REGISTRY }}/${{ env.NAMESPACE }}/${{ env.SUB_NAMESPACE }}/${{ matrix.image.name }}
tags: |
type=ref,event=branch
type=ref,event=tag
type=ref,event=pr
type=match,pattern=\d.\d.\d.*
type=sha

- name: Build Docker image
uses: docker/build-push-action@3b5e8027fcad23fda98b2e3ac259d8d67585f671
with:
context: ${{ matrix.image.directory }}
file: ${{ matrix.image.directory }}/Dockerfile
push: false
tags: ${{ env.REGISTRY }}/${{ env.NAMESPACE }}/${{ env.SUB_NAMESPACE }}/${{ matrix.image.name }}:${{ github.sha }}
labels: ${{ steps.meta.outputs.labels }}

- name: Run Trivy vulnerability scanner
uses: aquasecurity/trivy-action@master
with:
image-ref: '${{ env.REGISTRY }}/${{ env.NAMESPACE }}/${{ env.SUB_NAMESPACE }}/${{ matrix.image.name }}:${{ github.sha }}'
format: 'sarif'
output: 'trivy-results.sarif'
exit-code: '1'
severity: 'CRITICAL,HIGH'

- name: Push Docker image
uses: docker/build-push-action@3b5e8027fcad23fda98b2e3ac259d8d67585f671
with:
context: ${{ matrix.image.directory }}
file: ${{ matrix.image.directory }}/Dockerfile
push: true
tags: ${{ env.REGISTRY }}/${{ env.NAMESPACE }}/${{ env.SUB_NAMESPACE }}/${{ matrix.image.name }}:${{ github.sha }}
labels: ${{ steps.meta.outputs.labels }}

- name: Upload Trivy scan results to GitHub Security tab
if: always()
uses: github/codeql-action/upload-sarif@v2
with:
sarif_file: 'trivy-results.sarif'
category: ${{ matrix.image.name }}
3 changes: 3 additions & 0 deletions api/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
config.yml
README.md
.idea
7 changes: 3 additions & 4 deletions api/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,12 @@ WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download

# Copy the source code. Note the slash at the end, as explained in
# https://docs.docker.com/reference/dockerfile/#copy
COPY *.go */*.go /*/*/*.go /*/*/*/*.go ./
# Add the directories which contain the golang scripts
COPY . .
COPY config.prod.yml ./config.yml

# Build
RUN CGO_ENABLED=0 GOOS=linux go build -o /api
RUN CGO_ENABLED=0 GOOS=linux go build -C cmd -o /api

# Optional:
# To bind to a TCP port, runtime parameters must be supplied to the docker command.
Expand Down
66 changes: 17 additions & 49 deletions api/cmd/main.go
Original file line number Diff line number Diff line change
@@ -1,70 +1,38 @@
package main

import (
"api/controllers"
"api/repositories"
"api/services"
"fmt"
"github.com/gin-gonic/gin"
"github.com/spf13/viper"
"log"
"net/http"
)

var db = make(map[string]string)

func setupRouter() *gin.Engine {
// Disable Console Color
// gin.DisableConsoleColor()
//Setup Gin
r := gin.Default()

//Setup Repositories
gamesRepository := repositories.GameRepository()
gamesService := services.GameService(gamesRepository)
gamesController := controllers.GameController(gamesService)

// Ping test
r.GET("/ping", func(c *gin.Context) {
c.String(http.StatusOK, "pong")
})

// Get user value
r.GET("/user/:name", func(c *gin.Context) {
user := c.Params.ByName("name")
value, ok := db[user]
if ok {
c.JSON(http.StatusOK, gin.H{"user": user, "value": value})
} else {
c.JSON(http.StatusOK, gin.H{"user": user, "status": "no value"})
}
})

// Authorized group (uses gin.BasicAuth() middleware)
// Same than:
// authorized := r.Group("/")
// authorized.Use(gin.BasicAuth(gin.Credentials{
// "foo": "bar",
// "manu": "123",
//}))
authorized := r.Group("/", gin.BasicAuth(gin.Accounts{
"foo": "bar", // user:foo password:bar
"manu": "123", // user:manu password:123
}))

/* example curl for /admin with basicauth header
Zm9vOmJhcg== is base64("foo:bar")

curl -X POST \
http://localhost:8080/admin \
-H 'authorization: Basic Zm9vOmJhcg==' \
-H 'content-type: application/json' \
-d '{"value":"bar"}'
*/
authorized.POST("admin", func(c *gin.Context) {
user := c.MustGet(gin.AuthUserKey).(string)

// Parse JSON
var json struct {
Value string `json:"value" binding:"required"`
}

if c.Bind(&json) == nil {
db[user] = json.Value
c.JSON(http.StatusOK, gin.H{"status": "ok"})
}
})
//Upload a game
r.POST("/games/", gamesController.UploadGame)
//Get all uploaded games
r.GET("/games", gamesController.GetAllGames)
//Get a specific game by its id
r.GET("/games/:id", gamesController.GetGameById)
//Delete a specific game, identified by its id
r.DELETE("/games/:id", gamesController.DeleteGameById)

return r
}
Expand Down
114 changes: 114 additions & 0 deletions api/controllers/gameController.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
package controllers

import (
"api/dtos"
"api/services"
"fmt"
"github.com/dranikpg/dto-mapper"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
"net/http"
)

type IGameController interface {
GetAllGames(c *gin.Context)
GetGameById(c *gin.Context)
UploadGame(c *gin.Context)
DeleteGameById(c *gin.Context)
}

type gameController struct {
service services.IGameService
}

func (g gameController) GetAllGames(c *gin.Context) {
//Get Games
games, err := g.service.FindAll()
if err != nil { //TODO handle different errors
c.JSON(http.StatusInternalServerError, gin.H{"message": err.Error()})
return
}

//Map to dto
resultDto := []dtos.GetGameByIdResponseBody{}
err = dto.Map(&resultDto, games)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"message": err.Error()})
return
}

c.IndentedJSON(http.StatusOK, resultDto)
}

func (g gameController) GetGameById(c *gin.Context) {
_uuid := getUUIDFromRequest(c)
if _uuid != uuid.Nil {
//Get game by uuid
game, err := g.service.FindByID(_uuid)
if err != nil { //TODO handle different errors
c.JSON(http.StatusInternalServerError, gin.H{"message": err.Error()})
return
}

//Map to dto
resultDto := dtos.GetGameByIdResponseBody{}
err = dto.Map(&resultDto, game)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"message": err.Error()})
return
}

c.IndentedJSON(http.StatusOK, resultDto)
}
}

func (g gameController) UploadGame(c *gin.Context) {
file, err := c.FormFile("file")
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"message": err.Error()})
}

_, err = g.service.Save(file)

if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"message": err.Error()})
}

c.Header("content-location", fmt.Sprintf("%s/games/%s", c.Request.Host, file.Filename))
c.String(http.StatusCreated, "")
}

func (g gameController) DeleteGameById(c *gin.Context) {
_uuid := getUUIDFromRequest(c)
if _uuid != uuid.Nil {
err := g.service.Delete(_uuid)
if err != nil { //TODO handle different errors
c.JSON(http.StatusInternalServerError, gin.H{"message": err.Error()})
} else {
c.Status(http.StatusNoContent)
}
}

//TODO Implement delete game
c.String(http.StatusNoContent, "")
}

func GameController(service services.IGameService) IGameController {
return &gameController{
service: service,
}
}

// Parses the UUID from the request param "uuid" and returns it.
// It returns HTTP 400 and uuid.nil if the uuid is invalid or null
func getUUIDFromRequest(c *gin.Context) uuid.UUID {
_uuid, err := uuid.Parse(c.Param("id"))
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"message": "Invalid game ID"})
return uuid.Nil
} else if _uuid == uuid.Nil {
c.JSON(http.StatusBadRequest, gin.H{"message": "Invalid game ID"})
return uuid.Nil
}
return _uuid
}
Loading