Setup completion was previously tracked by a manually-flipped setup_complete row in system_settings, written via a JWT-protected PUT /api/setup/complete endpoint. This meant any admin user created outside the setup wizard (future CLI, seed scripts, direct DB inserts) would not flip the switch, leaving the app stuck redirecting to /setup.
The trigger is now derived from real data: setup is complete iff at least one admin user exists. This is self-correcting regardless of how users are created, and re-engages setup automatically if all admins are ever removed.
Changes:
- Add internal/setupstatus package with IsSetupComplete() (queries CountAdmins, 10s in-memory cache, fails open on DB error) and Invalidate() to clear the cache. Uses an AdminCounter interface to avoid importing the database package.
- Add CountAdmins sqlc query (SELECT COUNT(*) FROM users WHERE role = 'admin') and regenerate.
- Rewire router/setup.go isSetupComplete() to delegate to setupstatus; drop the old setup_complete setting read, cache vars, and the PUT /api/setup/complete route.
- Call setupstatus.Invalidate() in the auth handler after CreateUser, UpdateUserRole, and DeleteUser so the cache reflects admin-count changes immediately.
- Align first-user promotion in Register to key off !adminExists instead of len(users) == 0, so the two checks cannot diverge.
- Remove the now-dead SetSetupComplete/GetSetupStatus handlers.
- Drop the setup_complete seed row from schema.sql.
- Remove the apiPut('/setup/complete') call from the setup wizard finishSetup(); the admin account created in submitAdmin already marks setup complete server-side.
61 lines
1.3 KiB
Go
61 lines
1.3 KiB
Go
package router
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"log"
|
|
"net/http"
|
|
"strings"
|
|
|
|
"bookhoard/internal/setupstatus"
|
|
"bookhoard/templates"
|
|
|
|
"github.com/labstack/echo/v5"
|
|
)
|
|
|
|
func isSetupComplete(cfg *Config) bool {
|
|
return setupstatus.IsSetupComplete(context.Background(), cfg.Queries)
|
|
}
|
|
|
|
func setupRedirectMiddleware(cfg *Config) echo.MiddlewareFunc {
|
|
return func(next echo.HandlerFunc) echo.HandlerFunc {
|
|
return func(c *echo.Context) error {
|
|
path := c.Request().URL.Path
|
|
|
|
if path == "/setup" || path == "/setup/" {
|
|
return next(c)
|
|
}
|
|
|
|
if strings.HasPrefix(path, "/api/") {
|
|
return next(c)
|
|
}
|
|
|
|
if strings.HasPrefix(path, "/static/") || path == "/health" || path == "/favicon.ico" {
|
|
return next(c)
|
|
}
|
|
|
|
if !isSetupComplete(cfg) {
|
|
return c.Redirect(http.StatusFound, "/setup")
|
|
}
|
|
|
|
return next(c)
|
|
}
|
|
}
|
|
}
|
|
|
|
func registerSetupRoutes(cfg *Config) {
|
|
e := cfg.Echo
|
|
|
|
e.GET("/setup", func(c *echo.Context) error {
|
|
if isSetupComplete(cfg) {
|
|
return c.Redirect(http.StatusFound, "/")
|
|
}
|
|
var buf bytes.Buffer
|
|
if err := templates.Setup().Render(c.Request().Context(), &buf); err != nil {
|
|
log.Printf("Failed to render setup template: %v", err)
|
|
return c.HTML(http.StatusInternalServerError, "Failed to render setup page")
|
|
}
|
|
return c.HTML(http.StatusOK, buf.String())
|
|
})
|
|
}
|