package router import ( "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) coreHandlers := apiHandlers.NewCoreHandlers( loginUC, registerCandidateUC, createCompanyUC, createUserUC, listUsersUC, deleteUserUC, updateUserUC, listCompaniesUC, auditService, notificationService, // Added ticketService, // Added ) authMiddleware := middleware.NewMiddleware(authService) adminService := services.NewAdminService(database.DB) adminHandlers := apiHandlers.NewAdminHandlers(adminService, auditService, jobService) // Initialize Legacy Handlers jobHandler := handlers.NewJobHandler(jobService) applicationHandler := handlers.NewApplicationHandler(applicationService) // cachedPublicIP stores the public IP to avoid repeated external calls var cachedPublicIP string // Helper to get public IP getPublicIP := func() string { if cachedPublicIP != "" { return cachedPublicIP } client := http.Client{ Timeout: 2 * time.Second, } resp, err := client.Get("https://api.ipify.org?format=text") if err != nil { return "127.0.0.1" // Fallback } defer resp.Body.Close() // simple read buf := make([]byte, 64) n, err := resp.Body.Read(buf) if err != nil && err.Error() != "EOF" { return "127.0.0.1" } cachedPublicIP = string(buf[:n]) return cachedPublicIP } // --- ROOT ROUTE --- mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { if r.URL.Path != "/" { http.NotFound(w, r) return } serverIP := getPublicIP() w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) response := `{ "message": "🐴 GoHorseJobs API is running!", "ip": "` + serverIP + `", "docs": "/docs", "health": "/health", "version": "1.0.0" }` w.Write([]byte(response)) }) mux.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) w.Write([]byte("OK")) }) // --- 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) mux.HandleFunc("GET /api/v1/companies", coreHandlers.ListCompanies) // Protected // Note: In Go 1.22+, we can wrap specific patterns. Or we can just wrap the handler. // For simplicity, we wrap the handler function. mux.Handle("POST /api/v1/users", authMiddleware.HeaderAuthGuard(http.HandlerFunc(coreHandlers.CreateUser))) mux.Handle("GET /api/v1/users", authMiddleware.HeaderAuthGuard(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.HandleFunc("POST /api/v1/jobs", jobHandler.CreateJob) mux.HandleFunc("GET /api/v1/jobs/{id}", jobHandler.GetJobByID) mux.HandleFunc("PUT /api/v1/jobs/{id}", jobHandler.UpdateJob) mux.HandleFunc("DELETE /api/v1/jobs/{id}", jobHandler.DeleteJob) // --- ADMIN ROUTES --- adminOnly := authMiddleware.RequireRoles("ADMIN", "SUPERADMIN", "admin", "superadmin") mux.Handle("GET /api/v1/admin/access/roles", authMiddleware.HeaderAuthGuard(adminOnly(http.HandlerFunc(adminHandlers.ListAccessRoles)))) mux.Handle("GET /api/v1/admin/audit/logins", authMiddleware.HeaderAuthGuard(adminOnly(http.HandlerFunc(adminHandlers.ListLoginAudits)))) mux.Handle("GET /api/v1/admin/companies", authMiddleware.HeaderAuthGuard(adminOnly(http.HandlerFunc(adminHandlers.ListCompanies)))) mux.Handle("PATCH /api/v1/admin/companies/{id}", authMiddleware.HeaderAuthGuard(adminOnly(http.HandlerFunc(adminHandlers.UpdateCompanyStatus)))) mux.Handle("GET /api/v1/admin/jobs", authMiddleware.HeaderAuthGuard(adminOnly(http.HandlerFunc(adminHandlers.ListJobs)))) mux.Handle("PATCH /api/v1/admin/jobs/{id}/status", authMiddleware.HeaderAuthGuard(adminOnly(http.HandlerFunc(adminHandlers.UpdateJobStatus)))) mux.Handle("POST /api/v1/admin/jobs/{id}/duplicate", authMiddleware.HeaderAuthGuard(adminOnly(http.HandlerFunc(adminHandlers.DuplicateJob)))) mux.Handle("GET /api/v1/admin/tags", authMiddleware.HeaderAuthGuard(adminOnly(http.HandlerFunc(adminHandlers.ListTags)))) mux.Handle("POST /api/v1/admin/tags", authMiddleware.HeaderAuthGuard(adminOnly(http.HandlerFunc(adminHandlers.CreateTag)))) mux.Handle("PATCH /api/v1/admin/tags/{id}", authMiddleware.HeaderAuthGuard(adminOnly(http.HandlerFunc(adminHandlers.UpdateTag)))) mux.Handle("GET /api/v1/admin/candidates", authMiddleware.HeaderAuthGuard(adminOnly(http.HandlerFunc(adminHandlers.ListCandidates)))) // 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 }