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 }}
+
+
+
+
+
+ | Title |
+ Publisher |
+ Completed |
+ Actions |
+
+
+ {{ range .comics }}
+
+ | {{.Title}} |
+ {{ if .Publisher }}
+ {{.Publisher.Name}} |
+ {{ else }}
+ Publisher not set |
+ {{ end }}
+ {{ .Completed }} |
+ Edit |
+ Delete |
+
+ {{ end }}
+
+
+
+
+
+ {{ 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" . }}