package router import ( "encoding/json" "log" "net/http" "os" "time" "github.com/rede5/gohorsejobs/backend/internal/api/middleware" "github.com/rede5/gohorsejobs/backend/internal/database" "github.com/rede5/gohorsejobs/backend/internal/handlers" "github.com/rede5/gohorsejobs/backend/internal/infrastructure/persistence/postgres" "github.com/rede5/gohorsejobs/backend/internal/infrastructure/storage" "github.com/rede5/gohorsejobs/backend/internal/services" // Core Imports apiHandlers "github.com/rede5/gohorsejobs/backend/internal/api/handlers" authUC "github.com/rede5/gohorsejobs/backend/internal/core/usecases/auth" tenantUC "github.com/rede5/gohorsejobs/backend/internal/core/usecases/tenant" userUC "github.com/rede5/gohorsejobs/backend/internal/core/usecases/user" authInfra "github.com/rede5/gohorsejobs/backend/internal/infrastructure/auth" legacyMiddleware "github.com/rede5/gohorsejobs/backend/internal/middleware" // Admin Imports _ "github.com/rede5/gohorsejobs/backend/docs" // Import generated docs httpSwagger "github.com/swaggo/http-swagger/v2" ) func NewRouter() http.Handler { mux := http.NewServeMux() // Initialize Services jobService := services.NewJobService(database.DB) applicationService := services.NewApplicationService(database.DB) // --- CORE ARCHITECTURE INITIALIZATION --- // Infrastructure // Infrastructure, userRepo := postgres.NewUserRepository(database.DB) companyRepo := postgres.NewCompanyRepository(database.DB) jwtSecret := os.Getenv("JWT_SECRET") if jwtSecret == "" { // Fallback for dev, but really should be in env jwtSecret = "default-dev-secret-do-not-use-in-prod" } authService := authInfra.NewJWTService(jwtSecret, "todai-jobs") // UseCases loginUC := authUC.NewLoginUseCase(userRepo, authService) registerCandidateUC := authUC.NewRegisterCandidateUseCase(userRepo, companyRepo, authService) createCompanyUC := tenantUC.NewCreateCompanyUseCase(companyRepo, userRepo, authService) listCompaniesUC := tenantUC.NewListCompaniesUseCase(companyRepo) createUserUC := userUC.NewCreateUserUseCase(userRepo, authService) listUsersUC := userUC.NewListUsersUseCase(userRepo) deleteUserUC := userUC.NewDeleteUserUseCase(userRepo) updateUserUC := userUC.NewUpdateUserUseCase(userRepo) // Handlers & Middleware auditService := services.NewAuditService(database.DB) notificationService := services.NewNotificationService(database.DB) ticketService := services.NewTicketService(database.DB) authMiddleware := middleware.NewMiddleware(authService) adminService := services.NewAdminService(database.DB) coreHandlers := apiHandlers.NewCoreHandlers( loginUC, registerCandidateUC, createCompanyUC, createUserUC, listUsersUC, deleteUserUC, updateUserUC, listCompaniesUC, auditService, notificationService, // Added ticketService, // Added adminService, // Added for RBAC support ) adminHandlers := apiHandlers.NewAdminHandlers(adminService, auditService, jobService) // Initialize Legacy Handlers jobHandler := handlers.NewJobHandler(jobService) applicationHandler := handlers.NewApplicationHandler(applicationService) // --- IP HELPER --- GetClientIP := func(r *http.Request) string { forwarded := r.Header.Get("X-Forwarded-For") if forwarded != "" { return forwarded } return r.RemoteAddr } // --- HEALTH CHECK --- mux.HandleFunc("GET /health", func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) w.Header().Set("Content-Type", "text/plain") w.Write([]byte("OK")) }) // --- ROOT ROUTE --- mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { if r.URL.Path != "/" { http.NotFound(w, r) return } response := map[string]interface{}{ "message": "🐴 GoHorseJobs API is running!", "ip": GetClientIP(r), "docs": "/docs", "health": "/health", "version": "1.0.0", } w.Header().Set("Content-Type", "application/json") if err := json.NewEncoder(w).Encode(response); err != nil { http.Error(w, "Internal Server Error", http.StatusInternalServerError) } }) // --- CORE ROUTES --- // Public mux.HandleFunc("POST /api/v1/auth/login", coreHandlers.Login) mux.HandleFunc("POST /api/v1/auth/register", coreHandlers.RegisterCandidate) mux.HandleFunc("POST /api/v1/companies", coreHandlers.CreateCompany) // Public/Protected with RBAC (Smart Handler) mux.Handle("GET /api/v1/companies", authMiddleware.OptionalHeaderAuthGuard(http.HandlerFunc(coreHandlers.ListCompanies))) adminOnly := authMiddleware.RequireRoles("ADMIN", "SUPERADMIN", "admin", "superadmin") // Protected Core mux.Handle("POST /api/v1/users", authMiddleware.HeaderAuthGuard(http.HandlerFunc(coreHandlers.CreateUser))) mux.Handle("GET /api/v1/users", authMiddleware.HeaderAuthGuard(adminOnly(http.HandlerFunc(coreHandlers.ListUsers)))) mux.Handle("PATCH /api/v1/users/{id}", authMiddleware.HeaderAuthGuard(http.HandlerFunc(coreHandlers.UpdateUser))) mux.Handle("DELETE /api/v1/users/{id}", authMiddleware.HeaderAuthGuard(http.HandlerFunc(coreHandlers.DeleteUser))) // Job Routes mux.HandleFunc("GET /api/v1/jobs", jobHandler.GetJobs) mux.Handle("POST /api/v1/jobs", authMiddleware.HeaderAuthGuard(http.HandlerFunc(jobHandler.CreateJob))) mux.HandleFunc("GET /api/v1/jobs/{id}", jobHandler.GetJobByID) mux.Handle("PUT /api/v1/jobs/{id}", authMiddleware.HeaderAuthGuard(http.HandlerFunc(jobHandler.UpdateJob))) mux.Handle("DELETE /api/v1/jobs/{id}", authMiddleware.HeaderAuthGuard(http.HandlerFunc(jobHandler.DeleteJob))) // --- ADMIN ROUTES (Consolidated to Standard Paths with RBAC) --- // /api/v1/admin/access/roles -> /api/v1/users/roles mux.Handle("GET /api/v1/users/roles", authMiddleware.HeaderAuthGuard(adminOnly(http.HandlerFunc(adminHandlers.ListAccessRoles)))) // /api/v1/admin/audit/logins -> /api/v1/audit/logins mux.Handle("GET /api/v1/audit/logins", authMiddleware.HeaderAuthGuard(adminOnly(http.HandlerFunc(adminHandlers.ListLoginAudits)))) // Public /api/v1/users/me (Authenticated) mux.Handle("GET /api/v1/users/me", authMiddleware.HeaderAuthGuard(http.HandlerFunc(coreHandlers.Me))) // /api/v1/admin/companies -> Handled by coreHandlers.ListCompanies (Smart Branching) // Needs to be wired with Optional Auth to support both Public and Admin. // I will create OptionalHeaderAuthGuard in middleware next. // /api/v1/admin/companies/{id} -> PATCH /api/v1/companies/{id}/status mux.Handle("PATCH /api/v1/companies/{id}/status", authMiddleware.HeaderAuthGuard(adminOnly(http.HandlerFunc(adminHandlers.UpdateCompanyStatus)))) // /api/v1/admin/jobs -> /api/v1/jobs?mode=admin (Need Smart Handler) or just separate path /api/v1/jobs/management? // User said "remove admin from ALL routes". // Maybe /api/v1/management/jobs? // Or just /api/v1/jobs (guarded)? // JobHandler.GetJobs is Public. // I will leave /api/v1/admin/jobs mapped to `GET /api/v1/jobs` for now (Collision). // OK, I will map it to `GET /api/v1/jobs/moderation` for clearer distinction without "admin" prefix? // Or simply `GET /api/v1/jobs` handle it? // Given safe constraints, `GET /api/v1/jobs/moderation` is safer than breaking public `GET /api/v1/jobs`. mux.Handle("GET /api/v1/jobs/moderation", authMiddleware.HeaderAuthGuard(adminOnly(http.HandlerFunc(adminHandlers.ListJobs)))) // /api/v1/admin/jobs/{id}/status mux.Handle("PATCH /api/v1/jobs/{id}/status", authMiddleware.HeaderAuthGuard(adminOnly(http.HandlerFunc(adminHandlers.UpdateJobStatus)))) // /api/v1/admin/jobs/{id}/duplicate -> /api/v1/jobs/{id}/duplicate mux.Handle("POST /api/v1/jobs/{id}/duplicate", authMiddleware.HeaderAuthGuard(adminOnly(http.HandlerFunc(adminHandlers.DuplicateJob)))) // /api/v1/admin/tags -> /api/v1/tags mux.Handle("GET /api/v1/tags", authMiddleware.HeaderAuthGuard(adminOnly(http.HandlerFunc(adminHandlers.ListTags)))) mux.Handle("POST /api/v1/tags", authMiddleware.HeaderAuthGuard(adminOnly(http.HandlerFunc(adminHandlers.CreateTag)))) mux.Handle("PATCH /api/v1/tags/{id}", authMiddleware.HeaderAuthGuard(adminOnly(http.HandlerFunc(adminHandlers.UpdateTag)))) // /api/v1/admin/candidates -> /api/v1/candidates mux.Handle("GET /api/v1/candidates", authMiddleware.HeaderAuthGuard(adminOnly(http.HandlerFunc(adminHandlers.ListCandidates)))) // Notifications Route mux.Handle("GET /api/v1/notifications", authMiddleware.HeaderAuthGuard(http.HandlerFunc(coreHandlers.ListNotifications))) // Application Routes mux.HandleFunc("POST /api/v1/applications", applicationHandler.CreateApplication) mux.HandleFunc("GET /api/v1/applications", applicationHandler.GetApplications) mux.HandleFunc("GET /api/v1/applications/{id}", applicationHandler.GetApplicationByID) mux.HandleFunc("PUT /api/v1/applications/{id}/status", applicationHandler.UpdateApplicationStatus) // --- STORAGE ROUTES --- // Initialize S3 Storage (optional - graceful degradation if not configured) s3Storage, err := storage.NewS3Storage() if err != nil { log.Printf("Warning: S3 storage not available: %v", err) } else { storageHandler := handlers.NewStorageHandler(s3Storage) mux.Handle("POST /api/v1/storage/upload-url", authMiddleware.HeaderAuthGuard(http.HandlerFunc(storageHandler.GenerateUploadURL))) mux.Handle("POST /api/v1/storage/download-url", authMiddleware.HeaderAuthGuard(http.HandlerFunc(storageHandler.GenerateDownloadURL))) mux.Handle("DELETE /api/v1/storage/files", authMiddleware.HeaderAuthGuard(http.HandlerFunc(storageHandler.DeleteFile))) log.Println("S3 storage routes registered successfully") } // Swagger Route - available at /docs mux.HandleFunc("/docs/", httpSwagger.WrapHandler) // Apply middleware chain: Security Headers -> Rate Limiting -> CORS -> Router // Order matters: outer middleware runs first var handler http.Handler = mux handler = middleware.CORSMiddleware(handler) handler = legacyMiddleware.SanitizeMiddleware(handler) // Sanitize XSS from JSON bodies handler = legacyMiddleware.RateLimitMiddleware(100, time.Minute)(handler) // 100 req/min per IP handler = legacyMiddleware.SecurityHeadersMiddleware(handler) return handler }