Add quick-complete endpoint for iOS widget task completion
- Add lightweight POST /api/tasks/:id/quick-complete/ endpoint - Creates task completion with minimal processing for widget use - Returns only 200 OK on success (no response body) - Updates task status and next_due_date based on frequency - Sends completion notification asynchronously 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -295,6 +295,31 @@ func (h *TaskHandler) UnarchiveTask(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, response)
|
||||
}
|
||||
|
||||
// QuickComplete handles POST /api/tasks/:id/quick-complete/
|
||||
// Lightweight endpoint for widget - just returns 200 OK on success
|
||||
func (h *TaskHandler) QuickComplete(c *gin.Context) {
|
||||
user := c.MustGet(middleware.AuthUserKey).(*models.User)
|
||||
taskID, err := strconv.ParseUint(c.Param("id"), 10, 32)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": i18n.LocalizedMessage(c, "error.invalid_task_id")})
|
||||
return
|
||||
}
|
||||
|
||||
err = h.taskService.QuickComplete(uint(taskID), user.ID)
|
||||
if err != nil {
|
||||
switch {
|
||||
case errors.Is(err, services.ErrTaskNotFound):
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": i18n.LocalizedMessage(c, "error.task_not_found")})
|
||||
case errors.Is(err, services.ErrTaskAccessDenied):
|
||||
c.JSON(http.StatusForbidden, gin.H{"error": i18n.LocalizedMessage(c, "error.task_access_denied")})
|
||||
default:
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
}
|
||||
return
|
||||
}
|
||||
c.Status(http.StatusOK)
|
||||
}
|
||||
|
||||
// === Task Completions ===
|
||||
|
||||
// GetTaskCompletions handles GET /api/tasks/:id/completions/
|
||||
|
||||
@@ -298,6 +298,7 @@ func setupTaskRoutes(api *gin.RouterGroup, taskHandler *handlers.TaskHandler) {
|
||||
tasks.POST("/:id/uncancel/", taskHandler.UncancelTask)
|
||||
tasks.POST("/:id/archive/", taskHandler.ArchiveTask)
|
||||
tasks.POST("/:id/unarchive/", taskHandler.UnarchiveTask)
|
||||
tasks.POST("/:id/quick-complete/", taskHandler.QuickComplete)
|
||||
tasks.GET("/:id/completions/", taskHandler.GetTaskCompletions)
|
||||
}
|
||||
|
||||
|
||||
@@ -593,6 +593,65 @@ func (s *TaskService) CreateCompletion(req *requests.CreateTaskCompletionRequest
|
||||
}, nil
|
||||
}
|
||||
|
||||
// QuickComplete creates a minimal task completion (for widget use)
|
||||
// Returns only success/error, no response body
|
||||
func (s *TaskService) QuickComplete(taskID uint, userID uint) error {
|
||||
// Get the task
|
||||
task, err := s.taskRepo.FindByID(taskID)
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return ErrTaskNotFound
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// Check access
|
||||
hasAccess, err := s.residenceRepo.HasAccess(task.ResidenceID, userID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !hasAccess {
|
||||
return ErrTaskAccessDenied
|
||||
}
|
||||
|
||||
completedAt := time.Now().UTC()
|
||||
|
||||
completion := &models.TaskCompletion{
|
||||
TaskID: taskID,
|
||||
CompletedByID: userID,
|
||||
CompletedAt: completedAt,
|
||||
Notes: "Completed from widget",
|
||||
}
|
||||
|
||||
if err := s.taskRepo.CreateCompletion(completion); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Update next_due_date and status based on frequency
|
||||
if task.Frequency == nil || task.Frequency.Days == nil || *task.Frequency.Days == 0 {
|
||||
// One-time task - clear next_due_date and set status to "Completed" (ID=3)
|
||||
task.NextDueDate = nil
|
||||
completedStatusID := uint(3)
|
||||
task.StatusID = &completedStatusID
|
||||
} else {
|
||||
// Recurring task - calculate next due date from completion date + frequency
|
||||
nextDue := completedAt.AddDate(0, 0, *task.Frequency.Days)
|
||||
task.NextDueDate = &nextDue
|
||||
|
||||
// Reset status to "Pending" (ID=1)
|
||||
pendingStatusID := uint(1)
|
||||
task.StatusID = &pendingStatusID
|
||||
}
|
||||
if err := s.taskRepo.Update(task); err != nil {
|
||||
log.Error().Err(err).Uint("task_id", task.ID).Msg("Failed to update task after quick completion")
|
||||
}
|
||||
|
||||
// Send notification (fire and forget)
|
||||
go s.sendTaskCompletedNotification(task, completion)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// sendTaskCompletedNotification sends notifications when a task is completed
|
||||
func (s *TaskService) sendTaskCompletedNotification(task *models.Task, completion *models.TaskCompletion) {
|
||||
// Get all users with access to this residence
|
||||
|
||||
Reference in New Issue
Block a user