From f0f1819a1f57d2e42877ada510eb05e165a4174f Mon Sep 17 00:00:00 2001 From: Thomas Peetz Date: Fri, 25 Sep 2026 16:57:23 +0200 Subject: [PATCH] umstellung auf Service und Repository Verwendung - WIP --- kontor-gin/cmd/kontor-cli/main.go | 8 + kontor-gin/cmd/kontor-gin/main.go | 7 +- kontor-gin/cmd/kontor/main.go | 125 +++++++-------- kontor-gin/go.mod | 21 +-- kontor-gin/go.sum | 45 +++--- .../globals => internal/config}/config.go | 2 +- kontor-gin/internal/config/database.go | 35 ++++ kontor-gin/internal/globals/config.go | 40 +++++ .../{pkg => internal}/globals/globals.go | 0 kontor-gin/internal/handlers/auth.go | 16 ++ kontor-gin/internal/handlers/comic.go | 21 +++ kontor-gin/{pkg => internal}/helpers/auth.go | 0 kontor-gin/internal/helpers/token.go | 90 +++++++++++ kontor-gin/internal/middleware/jwt.go | 35 ++++ kontor-gin/internal/middleware/ratelimit.go | 17 ++ kontor-gin/internal/middleware/request_id.go | 18 +++ kontor-gin/internal/models/comics/artist.go | 21 +++ kontor-gin/internal/models/comics/comic.go | 38 +++++ .../internal/models/comics/comicwork.go | 21 +++ kontor-gin/internal/models/comics/issue.go | 26 +++ .../internal/models/comics/issuework.go | 21 +++ .../internal/models/comics/publisher.go | 26 +++ kontor-gin/internal/models/comics/storyarc.go | 20 +++ .../internal/models/comics/tradepaperback.go | 20 +++ kontor-gin/internal/models/comics/volume.go | 18 +++ kontor-gin/internal/models/comics/worktype.go | 16 ++ kontor-gin/internal/repository/comic.go | 12 ++ kontor-gin/internal/repository/profile.go | 14 ++ kontor-gin/internal/router/router.go | 49 ++++++ kontor-gin/internal/schema/admin.go | 121 ++++++++++++++ kontor-gin/{pkg => internal}/schema/base.go | 0 .../{pkg => internal}/schema/database.go | 7 +- kontor-gin/internal/schema/media.go | 107 +++++++++++++ kontor-gin/internal/service/auth.go | 12 ++ kontor-gin/internal/service/comic.go | 20 +++ kontor-gin/pkg/controllers/auth.go | 29 ++++ kontor-gin/pkg/controllers/health.go | 2 + kontor-gin/pkg/controllers/media.go | 5 +- .../{authRequired.go => middlewares.go} | 15 +- kontor-gin/pkg/routes/api.go | 6 +- kontor-gin/pkg/schema/comic.go | 151 ------------------ kontor-gin/pkg/schema/media.go | 92 ----------- 42 files changed, 998 insertions(+), 351 deletions(-) create mode 100644 kontor-gin/cmd/kontor-cli/main.go rename kontor-gin/{pkg/globals => internal/config}/config.go (97%) create mode 100644 kontor-gin/internal/config/database.go create mode 100644 kontor-gin/internal/globals/config.go rename kontor-gin/{pkg => internal}/globals/globals.go (100%) create mode 100644 kontor-gin/internal/handlers/auth.go create mode 100644 kontor-gin/internal/handlers/comic.go rename kontor-gin/{pkg => internal}/helpers/auth.go (100%) create mode 100644 kontor-gin/internal/helpers/token.go create mode 100644 kontor-gin/internal/middleware/jwt.go create mode 100644 kontor-gin/internal/middleware/ratelimit.go create mode 100644 kontor-gin/internal/middleware/request_id.go create mode 100644 kontor-gin/internal/models/comics/artist.go create mode 100644 kontor-gin/internal/models/comics/comic.go create mode 100644 kontor-gin/internal/models/comics/comicwork.go create mode 100644 kontor-gin/internal/models/comics/issue.go create mode 100644 kontor-gin/internal/models/comics/issuework.go create mode 100644 kontor-gin/internal/models/comics/publisher.go create mode 100644 kontor-gin/internal/models/comics/storyarc.go create mode 100644 kontor-gin/internal/models/comics/tradepaperback.go create mode 100644 kontor-gin/internal/models/comics/volume.go create mode 100644 kontor-gin/internal/models/comics/worktype.go create mode 100644 kontor-gin/internal/repository/comic.go create mode 100644 kontor-gin/internal/repository/profile.go create mode 100644 kontor-gin/internal/router/router.go create mode 100644 kontor-gin/internal/schema/admin.go rename kontor-gin/{pkg => internal}/schema/base.go (100%) rename kontor-gin/{pkg => internal}/schema/database.go (95%) create mode 100644 kontor-gin/internal/schema/media.go create mode 100644 kontor-gin/internal/service/auth.go create mode 100644 kontor-gin/internal/service/comic.go rename kontor-gin/pkg/middleware/{authRequired.go => middlewares.go} (60%) delete mode 100644 kontor-gin/pkg/schema/comic.go delete mode 100644 kontor-gin/pkg/schema/media.go diff --git a/kontor-gin/cmd/kontor-cli/main.go b/kontor-gin/cmd/kontor-cli/main.go new file mode 100644 index 0000000..31bad7c --- /dev/null +++ b/kontor-gin/cmd/kontor-cli/main.go @@ -0,0 +1,8 @@ +package main + +import "log/slog" + +func main() { + slog.Info("Kontor started") + slog.Info("Kontor finished") +} diff --git a/kontor-gin/cmd/kontor-gin/main.go b/kontor-gin/cmd/kontor-gin/main.go index 67dfdfe..03a5fc3 100644 --- a/kontor-gin/cmd/kontor-gin/main.go +++ b/kontor-gin/cmd/kontor-gin/main.go @@ -3,10 +3,10 @@ package main import ( // "kontor-api-gin/pkg/handler" - "kontor-api-gin/pkg/globals" + "kontor-api-gin/internal/globals" + "kontor-api-gin/internal/schema" "kontor-api-gin/pkg/middleware" "kontor-api-gin/pkg/routes" - "kontor-api-gin/pkg/schema" "log" "log/slog" "os" @@ -27,6 +27,8 @@ func main() { viper.SetDefault("db.user", "kontor") viper.SetDefault("db.password", "kontor") viper.SetDefault("port", 8550) + viper.SetDefault("token_hour_lifespan", 1) + viper.SetDefault("api_secret", "secret") if err := globals.InitConfig(); err != nil { slog.Info("Failed to initialize config", "error", err) @@ -66,6 +68,7 @@ func main() { // Setup api routes api := routes.SetupApiRoutes(router) api.Use(middleware.CorsMiddleware) + api.Use(middleware.JwtRequired) routes.SetupMediaRoutes(api) routes.SetupComicRoutes(api) diff --git a/kontor-gin/cmd/kontor/main.go b/kontor-gin/cmd/kontor/main.go index 9e85c16..6e2a57c 100644 --- a/kontor-gin/cmd/kontor/main.go +++ b/kontor-gin/cmd/kontor/main.go @@ -1,23 +1,23 @@ package main import ( - // "kontor-api-gin/pkg/handler" - - "html/template" - "kontor-api-gin/assets" - "kontor-api-gin/pkg/globals" - "kontor-api-gin/pkg/middleware" - "kontor-api-gin/pkg/routes" - "kontor-api-gin/pkg/schema" - "kontor-api-gin/templates" + "context" + "fmt" + "kontor-api-gin/internal/config" + "kontor-api-gin/internal/globals" + "kontor-api-gin/internal/handlers" + "kontor-api-gin/internal/repository" + "kontor-api-gin/internal/router" + "kontor-api-gin/internal/service" "log" "log/slog" "net/http" + "os" + "os/signal" "strconv" + "syscall" + "time" - "github.com/gin-contrib/sessions" - "github.com/gin-contrib/sessions/cookie" - gin_slog "github.com/gin-contrib/slog" "github.com/gin-gonic/gin" "github.com/spf13/viper" ) @@ -25,70 +25,67 @@ import ( func main() { slog.Info("Kontor started") - viper.SetDefault("db.host", "localhost") - viper.SetDefault("db.port", 5432) - viper.SetDefault("db.name", "kontor") - viper.SetDefault("db.user", "kontor") - viper.SetDefault("db.password", "kontor") - viper.SetDefault("port", 8550) - viper.SetDefault("gin.mode", "release") - if err := globals.InitConfig(); err != nil { log.Fatalf("Failed to initialize config: %v", err) } + dsn := fmt.Sprintf( + "host=%s user=%s password=%s dbname=%s port=%d sslmode=disable", + viper.GetString("db.host"), + viper.GetString("db.user"), + viper.GetString("db.password"), + viper.GetString("db.name"), + viper.GetInt("db.port"), + ) + + db, err := config.NewDB(dsn) + if err != nil { + log.Fatal(err) + } + if err := db.AutoMigrate(); err != nil { + log.Fatal(err) + } + + comicRepo := repository.NewComicRepository(db) + profileRepo := repository.NewProfileRepository(db) + comicService := service.NewComicService(comicRepo) + authService := service.NewAuthService(profileRepo, []byte(viper.GetString("api_secret"))) + // Set Gin mode based on environment // Production mode disables debug output if viper.GetString("gin.mode") == "release" { gin.SetMode(gin.ReleaseMode) } - // Connect to database - schema.Connect() + r := router.New( + &handlers.ComicHandler{Service: comicService}, + &handlers.AuthHandler{Service: authService}, + []byte(viper.GetString("api_secret")), + ) + // router.Use(sessions.Sessions("session", cookie.NewStore(globals.Secret))) - // Run migrations - schema.RunMigrations() - - // Create Gin router with default middleware - // Default includes Logger and Recovery middleware - // router := gin.Default() - // Create a new router without default mmiddleware - router := gin.New() - // Add recovery middleware - router.Use(gin.Recovery()) - router.Use(gin_slog.SetLogger( - gin_slog.WithSkipPath([]string{"/health", "/public/style/main.css"}), - gin_slog.WithDefaultLevel(slog.LevelInfo), - gin_slog.WithClientErrorLevel(slog.LevelInfo), - gin_slog.WithServerErrorLevel(slog.LevelInfo), - )) - - templ := template.Must(template.New("").ParseFS(templates.TemplateFiles, "layouts/*.html", "pages/*.html")) - router.SetHTMLTemplate(templ) - - router.StaticFS("/public", http.FS(assets.StaticFiles)) - - router.Use(sessions.Sessions("session", cookie.NewStore(globals.Secret))) - - // Setup api routes - api := routes.SetupApiRoutes(router) - api.Use(middleware.CorsMiddleware) - routes.SetupMediaRoutes(api) - routes.SetupComicRoutes(api) - - // Setup Webapp routes - routes.AuthRoutes(router) - routes.ComicRoutes(router) - routes.MediaRoutes(router) - routes.PublicRoutes(router) - - // Start server - port := viper.GetInt("port") - - slog.Info("Server starting", "port", port) - if err := router.Run(":" + strconv.Itoa(port)); err != nil { - log.Fatal("Failed to start server:", err) + srv := &http.Server{ + Addr: ":" + strconv.Itoa(viper.GetInt("port")), + Handler: r, + ReadHeaderTimeout: 5 * time.Second, + WriteTimeout: 30 * time.Second, + IdleTimeout: 120 * time.Second, } + go func() { + if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed { + log.Fatal(err) + } + }() + slog.Info("server up", "address", srv.Addr) + + quit := make(chan os.Signal, 1) + signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) + <-quit + + ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second) + defer cancel() + _ = srv.Shutdown(ctx) + slog.Info("server stopped") slog.Info("Kontor finished") } diff --git a/kontor-gin/go.mod b/kontor-gin/go.mod index 89efd01..dd392b6 100644 --- a/kontor-gin/go.mod +++ b/kontor-gin/go.mod @@ -3,10 +3,13 @@ module kontor-api-gin go 1.26.8 require ( - github.com/gin-contrib/sessions v1.1.2 - github.com/gin-contrib/slog v1.2.3 - github.com/gin-gonic/gin v1.12.0 + github.com/gin-contrib/sessions v1.0.2 + github.com/gin-contrib/slog v1.1.0 + github.com/gin-gonic/gin v1.10.1 + github.com/golang-jwt/jwt/v5 v5.3.1 github.com/google/uuid v1.6.0 + github.com/spf13/viper v1.21.0 + golang.org/x/crypto v0.57.0 gorm.io/driver/postgres v1.6.3 gorm.io/gorm v1.31.2 ) @@ -18,13 +21,13 @@ require ( github.com/cloudwego/base64x v0.1.7 // indirect github.com/fsnotify/fsnotify v1.9.0 // indirect github.com/gabriel-vasile/mimetype v1.4.15 // indirect + github.com/gin-contrib/cors v1.7.2 // indirect github.com/gin-contrib/sse v1.1.2 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect - github.com/go-playground/validator/v10 v10.30.4 // indirect + github.com/go-playground/validator/v10 v10.22.0 // indirect github.com/go-viper/mapstructure/v2 v2.4.0 // indirect github.com/goccy/go-json v0.10.6 // indirect - github.com/goccy/go-yaml v1.19.2 // indirect github.com/gorilla/context v1.1.2 // indirect github.com/gorilla/securecookie v1.1.2 // indirect github.com/gorilla/sessions v1.4.0 // indirect @@ -41,24 +44,22 @@ require ( github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/pelletier/go-toml/v2 v2.4.3 // indirect - github.com/quic-go/qpack v0.6.0 // indirect - github.com/quic-go/quic-go v0.62.0 // indirect github.com/sagikazarmark/locafero v0.11.0 // indirect github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect github.com/spf13/afero v1.15.0 // indirect github.com/spf13/cast v1.10.0 // indirect github.com/spf13/pflag v1.0.10 // indirect - github.com/spf13/viper v1.21.0 // indirect + github.com/stretchr/testify v1.12.1 // indirect github.com/subosito/gotenv v1.6.0 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/ugorji/go/codec v1.3.2 // indirect - go.mongodb.org/mongo-driver/v2 v2.9.1 // indirect go.yaml.in/yaml/v3 v3.0.5 // indirect golang.org/x/arch v0.31.0 // indirect - golang.org/x/crypto v0.57.0 // indirect golang.org/x/net v0.59.0 // indirect golang.org/x/sync v0.23.0 // indirect golang.org/x/sys v0.48.0 // indirect golang.org/x/text v0.42.0 // indirect + golang.org/x/time v0.16.0 // indirect google.golang.org/protobuf v1.36.12 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/kontor-gin/go.sum b/kontor-gin/go.sum index d017dca..2ad8593 100644 --- a/kontor-gin/go.sum +++ b/kontor-gin/go.sum @@ -9,32 +9,36 @@ github.com/cloudwego/base64x v0.1.7/go.mod h1:Cu1PV9zfrSf7ET2tIbWbbEy7jO7HHJ13q4 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/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= +github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= github.com/gabriel-vasile/mimetype v1.4.15 h1:05iP/CYtZ/w455R/KZM6rZ5ieAdh99UPtd+d3YzLmaI= github.com/gabriel-vasile/mimetype v1.4.15/go.mod h1:azpTcoLcDZRNgFou5j+APrqQx9HqVPWa6ijYQIIVswQ= -github.com/gin-contrib/sessions v1.1.2 h1:utWRqOHT/kbqxxqF/mtutV9wfGLi1+uu5YuvWUhj/Ow= -github.com/gin-contrib/sessions v1.1.2/go.mod h1:qRxYwA/TYJw0wv28oFzs4gY/8G8z69id862SNgX9Gmw= -github.com/gin-contrib/slog v1.2.3 h1:lTTPqZENURDHFXs9V1rfYWo3zJteryClDZJAppMovzE= -github.com/gin-contrib/slog v1.2.3/go.mod h1:kHUAHrvyd65WrT2p5IykUe4dH51enJoClwagGgAGqAw= +github.com/gin-contrib/cors v1.7.2 h1:oLDHxdg8W/XDoN/8zamqk/Drgt4oVZDvaV0YmvVICQw= +github.com/gin-contrib/cors v1.7.2/go.mod h1:SUJVARKgQ40dmrzgXEVxj2m7Ig1v1qIboQkPDTQ9t2E= +github.com/gin-contrib/sessions v1.0.2 h1:UaIjUvTH1cMeOdj3in6dl+Xb6It8RiKRF9Z1anbUyCA= +github.com/gin-contrib/sessions v1.0.2/go.mod h1:KxKxWqWP5LJVDCInulOl4WbLzK2KSPlLesfZ66wRvMs= +github.com/gin-contrib/slog v1.1.0 h1:K9MVNrETT6r/C3u2Aheer/gxwVeVqrGL0hXlsmv3fm4= +github.com/gin-contrib/slog v1.1.0/go.mod h1:PvNXQVXcVOAaaiJR84LV1/xlQHIaXi9ygEXyBkmjdkY= github.com/gin-contrib/sse v1.1.2 h1:MU2fgl1RrdYTMcgJLtz2kJF+vPg3xrqaaKfUUU18tCo= github.com/gin-contrib/sse v1.1.2/go.mod h1:QXzuVkA0YO7o/gun03UI1Q+FTI8ZV/n5t03kIQAI89s= -github.com/gin-gonic/gin v1.12.0 h1:b3YAbrZtnf8N//yjKeU2+MQsh2mY5htkZidOM7O0wG8= -github.com/gin-gonic/gin v1.12.0/go.mod h1:VxccKfsSllpKshkBWgVgRniFFAzFb9csfngsqANjnLc= +github.com/gin-gonic/gin v1.10.1 h1:T0ujvqyCSqRopADpgPgiTT63DUQVSfojyME59Ei63pQ= +github.com/gin-gonic/gin v1.10.1/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= -github.com/go-playground/validator/v10 v10.30.4 h1:9Rcod2ZPO6mOEG6b4GqyoHE/H6//Ze0RuhOo1hT1x0w= -github.com/go-playground/validator/v10 v10.30.4/go.mod h1:numpT+RPLE91R9oYWMY/R9zRgJBewr3IXHko4OISPpk= +github.com/go-playground/validator/v10 v10.22.0 h1:k6HsTZ0sTnROkhS//R0O+55JgM8C4Bx7ia+JlgcnOao= +github.com/go-playground/validator/v10 v10.22.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs= github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/goccy/go-json v0.10.6 h1:p8HrPJzOakx/mn/bQtjgNjdTcN+/S6FcG2CTtQOrHVU= github.com/goccy/go-json v0.10.6/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= -github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM= -github.com/goccy/go-yaml v1.19.2/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= +github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY= +github.com/golang-jwt/jwt/v5 v5.3.1/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= @@ -64,6 +68,10 @@ github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnr github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/klauspost/cpuid/v2 v2.4.0 h1:S6Hrbc7+ywsr0r+RLapfGBHfyefhCTwEh3A0tV913Dw= github.com/klauspost/cpuid/v2 v2.4.0/go.mod h1:19jmZ9mjzoF//ddRSUsv0zfBTJWh3QJh9FNxZTMrGxU= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/leodido/go-urn v1.5.0 h1:pLqT2kq1zpHW/1D18QMjMpdtX7cekxqtJJjg5ANyWw0= github.com/leodido/go-urn v1.5.0/go.mod h1:9BORnCDhdPBJNDEX+w1bJisa8yOKYi116VeO96s4ifE= github.com/mattn/go-isatty v0.0.24 h1:tGZZoVgT/KiqK1c8ocVLeDS8BSWMRd47J3Lbz7vsReI= @@ -78,12 +86,8 @@ github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjY github.com/pelletier/go-toml/v2 v2.4.3 h1:GTRvJQutkOSftxIFD5xw9aepkYNuPWmVJpffdDPYVpY= github.com/pelletier/go-toml/v2 v2.4.3/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/quic-go/go-ossfuzz-seeds v0.1.0 h1:APacT+iIaNF6fd8AGEiN3bT/Jtkd2jz4v4TzM7MFjy0= -github.com/quic-go/go-ossfuzz-seeds v0.1.0/go.mod h1:3IOHRbJIc+L6YKMwfDtJAM9Vj9k0YY4muhuyUYk5tbk= -github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8= -github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII= -github.com/quic-go/quic-go v0.62.0 h1:ZHDjCk5OacATwGvs8PWE97CTvX7AqZiVoW7++ZOXTf8= -github.com/quic-go/quic-go v0.62.0/go.mod h1:RAro2j2yN9a9EiPACLHT9IB2NXCvGQmmo/alT0yYI0w= +github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/sagikazarmark/locafero v0.11.0 h1:1iurJgmM9G3PA/I+wWYIOw/5SyBtxapeHDcg+AAIFXc= github.com/sagikazarmark/locafero v0.11.0/go.mod h1:nVIGvgyzw595SUSUE6tvCp3YYTeHs15MvlmU87WwIik= github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 h1:+jumHNA0Wrelhe64i8F6HNlS8pkoyMv5sreGx2Ry5Rw= @@ -114,10 +118,6 @@ github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/ugorji/go/codec v1.3.2 h1:zkEASHHyEClGeURfgNT9PJZVfAbs9oEX9QXggwWNJbc= github.com/ugorji/go/codec v1.3.2/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4= -go.mongodb.org/mongo-driver/v2 v2.9.1 h1:jewiFs2m1/VOQp8qhFshX6hWZ+EAXDhZHXExAUMcOgQ= -go.mongodb.org/mongo-driver/v2 v2.9.1/go.mod h1:SHKN0IWkKmEVGHLjXnni6s4wPKX4v86FTgOeJJFuXcA= -go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y= -go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU= go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= golang.org/x/arch v0.31.0 h1:22MlEb14/O/EPCYHFxsDdv5TuLD5dMjT5e2QeJw4ULk= @@ -132,10 +132,15 @@ golang.org/x/sys v0.48.0 h1:bbX/i/6MgT9BVLM9RT1thmxL04yeTAhbEz4SyadbXoo= golang.org/x/sys v0.48.0/go.mod h1:hNLxWAXmnKAxqDtdwIYC4bM9oQPEecfsnNMuSxOs3og= golang.org/x/text v0.42.0 h1:JbOZXgfeCPU9gacVtYliJqOhD+zhrEqK4LfdpmlUZqI= golang.org/x/text v0.42.0/go.mod h1:ojzP1Z+2QtioaF8DTtO8K5q7JWVVYwZKenzujK0Zd0E= +golang.org/x/time v0.16.0 h1:vMb6ptszcQMkcwiRTAuNNU50gom6++Q/6gY2hDM6VDE= +golang.org/x/time v0.16.0/go.mod h1:rVKOqvZeKvrDKTQiAHJ7wmwP0RzleSphoEA9RcdLA0s= google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc= google.golang.org/protobuf v1.36.12/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 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/postgres v1.6.3 h1:bAn6O2pUa8LtpWEvL5NFU4+52Tfx8Ut7IVaIacCLcI0= gorm.io/driver/postgres v1.6.3/go.mod h1:0c4fQA44XhOklXDkgtuKqysHCycTa5i9e3EIpDGCwXk= diff --git a/kontor-gin/pkg/globals/config.go b/kontor-gin/internal/config/config.go similarity index 97% rename from kontor-gin/pkg/globals/config.go rename to kontor-gin/internal/config/config.go index f71a6b1..fb37f96 100644 --- a/kontor-gin/pkg/globals/config.go +++ b/kontor-gin/internal/config/config.go @@ -1,4 +1,4 @@ -package globals +package config import ( "fmt" diff --git a/kontor-gin/internal/config/database.go b/kontor-gin/internal/config/database.go new file mode 100644 index 0000000..9e25f9d --- /dev/null +++ b/kontor-gin/internal/config/database.go @@ -0,0 +1,35 @@ +package config + +import ( + "gorm.io/driver/postgres" + "gorm.io/gorm" + "gorm.io/gorm/logger" +) + +func NewDB(dsn string) (*gorm.DB, error) { + // Configure GORM with custom settings + // The logger helps with debugging SQL queries during development + return gorm.Open(postgres.Open(dsn), &gorm.Config{ + Logger: logger.Default.LogMode(logger.Info), + }) + // if err != nil { + // log.Fatal("Failed to connect to database:", err) + // } + + // // Get the underlying SQL database connection for pool configuration + // sqlDB, err := db.DB() + // if err != nil { + // log.Fatal("Failed to get database instance:", err) + // } + + // // Configure connection pool settings + // // MaxIdleConns: number of connections kept open when idle + // sqlDB.SetMaxIdleConns(10) + // // MaxOpenConns: maximum total connections to the database + // sqlDB.SetMaxOpenConns(100) + // // ConnMaxLifetime: how long a connection can be reused + // sqlDB.SetConnMaxLifetime(time.Hour) + + // slog.Info("Database connected successfully") + +} diff --git a/kontor-gin/internal/globals/config.go b/kontor-gin/internal/globals/config.go new file mode 100644 index 0000000..866d808 --- /dev/null +++ b/kontor-gin/internal/globals/config.go @@ -0,0 +1,40 @@ +package globals + +import ( + "fmt" + "log/slog" + "strings" + + "github.com/spf13/viper" +) + +func InitConfig() error { + viper.SetDefault("db.host", "localhost") + viper.SetDefault("db.port", 5432) + viper.SetDefault("db.name", "kontor") + viper.SetDefault("db.user", "kontor") + viper.SetDefault("db.password", "kontor") + viper.SetDefault("port", 8550) + viper.SetDefault("gin.mode", "release") + + viper.SetConfigName("kontor-gin") + viper.SetConfigType("yaml") + viper.AddConfigPath(".") + viper.AddConfigPath("$HOME/.config") + viper.AddConfigPath("/etc/kontor") + viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_")) + viper.AutomaticEnv() + + // Read the config file + if err := viper.ReadInConfig(); err != nil { + // Handle the case where config file is not found + if _, ok := err.(viper.ConfigFileNotFoundError); ok { + slog.Info("No config file found, using defaults") + return nil + } + return fmt.Errorf("error reading config file: %w", err) + } + + slog.Info("initConfig", "Using config file: ", viper.ConfigFileUsed()) + return nil +} diff --git a/kontor-gin/pkg/globals/globals.go b/kontor-gin/internal/globals/globals.go similarity index 100% rename from kontor-gin/pkg/globals/globals.go rename to kontor-gin/internal/globals/globals.go diff --git a/kontor-gin/internal/handlers/auth.go b/kontor-gin/internal/handlers/auth.go new file mode 100644 index 0000000..a3f756f --- /dev/null +++ b/kontor-gin/internal/handlers/auth.go @@ -0,0 +1,16 @@ +package handlers + +import ( + "kontor-api-gin/internal/service" + "net/http" + + "github.com/gin-gonic/gin" +) + +type AuthHandler struct { + Service *service.AuthService +} + +func (handler *AuthHandler) Login(c *gin.Context) { + c.JSON(http.StatusOK, gin.H{"token": "bla"}) +} diff --git a/kontor-gin/internal/handlers/comic.go b/kontor-gin/internal/handlers/comic.go new file mode 100644 index 0000000..2585011 --- /dev/null +++ b/kontor-gin/internal/handlers/comic.go @@ -0,0 +1,21 @@ +package handlers + +import ( + "kontor-api-gin/internal/service" + "net/http" + + "github.com/gin-gonic/gin" +) + +type ComicHandler struct { + Service *service.ComicService +} + +func (handler *ComicHandler) List(c *gin.Context) { + comics, err := handler.Service.List(c.Request.Context()) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + c.JSON(http.StatusOK, comics) +} diff --git a/kontor-gin/pkg/helpers/auth.go b/kontor-gin/internal/helpers/auth.go similarity index 100% rename from kontor-gin/pkg/helpers/auth.go rename to kontor-gin/internal/helpers/auth.go diff --git a/kontor-gin/internal/helpers/token.go b/kontor-gin/internal/helpers/token.go new file mode 100644 index 0000000..c9a61e3 --- /dev/null +++ b/kontor-gin/internal/helpers/token.go @@ -0,0 +1,90 @@ +package helpers + +import ( + "fmt" + "strings" + "time" + + "github.com/gin-contrib/slog" + "github.com/gin-gonic/gin" + + "github.com/golang-jwt/jwt/v5" + "github.com/spf13/viper" +) + +func GenerateToken(profile_id string) (string, error) { + + // token_lifespan := viper.GetInt("token_hour_lifespan") + + claims := jwt.MapClaims{ + "name": "Thomas Peetz", + "admin": true, + "authorized": true, + "profile_id": profile_id, + "exp": time.Now().Add(time.Hour * 72).Unix(), + } + // claims["authorized"] = true + // claims["profile_id"] = profile_id + // claims["exp"] = time.Now().Add(time.Hour * time.Duration(token_lifespan)).Unix() + token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) + + return token.SignedString([]byte(viper.GetString("api_secret"))) + +} + +func TokenValid(c *gin.Context) error { + logger := slog.Get(c) + + tokenString := ExtractToken(c) + _, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) { + if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { + logger.Info("TokenValid", "Unexpected signing method", token.Header["alg"]) + return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"]) + } + return []byte(viper.GetString("api_secret")), nil + }) + if err != nil { + return err + } + return nil +} + +func ExtractToken(c *gin.Context) string { + logger := slog.Get(c) + + token := c.Query("token") + if token != "" { + logger.Info("ExtractToken", "token from query", token) + return token + } + bearerToken := c.Request.Header.Get("Authorization") + if len(strings.Split(bearerToken, " ")) == 2 { + logger.Info("ExtractToken", "token from query", token) + + return strings.Split(bearerToken, " ")[1] + } + return "" +} + +func ExtractTokenID(c *gin.Context) (string, error) { + + tokenString := ExtractToken(c) + token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) { + if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { + return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"]) + } + return []byte(viper.GetString("api_secret")), nil + }) + if err != nil { + return "", err + } + claims, ok := token.Claims.(jwt.MapClaims) + if ok && token.Valid { + uid, ok := claims["profile_id"].(string) + if !ok { + return "", err + } + return uid, nil + } + return "", nil +} diff --git a/kontor-gin/internal/middleware/jwt.go b/kontor-gin/internal/middleware/jwt.go new file mode 100644 index 0000000..918d268 --- /dev/null +++ b/kontor-gin/internal/middleware/jwt.go @@ -0,0 +1,35 @@ +package middleware + +import ( + "net/http" + "strings" + + "github.com/gin-gonic/gin" + "github.com/golang-jwt/jwt/v5" +) + +func JWT(secret []byte) gin.HandlerFunc { + return func(c *gin.Context) { + h := c.GetHeader("Authorization") + if !strings.HasPrefix(h, "Bearer ") { + c.AbortWithStatusJSON(http.StatusUnauthorized, + gin.H{"error": "missing bearer token"}) + return + } + raw := strings.TrimPrefix(h, "Bearer ") + token, err := jwt.Parse(raw, func(t *jwt.Token) (any, error) { + if _, ok := t.Method.(*jwt.SigningMethodHMAC); !ok { + return nil, jwt.ErrSignatureInvalid + } + return secret, nil + }) + if err != nil || !token.Valid { + c.AbortWithStatusJSON(http.StatusUnauthorized, + gin.H{"error": "invalid token"}) + return + } + claims := token.Claims.(jwt.MapClaims) + c.Set("profile_id", claims["sub"].(string)) + c.Next() + } +} diff --git a/kontor-gin/internal/middleware/ratelimit.go b/kontor-gin/internal/middleware/ratelimit.go new file mode 100644 index 0000000..4d07c59 --- /dev/null +++ b/kontor-gin/internal/middleware/ratelimit.go @@ -0,0 +1,17 @@ +package middleware + +import ( + "github.com/gin-gonic/gin" + "golang.org/x/time/rate" +) + +func RateLimit(rps int, burst int) gin.HandlerFunc { + limiter := rate.NewLimiter(rate.Limit(rps), burst) + return func(c *gin.Context) { + if !limiter.Allow() { + c.AbortWithStatusJSON(429, gin.H{"error": "too many requests"}) + return + } + c.Next() + } +} diff --git a/kontor-gin/internal/middleware/request_id.go b/kontor-gin/internal/middleware/request_id.go new file mode 100644 index 0000000..ef7e5a3 --- /dev/null +++ b/kontor-gin/internal/middleware/request_id.go @@ -0,0 +1,18 @@ +package middleware + +import ( + "github.com/gin-gonic/gin" + "github.com/google/uuid" +) + +func RequestID() gin.HandlerFunc { + return func(c *gin.Context) { + id := c.GetHeader("X-Request-ID") + if id == "" { + id = uuid.NewString() + } + c.Set("request_id", id) + c.Writer.Header().Set("X-Request-ID", id) + c.Next() + } +} diff --git a/kontor-gin/internal/models/comics/artist.go b/kontor-gin/internal/models/comics/artist.go new file mode 100644 index 0000000..a533ca1 --- /dev/null +++ b/kontor-gin/internal/models/comics/artist.go @@ -0,0 +1,21 @@ +package comics + +import "time" + +type Artist struct { + ID string `gorm:"type:string;primary_key;"` + CreatedAt time.Time `gorm:"column:created_date" json:"created_date"` + UpdatedAt time.Time `gorm:"column:last_modified_date" json:"last_modified_date"` + DeletedAt *time.Time `gorm:"column:deleted_date" json:"deleted_date"` + Version int `gorm:"default:0" json:"version"` + Name string `gorm:"uniqueIndex" json:"name"` + WebLink string `gorm:"column:weblink" json:"weblink"` +} + +func (artist *Artist) TableName() string { + return "artist" +} + +type CreateArtistDTO struct { + Name string `json:"name" binding:"required"` +} diff --git a/kontor-gin/internal/models/comics/comic.go b/kontor-gin/internal/models/comics/comic.go new file mode 100644 index 0000000..c004766 --- /dev/null +++ b/kontor-gin/internal/models/comics/comic.go @@ -0,0 +1,38 @@ +package comics + +import ( + "time" +) + +type Comic struct { + ID string `gorm:"type:string;primary_key;"` + CreatedAt time.Time `gorm:"column:created_date" json:"created_date"` + UpdatedAt time.Time `gorm:"column:last_modified_date" json:"last_modified_date"` + DeletedAt *time.Time `gorm:"column:deleted_date" json:"deleted_date"` + Version int `gorm:"default:0" json:"version"` + Title string `gorm:"uniqueIndex" json:"title"` + CurrentOrder bool `gorm:"column:current_order" json:"current_order"` + Completed bool `gorm:"column:completed" json:"completed"` + WebLink string `gorm:"column:weblink" json:"weblink"` + PublisherID *string `gorm:"column:publisher_id" json:"publisher_id"` + Issues []Issue `gorm:"foreignKey:ComicID"` + StoryArcs []StoryArc `gorm:"foreignKey:ComicID"` + TradePaperbacks []TradePaperback `gorm:"foreignKey:ComicID"` + Volumes []Volume `gorm:"foreignKey:ComicID"` + ComicWorks []ComicWork `gorm:"foreignKey:ComicID"` +} + +func (comic *Comic) TableName() string { + return "comic" +} + +type CreateComicDTO struct { + Title string `json:"title" binding:"required"` +} + +type UpdateComicDTO struct { + Title *string `json:"title,omitempty" binding:"omitempty"` + WebLink *string `json:"weblink,omitempty" binding:"omitempty"` + Completed *bool `json:"cimpleted,omitempty" binding:"omitempty"` + CurrentOrder *bool `json:"current_order,omitempty" binding:"omitempty"` +} diff --git a/kontor-gin/internal/models/comics/comicwork.go b/kontor-gin/internal/models/comics/comicwork.go new file mode 100644 index 0000000..1e557c7 --- /dev/null +++ b/kontor-gin/internal/models/comics/comicwork.go @@ -0,0 +1,21 @@ +package comics + +import "time" + +type ComicWork struct { + ID string `gorm:"type:string;primary_key;"` + CreatedAt time.Time `gorm:"column:created_date" json:"created_date"` + UpdatedAt time.Time `gorm:"column:last_modified_date" json:"last_modified_date"` + DeletedAt *time.Time `gorm:"column:deleted_date" json:"deleted_date"` + Version int `gorm:"default:0" json:"version"` + ArtistID *string `gorm:"column:artist_id" json:"artist_id"` + Artist *Artist `gorm:"foreignKey:ArtistID"` + ComicID *string `gorm:"column:comic_id" json:"comic_id"` + Comic *Comic `gorm:"foreignKey:ComicID"` + WorkTypeID *string `gorm:"column:work_type_id" json:"work_type_id"` + WorkType *WorkType `gorm:"foreignKey:WorkTypeID"` +} + +func (comic_work *ComicWork) TableName() string { + return "comic_work" +} diff --git a/kontor-gin/internal/models/comics/issue.go b/kontor-gin/internal/models/comics/issue.go new file mode 100644 index 0000000..b61e1a9 --- /dev/null +++ b/kontor-gin/internal/models/comics/issue.go @@ -0,0 +1,26 @@ +package comics + +import "time" + +type Issue struct { + ID string `gorm:"type:string;primary_key;"` + CreatedAt time.Time `gorm:"column:created_date" json:"created_date"` + UpdatedAt time.Time `gorm:"column:last_modified_date" json:"last_modified_date"` + DeletedAt *time.Time `gorm:"column:deleted_date" json:"deleted_date"` + Version int `gorm:"default:0" json:"version"` + InStock bool `gorm:"column:in_stock" json:"in_stock"` + IsRead bool `gorm:"column:is_read" json:"is_read"` + IssueNumber string `gorm:"column:issue_number" json:"issue_number"` + Title string `gorm:"column:title" json:"title"` + PublishedOn time.Time `gorm:"column:published_on" json:"published_on"` + ComicID *string `gorm:"column:comic_id" json:"comic_id"` + Comic *Comic `gorm:"foreignKey:ComicID"` + StoryArcID *string `gorm:"column:story_arc_id" json:"story_arc_id"` + StoryArc *StoryArc `gorm:"foreignKey:StoryArcID"` + VolumeID *string `gorm:"column:volume_id" json:"volume_id"` + Volume *Volume `gorm:"foreignKey:VolumeID"` +} + +func (issue *Issue) TableName() string { + return "issue" +} diff --git a/kontor-gin/internal/models/comics/issuework.go b/kontor-gin/internal/models/comics/issuework.go new file mode 100644 index 0000000..5da4231 --- /dev/null +++ b/kontor-gin/internal/models/comics/issuework.go @@ -0,0 +1,21 @@ +package comics + +import "time" + +type IssueWork struct { + ID string `gorm:"type:string;primary_key;"` + CreatedAt time.Time `gorm:"column:created_date" json:"created_date"` + UpdatedAt time.Time `gorm:"column:last_modified_date" json:"last_modified_date"` + DeletedAt *time.Time `gorm:"column:deleted_date" json:"deleted_date"` + Version int `gorm:"default:0" json:"version"` + ArtistID *string `gorm:"column:artist_id" json:"artist_id"` + Artist *Artist `gorm:"foreignKey:ArtistID"` + IssueID *string `gorm:"column:issue_id" json:"issue_id"` + Issue *Issue `gorm:"foreignKey:IssueID"` + WorkTypeID *string `gorm:"column:work_type_id" json:"work_type_id"` + WorkType *WorkType `gorm:"foreignKey:WorkTypeID"` +} + +func (issue_work *IssueWork) TableName() string { + return "issue_work" +} diff --git a/kontor-gin/internal/models/comics/publisher.go b/kontor-gin/internal/models/comics/publisher.go new file mode 100644 index 0000000..d917d10 --- /dev/null +++ b/kontor-gin/internal/models/comics/publisher.go @@ -0,0 +1,26 @@ +package comics + +import "time" + +type Publisher struct { + ID string `gorm:"type:string;primary_key;"` + CreatedAt time.Time `gorm:"column:created_date" json:"created_date"` + UpdatedAt time.Time `gorm:"column:last_modified_date" json:"last_modified_date"` + DeletedAt *time.Time `gorm:"column:deleted_date" json:"deleted_date"` + Version int `gorm:"default:0" json:"version"` + Name string `gorm:"uniqueIndex" json:"name"` + WebLink string `gorm:"column:weblink" json:"weblink"` + ParentPublisherID *string `gorm:"column:parent_publisher_id" json:"parent_publisher_id"` + ParentPublisher *Publisher `gorm:"foreignKey:ParentPublisherID"` + Imprints []Publisher `gorm:"foreignKey:ParentPublisherID"` + Comics []Comic `gorm:"foreignKey:PublisherID"` +} + +func (publisher *Publisher) TableName() string { + return "publisher" +} + +type CreatePublisherDTO struct { + Name string `json:"name" binding:"required"` + WebLink string `json:"weblink" binding:"required"` +} diff --git a/kontor-gin/internal/models/comics/storyarc.go b/kontor-gin/internal/models/comics/storyarc.go new file mode 100644 index 0000000..44edb94 --- /dev/null +++ b/kontor-gin/internal/models/comics/storyarc.go @@ -0,0 +1,20 @@ +package comics + +import "time" + +type StoryArc struct { + ID string `gorm:"type:string;primary_key;"` + CreatedAt time.Time `gorm:"column:created_date" json:"created_date"` + UpdatedAt time.Time `gorm:"column:last_modified_date" json:"last_modified_date"` + DeletedAt *time.Time `gorm:"column:deleted_date" json:"deleted_date"` + Version int `gorm:"default:0" json:"version"` + Name string `gorm:"uniqueIndex" json:"name"` + ComicID *string `gorm:"column:comic_id" json:"comic_id"` + Comic *Comic `gorm:"foreignKey:ComicID"` + VolumeID *string `gorm:"column:volume_id" json:"volume_id"` + Volume *Volume `gorm:"foreignKey:VolumeID"` +} + +func (story_arc *StoryArc) TableName() string { + return "story_arc" +} diff --git a/kontor-gin/internal/models/comics/tradepaperback.go b/kontor-gin/internal/models/comics/tradepaperback.go new file mode 100644 index 0000000..5b58cc8 --- /dev/null +++ b/kontor-gin/internal/models/comics/tradepaperback.go @@ -0,0 +1,20 @@ +package comics + +import "time" + +type TradePaperback struct { + ID string `gorm:"type:string;primary_key;"` + CreatedAt time.Time `gorm:"column:created_date" json:"created_date"` + UpdatedAt time.Time `gorm:"column:last_modified_date" json:"last_modified_date"` + DeletedAt *time.Time `gorm:"column:deleted_date" json:"deleted_date"` + Version int `gorm:"default:0" json:"version"` + Name string `gorm:"uniqueIndex" json:"name"` + IssueStart int `gorm:"column:issue_start" json:"issue_start"` + IssueEnd int `gorm:"column:issue_end" json:"issue_end"` + ComicID *string `gorm:"column:comic_id" json:"comic_id"` + Comic *Comic `gorm:"foreignKey:ComicID"` +} + +func (trade_paperback *TradePaperback) TableName() string { + return "trade_paperback" +} diff --git a/kontor-gin/internal/models/comics/volume.go b/kontor-gin/internal/models/comics/volume.go new file mode 100644 index 0000000..1e8960f --- /dev/null +++ b/kontor-gin/internal/models/comics/volume.go @@ -0,0 +1,18 @@ +package comics + +import "time" + +type Volume struct { + ID string `gorm:"type:string;primary_key;"` + CreatedAt time.Time `gorm:"column:created_date" json:"created_date"` + UpdatedAt time.Time `gorm:"column:last_modified_date" json:"last_modified_date"` + DeletedAt *time.Time `gorm:"column:deleted_date" json:"deleted_date"` + Version int `gorm:"default:0" json:"version"` + Name string `gorm:"uniqueIndex" json:"name"` + ComicID *string `gorm:"column:comic_id" json:"comic_id"` + Comic *Comic `gorm:"foreignKey:ComicID"` +} + +func (volume *Volume) TableName() string { + return "volume" +} diff --git a/kontor-gin/internal/models/comics/worktype.go b/kontor-gin/internal/models/comics/worktype.go new file mode 100644 index 0000000..66db81c --- /dev/null +++ b/kontor-gin/internal/models/comics/worktype.go @@ -0,0 +1,16 @@ +package comics + +import "time" + +type WorkType struct { + ID string `gorm:"type:string;primary_key;"` + CreatedAt time.Time `gorm:"column:created_date" json:"created_date"` + UpdatedAt time.Time `gorm:"column:last_modified_date" json:"last_modified_date"` + DeletedAt *time.Time `gorm:"column:deleted_date" json:"deleted_date"` + Version int `gorm:"default:0" json:"version"` + Name string `gorm:"uniqueIndex" json:"name"` +} + +func (worktype *WorkType) TableName() string { + return "worktype" +} diff --git a/kontor-gin/internal/repository/comic.go b/kontor-gin/internal/repository/comic.go new file mode 100644 index 0000000..73778c5 --- /dev/null +++ b/kontor-gin/internal/repository/comic.go @@ -0,0 +1,12 @@ +package repository + +import "gorm.io/gorm" + +type ComicRepository struct { + db *gorm.DB +} + +func NewComicRepository(db *gorm.DB) *ComicRepository { + repo := &ComicRepository{db: db} + return repo +} diff --git a/kontor-gin/internal/repository/profile.go b/kontor-gin/internal/repository/profile.go new file mode 100644 index 0000000..14d8e7f --- /dev/null +++ b/kontor-gin/internal/repository/profile.go @@ -0,0 +1,14 @@ +package repository + +import ( + "gorm.io/gorm" +) + +type ProfileRepository struct { + db *gorm.DB +} + +func NewProfileRepository(db *gorm.DB) *ProfileRepository { + repository := &ProfileRepository{db: db} + return repository +} diff --git a/kontor-gin/internal/router/router.go b/kontor-gin/internal/router/router.go new file mode 100644 index 0000000..a1be249 --- /dev/null +++ b/kontor-gin/internal/router/router.go @@ -0,0 +1,49 @@ +package router + +import ( + "html/template" + "kontor-api-gin/assets" + "kontor-api-gin/internal/handlers" + "kontor-api-gin/internal/middleware" + "kontor-api-gin/templates" + "net/http" + + "log/slog" + + "github.com/gin-contrib/cors" + gin_slog "github.com/gin-contrib/slog" + "github.com/gin-gonic/gin" +) + +func New(comics *handlers.ComicHandler, auth *handlers.AuthHandler, secret []byte) *gin.Engine { + router := gin.New() + router.Use(gin.Recovery()) + router.Use(gin_slog.SetLogger( + gin_slog.WithSkipPath([]string{"/health", "/public/style/main.css"}), + gin_slog.WithDefaultLevel(slog.LevelInfo), + gin_slog.WithClientErrorLevel(slog.LevelInfo), + gin_slog.WithServerErrorLevel(slog.LevelInfo), + )) + router.Use(cors.New(cors.Config{ + AllowOrigins: []string{"https://app.example.com"}, + AllowMethods: []string{"GET", "POST", "PUT", "PATCH", "DELETE"}, + AllowHeaders: []string{"Authorization", "Content-Type"}, + })) + templ := template.Must(template.New("").ParseFS(templates.TemplateFiles, "layouts/*.html", "pages/*.html")) + router.SetHTMLTemplate(templ) + + router.StaticFS("/public", http.FS(assets.StaticFiles)) + + router.GET("/health", func(c *gin.Context) { c.JSON(200, gin.H{"ok": true}) }) + + api := router.Group("/api") + api.POST("/login", auth.Login) + + v1 := api.Group("/v1") + { + secured := v1.Group("") + secured.Use(middleware.JWT(secret)) + secured.GET("comics/comics", comics.List) + } + return router +} diff --git a/kontor-gin/internal/schema/admin.go b/kontor-gin/internal/schema/admin.go new file mode 100644 index 0000000..f65d379 --- /dev/null +++ b/kontor-gin/internal/schema/admin.go @@ -0,0 +1,121 @@ +package schema + +import ( + "kontor-api-gin/internal/helpers" + "time" + + "golang.org/x/crypto/bcrypt" +) + +func VerifyPassword(password, hashedPassword string) error { + return bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(password)) +} + +func LoginCheck(email string, password string) (string, error) { + + var err error + + profile := Profile{} + + err = DB.Model(Profile{}).Where("email = ?", email).Take(&profile).Error + + if err != nil { + return "", err + } + + err = VerifyPassword(password, profile.Password) + + if err != nil && err == bcrypt.ErrMismatchedHashAndPassword { + return "", err + } + + token, err := helpers.GenerateToken(profile.ID) + + if err != nil { + return "", err + } + + return token, nil + +} + +type Profile struct { + ID string `gorm:"type:string;primary_key;"` + Created_Date time.Time `json:"created_date"` + Last_Modified_date time.Time `json:"last_modified_date"` + Deleted_Date *time.Time `json:"deleted_date"` + Version int `gorm:"default:0" json:"version"` + First_Name *string `json:"first_name"` + Last_Name *string `json:"last_name"` + User_Name string `json:"user_name"` + Email string `json:"email"` + Password string `json:"password"` + Enabled bool `json:"enabled"` + + Assignments []Assignment `gorm:"foreignKey:ProfileID" json:"assignments"` + Tokens []Token `gorm:"foreignKey:ProfileID" json:"tokens"` +} + +func (profile *Profile) TableName() string { + return "profile" +} + +func (profile *Profile) GetFullName() string { + full_name := "" + if profile.First_Name != nil { + full_name += *profile.First_Name + } + if profile.Last_Name != nil { + full_name += " " + full_name += *profile.Last_Name + } + return full_name +} + +type Token struct { + ID string `gorm:"type:string;primary_key;"` + Created_Date time.Time `json:"created_date"` + Last_Modified_date time.Time `json:"last_modified_date"` + Deleted_Date *time.Time `json:"deleted_date"` + Version int `gorm:"default:0" json:"version"` + Token string `gorm:"uniqueIndex" json:"token"` + Name string `json:"name"` + Last_Used_date time.Time `json:"last_used_date"` + Enabled bool `json:"enabled"` + ProfileID *string `json:"profile_id"` + Profile *Profile `gorm:"foreignKey:ProfileID" json:"profile"` +} + +func (token *Token) TableName() string { + return "token" +} + +type Permission struct { + ID string `gorm:"type:string;primary_key;"` + Created_Date time.Time `json:"created_date"` + Last_Modified_date time.Time `json:"last_modified_date"` + Deleted_Date *time.Time `json:"deleted_date"` + Version int `gorm:"default:0" json:"version"` + Name string `json:"name"` + Assignments []Assignment `gorm:"foreignKey:PermissionID" json:"assignments"` +} + +func (permission *Permission) TableName() string { + return "permission" +} + +type Assignment struct { + ID string `gorm:"type:string;primary_key;"` + Created_Date time.Time `json:"created_date"` + Last_Modified_date time.Time `json:"last_modified_date"` + Deleted_Date *time.Time `json:"deleted_date"` + Version int `gorm:"default:0" json:"version"` + ProfileID *string `json:"profile_id"` + Profile *Profile `gorm:"foreignKey:ProfileID" json:"profile"` + PermissionID *string `json:"permission_id"` + Permission *Profile `gorm:"foreignKey:PermissionID" json:"permission"` +} + +func (assignment *Assignment) TableName() string { + return "assignment" +} diff --git a/kontor-gin/pkg/schema/base.go b/kontor-gin/internal/schema/base.go similarity index 100% rename from kontor-gin/pkg/schema/base.go rename to kontor-gin/internal/schema/base.go diff --git a/kontor-gin/pkg/schema/database.go b/kontor-gin/internal/schema/database.go similarity index 95% rename from kontor-gin/pkg/schema/database.go rename to kontor-gin/internal/schema/database.go index 9ee2a24..8127aef 100644 --- a/kontor-gin/pkg/schema/database.go +++ b/kontor-gin/internal/schema/database.go @@ -5,6 +5,7 @@ import ( "log" "log/slog" "time" + "github.com/spf13/viper" "gorm.io/driver/postgres" @@ -56,11 +57,7 @@ func Connect() { func RunMigrations() { // AutoMigrate creates tables, missing columns, and missing indexes // It will NOT delete unused columns to protect your data - err := DB.AutoMigrate( - &MediaActor{}, - &MediaFile{}, - &MediaActorFile{}, - ) + err := DB.AutoMigrate() if err != nil { log.Fatal("Migration failed:", err) } diff --git a/kontor-gin/internal/schema/media.go b/kontor-gin/internal/schema/media.go new file mode 100644 index 0000000..2fb23d0 --- /dev/null +++ b/kontor-gin/internal/schema/media.go @@ -0,0 +1,107 @@ +package schema + +import "time" + +type MediaLoFi struct { + ID string `gorm:"type:string;primary_key;"` + Created_Date time.Time `json:"created_date"` + Last_Modified_date time.Time `json:"last_modified_date"` + Deleted_Date *time.Time `json:"deleted_date"` + Version int `gorm:"default:0" json:"version"` + Url string `gorm:"uniqueIndex" json:"url"` + Review bool `json:"review"` + ShouldDownload bool `json:"should_download"` + Title string `gorm:"size:300" json:"title"` + FileName string `json:"file_name"` +} + +func (media_lofi *MediaLoFi) TableName() string { + return "media_lofi" +} + +type MediaFile struct { + ID string `gorm:"type:string;primary_key;"` + Created_Date time.Time `json:"created_date"` + Last_Modified_date time.Time `json:"last_modified_date"` + Deleted_Date *time.Time `json:"deleted_date"` + Version int `gorm:"default:0" json:"version"` + CloudLink string `gorm:"column:cloud_link" json:"cloud_link"` + FileName string `json:"file_name"` + Path string `json:"path"` + Review bool `json:"review"` + Title string `gorm:"size:300" json:"title"` + WebLink string `gorm:"column:url" json:"url"` + ShouldDownload bool `json:"should_download"` + + MediaActorFiles []MediaActorFile `gorm:"foreignKey:MediaFileID" json:"media_actor_files"` +} + +func (media_file *MediaFile) TableName() string { + return "media_file" +} + +type MediaActor struct { + ID string `gorm:"type:string;primary_key;"` + Created_Date time.Time `json:"created_date"` + Last_Modified_date time.Time `json:"last_modified_date"` + Deleted_Date *time.Time `json:"deleted_date"` + Version int `gorm:"default:0" json:"version"` + Name string `json:"name"` + Url string `json:"url"` + + MediaActorFiles []MediaActorFile `gorm:"foreignKey:MediaActorID" json:"media_actor_files"` +} + +func (media_actor *MediaActor) TableName() string { + return "media_actor" +} + +type MediaActorFile struct { + ID string `gorm:"type:string;primary_key;"` + Created_Date time.Time `json:"created_date"` + Last_Modified_date time.Time `json:"last_modified_date"` + Deleted_Date *time.Time `json:"deleted_date"` + Version int `gorm:"default:0" json:"version"` + MediaActorID *string `json:"media_actor_id"` + MediaActor *MediaActor `gorm:"foreignKey:MediaActorID" json:"media_actor"` + MediaFileID *string `json:"media_file_id"` + MediaFile *MediaFile `gorm:"foreignKey:MediaFileID" json:"media_file"` +} + +func (media_file *MediaActorFile) TableName() string { + return "media_actor_file" +} + +type MediaArticle struct { + ID string `gorm:"type:string;primary_key;"` + Created_Date time.Time `json:"created_date"` + Last_Modified_date time.Time `json:"last_modified_date"` + Deleted_Date *time.Time `json:"deleted_date"` + Version int `gorm:"default:0" json:"version"` + Review bool `json:"review"` + Title string `gorm:"size:300" json:"title"` + Url string `gorm:"uniqueIndex" json:"url"` +} + +func (media_article *MediaArticle) TableName() string { + return "media_article" +} + +type MediaVideo struct { + ID string `gorm:"type:string;primary_key;"` + Created_Date time.Time `json:"created_date"` + Last_Modified_date time.Time `json:"last_modified_date"` + Deleted_Date *time.Time `json:"deleted_date"` + Version int `gorm:"default:0" json:"version"` + CloudLink string `json:"cloud_link"` + FileName string `json:"file_name"` + Path string `json:"path"` + Review bool `json:"review"` + Title string `gorm:"size:300" json:"title"` + Url string `gorm:"uniqueIndex" json:"url"` + ShouldDownload bool `json:"should_download"` +} + +func (media_video *MediaVideo) TableName() string { + return "media_video" +} diff --git a/kontor-gin/internal/service/auth.go b/kontor-gin/internal/service/auth.go new file mode 100644 index 0000000..e7b651b --- /dev/null +++ b/kontor-gin/internal/service/auth.go @@ -0,0 +1,12 @@ +package service + +import "kontor-api-gin/internal/repository" + +type AuthService struct { + repository *repository.ProfileRepository +} + +func NewAuthService(repository *repository.ProfileRepository, secret []byte) *AuthService { + service := &AuthService{repository: repository} + return service +} diff --git a/kontor-gin/internal/service/comic.go b/kontor-gin/internal/service/comic.go new file mode 100644 index 0000000..b94f610 --- /dev/null +++ b/kontor-gin/internal/service/comic.go @@ -0,0 +1,20 @@ +package service + +import ( + "context" + "kontor-api-gin/internal/models/comics" + "kontor-api-gin/internal/repository" +) + +type ComicService struct { + repository *repository.ComicRepository +} + +func NewComicService(repo *repository.ComicRepository) *ComicService { + service := &ComicService{repository: repo} + return service +} + +func (service *ComicService) List(context context.Context) ([]comics.Comic, error) { + return nil, nil +} diff --git a/kontor-gin/pkg/controllers/auth.go b/kontor-gin/pkg/controllers/auth.go index 0b02873..2504cbe 100644 --- a/kontor-gin/pkg/controllers/auth.go +++ b/kontor-gin/pkg/controllers/auth.go @@ -3,6 +3,7 @@ package controllers import ( "kontor-api-gin/pkg/globals" "kontor-api-gin/pkg/helpers" + "kontor-api-gin/pkg/schema" "net/http" "github.com/gin-contrib/sessions" @@ -11,6 +12,34 @@ import ( "github.com/gin-gonic/gin" ) +type LoginInput struct { + Email string `json:"email" binding:"required"` + Password string `json:"password" binding:"required"` +} + +func LoginHandler(c *gin.Context) { + var input LoginInput + + if err := c.ShouldBindJSON(&input); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + profile := schema.Profile{} + + profile.Email = input.Email + profile.Password = input.Password + + token, err := schema.LoginCheck(profile.Email, profile.Password) + + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "username or password is incorrect."}) + return + } + + c.JSON(http.StatusOK, gin.H{"token": token}) +} + func LoginGetHandler(c *gin.Context) { session := sessions.Default(c) user := session.Get(globals.UserKey) diff --git a/kontor-gin/pkg/controllers/health.go b/kontor-gin/pkg/controllers/health.go index 94d0de8..98e5b0b 100644 --- a/kontor-gin/pkg/controllers/health.go +++ b/kontor-gin/pkg/controllers/health.go @@ -3,6 +3,7 @@ package controllers import ( "kontor-api-gin/pkg/globals" "net/http" + "time" "github.com/gin-gonic/gin" ) @@ -11,5 +12,6 @@ func HealthGetHandler(c *gin.Context) { c.JSON(http.StatusOK, gin.H{ "status": "healthy", "version": globals.Version, + "time": time.Now().UTC().Format(time.RFC3339), }) } diff --git a/kontor-gin/pkg/controllers/media.go b/kontor-gin/pkg/controllers/media.go index 0cf2773..44fc647 100644 --- a/kontor-gin/pkg/controllers/media.go +++ b/kontor-gin/pkg/controllers/media.go @@ -18,10 +18,7 @@ func ListMediaFiles(c *gin.Context) { // result := query.Find(&files) result := schema.DB.Find(&files) logger.Info("ListMediaFiles", "count", result.RowsAffected) - c.JSON(http.StatusOK, gin.H{ - "message": "All MediaFiles", - "files": files, - }) + c.JSON(http.StatusOK, files) } func ListMediaLoFis(c *gin.Context) { diff --git a/kontor-gin/pkg/middleware/authRequired.go b/kontor-gin/pkg/middleware/middlewares.go similarity index 60% rename from kontor-gin/pkg/middleware/authRequired.go rename to kontor-gin/pkg/middleware/middlewares.go index cb5eb55..ed444a1 100644 --- a/kontor-gin/pkg/middleware/authRequired.go +++ b/kontor-gin/pkg/middleware/middlewares.go @@ -1,7 +1,8 @@ package middleware import ( - "kontor-api-gin/pkg/globals" + "kontor-api-gin/internal/globals" + "kontor-api-gin/internal/helpers" "net/http" "github.com/gin-contrib/sessions" @@ -22,3 +23,15 @@ func AuthRequired(c *gin.Context) { } c.Next() } + +func JwtRequired(c *gin.Context) { + logger := slog.Get(c) + err := helpers.TokenValid(c) + logger.Info("JwtRequired", "TokenValid", err) + if err != nil { + c.String(http.StatusUnauthorized, "Unauthorized") + c.Abort() + return + } + c.Next() +} diff --git a/kontor-gin/pkg/routes/api.go b/kontor-gin/pkg/routes/api.go index 41c6bfe..f03d231 100644 --- a/kontor-gin/pkg/routes/api.go +++ b/kontor-gin/pkg/routes/api.go @@ -1,10 +1,14 @@ package routes import ( + "kontor-api-gin/pkg/controllers" + "github.com/gin-gonic/gin" ) func SetupApiRoutes(router *gin.Engine) *gin.RouterGroup { - v1 := router.Group("api/v1") + api := router.Group("/api") + api.POST("/login", controllers.LoginHandler) + v1 := api.Group("/v1") return v1 } diff --git a/kontor-gin/pkg/schema/comic.go b/kontor-gin/pkg/schema/comic.go deleted file mode 100644 index b88719d..0000000 --- a/kontor-gin/pkg/schema/comic.go +++ /dev/null @@ -1,151 +0,0 @@ -package schema - -import ( - "time" -) - -type Publisher struct { - Base - - Name string `gorm:"uniqueIndex" json:"name"` - WebLink string `json:"weblink"` - ParentPublisherID *string `json:"parent_publisher_id"` - ParentPublisher *Publisher `gorm:"foreignKey:ParentPublisherID"` - Imprints []Publisher `gorm:"foreignKey:ParentPublisherID"` - Comics []Comic `gorm:"foreignKey:PublisherID"` -} - -func (publisher *Publisher) TableName() string { - return "publisher" -} - -type Comic struct { - Base - - Title string `gorm:"uniqueIndex" json:"title"` - CurrentOrder bool `json:"current_order"` - Completed bool `json:"completed"` - WebLink string `json:"weblink"` - PublisherID *string `json:"publisher_id"` - Publisher *Publisher `gorm:"foreignKey:PublisherID"` - Issues []Issue `gorm:"foreignKey:ComicID"` - StoryArcs []StoryArc `gorm:"foreignKey:ComicID"` - TradePaperbacks []TradePaperback `gorm:"foreignKey:ComicID"` - Volumes []Volume `gorm:"foreignKey:ComicID"` - ComicWorks []ComicWork `gorm:"foreignKey:ComicID"` -} - -func (comic *Comic) TableName() string { - return "comic" -} - -type Artist struct { - Base - - Name string `gorm:"uniqueIndex" json:"name"` - WebLink string `json:"weblink"` -} - -func (artist *Artist) TableName() string { - return "artist" -} - -type Issue struct { - Base - - InStock bool `json:"in_stock"` - IsRead bool `json:"is_read"` - IssueNumber string `json:"issue_number"` - Title string `json:"title"` - PublishedOn time.Time `json:"published_on"` - ComicID *string `json:"comic_id"` - Comic *Comic `gorm:"foreignKey:ComicID"` - StoryArcID *string `json:"story_arc_id"` - StoryArc *StoryArc `gorm:"foreignKey:StoryArcID"` - VolumeID *string `json:"volume_id"` - Volume *Volume `gorm:"foreignKey:VolumeID"` -} - -func (issue *Issue) TableName() string { - return "issue" -} - -type StoryArc struct { - Base - - Name string `gorm:"uniqueIndex" json:"name"` - ComicID *string `json:"comic_id"` - Comic *Comic `gorm:"foreignKey:ComicID"` - VolumeID *string `json:"volume_id"` - Volume *Volume `gorm:"foreignKey:VolumeID"` -} - -func (story_arc *StoryArc) TableName() string { - return "story_arc" -} - -type TradePaperback struct { - Base - - Name string `gorm:"uniqueIndex" json:"name"` - IssueStart int `json:"issue_start"` - IssueEnd int `json:"issue_end"` - ComicID *string `json:"comic_id"` - Comic *Comic `gorm:"foreignKey:ComicID"` -} - -func (trade_paperback *TradePaperback) TableName() string { - return "trade_paperback" -} - -type Volume struct { - Base - - Name string `gorm:"uniqueIndex" json:"name"` - ComicID *string `json:"comic_id"` - Comic *Comic `gorm:"foreignKey:ComicID"` -} - -func (volume *Volume) TableName() string { - return "volume" -} - -type WorkType struct { - Base - - Name string `gorm:"uniqueIndex" json:"name"` -} - -func (worktype *WorkType) TableName() string { - return "worktype" -} - -type ComicWork struct { - Base - - ArtistID *string `json:"artist_id"` - Artist *Artist `gorm:"foreignKey:ArtistID"` - ComicID *string `json:"comic_id"` - Comic *Comic `gorm:"foreignKey:ComicID"` - WorkTypeID *string `json:"work_type_id"` - WorkType *WorkType `gorm:"foreignKey:WorkTypeID"` -} - -func (comic_work *ComicWork) TableName() string { - return "comic_work" -} - -type IssueWork struct { - Base - - ArtistID *string `json:"artist_id"` - Artist *Artist `gorm:"foreignKey:ArtistID"` - IssueID *string `json:"issue_id"` - Issue *Issue `gorm:"foreignKey:IssueID"` - WorkTypeID *string `json:"work_type_id"` - WorkType *WorkType `gorm:"foreignKey:WorkTypeID"` -} - -func (issue_work *IssueWork) TableName() string { - return "issue_work" -} diff --git a/kontor-gin/pkg/schema/media.go b/kontor-gin/pkg/schema/media.go deleted file mode 100644 index 465b7fe..0000000 --- a/kontor-gin/pkg/schema/media.go +++ /dev/null @@ -1,92 +0,0 @@ -package schema - -import "time" - -type MediaLoFi struct { - ID string `gorm:"type:string;primary_key;"` - Created_Date time.Time `json:"created_date"` - Last_Modified_date time.Time `json:"last_modified_date"` - Deleted_Date *time.Time `json:"deleted_date"` - Version int `gorm:"default:0" json:"version"` - Url string `gorm:"uniqueIndex" json:"url"` - Review bool `json:"review"` - ShouldDownload bool `json:"should_download"` - Title string `gorm:"size:300" json:"title"` - FileName string `json:"file_name"` -} - -func (media_lofi *MediaLoFi) TableName() string { - return "media_lofi" -} - -type MediaFile struct { - Base - - CloudLink string `json:"cloud_link"` - FileName string `json:"file_name"` - Path string `json:"path"` - Review bool `json:"review"` - Title string `gorm:"size:300" json:"title"` - WebLink string `gorm:"uniqueIndex" json:"url"` - ShouldDownload bool `json:"should_download"` - - MediaActorFiles []MediaActorFile `gorm:"foreignKey:MediaFileID" json:"media_actor_files"` -} - -func (media_file *MediaFile) TableName() string { - return "media_file" -} - -type MediaActor struct { - Base - - Name string `json:"name"` - WebLink string `gorm:"uniqueIndex" json:"url"` - - MediaActorFiles []MediaActorFile `gorm:"foreignKey:MediaActorID" json:"media_actor_files"` -} - -func (media_actor *MediaActor) TableName() string { - return "media_actor" -} - -type MediaActorFile struct { - Base - - MediaActorID *string `json:"media_actor_id"` - MediaActor *MediaActor `gorm:"foreignKey:MediaActorID" json:"media_actor"` - MediaFileID *string `json:"media_file_id"` - MediaFile *MediaFile `gorm:"foreignKey:MediaFileID" json:"media_file"` -} - -func (media_file *MediaActorFile) TableName() string { - return "media_actor_file" -} - -// type MediaArticle struct { -// bun.BaseModel `bun:"table:media_article"` - -// ID string `bun:"id,pk"` -// CreatedAt time.Time `bun:"created_date,nullzero,notnull,default:current_timestamp"` -// UpdatedAt time.Time `bun:"last_modified_date,nullzero,notnull,default:current_timestamp"` -// Version int `bun:"version,default:0"` -// Review bool `bun:"review"` -// Title string `bun:"title"` -// WebLink string `bun:"url,unique:url"` -// } - -// type MediaVideo struct { -// bun.BaseModel `bun:"table:media_article"` - -// ID string `bun:"id,pk"` -// CreatedAt time.Time `bun:"created_date,nullzero,notnull,default:current_timestamp"` -// UpdatedAt time.Time `bun:"last_modified_date,nullzero,notnull,default:current_timestamp"` -// Version int `bun:"version,default:0"` -// CloudLink string `bun:"cloud_link"` -// FileName string `bun:"file_name"` -// Path string `bun:"path"` -// Review bool `bun:"review"` -// Title string `bun:"title"` -// WebLink string `bun:"url,unique:url"` -// ShouldDownload bool `bun:"should_download"` -// }