package httpserver import ( "log/slog" "net/http" "github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5/middleware" "update_server/internal/apikeys" authservice "update_server/internal/auth" "update_server/internal/config" "update_server/internal/db" "update_server/internal/releases" ) type handler struct { config config.Config store *db.Store logger *slog.Logger renderer *Renderer auth *authservice.Service apiKeys *apikeys.Service releases *releases.Service loginRateLimiter *rateLimitStore clientRateLimiter *rateLimitStore } func NewRouter(cfg config.Config, logger *slog.Logger, renderer *Renderer, store *db.Store, auth *authservice.Service, apiKeyService *apikeys.Service, releaseService *releases.Service) http.Handler { h := &handler{ config: cfg, store: store, logger: logger, renderer: renderer, auth: auth, apiKeys: apiKeyService, releases: releaseService, loginRateLimiter: newRateLimitStore(cfg.LoginRateLimitPerMinute, cfg.LoginRateLimitBurst), clientRateLimiter: newRateLimitStore(cfg.ClientRateLimitPerMinute, cfg.ClientRateLimitBurst), } router := chi.NewRouter() router.Use(middleware.RequestID) if cfg.TrustProxyHeaders { router.Use(middleware.RealIP) } router.Use(requestLogger(logger)) router.Use(middleware.Recoverer) router.Use(middleware.StripSlashes) router.Use(securityHeaders(cfg)) router.Get("/healthz", h.health) router.Handle("/static/*", http.StripPrefix("/static/", http.FileServer(http.Dir(cfg.StaticDir)))) router.Get("/", h.home) router.Route("/admin", func(r chi.Router) { r.Use(adminResponseHeaders) r.Use(h.adminCSRF) r.Get("/login", h.adminLoginForm) r.With(h.loginRateLimit).Post("/login", h.adminLogin) r.Group(func(r chi.Router) { r.Use(h.requireAuthenticatedSession) r.Use(h.requireRole(db.UserRoleAdmin)) r.Get("/", h.adminHome) r.Post("/logout", h.adminLogout) r.Get("/projects", h.adminProjects) r.Get("/projects/new", h.adminProjectNew) r.Post("/projects", h.adminProjectCreate) r.Get("/projects/{projectID}", h.adminProjectDetail) r.Post("/projects/{projectID}", h.adminProjectUpdate) r.Post("/projects/{projectID}/archive", h.adminProjectArchive) r.Post("/projects/{projectID}/tags", h.adminProjectAttachTag) r.Post("/projects/{projectID}/tags/{tagID}/detach", h.adminProjectDetachTag) r.Post("/projects/{projectID}/releases", h.adminProjectUploadRelease) r.Get("/tags", h.adminTags) r.Get("/tags/new", h.adminTagNew) r.Post("/tags", h.adminTagCreate) r.Get("/tags/{tagID}", h.adminTagDetail) r.Post("/tags/{tagID}", h.adminTagUpdate) r.Post("/tags/{tagID}/delete", h.adminTagDelete) r.Get("/api-keys", h.adminAPIKeys) r.Get("/api-keys/new", h.adminAPIKeyNew) r.Post("/api-keys", h.adminAPIKeyCreate) r.Get("/api-keys/{apiKeyID}", h.adminAPIKeyDetail) r.Post("/api-keys/{apiKeyID}", h.adminAPIKeyUpdate) r.Post("/api-keys/{apiKeyID}/activate", h.adminAPIKeyToggleActive) }) }) router.Route("/api", func(r chi.Router) { r.Route("/v1", func(r chi.Router) { r.Use(apiResponseHeaders) r.Get("/", h.apiIndex) r.Group(func(r chi.Router) { r.Use(protectedAPIResponseHeaders) r.Use(h.clientAPIRateLimit) r.Use(h.requireAPIKey) r.Use(h.requireAPIKeyPermission(apikeys.PermissionDownload)) r.Get("/projects", h.apiAccessibleProjects) r.Get("/projects/{projectSlug}/releases/latest", h.apiLatestRelease) r.Get("/releases/{releaseID}", h.apiReleaseMetadata) r.Get("/releases/{releaseID}/download", h.apiReleaseDownload) }) }) }) return router }