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:
@@ -75,8 +75,8 @@ func (s *TaskService) GetTask(taskID, userID uint) (*responses.TaskResponse, err
|
||||
return &resp, nil
|
||||
}
|
||||
|
||||
// ListTasks lists all tasks accessible to a user
|
||||
func (s *TaskService) ListTasks(userID uint) (*responses.TaskListResponse, error) {
|
||||
// ListTasks lists all tasks accessible to a user as a kanban board
|
||||
func (s *TaskService) ListTasks(userID uint) (*responses.KanbanBoardResponse, error) {
|
||||
// Get all residence IDs accessible to user
|
||||
residences, err := s.residenceRepo.FindByUser(userID)
|
||||
if err != nil {
|
||||
@@ -89,15 +89,21 @@ func (s *TaskService) ListTasks(userID uint) (*responses.TaskListResponse, error
|
||||
}
|
||||
|
||||
if len(residenceIDs) == 0 {
|
||||
return &responses.TaskListResponse{Count: 0, Results: []responses.TaskResponse{}}, nil
|
||||
// Return empty kanban board
|
||||
return &responses.KanbanBoardResponse{
|
||||
Columns: []responses.KanbanColumnResponse{},
|
||||
DaysThreshold: 30,
|
||||
ResidenceID: "all",
|
||||
}, nil
|
||||
}
|
||||
|
||||
tasks, err := s.taskRepo.FindByUser(userID, residenceIDs)
|
||||
// Get kanban data aggregated across all residences
|
||||
board, err := s.taskRepo.GetKanbanDataForMultipleResidences(residenceIDs, 30)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp := responses.NewTaskListResponse(tasks)
|
||||
resp := responses.NewKanbanBoardResponseForAll(board)
|
||||
return &resp, nil
|
||||
}
|
||||
|
||||
@@ -146,7 +152,7 @@ func (s *TaskService) CreateTask(req *requests.CreateTaskRequest, userID uint) (
|
||||
StatusID: req.StatusID,
|
||||
FrequencyID: req.FrequencyID,
|
||||
AssignedToID: req.AssignedToID,
|
||||
DueDate: req.DueDate,
|
||||
DueDate: req.DueDate.ToTimePtr(),
|
||||
EstimatedCost: req.EstimatedCost,
|
||||
ContractorID: req.ContractorID,
|
||||
}
|
||||
@@ -207,7 +213,7 @@ func (s *TaskService) UpdateTask(taskID, userID uint, req *requests.UpdateTaskRe
|
||||
task.AssignedToID = req.AssignedToID
|
||||
}
|
||||
if req.DueDate != nil {
|
||||
task.DueDate = req.DueDate
|
||||
task.DueDate = req.DueDate.ToTimePtr()
|
||||
}
|
||||
if req.EstimatedCost != nil {
|
||||
task.EstimatedCost = req.EstimatedCost
|
||||
@@ -583,7 +589,7 @@ func (s *TaskService) GetCompletion(completionID, userID uint) (*responses.TaskC
|
||||
}
|
||||
|
||||
// ListCompletions lists all task completions for a user
|
||||
func (s *TaskService) ListCompletions(userID uint) (*responses.TaskCompletionListResponse, error) {
|
||||
func (s *TaskService) ListCompletions(userID uint) ([]responses.TaskCompletionResponse, error) {
|
||||
// Get all residence IDs
|
||||
residences, err := s.residenceRepo.FindByUser(userID)
|
||||
if err != nil {
|
||||
@@ -596,7 +602,7 @@ func (s *TaskService) ListCompletions(userID uint) (*responses.TaskCompletionLis
|
||||
}
|
||||
|
||||
if len(residenceIDs) == 0 {
|
||||
return &responses.TaskCompletionListResponse{Count: 0, Results: []responses.TaskCompletionResponse{}}, nil
|
||||
return []responses.TaskCompletionResponse{}, nil
|
||||
}
|
||||
|
||||
completions, err := s.taskRepo.FindCompletionsByUser(userID, residenceIDs)
|
||||
@@ -604,8 +610,7 @@ func (s *TaskService) ListCompletions(userID uint) (*responses.TaskCompletionLis
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp := responses.NewTaskCompletionListResponse(completions)
|
||||
return &resp, nil
|
||||
return responses.NewTaskCompletionListResponse(completions), nil
|
||||
}
|
||||
|
||||
// DeleteCompletion deletes a task completion
|
||||
@@ -630,6 +635,35 @@ func (s *TaskService) DeleteCompletion(completionID, userID uint) error {
|
||||
return s.taskRepo.DeleteCompletion(completionID)
|
||||
}
|
||||
|
||||
// GetCompletionsByTask gets all completions for a specific task
|
||||
func (s *TaskService) GetCompletionsByTask(taskID, userID uint) ([]responses.TaskCompletionResponse, error) {
|
||||
// Get the task to check access
|
||||
task, err := s.taskRepo.FindByID(taskID)
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, ErrTaskNotFound
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Check access via residence
|
||||
hasAccess, err := s.residenceRepo.HasAccess(task.ResidenceID, userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !hasAccess {
|
||||
return nil, ErrTaskAccessDenied
|
||||
}
|
||||
|
||||
// Get completions for the task
|
||||
completions, err := s.taskRepo.FindCompletionsByTask(taskID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return responses.NewTaskCompletionListResponse(completions), nil
|
||||
}
|
||||
|
||||
// === Lookups ===
|
||||
|
||||
// GetCategories returns all task categories
|
||||
|
||||
Reference in New Issue
Block a user