From 044da5353a69b0463f9e2bd354b2de27fafb77f6 Mon Sep 17 00:00:00 2001 From: Thomas Peetz Date: Tue, 22 Sep 2026 19:19:51 +0200 Subject: [PATCH] add templates for kontor-gin --- kontor-gin/Containerfile.local | 11 ++ kontor-gin/cmd/kontor/main.go | 38 +++-- kontor-gin/go.mod | 1 + kontor-gin/go.sum | 2 + kontor-gin/pkg/controllers/auth.go | 22 ++- kontor-gin/pkg/controllers/comic.go | 30 ++++ kontor-gin/pkg/controllers/comic_api.go | 92 ++++++++++++ kontor-gin/pkg/controllers/handlers.go | 4 + kontor-gin/pkg/controllers/health.go | 15 ++ kontor-gin/pkg/controllers/media.go | 59 ++++++++ kontor-gin/pkg/globals/globals.go | 6 + kontor-gin/pkg/middleware/authRequired.go | 12 +- kontor-gin/pkg/middleware/cors.go | 8 +- kontor-gin/pkg/routes/comic.go | 30 ++++ kontor-gin/pkg/routes/media.go | 8 +- kontor-gin/pkg/routes/webapp.go | 18 +-- kontor-gin/pkg/schema/comic.go | 151 ++++++++++++++++++++ kontor-gin/pkg/schema/database.go | 102 ++++++------- kontor-gin/templates/html/comic_comics.tmpl | 52 +++++++ kontor-gin/templates/html/login.tmpl | 2 +- kontor-gin/templates/html/media_actor.tmpl | 12 ++ kontor-gin/templates/html/media_files.tmpl | 12 ++ kontor-gin/templates/html/media_lofi.tmpl | 12 ++ kontor-gin/templates/html/navbar.tmpl | 5 +- 24 files changed, 603 insertions(+), 101 deletions(-) create mode 100644 kontor-gin/Containerfile.local create mode 100644 kontor-gin/pkg/controllers/comic.go create mode 100644 kontor-gin/pkg/controllers/comic_api.go create mode 100644 kontor-gin/pkg/controllers/health.go create mode 100644 kontor-gin/pkg/routes/comic.go create mode 100644 kontor-gin/pkg/schema/comic.go create mode 100644 kontor-gin/templates/html/comic_comics.tmpl create mode 100644 kontor-gin/templates/html/media_actor.tmpl create mode 100644 kontor-gin/templates/html/media_files.tmpl create mode 100644 kontor-gin/templates/html/media_lofi.tmpl diff --git a/kontor-gin/Containerfile.local b/kontor-gin/Containerfile.local new file mode 100644 index 0000000..a2f1b37 --- /dev/null +++ b/kontor-gin/Containerfile.local @@ -0,0 +1,11 @@ +FROM docker.io/library/golang:1.26-alpine +# Install runtime dependencies you actually need +RUN apk add --no-cache ca-certificates tzdata curl git make musl-dev go +# Set the working directory +WORKDIR /app +# Copy the binary and set ownership +COPY ./kontor kontor +EXPOSE 8550 +ENV DB_HOST=postgres DB_USER=kontor DB_PASSWORD=kontor DB_NAME=kontor DB_PORT=5432 +# Set the entrypoint command +ENTRYPOINT ["/app/kontor"] diff --git a/kontor-gin/cmd/kontor/main.go b/kontor-gin/cmd/kontor/main.go index 095e604..4b07ef2 100644 --- a/kontor-gin/cmd/kontor/main.go +++ b/kontor-gin/cmd/kontor/main.go @@ -10,16 +10,18 @@ import ( "kontor-api-gin/pkg/schema" "kontor-api-gin/templates" "log" + "log/slog" "net/http" "os" "github.com/gin-contrib/sessions" "github.com/gin-contrib/sessions/cookie" + gin_slog "github.com/gin-contrib/slog" "github.com/gin-gonic/gin" ) func main() { - log.Println("Kontor started") + slog.Info("Kontor started") // Set Gin mode based on environment // Production mode disables debug output @@ -35,27 +37,30 @@ func main() { // Create Gin router with default middleware // Default includes Logger and Recovery middleware - //router := gin.Default() + // 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, "html/*.tmpl")) router.SetHTMLTemplate(templ) router.StaticFS("/public", http.FS(assets.StaticFiles)) - // Add CORS middleware for frontend access - // router.Use(middleware.CorsMiddleware) - - router.Use(gin.LoggerWithConfig(gin.LoggerConfig{ - SkipPaths: []string{"/health", "/metrics"}, - })) - // Add recovery middleware - router.Use(gin.Recovery()) - 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 public Webapp routes public := router.Group("/") @@ -66,23 +71,16 @@ func main() { protected.Use(middleware.AuthRequired) routes.ProtectedRoutes(protected) - // Health check endpoint - router.GET("/health", func(c *gin.Context) { - c.JSON(http.StatusOK, gin.H{ - "status": "healthy", - }) - }) - // Start server port := os.Getenv("PORT") if port == "" { port = "8550" } - log.Printf("Server starting on port %s", port) + slog.Info("Server starting", "port", port) if err := router.Run(":" + port); err != nil { log.Fatal("Failed to start server:", err) } - log.Println("Kontor finished") + slog.Info("Kontor finished") } diff --git a/kontor-gin/go.mod b/kontor-gin/go.mod index 75c70ee..2fa1ca4 100644 --- a/kontor-gin/go.mod +++ b/kontor-gin/go.mod @@ -9,6 +9,7 @@ require ( github.com/cloudwego/base64x v0.1.7 // indirect github.com/gabriel-vasile/mimetype v1.4.15 // indirect github.com/gin-contrib/sessions v1.1.2 // indirect + github.com/gin-contrib/slog v1.2.3 // indirect github.com/gin-contrib/sse v1.1.2 // indirect github.com/gin-gonic/gin v1.12.0 // indirect github.com/go-playground/locales v0.14.1 // indirect diff --git a/kontor-gin/go.sum b/kontor-gin/go.sum index 1908f13..7adb271 100644 --- a/kontor-gin/go.sum +++ b/kontor-gin/go.sum @@ -12,6 +12,8 @@ github.com/gabriel-vasile/mimetype v1.4.15 h1:05iP/CYtZ/w455R/KZM6rZ5ieAdh99UPtd 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/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= diff --git a/kontor-gin/pkg/controllers/auth.go b/kontor-gin/pkg/controllers/auth.go index 6834f3f..76e1445 100644 --- a/kontor-gin/pkg/controllers/auth.go +++ b/kontor-gin/pkg/controllers/auth.go @@ -3,10 +3,10 @@ package controllers import ( "kontor-api-gin/pkg/globals" "kontor-api-gin/pkg/helpers" - "log" "net/http" "github.com/gin-contrib/sessions" + "github.com/gin-contrib/slog" "github.com/gin-gonic/gin" ) @@ -14,10 +14,13 @@ import ( func LoginGetHandler(c *gin.Context) { session := sessions.Default(c) user := session.Get(globals.UserKey) + logger := slog.Get(c) + logger.Info("LoginGetHandler", "user", user) if user != nil { c.HTML(http.StatusBadRequest, "login.tmpl", gin.H{ "title": "Kontor Gin", "version": "0.4.0", + "login": globals.LoginUrl, "content": "Please logout first", "user": user, }) @@ -26,6 +29,7 @@ func LoginGetHandler(c *gin.Context) { c.HTML(http.StatusOK, "login.tmpl", gin.H{ "title": "Kontor Gin", "version": "0.4.0", + "login": globals.LoginUrl, "content": "", "user": user, }) @@ -34,10 +38,13 @@ func LoginGetHandler(c *gin.Context) { func LoginPostHandler(c *gin.Context) { session := sessions.Default(c) user := session.Get(globals.UserKey) + logger := slog.Get(c) + logger.Info("LoginPostHandler", "user", user) if user != nil { c.HTML(http.StatusBadRequest, "login.tmpl", gin.H{ "title": "Kontor Gin", "version": "0.4.0", + "login": globals.LoginUrl, "content": "Please logout first", }) return @@ -50,6 +57,7 @@ func LoginPostHandler(c *gin.Context) { c.HTML(http.StatusBadRequest, "login.tmpl", gin.H{ "title": "Kontor Gin", "version": "0.4.0", + "login": globals.LoginUrl, "content": "Parameters can't be empty", }) } @@ -58,6 +66,7 @@ func LoginPostHandler(c *gin.Context) { c.HTML(http.StatusUnauthorized, "login.tmpl", gin.H{ "title": "Kontor Gin", "version": "0.4.0", + "login": globals.LoginUrl, "content": "Incorrect username or password", }) } @@ -67,10 +76,12 @@ func LoginPostHandler(c *gin.Context) { c.HTML(http.StatusInternalServerError, "login.tmpl", gin.H{ "title": "Kontor Gin", "version": "0.4.0", + "login": globals.LoginUrl, "content": "Failed to save session", }) return } + logger.Info("LoginGetHandler: redirect to index") c.Redirect(http.StatusMovedPermanently, "/") } @@ -78,16 +89,17 @@ func LoginPostHandler(c *gin.Context) { func LogoutGetHandler(c *gin.Context) { session := sessions.Default(c) user := session.Get(globals.UserKey) - log.Printf("logging out user %v", user) + logger := slog.Get(c) + logger.Info("LogoutGetHandler", "user", user) if user == nil { - log.Println("Invalid session token") + logger.Info("Invalid session token") return } session.Delete(globals.UserKey) if err := session.Save(); err != nil { - log.Printf("Failed to save session: %v", err) + logger.Info("Failed to save session", "error", err) return } - + logger.Info("LogoutGetHandler: redirect to index page") c.Redirect(http.StatusMovedPermanently, "/") } diff --git a/kontor-gin/pkg/controllers/comic.go b/kontor-gin/pkg/controllers/comic.go new file mode 100644 index 0000000..15088a2 --- /dev/null +++ b/kontor-gin/pkg/controllers/comic.go @@ -0,0 +1,30 @@ +package controllers + +import ( + "kontor-api-gin/pkg/globals" + "kontor-api-gin/pkg/schema" + "net/http" + + "github.com/gin-contrib/sessions" + "github.com/gin-contrib/slog" + "github.com/gin-gonic/gin" +) + +func ListComicsGetHandler(c *gin.Context) { + session := sessions.Default(c) + user := session.Get(globals.UserKey) + logger := slog.Get(c) + + var comics []schema.Comic + + result := schema.DB.Find(&comics) + logger.Info("found Comics", "count", result.RowsAffected) + c.HTML(http.StatusOK, "comic_comics.tmpl", gin.H{ + "title": "Kontor Comics", + "version": "0.4.0", + "content": "Media Files...", + "comics": comics, + "user": user, + }) + +} diff --git a/kontor-gin/pkg/controllers/comic_api.go b/kontor-gin/pkg/controllers/comic_api.go new file mode 100644 index 0000000..2c8520e --- /dev/null +++ b/kontor-gin/pkg/controllers/comic_api.go @@ -0,0 +1,92 @@ +package controllers + +import ( + "kontor-api-gin/pkg/schema" + "net/http" + + "github.com/gin-contrib/slog" + "github.com/gin-gonic/gin" +) + +func ListComics(c *gin.Context) { + var comics []schema.Comic + + logger := slog.Get(c) + // query := schema.DB.Model(&schema.MediaFile{}) + // result := query.Find(&files) + result := schema.DB.Find(&comics) + logger.Info("found Comics", "count", result.RowsAffected) + c.JSON(http.StatusOK, comics) +} + +func ListComicPublishers(c *gin.Context) { + var publishers []schema.Publisher + + logger := slog.Get(c) + result := schema.DB.Find(&publishers) + logger.Info("found Comic Publishers", "count", result.RowsAffected) + c.JSON(http.StatusOK, publishers) +} + +func ListComicArtists(c *gin.Context) { + var artists []schema.Artist + + logger := slog.Get(c) + result := schema.DB.Find(&artists) + logger.Info("found Comic Artists", "count", result.RowsAffected) + c.JSON(http.StatusOK, artists) +} + +func ListComicIssues(c *gin.Context) { + var issues []schema.Issue + + logger := slog.Get(c) + result := schema.DB.Find(&issues) + logger.Info("found Comic Issues", "count", result.RowsAffected) + c.JSON(http.StatusOK, issues) +} + +func ListComicWorkTypes(c *gin.Context) { + var worktypes []schema.WorkType + + logger := slog.Get(c) + result := schema.DB.Find(&worktypes) + logger.Info("found Comic WorkTypes", "count", result.RowsAffected) + c.JSON(http.StatusOK, worktypes) +} + +func ListComicVolumes(c *gin.Context) { + var items []schema.Volume + + logger := slog.Get(c) + result := schema.DB.Find(&items) + logger.Info("found Comic Volumes", "count", result.RowsAffected) + c.JSON(http.StatusOK, items) +} + +func ListComicStoryArcs(c *gin.Context) { + var items []schema.StoryArc + + logger := slog.Get(c) + result := schema.DB.Find(&items) + logger.Info("found Comic StoryArcs", "count", result.RowsAffected) + c.JSON(http.StatusOK, items) +} + +func ListComicWorks(c *gin.Context) { + var items []schema.ComicWork + + logger := slog.Get(c) + result := schema.DB.Find(&items) + logger.Info("found Comic ComicWorks", "count", result.RowsAffected) + c.JSON(http.StatusOK, items) +} + +func ListComicIssueWorks(c *gin.Context) { + var items []schema.IssueWork + + logger := slog.Get(c) + result := schema.DB.Find(&items) + logger.Info("found Comic IssueWorks", "count", result.RowsAffected) + c.JSON(http.StatusOK, items) +} diff --git a/kontor-gin/pkg/controllers/handlers.go b/kontor-gin/pkg/controllers/handlers.go index 9b283c6..7cfddd2 100644 --- a/kontor-gin/pkg/controllers/handlers.go +++ b/kontor-gin/pkg/controllers/handlers.go @@ -5,16 +5,20 @@ import ( "net/http" "github.com/gin-contrib/sessions" + "github.com/gin-contrib/slog" "github.com/gin-gonic/gin" ) func IndexGetHandler(c *gin.Context) { session := sessions.Default(c) user := session.Get(globals.UserKey) + logger := slog.Get(c) + logger.Info("IndexGetHandler", "user", user) c.HTML(http.StatusOK, "index.tmpl", gin.H{ "title": "Kontor Gin", "version": "0.4.0", "content": "This is an index page...", + "logout": globals.LogoutUrl, "user": user, }) } diff --git a/kontor-gin/pkg/controllers/health.go b/kontor-gin/pkg/controllers/health.go new file mode 100644 index 0000000..94d0de8 --- /dev/null +++ b/kontor-gin/pkg/controllers/health.go @@ -0,0 +1,15 @@ +package controllers + +import ( + "kontor-api-gin/pkg/globals" + "net/http" + + "github.com/gin-gonic/gin" +) + +func HealthGetHandler(c *gin.Context) { + c.JSON(http.StatusOK, gin.H{ + "status": "healthy", + "version": globals.Version, + }) +} diff --git a/kontor-gin/pkg/controllers/media.go b/kontor-gin/pkg/controllers/media.go index 8053e67..24863bc 100644 --- a/kontor-gin/pkg/controllers/media.go +++ b/kontor-gin/pkg/controllers/media.go @@ -1,10 +1,13 @@ package controllers import ( + "kontor-api-gin/pkg/globals" "kontor-api-gin/pkg/schema" "log" "net/http" + "github.com/gin-contrib/sessions" + "github.com/gin-contrib/slog" "github.com/gin-gonic/gin" ) @@ -32,3 +35,59 @@ func ListMediaLoFis(c *gin.Context) { // "lofis": lofis, // }) } + +func ListMediaActorsGetHandler(c *gin.Context) { + session := sessions.Default(c) + user := session.Get(globals.UserKey) + + var actors []schema.MediaActor + + result := schema.DB.Find(&actors) + log.Printf("found %d MediaActors", result.RowsAffected) + c.HTML(http.StatusOK, "media_actor.tmpl", gin.H{ + "title": "Kontor Gin", + "version": "0.4.0", + "content": "Media Actors...", + "actors": actors, + "user": user, + }) +} + +func ListMediaFilesGetHandler(c *gin.Context) { + session := sessions.Default(c) + user := session.Get(globals.UserKey) + logger := slog.Get(c) + + var files []schema.MediaFile + + result := schema.DB.Find(&files) + logger.Info("found MediaFiles", "count", result.RowsAffected) + c.HTML(http.StatusOK, "media_files.tmpl", gin.H{ + "title": "Kontor Gin", + "version": "0.4.0", + "content": "Media Files...", + "files": files, + "user": user, + }) + +} + +func ListMediaLoFisGetHandler(c *gin.Context) { + session := sessions.Default(c) + user := session.Get(globals.UserKey) + logger := slog.Get(c) + + var lofis []schema.MediaLoFi + + result := schema.DB.Find(&lofis) + logger.Info("found MediaActors", "count", result.RowsAffected) + c.HTML(http.StatusOK, "media_lofi.tmpl", gin.H{ + "title": "Kontor Gin", + "version": "0.4.0", + "content": "Media LoFis...", + "lofis": lofis, + "logout": globals.LogoutUrl, + "user": user, + }) + +} diff --git a/kontor-gin/pkg/globals/globals.go b/kontor-gin/pkg/globals/globals.go index 11bbad1..3716acb 100644 --- a/kontor-gin/pkg/globals/globals.go +++ b/kontor-gin/pkg/globals/globals.go @@ -3,3 +3,9 @@ package globals var Secret = []byte("secret") const UserKey = "user" + +const Version = "0.4.0" + +const LoginUrl = "/login" + +const LogoutUrl = "/exit" diff --git a/kontor-gin/pkg/middleware/authRequired.go b/kontor-gin/pkg/middleware/authRequired.go index 3077951..cb5eb55 100644 --- a/kontor-gin/pkg/middleware/authRequired.go +++ b/kontor-gin/pkg/middleware/authRequired.go @@ -1,20 +1,22 @@ package middleware import ( - "log" + "kontor-api-gin/pkg/globals" "net/http" "github.com/gin-contrib/sessions" + "github.com/gin-contrib/slog" "github.com/gin-gonic/gin" ) func AuthRequired(c *gin.Context) { session := sessions.Default(c) - user := session.Get("user") - log.Printf("current user %v", user) + user := session.Get(globals.UserKey) + logger := slog.Get(c) + logger.Info("AuthRequired", globals.UserKey, user) if user == nil { - log.Println("User not logged in") - c.Redirect(http.StatusMovedPermanently, "/auth/login") + logger.Info("User not logged in") + c.Redirect(http.StatusMovedPermanently, globals.LoginUrl) c.Abort() return } diff --git a/kontor-gin/pkg/middleware/cors.go b/kontor-gin/pkg/middleware/cors.go index 9194bec..4c8a238 100644 --- a/kontor-gin/pkg/middleware/cors.go +++ b/kontor-gin/pkg/middleware/cors.go @@ -8,10 +8,12 @@ import ( // corsMiddleware adds CORS headers for cross-origin requests func CorsMiddleware(c *gin.Context) { - c.Writer.Header().Set("Access-Control-Allow-Origin", "*") - c.Writer.Header().Set("Access-Control-Allow-Methods", "GET, POST, PATCH, DELETE, OPTIONS") - c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization") + c.Header("Access-Control-Allow-Origin", "*") + c.Header("Access-Control-Allow-Methods", "GET, POST, PUT, PATCH, DELETE, OPTIONS") + c.Header("Access-Control-Allow-Headers", "Content-Type, Authorization") + c.Header("Access-Control-Allow-Credentials", "true") + // Handle preflight requests if c.Request.Method == "OPTIONS" { c.AbortWithStatus(http.StatusNoContent) return diff --git a/kontor-gin/pkg/routes/comic.go b/kontor-gin/pkg/routes/comic.go new file mode 100644 index 0000000..d4c4646 --- /dev/null +++ b/kontor-gin/pkg/routes/comic.go @@ -0,0 +1,30 @@ +package routes + +import ( + "kontor-api-gin/pkg/controllers" + + "github.com/gin-gonic/gin" +) + +func SetupComicRoutes(router *gin.RouterGroup) { + comic := router.Group("/comics") + + comics := comic.Group("/comics") + comics.GET("", controllers.ListComics) + publishers := comic.Group("/publishers") + publishers.GET("", controllers.ListComicPublishers) + artists := comic.Group("/artists") + artists.GET("/", controllers.ListComicArtists) + issues := comic.Group("/issues") + issues.GET("", controllers.ListComicIssues) + worktypes := comic.Group("/worktypes") + worktypes.GET("", controllers.ListComicWorkTypes) + volumes := comic.Group("/volumes") + volumes.GET("", controllers.ListComicVolumes) + storyarcs := comic.Group("/storyarcs") + storyarcs.GET("", controllers.ListComicStoryArcs) + comicworks := comic.Group("/comicworks") + comicworks.GET("", controllers.ListComicWorks) + issueworks := comic.Group("/issueworks") + issueworks.GET("", controllers.ListComicIssueWorks) +} diff --git a/kontor-gin/pkg/routes/media.go b/kontor-gin/pkg/routes/media.go index c762351..09d81c3 100644 --- a/kontor-gin/pkg/routes/media.go +++ b/kontor-gin/pkg/routes/media.go @@ -10,12 +10,8 @@ func SetupMediaRoutes(router *gin.RouterGroup) { media := router.Group("/media") files := media.Group("/files") - { - files.GET("", controllers.ListMediaFiles) - } + files.GET("", controllers.ListMediaFiles) lofi := media.Group("/lofi") - { - lofi.GET("", controllers.ListMediaLoFis) - } + lofi.GET("/", controllers.ListMediaLoFis) } diff --git a/kontor-gin/pkg/routes/webapp.go b/kontor-gin/pkg/routes/webapp.go index 8aa1ca9..c291058 100644 --- a/kontor-gin/pkg/routes/webapp.go +++ b/kontor-gin/pkg/routes/webapp.go @@ -2,20 +2,22 @@ package routes import ( "kontor-api-gin/pkg/controllers" + "kontor-api-gin/pkg/globals" "github.com/gin-gonic/gin" ) func PublicRoutes(g *gin.RouterGroup) { - g.GET("/auth/login", controllers.LoginGetHandler) - g.POST("/auth/login", controllers.LoginPostHandler) - g.GET("/", controllers.IndexGetHandler) + g.GET("/health", controllers.HealthGetHandler) + g.GET(globals.LoginUrl, controllers.LoginGetHandler) + g.POST(globals.LoginUrl, controllers.LoginPostHandler) + g.GET("/comic/comics", controllers.ListComicsGetHandler) + g.GET("", controllers.IndexGetHandler) } func ProtectedRoutes(g *gin.RouterGroup) { - g.GET("/media/actors", controllers.IndexGetHandler) - g.GET("/comic/comics", controllers.IndexGetHandler) - g.GET("/media/files", controllers.IndexGetHandler) - g.GET("/media/lofis", controllers.IndexGetHandler) - g.GET("/auth/logout", controllers.LogoutGetHandler) + g.GET(globals.LogoutUrl, controllers.LogoutGetHandler) + g.GET("/media/actors", controllers.ListMediaActorsGetHandler) + // g.GET("/media/files", controllers.ListMediaFilesGetHandler) + g.GET("/media/lofis", controllers.ListMediaLoFisGetHandler) } diff --git a/kontor-gin/pkg/schema/comic.go b/kontor-gin/pkg/schema/comic.go new file mode 100644 index 0000000..b88719d --- /dev/null +++ b/kontor-gin/pkg/schema/comic.go @@ -0,0 +1,151 @@ +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/database.go b/kontor-gin/pkg/schema/database.go index ce647f9..f68ffe9 100644 --- a/kontor-gin/pkg/schema/database.go +++ b/kontor-gin/pkg/schema/database.go @@ -1,69 +1,69 @@ package schema import ( - "fmt" - "log" - "os" - "time" + "fmt" + "log" + "log/slog" + "os" + "time" - "gorm.io/driver/postgres" - "gorm.io/gorm" - "gorm.io/gorm/logger" - + "gorm.io/driver/postgres" + "gorm.io/gorm" + "gorm.io/gorm/logger" ) var DB *gorm.DB func Connect() { - // Build the connection string from environment variables - // This keeps sensitive credentials out of the codebase - dsn := fmt.Sprintf( - "host=%s user=%s password=%s dbname=%s port=%s sslmode=disable", - os.Getenv("DB_HOST"), - os.Getenv("DB_USER"), - os.Getenv("DB_PASSWORD"), - os.Getenv("DB_NAME"), - os.Getenv("DB_PORT"), - ) + // Build the connection string from environment variables + // This keeps sensitive credentials out of the codebase + dsn := fmt.Sprintf( + "host=%s user=%s password=%s dbname=%s port=%s sslmode=disable", + os.Getenv("DB_HOST"), + os.Getenv("DB_USER"), + os.Getenv("DB_PASSWORD"), + os.Getenv("DB_NAME"), + os.Getenv("DB_PORT"), + ) - // Configure GORM with custom settings - // The logger helps with debugging SQL queries during development - db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{ - Logger: logger.Default.LogMode(logger.Info), - }) - if err != nil { - log.Fatal("Failed to connect to database:", err) - } + // Configure GORM with custom settings + // The logger helps with debugging SQL queries during development + db, err := 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) - } + // 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) + // 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) - DB = db - log.Println("Database connected successfully") + DB = db + slog.Info("Database connected successfully") } 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{}, - ) - if err != nil { - log.Fatal("Migration failed:", err) - } + // AutoMigrate creates tables, missing columns, and missing indexes + // It will NOT delete unused columns to protect your data + err := DB.AutoMigrate( + &MediaActor{}, + &MediaFile{}, + &MediaActorFile{}, + ) + if err != nil { + log.Fatal("Migration failed:", err) + } - log.Println("Database migration completed") + slog.Info("Database migration completed") } diff --git a/kontor-gin/templates/html/comic_comics.tmpl b/kontor-gin/templates/html/comic_comics.tmpl new file mode 100644 index 0000000..319625f --- /dev/null +++ b/kontor-gin/templates/html/comic_comics.tmpl @@ -0,0 +1,52 @@ + + +{{ template "header.tmpl" . }} + + +
+ {{ template "navbar.tmpl" . }} +
{{ .content }}
+
+
+ + + + + +
+
+
+
+ + + + + + + + + {{ range .comics }} + + + {{ if .Publisher }} + + {{ else }} + + {{ end }} + + + + + {{ end }} + +
TitlePublisherCompletedActions
{{.Title}}{{.Publisher.Name}}Publisher not set{{ .Completed }}EditDelete
+
+
+ + {{ template "footer.tmpl" . }} + + diff --git a/kontor-gin/templates/html/login.tmpl b/kontor-gin/templates/html/login.tmpl index 5a05568..92f6948 100644 --- a/kontor-gin/templates/html/login.tmpl +++ b/kontor-gin/templates/html/login.tmpl @@ -7,7 +7,7 @@ {{ template "navbar.tmpl" . }}
-
+
diff --git a/kontor-gin/templates/html/media_actor.tmpl b/kontor-gin/templates/html/media_actor.tmpl new file mode 100644 index 0000000..6663d53 --- /dev/null +++ b/kontor-gin/templates/html/media_actor.tmpl @@ -0,0 +1,12 @@ + + +{{ template "header.tmpl" . }} + + +
+ {{ template "navbar.tmpl" . }} +
{{ .content }}
+ + {{ template "footer.tmpl" . }} + + diff --git a/kontor-gin/templates/html/media_files.tmpl b/kontor-gin/templates/html/media_files.tmpl new file mode 100644 index 0000000..6663d53 --- /dev/null +++ b/kontor-gin/templates/html/media_files.tmpl @@ -0,0 +1,12 @@ + + +{{ template "header.tmpl" . }} + + +
+ {{ template "navbar.tmpl" . }} +
{{ .content }}
+ + {{ template "footer.tmpl" . }} + + diff --git a/kontor-gin/templates/html/media_lofi.tmpl b/kontor-gin/templates/html/media_lofi.tmpl new file mode 100644 index 0000000..6663d53 --- /dev/null +++ b/kontor-gin/templates/html/media_lofi.tmpl @@ -0,0 +1,12 @@ + + +{{ template "header.tmpl" . }} + + +
+ {{ template "navbar.tmpl" . }} +
{{ .content }}
+ + {{ template "footer.tmpl" . }} + + diff --git a/kontor-gin/templates/html/navbar.tmpl b/kontor-gin/templates/html/navbar.tmpl index 41e017f..e61d977 100644 --- a/kontor-gin/templates/html/navbar.tmpl +++ b/kontor-gin/templates/html/navbar.tmpl @@ -3,10 +3,11 @@ Kontor Comics Media + Lofi {{ if .user }} - Logout + Logout {{ else }} - Login + Login {{ end }}
{{ end }}