package server import ( "net/http" "strings" "time" "ddc/internal/auth" "ddc/internal/config" "ddc/internal/jobs" "ddc/web" ) const ( maxLoginFailures = 5 loginLockout = 15 * time.Minute ) type Server struct { settings config.Settings sessions *auth.SessionStore limiter *auth.LoginLimiter manager *jobs.Manager } func New(settings config.Settings, sessions *auth.SessionStore, manager *jobs.Manager) http.Handler { server := &Server{ settings: settings, sessions: sessions, limiter: auth.NewLoginLimiter(maxLoginFailures, loginLockout), manager: manager, } return server.routes() } func (server *Server) routes() http.Handler { api := http.NewServeMux() api.HandleFunc("POST /api/logout", server.handleLogout) api.HandleFunc("GET /api/libraries", server.handleLibraries) api.HandleFunc("GET /api/disk", server.handleDisk) api.HandleFunc("GET /api/settings", server.handleGetSettings) api.HandleFunc("POST /api/settings", server.handleUpdateSettings) api.HandleFunc("GET /api/jobs", server.handleListJobs) api.HandleFunc("POST /api/jobs", server.handleCreateJob) api.HandleFunc("GET /api/jobs/{id}/files", server.handleJobFiles) api.HandleFunc("POST /api/jobs/{id}/extract", server.handleExtract) api.HandleFunc("POST /api/jobs/{id}/move", server.handleMove) api.HandleFunc("DELETE /api/jobs/{id}", server.handleDeleteJob) root := http.NewServeMux() root.HandleFunc("POST /api/login", server.handleLogin) root.HandleFunc("GET /api/session", server.handleSession) root.HandleFunc("GET /healthz", handleHealth) root.Handle("/api/", server.requireSession(api)) root.Handle("/", staticHandler()) return securityHeaders(protectStateChanges(root)) } func staticHandler() http.Handler { fileServer := http.FileServerFS(web.Static()) return http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) { if request.URL.Path != "/" && strings.HasSuffix(request.URL.Path, "/") { http.NotFound(writer, request) return } fileServer.ServeHTTP(writer, request) }) } func handleHealth(writer http.ResponseWriter, _ *http.Request) { writer.WriteHeader(http.StatusNoContent) }