commit 860135aff18b34a999678b033896e7c9f9e2a5f9 Author: Timo Riegebauer Date: Tue Apr 22 09:08:17 2025 +0000 first commit diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000..3495528 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,22 @@ +// For format details, see https://aka.ms/devcontainer.json. For config options, see the +// README at: https://github.com/devcontainers/templates/tree/main/src/go +{ + "name": "Go", + // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile + "image": "mcr.microsoft.com/devcontainers/go:1-1.23-bookworm" + + // Features to add to the dev container. More info: https://containers.dev/features. + // "features": {}, + + // Use 'forwardPorts' to make a list of ports inside the container available locally. + // "forwardPorts": [], + + // Use 'postCreateCommand' to run commands after the container is created. + // "postCreateCommand": "go version", + + // Configure tool-specific properties. + // "customizations": {}, + + // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root. + // "remoteUser": "root" +} diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..41fa9b1 --- /dev/null +++ b/.env.example @@ -0,0 +1,14 @@ +# Application Configuration +APP_HOST="localhost" +APP_PORT="8123" + +# Database Configuration +DB_HOST="" +DB_PORT="" +DB_USERNAME="" +DB_PASSWORD="" +DB_DATABASE="" + +# JWT Authentication +JWT_SECRET="abc123" +JWT_EXPIRATION="3600" diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..f33a02c --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,12 @@ +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for more information: +# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates +# https://containers.dev/guide/dependabot + +version: 2 +updates: + - package-ecosystem: "devcontainers" + directory: "/" + schedule: + interval: weekly diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a39210b --- /dev/null +++ b/.gitignore @@ -0,0 +1,32 @@ +# Go-specific +*.exe +*.exe~ +*.dll +*.so +*.dylib +*.test +*.out +bin/ +build/ + +# Docker +docker-compose.override.yml +Dockerfile.* +*.log + +# IDE specific +.vscode/ + +# Build artifacts +*.tgz +*.tar.gz + +# Logs +*.log + +# MacOS specific +.DS_Store + +# Environment Files +.env* +!.env.example \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..0f12bb2 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,13 @@ +FROM golang:1.24-alpine AS builder +RUN apk add --no-cache make +WORKDIR /app +COPY . . +RUN make build + +FROM alpine:latest +RUN apk add --no-cache gcompat +WORKDIR /app +COPY --from=builder /app/build/server . +COPY .env .env +EXPOSE 8080 +CMD ["./server"] diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..0c300fa --- /dev/null +++ b/Makefile @@ -0,0 +1,7 @@ +default: build run + +build: + @CGO_ENABLED=0 GOOS=linux go build -o build/server main.go + +run: build + @./build/server diff --git a/api/controller/.gitkeep b/api/controller/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/api/handler/.gitkeep b/api/handler/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/api/middleware/.gitkeep b/api/middleware/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/api/model/.gitkeep b/api/model/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/app/app.go b/app/app.go new file mode 100644 index 0000000..991d659 --- /dev/null +++ b/app/app.go @@ -0,0 +1,55 @@ +package app + +import ( + "fmt" + "log" + "os" + + "backend/router" + + "git.trcreatives.com/triegebauer/go-dart" + "github.com/joho/godotenv" +) + +type App struct { + d *dart.Dart +} + +func New() *App { + if err := godotenv.Load(); err != nil { + log.Fatal(err) + } + + d := dart.New() + d.WithMySQLDatabase(dart.MySQLConfig{ + Host: os.Getenv("DB_HOST"), + Port: os.Getenv("DB_PORT"), + Username: os.Getenv("DB_USERNAME"), + Password: os.Getenv("DB_PASSWORD"), + Database: os.Getenv("DB_DATABASE"), + }) + + router.SetupRoutes(d) + + return &App{ + d, + } +} + +func (a *App) Start() { + listenAddr := fmt.Sprintf("%s:%s", os.Getenv("APP_HOST"), os.Getenv("APP_PORT")) + log.Fatal(a.d.Start(listenAddr)) +} + +func (a *App) Migrate(dst ...interface{}) error { + db, err := a.d.DB() + if err != nil { + return err + } + + if err := db.Conn.AutoMigrate(dst); err != nil { + return err + } + + return nil +} diff --git a/cloud_functions/.gitkeep b/cloud_functions/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..d97334a --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,6 @@ +services: + backend: + build: . + ports: + - "${APP_PORT}:${APP_PORT}" + restart: unless-stopped diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..552c141 --- /dev/null +++ b/go.mod @@ -0,0 +1,33 @@ +module backend + +go 1.23.8 + +require ( + git.trcreatives.com/triegebauer/go-dart v1.0.0 + github.com/golang-jwt/jwt/v5 v5.2.2 + github.com/joho/godotenv v1.5.1 +) + +require ( + filippo.io/edwards25519 v1.1.0 // indirect + github.com/go-sql-driver/mysql v1.9.2 // indirect + github.com/gorilla/schema v1.4.1 // indirect + github.com/gorilla/websocket v1.5.3 // indirect + github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect + github.com/jackc/pgx/v5 v5.5.5 // indirect + github.com/jackc/puddle/v2 v2.2.1 // indirect + github.com/jinzhu/inflection v1.0.0 // indirect + github.com/jinzhu/now v1.1.5 // indirect + github.com/julienschmidt/httprouter v1.3.0 // indirect + github.com/mattn/go-sqlite3 v1.14.22 // indirect + github.com/robfig/cron/v3 v3.0.1 // indirect + github.com/stretchr/testify v1.9.0 // indirect + golang.org/x/crypto v0.31.0 // indirect + golang.org/x/sync v0.13.0 // indirect + golang.org/x/text v0.24.0 // indirect + gorm.io/driver/mysql v1.5.7 // indirect + gorm.io/driver/postgres v1.5.11 // indirect + gorm.io/driver/sqlite v1.5.7 // indirect + gorm.io/gorm v1.25.12 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..181961a --- /dev/null +++ b/go.sum @@ -0,0 +1,62 @@ +filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= +filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= +git.trcreatives.com/triegebauer/go-dart v1.0.0 h1:PurYYjuh1sJyO26+7TIhuTIjan0c0Xnxua7GMuOEPDU= +git.trcreatives.com/triegebauer/go-dart v1.0.0/go.mod h1:TX7rQjm034UutxlGkIcSeVPtD2BHwvVqpfr2cA2d4hE= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= +github.com/go-sql-driver/mysql v1.9.2 h1:4cNKDYQ1I84SXslGddlsrMhc8k4LeDVj6Ad6WRjiHuU= +github.com/go-sql-driver/mysql v1.9.2/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU= +github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8= +github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= +github.com/gorilla/schema v1.4.1 h1:jUg5hUjCSDZpNGLuXQOgIWGdlgrIdYvgQ0wZtdK1M3E= +github.com/gorilla/schema v1.4.1/go.mod h1:Dg5SSm5PV60mhF2NFaTV1xuYYj8tV8NOPRo4FggUMnM= +github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= +github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk= +github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgx/v5 v5.5.5 h1:amBjrZVmksIdNjxGW/IiIMzxMKZFelXbUoPNb+8sjQw= +github.com/jackc/pgx/v5 v5.5.5/go.mod h1:ez9gk+OAat140fv9ErkZDYFWmXLfV+++K0uAOiwgm1A= +github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk= +github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= +github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= +github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= +github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U= +github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= +github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= +github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= +github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= +golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= +golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610= +golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0= +golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gorm.io/driver/mysql v1.5.7 h1:MndhOPYOfEp2rHKgkZIhJ16eVUIRf2HmzgoPmh7FCWo= +gorm.io/driver/mysql v1.5.7/go.mod h1:sEtPWMiqiN1N1cMXoXmBbd8C6/l+TESwriotuRRpkDM= +gorm.io/driver/postgres v1.5.11 h1:ubBVAfbKEUld/twyKZ0IYn9rSQh448EdelLYk9Mv314= +gorm.io/driver/postgres v1.5.11/go.mod h1:DX3GReXH+3FPWGrrgffdvCk3DQ1dwDPdmbenSkweRGI= +gorm.io/driver/sqlite v1.5.7 h1:8NvsrhP0ifM7LX9G4zPB97NwovUakUxc+2V2uuf3Z1I= +gorm.io/driver/sqlite v1.5.7/go.mod h1:U+J8craQU6Fzkcvu8oLeAQmi50TkwPEhHDEjQZXDah4= +gorm.io/gorm v1.25.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= +gorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8= +gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ= diff --git a/helper/token.go b/helper/token.go new file mode 100644 index 0000000..d75d614 --- /dev/null +++ b/helper/token.go @@ -0,0 +1,65 @@ +package helper + +import ( + "errors" + "os" + "time" + + "github.com/golang-jwt/jwt/v5" +) + +var ( + secretKey = []byte(os.Getenv("JWT_SECRET")) + jwtExpiration = os.Getenv("JWT_EXPIRATION") +) + +func GenerateToken(userId string) (string, error) { + if len(secretKey) == 0 { + return "", errors.New("JWT_SECRET is not set") + } + + if jwtExpiration == "" { + return "", errors.New("JWT_EXPIRATION is not set") + } + + expiration, err := time.ParseDuration(jwtExpiration + "s") + if err != nil { + return "", errors.New("invalid JWT_EXPIRATION value") + } + + claims := jwt.MapClaims{ + "userId": userId, + "exp": time.Now().Add(expiration).Unix(), + } + + token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) + + signedToken, err := token.SignedString(secretKey) + if err != nil { + return "", err + } + + return signedToken, nil +} + +func ValidateToken(tokenString string) (jwt.MapClaims, error) { + if len(secretKey) == 0 { + return nil, errors.New("JWT_SECRET is not set") + } + + token, err := jwt.Parse(tokenString, func(t *jwt.Token) (interface{}, error) { + if _, ok := t.Method.(*jwt.SigningMethodHMAC); !ok { + return nil, errors.New("unexpected signing method") + } + return secretKey, nil + }) + if err != nil { + return nil, err + } + + if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid { + return claims, nil + } + + return nil, errors.New("invalid token") +} diff --git a/main.go b/main.go new file mode 100644 index 0000000..37bce61 --- /dev/null +++ b/main.go @@ -0,0 +1,16 @@ +package main + +import ( + "backend/app" + "log" +) + +func main() { + a := app.New() + + if err := a.Migrate(); err != nil { + log.Fatal(err) + } + + a.Start() +} diff --git a/router/router.go b/router/router.go new file mode 100644 index 0000000..50daae3 --- /dev/null +++ b/router/router.go @@ -0,0 +1,13 @@ +package router + +import ( + "net/http" + + "git.trcreatives.com/triegebauer/go-dart" +) + +func SetupRoutes(b *dart.Dart) { + b.GET("/", func(ctx *dart.Context) error { + return ctx.Text(http.StatusOK, "Hello, Dart!") + }) +}