636 lines
17 KiB
Go
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)
|
|
} |