Add PDF reports, file uploads, admin auth, and comprehensive tests

Features:
- PDF service for generating task reports with ReportLab-style formatting
- Storage service for file uploads (local and S3-compatible)
- Admin authentication middleware with JWT support
- Admin user model and repository

Infrastructure:
- Updated Docker configuration for admin panel builds
- Email service enhancements for task notifications
- Updated router with admin and file upload routes
- Environment configuration updates

Tests:
- Unit tests for handlers (auth, residence, task)
- Unit tests for models (user, residence, task)
- Unit tests for repositories (user, residence, task)
- Unit tests for services (residence, task)
- Integration test setup
- Test utilities for mocking database and services

Database:
- Admin user seed data
- Updated test data seeds

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Trey t
2025-11-27 23:36:20 -06:00
parent 2817deee3c
commit 469f21a833
50 changed files with 6795 additions and 582 deletions

View File

@@ -55,7 +55,7 @@ func (s *ContractorService) GetContractor(contractorID, userID uint) (*responses
}
// ListContractors lists all contractors accessible to a user
func (s *ContractorService) ListContractors(userID uint) (*responses.ContractorListResponse, error) {
func (s *ContractorService) ListContractors(userID uint) ([]responses.ContractorResponse, error) {
residences, err := s.residenceRepo.FindByUser(userID)
if err != nil {
return nil, err
@@ -67,7 +67,7 @@ func (s *ContractorService) ListContractors(userID uint) (*responses.ContractorL
}
if len(residenceIDs) == 0 {
return &responses.ContractorListResponse{Count: 0, Results: []responses.ContractorResponse{}}, nil
return []responses.ContractorResponse{}, nil
}
contractors, err := s.contractorRepo.FindByUser(residenceIDs)
@@ -75,8 +75,7 @@ func (s *ContractorService) ListContractors(userID uint) (*responses.ContractorL
return nil, err
}
resp := responses.NewContractorListResponse(contractors)
return &resp, nil
return responses.NewContractorListResponse(contractors), nil
}
// CreateContractor creates a new contractor
@@ -234,8 +233,8 @@ func (s *ContractorService) DeleteContractor(contractorID, userID uint) error {
return s.contractorRepo.Delete(contractorID)
}
// ToggleFavorite toggles the favorite status of a contractor
func (s *ContractorService) ToggleFavorite(contractorID, userID uint) (*responses.ToggleFavoriteResponse, error) {
// ToggleFavorite toggles the favorite status of a contractor and returns the updated contractor
func (s *ContractorService) ToggleFavorite(contractorID, userID uint) (*responses.ContractorResponse, error) {
contractor, err := s.contractorRepo.FindByID(contractorID)
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
@@ -253,24 +252,23 @@ func (s *ContractorService) ToggleFavorite(contractorID, userID uint) (*response
return nil, ErrContractorAccessDenied
}
newStatus, err := s.contractorRepo.ToggleFavorite(contractorID)
_, err = s.contractorRepo.ToggleFavorite(contractorID)
if err != nil {
return nil, err
}
message := "Contractor removed from favorites"
if newStatus {
message = "Contractor added to favorites"
// Re-fetch the contractor to get the updated state with all relations
contractor, err = s.contractorRepo.FindByID(contractorID)
if err != nil {
return nil, err
}
return &responses.ToggleFavoriteResponse{
Message: message,
IsFavorite: newStatus,
}, nil
resp := responses.NewContractorResponse(contractor)
return &resp, nil
}
// GetContractorTasks gets all tasks for a contractor
func (s *ContractorService) GetContractorTasks(contractorID, userID uint) (*responses.TaskListResponse, error) {
func (s *ContractorService) GetContractorTasks(contractorID, userID uint) ([]responses.TaskResponse, error) {
contractor, err := s.contractorRepo.FindByID(contractorID)
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
@@ -293,8 +291,7 @@ func (s *ContractorService) GetContractorTasks(contractorID, userID uint) (*resp
return nil, err
}
resp := responses.NewTaskListResponse(tasks)
return &resp, nil
return responses.NewTaskListResponse(tasks), nil
}
// GetSpecialties returns all contractor specialties