Files
MOHPortalTest-AllAgents-All…/qwen/go/handlers/handlers.go
2025-10-24 16:29:40 -05:00

636 lines
17 KiB
Go

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)
}