package api import ( "io/fs" "net/http" "github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5/middleware" "github.com/go-chi/cors" "simplefinancedash/backend/internal/auth" "simplefinancedash/backend/internal/db" ) func NewRouter(queries *db.Queries, sessionSecret string, frontendFS fs.FS) http.Handler { r := chi.NewRouter() r.Use(middleware.Logger) r.Use(middleware.Recoverer) r.Use(middleware.Compress(5)) r.Use(cors.Handler(cors.Options{ AllowedOrigins: []string{"http://localhost:5173", "http://localhost:8080"}, AllowedMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"}, AllowedHeaders: []string{"Content-Type"}, AllowCredentials: true, })) h := NewHandlers(queries, sessionSecret) // Auth routes (no auth required) r.Route("/api/auth", func(r chi.Router) { r.Post("/register", h.Register) r.Post("/login", h.Login) r.Post("/logout", h.Logout) r.Get("/me", h.Me) r.Get("/oidc", h.OIDCStart) r.Get("/oidc/callback", h.OIDCCallback) }) // Protected routes r.Group(func(r chi.Router) { r.Use(auth.Middleware(sessionSecret)) r.Route("/api/categories", func(r chi.Router) { r.Get("/", h.ListCategories) r.Post("/", h.CreateCategory) r.Put("/{id}", h.UpdateCategory) r.Delete("/{id}", h.DeleteCategory) }) r.Route("/api/budgets", func(r chi.Router) { r.Get("/", h.ListBudgets) r.Post("/", h.CreateBudget) r.Post("/generate", h.GenerateBudget) r.Get("/{id}", h.GetBudget) r.Put("/{id}", h.UpdateBudget) r.Delete("/{id}", h.DeleteBudget) r.Post("/{id}/copy-from/{srcId}", h.CopyBudgetItems) r.Post("/{id}/items", h.CreateBudgetItem) r.Put("/{id}/items/{itemId}", h.UpdateBudgetItem) r.Delete("/{id}/items/{itemId}", h.DeleteBudgetItem) }) r.Route("/api/template", func(r chi.Router) { r.Get("/", h.GetTemplate) r.Put("/", h.UpdateTemplateName) r.Post("/items", h.CreateTemplateItem) r.Put("/items/reorder", h.ReorderTemplateItems) r.Put("/items/{itemId}", h.UpdateTemplateItem) r.Delete("/items/{itemId}", h.DeleteTemplateItem) }) r.Get("/api/settings", h.GetSettings) r.Put("/api/settings", h.UpdateSettings) }) // Serve SPA for all non-API routes spaHandler := http.FileServer(http.FS(frontendFS)) r.NotFound(func(w http.ResponseWriter, r *http.Request) { // Try to serve the file directly first f, err := frontendFS.Open(r.URL.Path[1:]) // strip leading / if err == nil { f.Close() spaHandler.ServeHTTP(w, r) return } // Fall back to index.html for SPA routing r.URL.Path = "/" spaHandler.ServeHTTP(w, r) }) return r }