This commit is contained in:
@@ -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"]
|
||||||
@@ -10,16 +10,18 @@ import (
|
|||||||
"kontor-api-gin/pkg/schema"
|
"kontor-api-gin/pkg/schema"
|
||||||
"kontor-api-gin/templates"
|
"kontor-api-gin/templates"
|
||||||
"log"
|
"log"
|
||||||
|
"log/slog"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/gin-contrib/sessions"
|
"github.com/gin-contrib/sessions"
|
||||||
"github.com/gin-contrib/sessions/cookie"
|
"github.com/gin-contrib/sessions/cookie"
|
||||||
|
gin_slog "github.com/gin-contrib/slog"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
log.Println("Kontor started")
|
slog.Info("Kontor started")
|
||||||
|
|
||||||
// Set Gin mode based on environment
|
// Set Gin mode based on environment
|
||||||
// Production mode disables debug output
|
// Production mode disables debug output
|
||||||
@@ -35,27 +37,30 @@ func main() {
|
|||||||
|
|
||||||
// Create Gin router with default middleware
|
// Create Gin router with default middleware
|
||||||
// Default includes Logger and Recovery middleware
|
// Default includes Logger and Recovery middleware
|
||||||
//router := gin.Default()
|
// router := gin.Default()
|
||||||
// Create a new router without default mmiddleware
|
// Create a new router without default mmiddleware
|
||||||
router := gin.New()
|
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"))
|
templ := template.Must(template.New("").ParseFS(templates.TemplateFiles, "html/*.tmpl"))
|
||||||
router.SetHTMLTemplate(templ)
|
router.SetHTMLTemplate(templ)
|
||||||
|
|
||||||
router.StaticFS("/public", http.FS(assets.StaticFiles))
|
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)))
|
router.Use(sessions.Sessions("session", cookie.NewStore(globals.Secret)))
|
||||||
|
|
||||||
// Setup api routes
|
// Setup api routes
|
||||||
api := routes.SetupApiRoutes(router)
|
api := routes.SetupApiRoutes(router)
|
||||||
|
api.Use(middleware.CorsMiddleware)
|
||||||
routes.SetupMediaRoutes(api)
|
routes.SetupMediaRoutes(api)
|
||||||
|
routes.SetupComicRoutes(api)
|
||||||
|
|
||||||
// Setup public Webapp routes
|
// Setup public Webapp routes
|
||||||
public := router.Group("/")
|
public := router.Group("/")
|
||||||
@@ -66,23 +71,16 @@ func main() {
|
|||||||
protected.Use(middleware.AuthRequired)
|
protected.Use(middleware.AuthRequired)
|
||||||
routes.ProtectedRoutes(protected)
|
routes.ProtectedRoutes(protected)
|
||||||
|
|
||||||
// Health check endpoint
|
|
||||||
router.GET("/health", func(c *gin.Context) {
|
|
||||||
c.JSON(http.StatusOK, gin.H{
|
|
||||||
"status": "healthy",
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
// Start server
|
// Start server
|
||||||
port := os.Getenv("PORT")
|
port := os.Getenv("PORT")
|
||||||
if port == "" {
|
if port == "" {
|
||||||
port = "8550"
|
port = "8550"
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Printf("Server starting on port %s", port)
|
slog.Info("Server starting", "port", port)
|
||||||
if err := router.Run(":" + port); err != nil {
|
if err := router.Run(":" + port); err != nil {
|
||||||
log.Fatal("Failed to start server:", err)
|
log.Fatal("Failed to start server:", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Println("Kontor finished")
|
slog.Info("Kontor finished")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ require (
|
|||||||
github.com/cloudwego/base64x v0.1.7 // indirect
|
github.com/cloudwego/base64x v0.1.7 // indirect
|
||||||
github.com/gabriel-vasile/mimetype v1.4.15 // indirect
|
github.com/gabriel-vasile/mimetype v1.4.15 // indirect
|
||||||
github.com/gin-contrib/sessions v1.1.2 // 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-contrib/sse v1.1.2 // indirect
|
||||||
github.com/gin-gonic/gin v1.12.0 // indirect
|
github.com/gin-gonic/gin v1.12.0 // indirect
|
||||||
github.com/go-playground/locales v0.14.1 // indirect
|
github.com/go-playground/locales v0.14.1 // indirect
|
||||||
|
|||||||
@@ -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/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 h1:utWRqOHT/kbqxxqF/mtutV9wfGLi1+uu5YuvWUhj/Ow=
|
||||||
github.com/gin-contrib/sessions v1.1.2/go.mod h1:qRxYwA/TYJw0wv28oFzs4gY/8G8z69id862SNgX9Gmw=
|
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 h1:MU2fgl1RrdYTMcgJLtz2kJF+vPg3xrqaaKfUUU18tCo=
|
||||||
github.com/gin-contrib/sse v1.1.2/go.mod h1:QXzuVkA0YO7o/gun03UI1Q+FTI8ZV/n5t03kIQAI89s=
|
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 h1:b3YAbrZtnf8N//yjKeU2+MQsh2mY5htkZidOM7O0wG8=
|
||||||
|
|||||||
@@ -3,10 +3,10 @@ package controllers
|
|||||||
import (
|
import (
|
||||||
"kontor-api-gin/pkg/globals"
|
"kontor-api-gin/pkg/globals"
|
||||||
"kontor-api-gin/pkg/helpers"
|
"kontor-api-gin/pkg/helpers"
|
||||||
"log"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/gin-contrib/sessions"
|
"github.com/gin-contrib/sessions"
|
||||||
|
"github.com/gin-contrib/slog"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
@@ -14,10 +14,13 @@ import (
|
|||||||
func LoginGetHandler(c *gin.Context) {
|
func LoginGetHandler(c *gin.Context) {
|
||||||
session := sessions.Default(c)
|
session := sessions.Default(c)
|
||||||
user := session.Get(globals.UserKey)
|
user := session.Get(globals.UserKey)
|
||||||
|
logger := slog.Get(c)
|
||||||
|
logger.Info("LoginGetHandler", "user", user)
|
||||||
if user != nil {
|
if user != nil {
|
||||||
c.HTML(http.StatusBadRequest, "login.tmpl", gin.H{
|
c.HTML(http.StatusBadRequest, "login.tmpl", gin.H{
|
||||||
"title": "Kontor Gin",
|
"title": "Kontor Gin",
|
||||||
"version": "0.4.0",
|
"version": "0.4.0",
|
||||||
|
"login": globals.LoginUrl,
|
||||||
"content": "Please logout first",
|
"content": "Please logout first",
|
||||||
"user": user,
|
"user": user,
|
||||||
})
|
})
|
||||||
@@ -26,6 +29,7 @@ func LoginGetHandler(c *gin.Context) {
|
|||||||
c.HTML(http.StatusOK, "login.tmpl", gin.H{
|
c.HTML(http.StatusOK, "login.tmpl", gin.H{
|
||||||
"title": "Kontor Gin",
|
"title": "Kontor Gin",
|
||||||
"version": "0.4.0",
|
"version": "0.4.0",
|
||||||
|
"login": globals.LoginUrl,
|
||||||
"content": "",
|
"content": "",
|
||||||
"user": user,
|
"user": user,
|
||||||
})
|
})
|
||||||
@@ -34,10 +38,13 @@ func LoginGetHandler(c *gin.Context) {
|
|||||||
func LoginPostHandler(c *gin.Context) {
|
func LoginPostHandler(c *gin.Context) {
|
||||||
session := sessions.Default(c)
|
session := sessions.Default(c)
|
||||||
user := session.Get(globals.UserKey)
|
user := session.Get(globals.UserKey)
|
||||||
|
logger := slog.Get(c)
|
||||||
|
logger.Info("LoginPostHandler", "user", user)
|
||||||
if user != nil {
|
if user != nil {
|
||||||
c.HTML(http.StatusBadRequest, "login.tmpl", gin.H{
|
c.HTML(http.StatusBadRequest, "login.tmpl", gin.H{
|
||||||
"title": "Kontor Gin",
|
"title": "Kontor Gin",
|
||||||
"version": "0.4.0",
|
"version": "0.4.0",
|
||||||
|
"login": globals.LoginUrl,
|
||||||
"content": "Please logout first",
|
"content": "Please logout first",
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
@@ -50,6 +57,7 @@ func LoginPostHandler(c *gin.Context) {
|
|||||||
c.HTML(http.StatusBadRequest, "login.tmpl", gin.H{
|
c.HTML(http.StatusBadRequest, "login.tmpl", gin.H{
|
||||||
"title": "Kontor Gin",
|
"title": "Kontor Gin",
|
||||||
"version": "0.4.0",
|
"version": "0.4.0",
|
||||||
|
"login": globals.LoginUrl,
|
||||||
"content": "Parameters can't be empty",
|
"content": "Parameters can't be empty",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -58,6 +66,7 @@ func LoginPostHandler(c *gin.Context) {
|
|||||||
c.HTML(http.StatusUnauthorized, "login.tmpl", gin.H{
|
c.HTML(http.StatusUnauthorized, "login.tmpl", gin.H{
|
||||||
"title": "Kontor Gin",
|
"title": "Kontor Gin",
|
||||||
"version": "0.4.0",
|
"version": "0.4.0",
|
||||||
|
"login": globals.LoginUrl,
|
||||||
"content": "Incorrect username or password",
|
"content": "Incorrect username or password",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -67,10 +76,12 @@ func LoginPostHandler(c *gin.Context) {
|
|||||||
c.HTML(http.StatusInternalServerError, "login.tmpl", gin.H{
|
c.HTML(http.StatusInternalServerError, "login.tmpl", gin.H{
|
||||||
"title": "Kontor Gin",
|
"title": "Kontor Gin",
|
||||||
"version": "0.4.0",
|
"version": "0.4.0",
|
||||||
|
"login": globals.LoginUrl,
|
||||||
"content": "Failed to save session",
|
"content": "Failed to save session",
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
logger.Info("LoginGetHandler: redirect to index")
|
||||||
|
|
||||||
c.Redirect(http.StatusMovedPermanently, "/")
|
c.Redirect(http.StatusMovedPermanently, "/")
|
||||||
}
|
}
|
||||||
@@ -78,16 +89,17 @@ func LoginPostHandler(c *gin.Context) {
|
|||||||
func LogoutGetHandler(c *gin.Context) {
|
func LogoutGetHandler(c *gin.Context) {
|
||||||
session := sessions.Default(c)
|
session := sessions.Default(c)
|
||||||
user := session.Get(globals.UserKey)
|
user := session.Get(globals.UserKey)
|
||||||
log.Printf("logging out user %v", user)
|
logger := slog.Get(c)
|
||||||
|
logger.Info("LogoutGetHandler", "user", user)
|
||||||
if user == nil {
|
if user == nil {
|
||||||
log.Println("Invalid session token")
|
logger.Info("Invalid session token")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
session.Delete(globals.UserKey)
|
session.Delete(globals.UserKey)
|
||||||
if err := session.Save(); err != nil {
|
if err := session.Save(); err != nil {
|
||||||
log.Printf("Failed to save session: %v", err)
|
logger.Info("Failed to save session", "error", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
logger.Info("LogoutGetHandler: redirect to index page")
|
||||||
c.Redirect(http.StatusMovedPermanently, "/")
|
c.Redirect(http.StatusMovedPermanently, "/")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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)
|
||||||
|
}
|
||||||
@@ -5,16 +5,20 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/gin-contrib/sessions"
|
"github.com/gin-contrib/sessions"
|
||||||
|
"github.com/gin-contrib/slog"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
func IndexGetHandler(c *gin.Context) {
|
func IndexGetHandler(c *gin.Context) {
|
||||||
session := sessions.Default(c)
|
session := sessions.Default(c)
|
||||||
user := session.Get(globals.UserKey)
|
user := session.Get(globals.UserKey)
|
||||||
|
logger := slog.Get(c)
|
||||||
|
logger.Info("IndexGetHandler", "user", user)
|
||||||
c.HTML(http.StatusOK, "index.tmpl", gin.H{
|
c.HTML(http.StatusOK, "index.tmpl", gin.H{
|
||||||
"title": "Kontor Gin",
|
"title": "Kontor Gin",
|
||||||
"version": "0.4.0",
|
"version": "0.4.0",
|
||||||
"content": "This is an index page...",
|
"content": "This is an index page...",
|
||||||
|
"logout": globals.LogoutUrl,
|
||||||
"user": user,
|
"user": user,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -1,10 +1,13 @@
|
|||||||
package controllers
|
package controllers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"kontor-api-gin/pkg/globals"
|
||||||
"kontor-api-gin/pkg/schema"
|
"kontor-api-gin/pkg/schema"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/gin-contrib/sessions"
|
||||||
|
"github.com/gin-contrib/slog"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -32,3 +35,59 @@ func ListMediaLoFis(c *gin.Context) {
|
|||||||
// "lofis": lofis,
|
// "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,
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,3 +3,9 @@ package globals
|
|||||||
var Secret = []byte("secret")
|
var Secret = []byte("secret")
|
||||||
|
|
||||||
const UserKey = "user"
|
const UserKey = "user"
|
||||||
|
|
||||||
|
const Version = "0.4.0"
|
||||||
|
|
||||||
|
const LoginUrl = "/login"
|
||||||
|
|
||||||
|
const LogoutUrl = "/exit"
|
||||||
|
|||||||
@@ -1,20 +1,22 @@
|
|||||||
package middleware
|
package middleware
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"log"
|
"kontor-api-gin/pkg/globals"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/gin-contrib/sessions"
|
"github.com/gin-contrib/sessions"
|
||||||
|
"github.com/gin-contrib/slog"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
func AuthRequired(c *gin.Context) {
|
func AuthRequired(c *gin.Context) {
|
||||||
session := sessions.Default(c)
|
session := sessions.Default(c)
|
||||||
user := session.Get("user")
|
user := session.Get(globals.UserKey)
|
||||||
log.Printf("current user %v", user)
|
logger := slog.Get(c)
|
||||||
|
logger.Info("AuthRequired", globals.UserKey, user)
|
||||||
if user == nil {
|
if user == nil {
|
||||||
log.Println("User not logged in")
|
logger.Info("User not logged in")
|
||||||
c.Redirect(http.StatusMovedPermanently, "/auth/login")
|
c.Redirect(http.StatusMovedPermanently, globals.LoginUrl)
|
||||||
c.Abort()
|
c.Abort()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,10 +8,12 @@ import (
|
|||||||
|
|
||||||
// corsMiddleware adds CORS headers for cross-origin requests
|
// corsMiddleware adds CORS headers for cross-origin requests
|
||||||
func CorsMiddleware(c *gin.Context) {
|
func CorsMiddleware(c *gin.Context) {
|
||||||
c.Writer.Header().Set("Access-Control-Allow-Origin", "*")
|
c.Header("Access-Control-Allow-Origin", "*")
|
||||||
c.Writer.Header().Set("Access-Control-Allow-Methods", "GET, POST, PATCH, DELETE, OPTIONS")
|
c.Header("Access-Control-Allow-Methods", "GET, POST, PUT, PATCH, DELETE, OPTIONS")
|
||||||
c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")
|
c.Header("Access-Control-Allow-Headers", "Content-Type, Authorization")
|
||||||
|
c.Header("Access-Control-Allow-Credentials", "true")
|
||||||
|
|
||||||
|
// Handle preflight requests
|
||||||
if c.Request.Method == "OPTIONS" {
|
if c.Request.Method == "OPTIONS" {
|
||||||
c.AbortWithStatus(http.StatusNoContent)
|
c.AbortWithStatus(http.StatusNoContent)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -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)
|
||||||
|
}
|
||||||
@@ -10,12 +10,8 @@ func SetupMediaRoutes(router *gin.RouterGroup) {
|
|||||||
media := router.Group("/media")
|
media := router.Group("/media")
|
||||||
|
|
||||||
files := media.Group("/files")
|
files := media.Group("/files")
|
||||||
{
|
|
||||||
files.GET("", controllers.ListMediaFiles)
|
files.GET("", controllers.ListMediaFiles)
|
||||||
}
|
|
||||||
|
|
||||||
lofi := media.Group("/lofi")
|
lofi := media.Group("/lofi")
|
||||||
{
|
lofi.GET("/", controllers.ListMediaLoFis)
|
||||||
lofi.GET("", controllers.ListMediaLoFis)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,20 +2,22 @@ package routes
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"kontor-api-gin/pkg/controllers"
|
"kontor-api-gin/pkg/controllers"
|
||||||
|
"kontor-api-gin/pkg/globals"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
func PublicRoutes(g *gin.RouterGroup) {
|
func PublicRoutes(g *gin.RouterGroup) {
|
||||||
g.GET("/auth/login", controllers.LoginGetHandler)
|
g.GET("/health", controllers.HealthGetHandler)
|
||||||
g.POST("/auth/login", controllers.LoginPostHandler)
|
g.GET(globals.LoginUrl, controllers.LoginGetHandler)
|
||||||
g.GET("/", controllers.IndexGetHandler)
|
g.POST(globals.LoginUrl, controllers.LoginPostHandler)
|
||||||
|
g.GET("/comic/comics", controllers.ListComicsGetHandler)
|
||||||
|
g.GET("", controllers.IndexGetHandler)
|
||||||
}
|
}
|
||||||
|
|
||||||
func ProtectedRoutes(g *gin.RouterGroup) {
|
func ProtectedRoutes(g *gin.RouterGroup) {
|
||||||
g.GET("/media/actors", controllers.IndexGetHandler)
|
g.GET(globals.LogoutUrl, controllers.LogoutGetHandler)
|
||||||
g.GET("/comic/comics", controllers.IndexGetHandler)
|
g.GET("/media/actors", controllers.ListMediaActorsGetHandler)
|
||||||
g.GET("/media/files", controllers.IndexGetHandler)
|
// g.GET("/media/files", controllers.ListMediaFilesGetHandler)
|
||||||
g.GET("/media/lofis", controllers.IndexGetHandler)
|
g.GET("/media/lofis", controllers.ListMediaLoFisGetHandler)
|
||||||
g.GET("/auth/logout", controllers.LogoutGetHandler)
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"
|
||||||
|
}
|
||||||
@@ -3,13 +3,13 @@ package schema
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
|
"log/slog"
|
||||||
"os"
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"gorm.io/driver/postgres"
|
"gorm.io/driver/postgres"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
"gorm.io/gorm/logger"
|
"gorm.io/gorm/logger"
|
||||||
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var DB *gorm.DB
|
var DB *gorm.DB
|
||||||
@@ -50,7 +50,7 @@ func Connect() {
|
|||||||
sqlDB.SetConnMaxLifetime(time.Hour)
|
sqlDB.SetConnMaxLifetime(time.Hour)
|
||||||
|
|
||||||
DB = db
|
DB = db
|
||||||
log.Println("Database connected successfully")
|
slog.Info("Database connected successfully")
|
||||||
}
|
}
|
||||||
|
|
||||||
func RunMigrations() {
|
func RunMigrations() {
|
||||||
@@ -65,5 +65,5 @@ func RunMigrations() {
|
|||||||
log.Fatal("Migration failed:", err)
|
log.Fatal("Migration failed:", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Println("Database migration completed")
|
slog.Info("Database migration completed")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,52 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="de-DE">
|
||||||
|
{{ template "header.tmpl" . }}
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div class="header"></div>
|
||||||
|
{{ template "navbar.tmpl" . }}
|
||||||
|
<div class="content">{{ .content }}</div>
|
||||||
|
<div class="row">
|
||||||
|
<form class="form-inline" action="/comic/comics">
|
||||||
|
<input type="text" placeholder="Search" name="query">
|
||||||
|
<label>
|
||||||
|
<input type="checkbox" name="completed" {{ if .completed }}checked{{ end }}>Completed
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
<input type="checkbox" name="order" {{ if .order }}checked{{ end }}>Order
|
||||||
|
</label>
|
||||||
|
<button type="submit">Search</button>
|
||||||
|
<div class="pill-nav"><a href="/comic/comic/add">Add Comic</a></div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="maincolumn">
|
||||||
|
<table class="table table-hover">
|
||||||
|
<thead><tr>
|
||||||
|
<th scope="col">Title</th>
|
||||||
|
<th scope="col">Publisher</th>
|
||||||
|
<th scope="col">Completed</th>
|
||||||
|
<th colspan="2">Actions</th>
|
||||||
|
</tr></thead>
|
||||||
|
<tbody>
|
||||||
|
{{ range .comics }}
|
||||||
|
<tr>
|
||||||
|
<th scope="row"><a href="/comic/comics/{{.ID}}">{{.Title}}</a></th>
|
||||||
|
{{ if .Publisher }}
|
||||||
|
<td><a href="/comic/publishers/{{.Publisher.ID}}">{{.Publisher.Name}}</a></td>
|
||||||
|
{{ else }}
|
||||||
|
<td>Publisher not set</td>
|
||||||
|
{{ end }}
|
||||||
|
<td>{{ .Completed }}</td>
|
||||||
|
<td><a href="/comic/comics/edit/{{.ID}}" class="btn btn-outline-primary btn-sm active" role="button" aria-pressed="true">Edit</a></td>
|
||||||
|
<td><a href="/comic/comics/delete/{{.ID}}" class="btn btn-outline-danger btn-sm active" role="button" aria-pressed="true">Delete</a></td>
|
||||||
|
</tr>
|
||||||
|
{{ end }}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{ template "footer.tmpl" . }}
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -7,7 +7,7 @@
|
|||||||
{{ template "navbar.tmpl" . }}
|
{{ template "navbar.tmpl" . }}
|
||||||
|
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<form action="/auth/login" method="post">
|
<form action={{ .login }} method="post">
|
||||||
|
|
||||||
<div class="container" style="border: 1px solid lightgray; border-radius: 3px; padding: 15px;">
|
<div class="container" style="border: 1px solid lightgray; border-radius: 3px; padding: 15px;">
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="de-DE">
|
||||||
|
{{ template "header.tmpl" . }}
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div class="header"></div>
|
||||||
|
{{ template "navbar.tmpl" . }}
|
||||||
|
<div class="content">{{ .content }}</div>
|
||||||
|
|
||||||
|
{{ template "footer.tmpl" . }}
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="de-DE">
|
||||||
|
{{ template "header.tmpl" . }}
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div class="header"></div>
|
||||||
|
{{ template "navbar.tmpl" . }}
|
||||||
|
<div class="content">{{ .content }}</div>
|
||||||
|
|
||||||
|
{{ template "footer.tmpl" . }}
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="de-DE">
|
||||||
|
{{ template "header.tmpl" . }}
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div class="header"></div>
|
||||||
|
{{ template "navbar.tmpl" . }}
|
||||||
|
<div class="content">{{ .content }}</div>
|
||||||
|
|
||||||
|
{{ template "footer.tmpl" . }}
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -3,10 +3,11 @@
|
|||||||
<a href="/">Kontor</a>
|
<a href="/">Kontor</a>
|
||||||
<a href="/comic/comics">Comics</a>
|
<a href="/comic/comics">Comics</a>
|
||||||
<a href="/media/files">Media</a>
|
<a href="/media/files">Media</a>
|
||||||
|
<a href="/media/lofis">Lofi</a>
|
||||||
{{ if .user }}
|
{{ if .user }}
|
||||||
<a style="float:right" href="/auth/logout/">Logout</a></li>
|
<a style="float:right" href={{ .logout }}>Logout</a></li>
|
||||||
{{ else }}
|
{{ else }}
|
||||||
<a style="float:right" href="/auth/login/">Login</a></li>
|
<a style="float:right" href="/login">Login</a></li>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
</div>
|
</div>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|||||||
Reference in New Issue
Block a user