diff --git a/internal/router/router.go b/internal/router/router.go index bdac62d..0166445 100644 --- a/internal/router/router.go +++ b/internal/router/router.go @@ -74,6 +74,9 @@ func SetupRouter(deps *Dependencies) *gin.Engine { contractorService := services.NewContractorService(contractorRepo, residenceRepo) documentService := services.NewDocumentService(documentRepo, residenceRepo) notificationService := services.NewNotificationService(notificationRepo, gorushClient) + + // Wire up notification service to task service (for task completion notifications) + taskService.SetNotificationService(notificationService) subscriptionService := services.NewSubscriptionService(subscriptionRepo, residenceRepo, taskRepo, contractorRepo, documentRepo) // Initialize middleware diff --git a/internal/services/task_service.go b/internal/services/task_service.go index 297d292..a3d7723 100644 --- a/internal/services/task_service.go +++ b/internal/services/task_service.go @@ -1,9 +1,12 @@ package services import ( + "context" "errors" + "fmt" "time" + "github.com/rs/zerolog/log" "gorm.io/gorm" "github.com/treytartt/mycrib-api/internal/dto/requests" @@ -23,8 +26,9 @@ var ( // TaskService handles task business logic type TaskService struct { - taskRepo *repositories.TaskRepository - residenceRepo *repositories.ResidenceRepository + taskRepo *repositories.TaskRepository + residenceRepo *repositories.ResidenceRepository + notificationService *NotificationService } // NewTaskService creates a new task service @@ -35,6 +39,11 @@ func NewTaskService(taskRepo *repositories.TaskRepository, residenceRepo *reposi } } +// SetNotificationService sets the notification service (for breaking circular dependency) +func (s *TaskService) SetNotificationService(ns *NotificationService) { + s.notificationService = ns +} + // === Task CRUD === // GetTask gets a task by ID with access check @@ -461,16 +470,69 @@ func (s *TaskService) CreateCompletion(req *requests.CreateTaskCompletionRequest return nil, err } - // Reload + // Reload completion with user info completion, err = s.taskRepo.FindCompletionByID(completion.ID) if err != nil { return nil, err } + // Send notification to residence owner and other users + s.sendTaskCompletedNotification(task, completion) + resp := responses.NewTaskCompletionResponse(completion) return &resp, nil } +// sendTaskCompletedNotification sends notifications when a task is completed +func (s *TaskService) sendTaskCompletedNotification(task *models.Task, completion *models.TaskCompletion) { + if s.notificationService == nil { + log.Debug().Msg("Notification service not configured, skipping task completion notification") + return + } + + // Get all users with access to this residence + users, err := s.residenceRepo.GetResidenceUsers(task.ResidenceID) + if err != nil { + log.Error().Err(err).Uint("task_id", task.ID).Msg("Failed to get residence users for notification") + return + } + + completedByName := "Someone" + if completion.CompletedBy.ID > 0 { + completedByName = completion.CompletedBy.GetFullName() + } + + title := "Task Completed" + body := fmt.Sprintf("%s completed: %s", completedByName, task.Title) + + data := map[string]interface{}{ + "task_id": task.ID, + "residence_id": task.ResidenceID, + "completion_id": completion.ID, + } + + // Notify all users except the one who completed the task + for _, user := range users { + if user.ID == completion.CompletedByID { + continue // Don't notify the person who completed it + } + + go func(userID uint) { + ctx := context.Background() + if err := s.notificationService.CreateAndSendNotification( + ctx, + userID, + models.NotificationTaskCompleted, + title, + body, + data, + ); err != nil { + log.Error().Err(err).Uint("user_id", userID).Uint("task_id", task.ID).Msg("Failed to send task completion notification") + } + }(user.ID) + } +} + // GetCompletion gets a task completion by ID func (s *TaskService) GetCompletion(completionID, userID uint) (*responses.TaskCompletionResponse, error) { completion, err := s.taskRepo.FindCompletionByID(completionID)