Add user theme support and migrate frontend to HTMX templates
- Add theme column to users table with default 'tokyo-night' - Update all user queries to include theme field - Add ListUsers and UpdateUserTheme database queries - Update auth handlers to support HTMX form submissions and JSON API - Add ListUsers API endpoint - Replace embedded static files with Go templates - Update Dockerfile to copy templates directory - Redesign index.html with inline styles and HTMX forms - Update Bruno API testing requests for auth endpoints
This commit is contained in:
+25
-38
@@ -4,12 +4,10 @@ import (
|
||||
"bookmann/internal/config"
|
||||
"bookmann/internal/database"
|
||||
"bookmann/internal/handlers"
|
||||
"embed"
|
||||
"html/template"
|
||||
"io"
|
||||
"log"
|
||||
"mime"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/go-playground/validator/v10"
|
||||
jwtgo "github.com/golang-jwt/jwt"
|
||||
@@ -17,8 +15,13 @@ import (
|
||||
"github.com/labstack/echo/v4/middleware"
|
||||
)
|
||||
|
||||
//go:embed static/*
|
||||
var staticFS embed.FS
|
||||
type TemplateRenderer struct {
|
||||
templates *template.Template
|
||||
}
|
||||
|
||||
func (t *TemplateRenderer) Render(w io.Writer, name string, data interface{}, c echo.Context) error {
|
||||
return t.templates.ExecuteTemplate(w, name, data)
|
||||
}
|
||||
|
||||
// CustomValidator wraps the go-playground validator
|
||||
type CustomValidator struct {
|
||||
@@ -45,6 +48,12 @@ func main() {
|
||||
// Set up validator
|
||||
e.Validator = &CustomValidator{validator: validator.New()}
|
||||
|
||||
// Set up templates
|
||||
renderer := &TemplateRenderer{
|
||||
templates: template.Must(template.ParseGlob("templates/*.html")),
|
||||
}
|
||||
e.Renderer = renderer
|
||||
|
||||
// Middleware
|
||||
e.Use(middleware.Logger())
|
||||
e.Use(middleware.Recover())
|
||||
@@ -69,42 +78,20 @@ func main() {
|
||||
// Protected routes
|
||||
protected := e.Group("/api", jwtMiddleware)
|
||||
protected.GET("/auth/profile", auth.GetProfile)
|
||||
protected.GET("/users", auth.ListUsers)
|
||||
|
||||
// Routes
|
||||
handlers.SetupRoutes(protected, queries)
|
||||
|
||||
// Serve static files with SPA fallback from embedded FS
|
||||
e.GET("/*", func(c echo.Context) error {
|
||||
path := c.Request().URL.Path
|
||||
if strings.HasPrefix(path, "/api") {
|
||||
return c.String(http.StatusNotFound, "Not found")
|
||||
}
|
||||
// Try to serve the file from embedded FS
|
||||
filePath := "static" + path
|
||||
if file, err := staticFS.Open(filePath); err == nil {
|
||||
defer file.Close()
|
||||
contentType := mime.TypeByExtension(filepath.Ext(filePath))
|
||||
if contentType == "" {
|
||||
contentType = "application/octet-stream"
|
||||
}
|
||||
return c.Stream(http.StatusOK, contentType, file)
|
||||
}
|
||||
// Try .html extension
|
||||
htmlPath := filePath + ".html"
|
||||
if file, err := staticFS.Open(htmlPath); err == nil {
|
||||
defer file.Close()
|
||||
return c.Stream(http.StatusOK, "text/html", file)
|
||||
}
|
||||
// Try /index.html
|
||||
indexPath := filePath + "/index.html"
|
||||
if file, err := staticFS.Open(indexPath); err == nil {
|
||||
defer file.Close()
|
||||
return c.Stream(http.StatusOK, "text/html", file)
|
||||
}
|
||||
// Fallback to index.html
|
||||
file, _ := staticFS.Open("static/index.html")
|
||||
defer file.Close()
|
||||
return c.Stream(http.StatusOK, "text/html", file)
|
||||
// Page routes
|
||||
e.GET("/", func(c echo.Context) error {
|
||||
return c.Render(http.StatusOK, "index.html", nil)
|
||||
})
|
||||
e.GET("/login", func(c echo.Context) error {
|
||||
return c.Render(http.StatusOK, "login.html", nil)
|
||||
})
|
||||
e.GET("/register", func(c echo.Context) error {
|
||||
return c.Render(http.StatusOK, "register.html", nil)
|
||||
})
|
||||
|
||||
// Start server
|
||||
|
||||
Reference in New Issue
Block a user