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/internal/admin/cloudflare" "github.com/rede5/gohorsejobs/backend/internal/admin/cpanel" _ "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) createCompanyUC := tenantUC.NewCreateCompanyUseCase(companyRepo, userRepo, authService) listCompaniesUC := tenantUC.NewListCompaniesUseCase(companyRepo) createUserUC := userUC.NewCreateUserUseCase(userRepo, authService) listUsersUC := userUC.NewListUsersUseCase(userRepo) deleteUserUC := userUC.NewDeleteUserUseCase(userRepo) // Handlers & Middleware coreHandlers := apiHandlers.NewCoreHandlers(loginUC, createCompanyUC, createUserUC, listUsersUC, deleteUserUC, listCompaniesUC) authMiddleware := middleware.NewMiddleware(authService) // Initialize Legacy Handlers jobHandler := handlers.NewJobHandler(jobService) applicationHandler := handlers.NewApplicationHandler(applicationService) // Initialize Admin Handlers cloudflareHandler := cloudflare.NewHandler() cpanelHandler := cpanel.NewHandler() // 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/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("DELETE /api/v1/users/{id}", authMiddleware.HeaderAuthGuard(http.HandlerFunc(coreHandlers.DeleteUser))) // Job Routes mux.HandleFunc("GET /jobs", jobHandler.GetJobs) mux.HandleFunc("POST /jobs", jobHandler.CreateJob) mux.HandleFunc("GET /jobs/{id}", jobHandler.GetJobByID) mux.HandleFunc("PUT /jobs/{id}", jobHandler.UpdateJob) mux.HandleFunc("DELETE /jobs/{id}", jobHandler.DeleteJob) // Application Routes mux.HandleFunc("POST /applications", applicationHandler.CreateApplication) mux.HandleFunc("GET /applications", applicationHandler.GetApplications) mux.HandleFunc("GET /applications/{id}", applicationHandler.GetApplicationByID) mux.HandleFunc("PUT /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") } // --- ADMIN ROUTES (Protected - SuperAdmin only) --- // Cloudflare Cache Management mux.Handle("GET /api/v1/admin/cloudflare/zones", authMiddleware.HeaderAuthGuard(http.HandlerFunc(cloudflareHandler.GetZones))) mux.Handle("POST /api/v1/admin/cloudflare/cache/purge-all", authMiddleware.HeaderAuthGuard(http.HandlerFunc(cloudflareHandler.PurgeAll))) mux.Handle("POST /api/v1/admin/cloudflare/cache/purge-urls", authMiddleware.HeaderAuthGuard(http.HandlerFunc(cloudflareHandler.PurgeByURLs))) mux.Handle("POST /api/v1/admin/cloudflare/cache/purge-tags", authMiddleware.HeaderAuthGuard(http.HandlerFunc(cloudflareHandler.PurgeByTags))) mux.Handle("POST /api/v1/admin/cloudflare/cache/purge-hosts", authMiddleware.HeaderAuthGuard(http.HandlerFunc(cloudflareHandler.PurgeByHosts))) // cPanel Email Management mux.Handle("GET /api/v1/admin/cpanel/emails", authMiddleware.HeaderAuthGuard(http.HandlerFunc(cpanelHandler.ListEmails))) mux.Handle("POST /api/v1/admin/cpanel/emails", authMiddleware.HeaderAuthGuard(http.HandlerFunc(cpanelHandler.CreateEmail))) mux.Handle("DELETE /api/v1/admin/cpanel/emails/{email}", authMiddleware.HeaderAuthGuard(http.HandlerFunc(cpanelHandler.DeleteEmail))) mux.Handle("PUT /api/v1/admin/cpanel/emails/{email}/password", authMiddleware.HeaderAuthGuard(http.HandlerFunc(cpanelHandler.ChangePassword))) mux.Handle("PUT /api/v1/admin/cpanel/emails/{email}/quota", authMiddleware.HeaderAuthGuard(http.HandlerFunc(cpanelHandler.UpdateQuota))) log.Println("Admin routes (Cloudflare, cPanel) 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.RateLimitMiddleware(100, time.Minute)(handler) // 100 req/min per IP handler = legacyMiddleware.SecurityHeadersMiddleware(handler) return handler }