the beginning of the idiots
This commit is contained in:
582
qwen/go/services/services.go
Normal file
582
qwen/go/services/services.go
Normal file
@@ -0,0 +1,582 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"mohportal/db"
|
||||
"mohportal/models"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// TenantService handles tenant-related operations
|
||||
type TenantService struct{}
|
||||
|
||||
// CreateTenant creates a new tenant
|
||||
func (ts *TenantService) CreateTenant(name, slug, description, logoURL string) (*models.Tenant, error) {
|
||||
tenant := &models.Tenant{
|
||||
ID: uuid.New(),
|
||||
Name: name,
|
||||
Slug: slug,
|
||||
Description: description,
|
||||
LogoURL: logoURL,
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
IsActive: true,
|
||||
}
|
||||
|
||||
if err := db.DB.Create(tenant).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return tenant, nil
|
||||
}
|
||||
|
||||
// GetTenant retrieves a tenant by ID
|
||||
func (ts *TenantService) GetTenant(id uuid.UUID) (*models.Tenant, error) {
|
||||
var tenant models.Tenant
|
||||
if err := db.DB.First(&tenant, "id = ?", id).Error; err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, errors.New("tenant not found")
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return &tenant, nil
|
||||
}
|
||||
|
||||
// GetTenantBySlug retrieves a tenant by slug
|
||||
func (ts *TenantService) GetTenantBySlug(slug string) (*models.Tenant, error) {
|
||||
var tenant models.Tenant
|
||||
if err := db.DB.First(&tenant, "slug = ?", slug).Error; err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, errors.New("tenant not found")
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return &tenant, nil
|
||||
}
|
||||
|
||||
// GetTenants retrieves all tenants (with optional filtering)
|
||||
func (ts *TenantService) GetTenants(limit, offset int) ([]*models.Tenant, error) {
|
||||
var tenants []*models.Tenant
|
||||
query := db.DB.Limit(limit).Offset(offset)
|
||||
|
||||
if err := query.Find(&tenants).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return tenants, nil
|
||||
}
|
||||
|
||||
// UpdateTenant updates an existing tenant
|
||||
func (ts *TenantService) UpdateTenant(id uuid.UUID, name, slug, description, logoURL string) (*models.Tenant, error) {
|
||||
var tenant models.Tenant
|
||||
if err := db.DB.First(&tenant, "id = ?", id).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tenant.Name = name
|
||||
tenant.Slug = slug
|
||||
tenant.Description = description
|
||||
tenant.LogoURL = logoURL
|
||||
tenant.UpdatedAt = time.Now()
|
||||
|
||||
if err := db.DB.Save(&tenant).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &tenant, nil
|
||||
}
|
||||
|
||||
// DeleteTenant deletes a tenant
|
||||
func (ts *TenantService) DeleteTenant(id uuid.UUID) error {
|
||||
return db.DB.Delete(&models.Tenant{}, "id = ?", id).Error
|
||||
}
|
||||
|
||||
// UserService handles user-related operations
|
||||
type UserService struct{}
|
||||
|
||||
// CreateUser creates a new user
|
||||
func (us *UserService) CreateUser(tenantID uuid.UUID, email, username, firstName, lastName, phone, role, password string) (*models.User, error) {
|
||||
// Check if user already exists
|
||||
var existingUser models.User
|
||||
if err := db.DB.Where("email = ? OR username = ?", email, username).First(&existingUser).Error; err == nil {
|
||||
return nil, errors.New("user with email or username already exists")
|
||||
}
|
||||
|
||||
// Hash the password
|
||||
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
user := &models.User{
|
||||
ID: uuid.New(),
|
||||
TenantID: tenantID,
|
||||
Email: email,
|
||||
Username: username,
|
||||
FirstName: firstName,
|
||||
LastName: lastName,
|
||||
Phone: phone,
|
||||
Role: role,
|
||||
PasswordHash: string(hashedPassword),
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
IsActive: true,
|
||||
}
|
||||
|
||||
if err := db.DB.Create(user).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return user, nil
|
||||
}
|
||||
|
||||
// GetUser retrieves a user by ID
|
||||
func (us *UserService) GetUser(id uuid.UUID) (*models.User, error) {
|
||||
var user models.User
|
||||
if err := db.DB.Preload("Tenant").First(&user, "id = ?", id).Error; err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, errors.New("user not found")
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return &user, nil
|
||||
}
|
||||
|
||||
// GetUserByEmail retrieves a user by email
|
||||
func (us *UserService) GetUserByEmail(email string) (*models.User, error) {
|
||||
var user models.User
|
||||
if err := db.DB.Preload("Tenant").First(&user, "email = ?", email).Error; err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, errors.New("user not found")
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return &user, nil
|
||||
}
|
||||
|
||||
// AuthenticateUser authenticates a user by email and password
|
||||
func (us *UserService) AuthenticateUser(email, password string) (*models.User, error) {
|
||||
var user models.User
|
||||
if err := db.DB.First(&user, "email = ?", email).Error; err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
// Return error without specifying whether email exists to prevent timing attacks
|
||||
return nil, errors.New("invalid credentials")
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !user.IsActive {
|
||||
return nil, errors.New("user account is deactivated")
|
||||
}
|
||||
|
||||
// Compare the password
|
||||
if err := bcrypt.CompareHashAndPassword([]byte(user.PasswordHash), []byte(password)); err != nil {
|
||||
return nil, errors.New("invalid credentials")
|
||||
}
|
||||
|
||||
// Update last login
|
||||
user.LastLogin = &time.Now()
|
||||
db.DB.Save(&user)
|
||||
|
||||
return &user, nil
|
||||
}
|
||||
|
||||
// UpdateUser updates an existing user
|
||||
func (us *UserService) UpdateUser(id uuid.UUID, email, username, firstName, lastName, phone, role string) (*models.User, error) {
|
||||
var user models.User
|
||||
if err := db.DB.First(&user, "id = ?", id).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Check if email or username is already taken by another user
|
||||
var existingUser models.User
|
||||
if err := db.DB.Where("email = ? AND id != ?", email, id).First(&existingUser).Error; err == nil {
|
||||
return nil, errors.New("email already in use by another user")
|
||||
}
|
||||
|
||||
if username != "" {
|
||||
if err := db.DB.Where("username = ? AND id != ?", username, id).First(&existingUser).Error; err == nil {
|
||||
return nil, errors.New("username already in use by another user")
|
||||
}
|
||||
}
|
||||
|
||||
user.Email = email
|
||||
if username != "" {
|
||||
user.Username = username
|
||||
}
|
||||
user.FirstName = firstName
|
||||
user.LastName = lastName
|
||||
user.Phone = phone
|
||||
user.Role = role
|
||||
user.UpdatedAt = time.Now()
|
||||
|
||||
if err := db.DB.Save(&user).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &user, nil
|
||||
}
|
||||
|
||||
// PositionService handles job position-related operations
|
||||
type PositionService struct{}
|
||||
|
||||
// CreatePosition creates a new job position
|
||||
func (ps *PositionService) CreatePosition(tenantID, userID uuid.UUID, title, description, requirements, location, employmentType, experienceLevel string, salaryMin, salaryMax *float64) (*models.JobPosition, error) {
|
||||
position := &models.JobPosition{
|
||||
ID: uuid.New(),
|
||||
TenantID: tenantID,
|
||||
UserID: userID,
|
||||
Title: title,
|
||||
Description: description,
|
||||
Requirements: requirements,
|
||||
Location: location,
|
||||
EmploymentType: employmentType,
|
||||
SalaryMin: salaryMin,
|
||||
SalaryMax: salaryMax,
|
||||
ExperienceLevel: experienceLevel,
|
||||
PostedAt: time.Now(),
|
||||
Status: "open",
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
IsActive: true,
|
||||
}
|
||||
|
||||
if err := db.DB.Create(position).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return position, nil
|
||||
}
|
||||
|
||||
// GetPosition retrieves a position by ID
|
||||
func (ps *PositionService) GetPosition(id uuid.UUID) (*models.JobPosition, error) {
|
||||
var position models.JobPosition
|
||||
if err := db.DB.Preload("User").First(&position, "id = ?", id).Error; err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, errors.New("position not found")
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return &position, nil
|
||||
}
|
||||
|
||||
// GetPositions retrieves positions with optional filtering
|
||||
func (ps *PositionService) GetPositions(tenantID *uuid.UUID, limit, offset int, status, employmentType, experienceLevel, location string) ([]*models.JobPosition, error) {
|
||||
var positions []*models.JobPosition
|
||||
query := db.DB.Preload("User").Limit(limit).Offset(offset)
|
||||
|
||||
if tenantID != nil {
|
||||
query = query.Where("tenant_id = ?", tenantID)
|
||||
}
|
||||
|
||||
if status != "" {
|
||||
query = query.Where("status = ?", status)
|
||||
}
|
||||
|
||||
if employmentType != "" {
|
||||
query = query.Where("employment_type = ?", employmentType)
|
||||
}
|
||||
|
||||
if experienceLevel != "" {
|
||||
query = query.Where("experience_level = ?", experienceLevel)
|
||||
}
|
||||
|
||||
if location != "" {
|
||||
query = query.Where("location LIKE ?", "%"+location+"%")
|
||||
}
|
||||
|
||||
query = query.Where("is_active = ? AND status = ?", true, "open")
|
||||
|
||||
if err := query.Find(&positions).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return positions, nil
|
||||
}
|
||||
|
||||
// UpdatePosition updates an existing position
|
||||
func (ps *PositionService) UpdatePosition(id uuid.UUID, title, description, requirements, location, employmentType, experienceLevel string, salaryMin, salaryMax *float64) (*models.JobPosition, error) {
|
||||
var position models.JobPosition
|
||||
if err := db.DB.First(&position, "id = ?", id).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
position.Title = title
|
||||
position.Description = description
|
||||
position.Requirements = requirements
|
||||
position.Location = location
|
||||
position.EmploymentType = employmentType
|
||||
position.ExperienceLevel = experienceLevel
|
||||
position.SalaryMin = salaryMin
|
||||
position.SalaryMax = salaryMax
|
||||
position.UpdatedAt = time.Now()
|
||||
|
||||
if err := db.DB.Save(&position).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &position, nil
|
||||
}
|
||||
|
||||
// ClosePosition closes a position (marks as filled or cancelled)
|
||||
func (ps *PositionService) ClosePosition(id uuid.UUID, status string) (*models.JobPosition, error) {
|
||||
var position models.JobPosition
|
||||
if err := db.DB.First(&position, "id = ?", id).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if status != "closed" && status != "filled" {
|
||||
return nil, errors.New("invalid status for closing position")
|
||||
}
|
||||
|
||||
position.Status = status
|
||||
position.ClosedAt = &time.Now()
|
||||
position.UpdatedAt = time.Now()
|
||||
|
||||
if err := db.DB.Save(&position).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &position, nil
|
||||
}
|
||||
|
||||
// DeletePosition deletes a position
|
||||
func (ps *PositionService) DeletePosition(id uuid.UUID) error {
|
||||
return db.DB.Delete(&models.JobPosition{}, "id = ?", id).Error
|
||||
}
|
||||
|
||||
// ResumeService handles resume-related operations
|
||||
type ResumeService struct{}
|
||||
|
||||
// UploadResume uploads a resume file and creates a record
|
||||
func (rs *ResumeService) UploadResume(userID uuid.UUID, fileHeader *multipart.FileHeader, title string) (*models.Resume, error) {
|
||||
// Validate file type
|
||||
allowedTypes := map[string]bool{
|
||||
"application/pdf": true,
|
||||
"application/msword": true,
|
||||
"application/vnd.openxmlformats-officedocument.wordprocessingml.document": true,
|
||||
"text/plain": true,
|
||||
}
|
||||
|
||||
file, err := fileHeader.Open()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
// Detect content type
|
||||
buffer := make([]byte, 512)
|
||||
_, err = file.Read(buffer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
fileType := http.DetectContentType(buffer)
|
||||
if !allowedTypes[fileType] {
|
||||
return nil, errors.New("file type not allowed")
|
||||
}
|
||||
|
||||
// Ensure the static/uploads directory exists
|
||||
uploadDir := "./static/uploads/resumes"
|
||||
if err := os.MkdirAll(uploadDir, 0755); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Generate unique filename
|
||||
filename := fmt.Sprintf("%s_%s", userID.String(), fileHeader.Filename)
|
||||
uploadPath := filepath.Join(uploadDir, filename)
|
||||
|
||||
// Copy file to upload directory
|
||||
src, err := fileHeader.Open()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer src.Close()
|
||||
|
||||
dst, err := os.Create(uploadPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer dst.Close()
|
||||
|
||||
if _, err := io.Copy(dst, src); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Create resume record
|
||||
resume := &models.Resume{
|
||||
ID: uuid.New(),
|
||||
UserID: userID,
|
||||
Title: title,
|
||||
FilePath: uploadPath,
|
||||
FileType: fileType,
|
||||
FileSize: fileHeader.Size,
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
IsActive: true,
|
||||
}
|
||||
|
||||
if err := db.DB.Create(resume).Error; err != nil {
|
||||
// Clean up the uploaded file if DB operation fails
|
||||
os.Remove(uploadPath)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return resume, nil
|
||||
}
|
||||
|
||||
// GetResume retrieves a resume by ID
|
||||
func (rs *ResumeService) GetResume(id uuid.UUID) (*models.Resume, error) {
|
||||
var resume models.Resume
|
||||
if err := db.DB.First(&resume, "id = ?", id).Error; err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, errors.New("resume not found")
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return &resume, nil
|
||||
}
|
||||
|
||||
// GetResumeByUser retrieves all resumes for a user
|
||||
func (rs *ResumeService) GetResumeByUser(userID uuid.UUID) ([]*models.Resume, error) {
|
||||
var resumes []*models.Resume
|
||||
if err := db.DB.Where("user_id = ?", userID).Find(&resumes).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resumes, nil
|
||||
}
|
||||
|
||||
// ApplicationService handles job application-related operations
|
||||
type ApplicationService struct{}
|
||||
|
||||
// CreateApplication creates a new job application
|
||||
func (as *ApplicationService) CreateApplication(positionID, userID uuid.UUID, resumeID *uuid.UUID, coverLetter string) (*models.Application, error) {
|
||||
// Check if user already applied for this position
|
||||
var existingApplication models.Application
|
||||
if err := db.DB.Where("position_id = ? AND user_id = ?", positionID, userID).First(&existingApplication).Error; err == nil {
|
||||
return nil, errors.New("user has already applied for this position")
|
||||
}
|
||||
|
||||
// Verify position exists and is open
|
||||
var position models.JobPosition
|
||||
if err := db.DB.First(&position, "id = ? AND status = ? AND is_active = ?", positionID, "open", true).Error; err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, errors.New("position not found or not open")
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
application := &models.Application{
|
||||
ID: uuid.New(),
|
||||
PositionID: positionID,
|
||||
UserID: userID,
|
||||
ResumeID: resumeID,
|
||||
CoverLetter: coverLetter,
|
||||
Status: "pending",
|
||||
AppliedAt: time.Now(),
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
|
||||
if err := db.DB.Create(application).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return application, nil
|
||||
}
|
||||
|
||||
// GetApplication retrieves an application by ID
|
||||
func (as *ApplicationService) GetApplication(id uuid.UUID) (*models.Application, error) {
|
||||
var application models.Application
|
||||
if err := db.DB.Preload("User").Preload("Position").Preload("Resume").First(&application, "id = ?", id).Error; err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, errors.New("application not found")
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return &application, nil
|
||||
}
|
||||
|
||||
// GetApplications retrieves applications with optional filtering
|
||||
func (as *ApplicationService) GetApplications(userID, positionID *uuid.UUID, status string, limit, offset int) ([]*models.Application, error) {
|
||||
var applications []*models.Application
|
||||
query := db.DB.Preload("User").Preload("Position").Preload("Resume").Limit(limit).Offset(offset)
|
||||
|
||||
if userID != nil {
|
||||
query = query.Where("user_id = ?", userID)
|
||||
}
|
||||
|
||||
if positionID != nil {
|
||||
query = query.Where("position_id = ?", positionID)
|
||||
}
|
||||
|
||||
if status != "" {
|
||||
query = query.Where("status = ?", status)
|
||||
}
|
||||
|
||||
if err := query.Find(&applications).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return applications, nil
|
||||
}
|
||||
|
||||
// UpdateApplication updates an application status
|
||||
func (as *ApplicationService) UpdateApplication(id uuid.UUID, status string, reviewerID uuid.UUID, notes string) (*models.Application, error) {
|
||||
var application models.Application
|
||||
if err := db.DB.First(&application, "id = ?", id).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Validate status
|
||||
if !models.ValidStatus(status) {
|
||||
return nil, errors.New("invalid status")
|
||||
}
|
||||
|
||||
application.Status = status
|
||||
application.ReviewerUserID = &reviewerID
|
||||
application.Notes = notes
|
||||
application.UpdatedAt = time.Now()
|
||||
application.ReviewedAt = &time.Now()
|
||||
|
||||
if err := db.DB.Save(&application).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &application, nil
|
||||
}
|
||||
|
||||
// DeleteApplication deletes an application
|
||||
func (as *ApplicationService) DeleteApplication(id uuid.UUID) error {
|
||||
return db.DB.Delete(&models.Application{}, "id = ?", id).Error
|
||||
}
|
||||
|
||||
// GetApplicationsForPosition retrieves all applications for a specific position
|
||||
func (as *ApplicationService) GetApplicationsForPosition(positionID uuid.UUID, status string, limit, offset int) ([]*models.Application, error) {
|
||||
var applications []*models.Application
|
||||
query := db.DB.Preload("User").Preload("Resume").Limit(limit).Offset(offset).Where("position_id = ?", positionID)
|
||||
|
||||
if status != "" {
|
||||
query = query.Where("status = ?", status)
|
||||
}
|
||||
|
||||
if err := query.Find(&applications).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return applications, nil
|
||||
}
|
||||
Reference in New Issue
Block a user