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 emailService := services.NewMockEmailService() jobService := services.NewJobService(database.DB) applicationService := services.NewApplicationService(database.DB, emailService) // --- 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") // Token Repository for Password Reset tokenRepo := postgres.NewPasswordResetTokenRepository(database.DB) // Frontend URL for reset link frontendURL := os.Getenv("FRONTEND_URL") if frontendURL == "" { frontendURL = "http://localhost:3000" } // 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) updateUserUC := userUC.NewUpdateUserUseCase(userRepo) forgotPasswordUC := authUC.NewForgotPasswordUseCase(userRepo, tokenRepo, emailService, frontendURL) resetPasswordUC := authUC.NewResetPasswordUseCase(userRepo, tokenRepo, authService) // Handlers & Middleware coreHandlers := apiHandlers.NewCoreHandlers( loginUC, createCompanyUC, createUserUC, listUsersUC, deleteUserUC, updateUserUC, listCompaniesUC, forgotPasswordUC, resetPasswordUC, ) authMiddleware := middleware.NewMiddleware(authService) // 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/forgot-password", coreHandlers.ForgotPassword) mux.HandleFunc("POST /api/v1/auth/reset-password", coreHandlers.ResetPassword) 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))) mux.Handle("PUT /api/v1/users/me", authMiddleware.HeaderAuthGuard(http.HandlerFunc(coreHandlers.UpdateMe))) // New Profile Update // 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) // Application Routes mux.HandleFunc("POST /api/v1/applications", applicationHandler.CreateApplication) mux.Handle("GET /api/v1/applications/me", authMiddleware.HeaderAuthGuard(http.HandlerFunc(applicationHandler.ListUserApplications))) // New endpoint 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") } // --- TICKET ROUTES --- ticketService := services.NewTicketService(database.DB) ticketHandler := handlers.NewTicketHandler(ticketService) mux.HandleFunc("GET /api/v1/tickets/stats", ticketHandler.GetTicketStats) mux.HandleFunc("GET /api/v1/tickets", ticketHandler.GetTickets) mux.HandleFunc("POST /api/v1/tickets", ticketHandler.CreateTicket) mux.HandleFunc("GET /api/v1/tickets/{id}", ticketHandler.GetTicketByID) mux.HandleFunc("PUT /api/v1/tickets/{id}", ticketHandler.UpdateTicket) mux.HandleFunc("GET /api/v1/tickets/{id}/messages", ticketHandler.GetTicketMessages) mux.HandleFunc("POST /api/v1/tickets/{id}/messages", ticketHandler.AddTicketMessage) // --- ACTIVITY LOG ROUTES --- activityLogService := services.NewActivityLogService(database.DB) activityLogHandler := handlers.NewActivityLogHandler(activityLogService) mux.HandleFunc("GET /api/v1/activity-logs/stats", activityLogHandler.GetActivityLogStats) mux.HandleFunc("GET /api/v1/activity-logs", activityLogHandler.GetActivityLogs) // 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 }