package router import ( "encoding/json" "net/http" "strings" "time" "github.com/danyel/go-guess/backend/internal/security" "github.com/danyel/go-guess/backend/internal/web/handler" mw "github.com/danyel/go-guess/backend/internal/web/middleware" "github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5/middleware" ) func New(h *handler.Handler, tokens security.ITokenManager, frontendURL string) http.Handler { router := chi.NewRouter() router.Use(middleware.RequestID, middleware.RealIP, middleware.Recoverer, middleware.Compress(5)) router.Use(cors(frontendURL)) router.Use(mw.TenantSchemaMiddleware(h.Db())) router.Use(middleware.Timeout(2 * time.Minute)) router.Get("/api/health", h.Health) router.Post("/api/auth/login", h.Login) router.Get("/api/interviews/{token}", h.GetInterview) router.Post("/api/interviews/{token}/accept", h.AcceptInterview) router.Put("/api/interviews/{token}/answers/{questionId}", h.SaveAnswer) router.Post("/api/interviews/{token}/finish", h.FinishInterview) router.Get("/api/participant-meetings/{token}", h.GetParticipantMeeting) router.Get("/api/participant-meetings/{token}/events", h.ParticipantMeetingEvents) router.Group(func(protected chi.Router) { protected.Use(authenticate(tokens)) protected.Get("/api/jobs", h.ListJobs) protected.Post("/api/jobs", h.CreateJob) protected.Get("/api/jobs/{id}", h.GetJob) protected.Patch("/api/jobs/{id}", h.UpdateJob) protected.Post("/api/jobs/{id}/questions/{questionId}", h.AttachQuestion) protected.Delete("/api/jobs/{id}/questions/{questionId}", h.DetachQuestion) protected.Get("/api/jobs/{id}/candidates", h.Candidates) protected.Get("/api/jobs/{id}/invitations", h.ListInvitations) protected.Post("/api/jobs/{id}/invitations", h.CreateInvitation) protected.Get("/api/jobs/{id}/invitations/{invitationId}", h.ReviewInvitation) protected.Patch("/api/jobs/{id}/invitations/{invitationId}/outcome", h.SetInvitationOutcome) protected.Get("/api/jobs/{id}/interviews", h.ListScheduledInterviews) protected.Post("/api/jobs/{id}/interviews", h.CreateScheduledInterview) protected.Get("/api/scheduled-interviews/{id}", h.GetScheduledInterview) protected.Patch("/api/scheduled-interviews/{id}/status", h.UpdateScheduledInterviewStatus) protected.Patch("/api/scheduled-interviews/{id}/document", h.UpdateScheduledInterviewDocument) protected.Get("/api/scheduled-interviews/{id}/notes", h.ListInterviewNotes) protected.Post("/api/scheduled-interviews/{id}/notes", h.CreateInterviewNote) protected.Get("/api/scheduled-interviews/{id}/events", h.InterviewEvents) protected.Get("/api/users", h.ListUsers) protected.Post("/api/users", h.CreateUser) protected.Get("/api/invitations", h.ListInbox) protected.Patch("/api/invitations/{id}", h.UpdateInbox) protected.Get("/api/schedule", h.ListCalendar) protected.Get("/api/questions", h.ListQuestions) protected.Post("/api/questions", h.CreateQuestion) protected.Put("/api/questions/{id}", h.UpdateQuestion) protected.Patch("/api/questions/{id}", h.SetQuestionDeprecated) protected.Get("/api/participants", h.ListParticipants) protected.Post("/api/participants", h.CreateParticipant) protected.Get("/api/participants/{id}", h.GetParticipant) protected.Put("/api/participants/{id}/cv", h.UpdateParticipantCV) protected.Get("/api/participants/{id}/photo", h.ParticipantPhoto) protected.Get("/api/participants/{id}/cv", h.ParticipantCV) }) return router } func writeUnauthorized(w http.ResponseWriter, message string) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusUnauthorized) _ = json.NewEncoder(w).Encode(map[string]string{"error": message}) } func authenticate(tokens security.ITokenManager) func(http.Handler) http.Handler { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { scheme, raw, ok := strings.Cut(r.Header.Get("Authorization"), " ") if !ok || !strings.EqualFold(scheme, "Bearer") { writeUnauthorized(w, "missing bearer token") return } claims, err := tokens.Parse(raw) if err != nil { writeUnauthorized(w, "invalid bearer token") return } next.ServeHTTP(w, r.WithContext(security.WithClaims(r.Context(), claims))) }) } } func cors(origin string) func(http.Handler) http.Handler { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Access-Control-Allow-Origin", origin) w.Header().Set("Access-Control-Allow-Headers", "Authorization, Content-Type") w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, PATCH, DELETE, OPTIONS") w.Header().Add("Vary", "Origin") if r.Method == http.MethodOptions { w.WriteHeader(http.StatusNoContent) return } next.ServeHTTP(w, r) }) } }