package router import ( "bytes" "context" "errors" "log" "net/http" "strconv" "strings" "sync" "time" "bookhoard/templates" "github.com/jackc/pgx/v5" "github.com/labstack/echo/v5" ) var ( setupCacheMu sync.RWMutex setupCacheComplete bool = true setupCacheExpiry time.Time setupCacheTTL = 10 * time.Second ) func isSetupComplete(cfg *Config) bool { setupCacheMu.RLock() if time.Now().Before(setupCacheExpiry) { complete := setupCacheComplete setupCacheMu.RUnlock() return complete } setupCacheMu.RUnlock() val, err := cfg.Queries.GetSystemSetting(context.Background(), "setup_complete") if err != nil { if errors.Is(err, pgx.ErrNoRows) { setupCacheMu.Lock() setupCacheComplete = false setupCacheExpiry = time.Now().Add(setupCacheTTL) setupCacheMu.Unlock() return false } return true } complete, err := strconv.ParseBool(val) if err != nil { complete = false } setupCacheMu.Lock() setupCacheComplete = complete setupCacheExpiry = time.Now().Add(setupCacheTTL) setupCacheMu.Unlock() return complete } func invalidateSetupCache() { setupCacheMu.Lock() setupCacheComplete = true setupCacheExpiry = time.Time{} setupCacheMu.Unlock() } 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()) }) jwtMiddleware := createJWTMiddleware(cfg) protected := e.Group("/api/setup", jwtMiddleware) protected.PUT("/complete", func(c *echo.Context) error { err := cfg.SystemSettingsHandler.SetSetupComplete(c) if err != nil { return err } invalidateSetupCache() return nil }) }