umstellung auf Service und Repository Verwendung - WIP
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 4s
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 4s
This commit is contained in:
@@ -0,0 +1,8 @@
|
||||
package main
|
||||
|
||||
import "log/slog"
|
||||
|
||||
func main() {
|
||||
slog.Info("Kontor started")
|
||||
slog.Info("Kontor finished")
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
+11
-10
@@ -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
|
||||
)
|
||||
|
||||
+25
-20
@@ -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=
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package globals
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
@@ -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")
|
||||
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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"})
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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"`
|
||||
}
|
||||
@@ -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"`
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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"`
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
+14
-1
@@ -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()
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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"`
|
||||
// }
|
||||
Reference in New Issue
Block a user