package handlers import ( "errors" "net/http" "strconv" "github.com/gin-gonic/gin" "github.com/treytartt/mycrib-api/internal/dto/requests" "github.com/treytartt/mycrib-api/internal/middleware" "github.com/treytartt/mycrib-api/internal/models" "github.com/treytartt/mycrib-api/internal/services" ) // TaskHandler handles task-related HTTP requests type TaskHandler struct { taskService *services.TaskService } // NewTaskHandler creates a new task handler func NewTaskHandler(taskService *services.TaskService) *TaskHandler { return &TaskHandler{taskService: taskService} } // ListTasks handles GET /api/tasks/ func (h *TaskHandler) ListTasks(c *gin.Context) { user := c.MustGet(middleware.AuthUserKey).(*models.User) response, err := h.taskService.ListTasks(user.ID) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, response) } // GetTask handles GET /api/tasks/:id/ func (h *TaskHandler) GetTask(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": "Invalid task ID"}) return } response, err := h.taskService.GetTask(uint(taskID), user.ID) if err != nil { switch { case errors.Is(err, services.ErrTaskNotFound): c.JSON(http.StatusNotFound, gin.H{"error": err.Error()}) case errors.Is(err, services.ErrTaskAccessDenied): c.JSON(http.StatusForbidden, gin.H{"error": err.Error()}) default: c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) } return } c.JSON(http.StatusOK, response) } // GetTasksByResidence handles GET /api/tasks/by-residence/:residence_id/ func (h *TaskHandler) GetTasksByResidence(c *gin.Context) { user := c.MustGet(middleware.AuthUserKey).(*models.User) residenceID, err := strconv.ParseUint(c.Param("residence_id"), 10, 32) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid residence ID"}) return } daysThreshold := 30 if d := c.Query("days_threshold"); d != "" { if parsed, err := strconv.Atoi(d); err == nil { daysThreshold = parsed } } response, err := h.taskService.GetTasksByResidence(uint(residenceID), user.ID, daysThreshold) if err != nil { switch { case errors.Is(err, services.ErrResidenceAccessDenied): c.JSON(http.StatusForbidden, gin.H{"error": err.Error()}) default: c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) } return } c.JSON(http.StatusOK, response) } // CreateTask handles POST /api/tasks/ func (h *TaskHandler) CreateTask(c *gin.Context) { user := c.MustGet(middleware.AuthUserKey).(*models.User) var req requests.CreateTaskRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } response, err := h.taskService.CreateTask(&req, user.ID) if err != nil { if errors.Is(err, services.ErrResidenceAccessDenied) { c.JSON(http.StatusForbidden, gin.H{"error": err.Error()}) return } c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusCreated, response) } // UpdateTask handles PUT/PATCH /api/tasks/:id/ func (h *TaskHandler) UpdateTask(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": "Invalid task ID"}) return } var req requests.UpdateTaskRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } response, err := h.taskService.UpdateTask(uint(taskID), user.ID, &req) if err != nil { switch { case errors.Is(err, services.ErrTaskNotFound): c.JSON(http.StatusNotFound, gin.H{"error": err.Error()}) case errors.Is(err, services.ErrTaskAccessDenied): c.JSON(http.StatusForbidden, gin.H{"error": err.Error()}) default: c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) } return } c.JSON(http.StatusOK, response) } // DeleteTask handles DELETE /api/tasks/:id/ func (h *TaskHandler) DeleteTask(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": "Invalid task ID"}) return } err = h.taskService.DeleteTask(uint(taskID), user.ID) if err != nil { switch { case errors.Is(err, services.ErrTaskNotFound): c.JSON(http.StatusNotFound, gin.H{"error": err.Error()}) case errors.Is(err, services.ErrTaskAccessDenied): c.JSON(http.StatusForbidden, gin.H{"error": err.Error()}) default: c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) } return } c.JSON(http.StatusOK, gin.H{"message": "Task deleted successfully"}) } // MarkInProgress handles POST /api/tasks/:id/mark-in-progress/ func (h *TaskHandler) MarkInProgress(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": "Invalid task ID"}) return } response, err := h.taskService.MarkInProgress(uint(taskID), user.ID) if err != nil { switch { case errors.Is(err, services.ErrTaskNotFound): c.JSON(http.StatusNotFound, gin.H{"error": err.Error()}) case errors.Is(err, services.ErrTaskAccessDenied): c.JSON(http.StatusForbidden, gin.H{"error": err.Error()}) default: c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) } return } c.JSON(http.StatusOK, gin.H{"message": "Task marked as in progress", "task": response}) } // CancelTask handles POST /api/tasks/:id/cancel/ func (h *TaskHandler) CancelTask(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": "Invalid task ID"}) return } response, err := h.taskService.CancelTask(uint(taskID), user.ID) if err != nil { switch { case errors.Is(err, services.ErrTaskNotFound): c.JSON(http.StatusNotFound, gin.H{"error": err.Error()}) case errors.Is(err, services.ErrTaskAccessDenied): c.JSON(http.StatusForbidden, gin.H{"error": err.Error()}) case errors.Is(err, services.ErrTaskAlreadyCancelled): c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) default: c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) } return } c.JSON(http.StatusOK, gin.H{"message": "Task cancelled", "task": response}) } // UncancelTask handles POST /api/tasks/:id/uncancel/ func (h *TaskHandler) UncancelTask(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": "Invalid task ID"}) return } response, err := h.taskService.UncancelTask(uint(taskID), user.ID) if err != nil { switch { case errors.Is(err, services.ErrTaskNotFound): c.JSON(http.StatusNotFound, gin.H{"error": err.Error()}) case errors.Is(err, services.ErrTaskAccessDenied): c.JSON(http.StatusForbidden, gin.H{"error": err.Error()}) default: c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) } return } c.JSON(http.StatusOK, gin.H{"message": "Task uncancelled", "task": response}) } // ArchiveTask handles POST /api/tasks/:id/archive/ func (h *TaskHandler) ArchiveTask(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": "Invalid task ID"}) return } response, err := h.taskService.ArchiveTask(uint(taskID), user.ID) if err != nil { switch { case errors.Is(err, services.ErrTaskNotFound): c.JSON(http.StatusNotFound, gin.H{"error": err.Error()}) case errors.Is(err, services.ErrTaskAccessDenied): c.JSON(http.StatusForbidden, gin.H{"error": err.Error()}) case errors.Is(err, services.ErrTaskAlreadyArchived): c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) default: c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) } return } c.JSON(http.StatusOK, gin.H{"message": "Task archived", "task": response}) } // UnarchiveTask handles POST /api/tasks/:id/unarchive/ func (h *TaskHandler) UnarchiveTask(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": "Invalid task ID"}) return } response, err := h.taskService.UnarchiveTask(uint(taskID), user.ID) if err != nil { switch { case errors.Is(err, services.ErrTaskNotFound): c.JSON(http.StatusNotFound, gin.H{"error": err.Error()}) case errors.Is(err, services.ErrTaskAccessDenied): c.JSON(http.StatusForbidden, gin.H{"error": err.Error()}) default: c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) } return } c.JSON(http.StatusOK, gin.H{"message": "Task unarchived", "task": response}) } // === Task Completions === // ListCompletions handles GET /api/task-completions/ func (h *TaskHandler) ListCompletions(c *gin.Context) { user := c.MustGet(middleware.AuthUserKey).(*models.User) response, err := h.taskService.ListCompletions(user.ID) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, response) } // GetCompletion handles GET /api/task-completions/:id/ func (h *TaskHandler) GetCompletion(c *gin.Context) { user := c.MustGet(middleware.AuthUserKey).(*models.User) completionID, err := strconv.ParseUint(c.Param("id"), 10, 32) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid completion ID"}) return } response, err := h.taskService.GetCompletion(uint(completionID), user.ID) if err != nil { switch { case errors.Is(err, services.ErrCompletionNotFound): c.JSON(http.StatusNotFound, gin.H{"error": err.Error()}) case errors.Is(err, services.ErrTaskAccessDenied): c.JSON(http.StatusForbidden, gin.H{"error": err.Error()}) default: c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) } return } c.JSON(http.StatusOK, response) } // CreateCompletion handles POST /api/task-completions/ func (h *TaskHandler) CreateCompletion(c *gin.Context) { user := c.MustGet(middleware.AuthUserKey).(*models.User) var req requests.CreateTaskCompletionRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } response, err := h.taskService.CreateCompletion(&req, user.ID) if err != nil { switch { case errors.Is(err, services.ErrTaskNotFound): c.JSON(http.StatusNotFound, gin.H{"error": err.Error()}) case errors.Is(err, services.ErrTaskAccessDenied): c.JSON(http.StatusForbidden, gin.H{"error": err.Error()}) default: c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) } return } c.JSON(http.StatusCreated, response) } // DeleteCompletion handles DELETE /api/task-completions/:id/ func (h *TaskHandler) DeleteCompletion(c *gin.Context) { user := c.MustGet(middleware.AuthUserKey).(*models.User) completionID, err := strconv.ParseUint(c.Param("id"), 10, 32) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid completion ID"}) return } err = h.taskService.DeleteCompletion(uint(completionID), user.ID) if err != nil { switch { case errors.Is(err, services.ErrCompletionNotFound): c.JSON(http.StatusNotFound, gin.H{"error": err.Error()}) case errors.Is(err, services.ErrTaskAccessDenied): c.JSON(http.StatusForbidden, gin.H{"error": err.Error()}) default: c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) } return } c.JSON(http.StatusOK, gin.H{"message": "Completion deleted successfully"}) } // === Lookups === // GetCategories handles GET /api/tasks/categories/ func (h *TaskHandler) GetCategories(c *gin.Context) { categories, err := h.taskService.GetCategories() if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, categories) } // GetPriorities handles GET /api/tasks/priorities/ func (h *TaskHandler) GetPriorities(c *gin.Context) { priorities, err := h.taskService.GetPriorities() if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, priorities) } // GetStatuses handles GET /api/tasks/statuses/ func (h *TaskHandler) GetStatuses(c *gin.Context) { statuses, err := h.taskService.GetStatuses() if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, statuses) } // GetFrequencies handles GET /api/tasks/frequencies/ func (h *TaskHandler) GetFrequencies(c *gin.Context) { frequencies, err := h.taskService.GetFrequencies() if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, frequencies) }