187 lines
8 KiB
Go
Executable file
187 lines
8 KiB
Go
Executable file
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)
|
|
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
|
|
auditService := services.NewAuditService(database.DB)
|
|
coreHandlers := apiHandlers.NewCoreHandlers(loginUC, createCompanyUC, createUserUC, listUsersUC, deleteUserUC, listCompaniesUC, auditService)
|
|
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/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 /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.RateLimitMiddleware(100, time.Minute)(handler) // 100 req/min per IP
|
|
handler = legacyMiddleware.SecurityHeadersMiddleware(handler)
|
|
|
|
return handler
|
|
}
|