package handlers import ( "net/http" "strconv" "mohportal/middleware" "mohportal/models" "mohportal/services" "github.com/gin-gonic/gin" "github.com/google/uuid" ) var ( tenantService *services.TenantService userService *services.UserService positionService *services.PositionService resumeService *services.ResumeService applicationService *services.ApplicationService ) // Initialize services func init() { tenantService = &services.TenantService{} userService = &services.UserService{} positionService = &services.PositionService{} resumeService = &services.ResumeService{} applicationService = &services.ApplicationService{} } // HealthCheck returns the health status of the application func HealthCheck(c *gin.Context) { c.JSON(http.StatusOK, gin.H{ "status": "healthy", "message": "MerchantsOfHope.org recruiting platform is running", "service": "MOH Portal API", }) } // Tenant Handlers func CreateTenant(c *gin.Context) { var req struct { Name string `json:"name" binding:"required"` Slug string `json:"slug" binding:"required"` Description string `json:"description"` LogoURL string `json:"logo_url"` } if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } tenant, err := tenantService.CreateTenant(req.Name, req.Slug, req.Description, req.LogoURL) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusCreated, tenant) } func GetTenants(c *gin.Context) { limit, _ := strconv.Atoi(c.DefaultQuery("limit", "10")) offset, _ := strconv.Atoi(c.DefaultQuery("offset", "0")) tenants, err := tenantService.GetTenants(limit, offset) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, tenants) } func GetTenant(c *gin.Context) { id, err := uuid.Parse(c.Param("id")) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid tenant ID"}) return } tenant, err := tenantService.GetTenant(id) if err != nil { c.JSON(http.StatusNotFound, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, tenant) } func UpdateTenant(c *gin.Context) { id, err := uuid.Parse(c.Param("id")) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid tenant ID"}) return } var req struct { Name string `json:"name"` Slug string `json:"slug"` Description string `json:"description"` LogoURL string `json:"logo_url"` } if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } tenant, err := tenantService.UpdateTenant(id, req.Name, req.Slug, req.Description, req.LogoURL) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, tenant) } func DeleteTenant(c *gin.Context) { id, err := uuid.Parse(c.Param("id")) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid tenant ID"}) return } if err := tenantService.DeleteTenant(id); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, gin.H{"message": "Tenant deleted successfully"}) } // Auth Handlers func Login(c *gin.Context) { var req struct { Email string `json:"email" binding:"required,email"` Password string `json:"password" binding:"required"` } if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } user, err := userService.AuthenticateUser(req.Email, req.Password) if err != nil { c.JSON(http.StatusUnauthorized, gin.H{"error": err.Error()}) return } // Generate JWT token (this is a simplified example) // In a real application, you'd use the jwt package to create a proper token // For now, return user info with a placeholder token c.JSON(http.StatusOK, gin.H{ "message": "Login successful", "user": user, "token": "placeholder_token", // In real implementation, return actual JWT }) } func Register(c *gin.Context) { var req struct { TenantID string `json:"tenant_id" binding:"required"` Email string `json:"email" binding:"required,email"` Username string `json:"username" binding:"required"` FirstName string `json:"first_name" binding:"required"` LastName string `json:"last_name" binding:"required"` Phone string `json:"phone"` Role string `json:"role" binding:"required"` Password string `json:"password" binding:"required,min=8"` } if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } tenantID, err := uuid.Parse(req.TenantID) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid tenant ID"}) return } // Validate role if !models.ValidRole(req.Role) { c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid role"}) return } user, err := userService.CreateUser(tenantID, req.Email, req.Username, req.FirstName, req.LastName, req.Phone, req.Role, req.Password) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusCreated, gin.H{ "message": "User registered successfully", "user": user, }) } func Logout(c *gin.Context) { middleware.LogoutHandler(c) // This will handle the response } func Profile(c *gin.Context) { // Get user from context (set by auth middleware) user, exists := c.Get("user") if !exists { c.JSON(http.StatusUnauthorized, gin.H{"error": "User not authenticated"}) return } c.JSON(http.StatusOK, gin.H{ "user": user, }) } // OIDC/Social Media Login func OIDCLogin(c *gin.Context) { middleware.OIDCLoginHandler(c) // This will redirect the user } func OIDCCallback(c *gin.Context) { middleware.OIDCCallbackHandler(c) // This will handle the callback } func SocialLogin(c *gin.Context) { middleware.SocialLoginHandler(c) // This will redirect the user } func SocialCallback(c *gin.Context) { middleware.SocialCallbackHandler(c) // This will handle the callback } // Position Handlers func GetPositions(c *gin.Context) { // Parse query parameters limit, _ := strconv.Atoi(c.DefaultQuery("limit", "10")) offset, _ := strconv.Atoi(c.DefaultQuery("offset", "0")) status := c.Query("status") employmentType := c.Query("employment_type") experienceLevel := c.Query("experience_level") location := c.Query("location") // Parse tenant ID if provided var tenantID *uuid.UUID if tenantStr := c.Query("tenant_id"); tenantStr != "" { id, err := uuid.Parse(tenantStr) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid tenant ID"}) return } tenantID = &id } positions, err := positionService.GetPositions(tenantID, limit, offset, status, employmentType, experienceLevel, location) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, positions) } func GetPosition(c *gin.Context) { id, err := uuid.Parse(c.Param("id")) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid position ID"}) return } position, err := positionService.GetPosition(id) if err != nil { c.JSON(http.StatusNotFound, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, position) } func CreatePosition(c *gin.Context) { // Get user from context user, exists := c.Get("user") if !exists { c.JSON(http.StatusUnauthorized, gin.H{"error": "User not authenticated"}) return } userData, ok := user.(models.User) if !ok { c.JSON(http.StatusInternalServerError, gin.H{"error": "Error getting user data"}) return } var req struct { Title string `json:"title" binding:"required"` Description string `json:"description" binding:"required"` Requirements string `json:"requirements"` Location string `json:"location"` EmploymentType string `json:"employment_type" binding:"required"` SalaryMin *float64 `json:"salary_min"` SalaryMax *float64 `json:"salary_max"` ExperienceLevel string `json:"experience_level" binding:"required"` } if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } // Validate employment type if !models.ValidEmploymentType(req.EmploymentType) { c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid employment type"}) return } // Validate experience level if !models.ValidExperienceLevel(req.ExperienceLevel) { c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid experience level"}) return } // Validate salary range if req.SalaryMin != nil && req.SalaryMax != nil && *req.SalaryMin > *req.SalaryMax { c.JSON(http.StatusBadRequest, gin.H{"error": "Minimum salary cannot be greater than maximum salary"}) return } position, err := positionService.CreatePosition(userData.TenantID, userData.ID, req.Title, req.Description, req.Requirements, req.Location, req.EmploymentType, req.ExperienceLevel, req.SalaryMin, req.SalaryMax) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusCreated, position) } func UpdatePosition(c *gin.Context) { id, err := uuid.Parse(c.Param("id")) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid position ID"}) return } var req struct { Title string `json:"title" binding:"required"` Description string `json:"description" binding:"required"` Requirements string `json:"requirements"` Location string `json:"location"` EmploymentType string `json:"employment_type" binding:"required"` SalaryMin *float64 `json:"salary_min"` SalaryMax *float64 `json:"salary_max"` ExperienceLevel string `json:"experience_level" binding:"required"` } if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } // Validate employment type if !models.ValidEmploymentType(req.EmploymentType) { c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid employment type"}) return } // Validate experience level if !models.ValidExperienceLevel(req.ExperienceLevel) { c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid experience level"}) return } // Validate salary range if req.SalaryMin != nil && req.SalaryMax != nil && *req.SalaryMin > *req.SalaryMax { c.JSON(http.StatusBadRequest, gin.H{"error": "Minimum salary cannot be greater than maximum salary"}) return } position, err := positionService.UpdatePosition(id, req.Title, req.Description, req.Requirements, req.Location, req.EmploymentType, req.ExperienceLevel, req.SalaryMin, req.SalaryMax) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, position) } func DeletePosition(c *gin.Context) { id, err := uuid.Parse(c.Param("id")) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid position ID"}) return } if err := positionService.DeletePosition(id); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, gin.H{"message": "Position deleted successfully"}) } // Application Handlers func GetApplications(c *gin.Context) { // Parse query parameters limit, _ := strconv.Atoi(c.DefaultQuery("limit", "10")) offset, _ := strconv.Atoi(c.DefaultQuery("offset", "0")) status := c.Query("status") // Parse user ID if provided var userID *uuid.UUID if userStr := c.Query("user_id"); userStr != "" { id, err := uuid.Parse(userStr) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid user ID"}) return } userID = &id } // Parse position ID if provided var positionID *uuid.UUID if positionStr := c.Query("position_id"); positionStr != "" { id, err := uuid.Parse(positionStr) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid position ID"}) return } positionID = &id } applications, err := applicationService.GetApplications(userID, positionID, status, limit, offset) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, applications) } func GetApplication(c *gin.Context) { id, err := uuid.Parse(c.Param("id")) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid application ID"}) return } application, err := applicationService.GetApplication(id) if err != nil { c.JSON(http.StatusNotFound, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, application) } func CreateApplication(c *gin.Context) { // Get user from context user, exists := c.Get("user") if !exists { c.JSON(http.StatusUnauthorized, gin.H{"error": "User not authenticated"}) return } userData, ok := user.(models.User) if !ok { c.JSON(http.StatusInternalServerError, gin.H{"error": "Error getting user data"}) return } var req struct { PositionID string `json:"position_id" binding:"required"` ResumeID string `json:"resume_id"` CoverLetter string `json:"cover_letter"` } if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } positionID, err := uuid.Parse(req.PositionID) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid position ID"}) return } var resumeID *uuid.UUID if req.ResumeID != "" { id, err := uuid.Parse(req.ResumeID) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid resume ID"}) return } resumeID = &id } application, err := applicationService.CreateApplication(positionID, userData.ID, resumeID, req.CoverLetter) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusCreated, application) } func UpdateApplication(c *gin.Context) { id, err := uuid.Parse(c.Param("id")) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid application ID"}) return } // Get reviewer user from context reviewer, exists := c.Get("user") if !exists { c.JSON(http.StatusUnauthorized, gin.H{"error": "User not authenticated"}) return } reviewerData, ok := reviewer.(models.User) if !ok { c.JSON(http.StatusInternalServerError, gin.H{"error": "Error getting user data"}) return } var req struct { Status string `json:"status" binding:"required"` Notes string `json:"notes"` } if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } // Validate status if !models.ValidStatus(req.Status) { c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid status"}) return } application, err := applicationService.UpdateApplication(id, req.Status, reviewerData.ID, req.Notes) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, application) } func DeleteApplication(c *gin.Context) { id, err := uuid.Parse(c.Param("id")) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid application ID"}) return } if err := applicationService.DeleteApplication(id); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, gin.H{"message": "Application deleted successfully"}) } // Resume Handlers func UploadResume(c *gin.Context) { // Get user from context user, exists := c.Get("user") if !exists { c.JSON(http.StatusUnauthorized, gin.H{"error": "User not authenticated"}) return } userData, ok := user.(models.User) if !ok { c.JSON(http.StatusInternalServerError, gin.H{"error": "Error getting user data"}) return } title := c.PostForm("title") if title == "" { title = "Resume" } file, err := c.FormFile("file") if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "File upload failed"}) return } resume, err := resumeService.UploadResume(userData.ID, file, title) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusCreated, resume) } func GetResume(c *gin.Context) { id, err := uuid.Parse(c.Param("id")) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid resume ID"}) return } // Get user from context user, exists := c.Get("user") if !exists { c.JSON(http.StatusUnauthorized, gin.H{"error": "User not authenticated"}) return } userData, ok := user.(models.User) if !ok { c.JSON(http.StatusInternalServerError, gin.H{"error": "Error getting user data"}) return } resume, err := resumeService.GetResume(id) if err != nil { c.JSON(http.StatusNotFound, gin.H{"error": err.Error()}) return } // Ensure the user owns the resume if resume.UserID != userData.ID { c.JSON(http.StatusForbidden, gin.H{"error": "Access denied"}) return } // Serve the file c.File(resume.FilePath) }