package handlers import ( "encoding/json" "net/http" "strings" "gitea.hostxtra.co.uk/mrhid6/notely/backend/internal/application/dto" "gitea.hostxtra.co.uk/mrhid6/notely/backend/internal/application/services" "github.com/gorilla/mux" "go.mongodb.org/mongo-driver/v2/bson" ) // TaskHandler handles task and task status endpoints. type TaskHandler struct { taskService *services.TaskService } // NewTaskHandler creates a task handler. func NewTaskHandler(taskService *services.TaskService) *TaskHandler { return &TaskHandler{taskService: taskService} } func parseIDsFromRequest(r *http.Request) (bson.ObjectID, bson.ObjectID, error) { userID, err := getUserObjectID(r) if err != nil { return bson.NilObjectID, bson.NilObjectID, err } spaceID, err := bson.ObjectIDFromHex(mux.Vars(r)["spaceId"]) if err != nil { return bson.NilObjectID, bson.NilObjectID, err } return userID, spaceID, nil } func (h *TaskHandler) CreateTask(w http.ResponseWriter, r *http.Request) { userID, spaceID, err := parseIDsFromRequest(r) if err != nil { http.Error(w, "invalid request", http.StatusBadRequest) return } var req dto.CreateTaskRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { http.Error(w, "invalid request body", http.StatusBadRequest) return } task, err := h.taskService.CreateTask(r.Context(), spaceID, userID, &req) if err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusCreated) json.NewEncoder(w).Encode(task) } func (h *TaskHandler) ListTasks(w http.ResponseWriter, r *http.Request) { userID, spaceID, err := parseIDsFromRequest(r) if err != nil { http.Error(w, "invalid request", http.StatusBadRequest) return } taskListID := strings.TrimSpace(r.URL.Query().Get("taskListId")) statusID := strings.TrimSpace(r.URL.Query().Get("statusId")) parentTaskID := strings.TrimSpace(r.URL.Query().Get("parentTaskId")) taskListFilter := &taskListID statusFilter := &statusID parentFilter := &parentTaskID if taskListID == "" { taskListFilter = nil } if statusID == "" { statusFilter = nil } if parentTaskID == "" { parentFilter = nil } tasks, err := h.taskService.ListTasks(r.Context(), spaceID, userID, taskListFilter, statusFilter, parentFilter) if err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(tasks) } func (h *TaskHandler) ListTaskLists(w http.ResponseWriter, r *http.Request) { userID, spaceID, err := parseIDsFromRequest(r) if err != nil { http.Error(w, "invalid request", http.StatusBadRequest) return } lists, err := h.taskService.ListTaskLists(r.Context(), spaceID, userID) if err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(lists) } func (h *TaskHandler) CreateTaskList(w http.ResponseWriter, r *http.Request) { userID, spaceID, err := parseIDsFromRequest(r) if err != nil { http.Error(w, "invalid request", http.StatusBadRequest) return } var req dto.CreateTaskListRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { http.Error(w, "invalid request body", http.StatusBadRequest) return } list, err := h.taskService.CreateTaskList(r.Context(), spaceID, userID, &req) if err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusCreated) json.NewEncoder(w).Encode(list) } func (h *TaskHandler) UpdateTaskList(w http.ResponseWriter, r *http.Request) { userID, spaceID, err := parseIDsFromRequest(r) if err != nil { http.Error(w, "invalid request", http.StatusBadRequest) return } taskListID, err := bson.ObjectIDFromHex(mux.Vars(r)["taskListId"]) if err != nil { http.Error(w, "invalid task list id", http.StatusBadRequest) return } var req dto.UpdateTaskListRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { http.Error(w, "invalid request body", http.StatusBadRequest) return } list, err := h.taskService.UpdateTaskList(r.Context(), spaceID, taskListID, userID, &req) if err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(list) } func (h *TaskHandler) DeleteTaskList(w http.ResponseWriter, r *http.Request) { userID, spaceID, err := parseIDsFromRequest(r) if err != nil { http.Error(w, "invalid request", http.StatusBadRequest) return } taskListID, err := bson.ObjectIDFromHex(mux.Vars(r)["taskListId"]) if err != nil { http.Error(w, "invalid task list id", http.StatusBadRequest) return } if err := h.taskService.DeleteTaskList(r.Context(), spaceID, taskListID, userID); err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } w.WriteHeader(http.StatusNoContent) } func (h *TaskHandler) SearchTasks(w http.ResponseWriter, r *http.Request) { userID, spaceID, err := parseIDsFromRequest(r) if err != nil { http.Error(w, "invalid request", http.StatusBadRequest) return } query := r.URL.Query().Get("q") tasks, err := h.taskService.SearchTasks(r.Context(), spaceID, userID, query) if err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(tasks) } func (h *TaskHandler) GetTask(w http.ResponseWriter, r *http.Request) { userID, spaceID, err := parseIDsFromRequest(r) if err != nil { http.Error(w, "invalid request", http.StatusBadRequest) return } taskID, err := bson.ObjectIDFromHex(mux.Vars(r)["taskId"]) if err != nil { http.Error(w, "invalid task id", http.StatusBadRequest) return } task, err := h.taskService.GetTaskByID(r.Context(), spaceID, taskID, userID) if err != nil { http.Error(w, err.Error(), http.StatusNotFound) return } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(task) } func (h *TaskHandler) UpdateTask(w http.ResponseWriter, r *http.Request) { userID, spaceID, err := parseIDsFromRequest(r) if err != nil { http.Error(w, "invalid request", http.StatusBadRequest) return } taskID, err := bson.ObjectIDFromHex(mux.Vars(r)["taskId"]) if err != nil { http.Error(w, "invalid task id", http.StatusBadRequest) return } var req dto.UpdateTaskRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { http.Error(w, "invalid request body", http.StatusBadRequest) return } task, err := h.taskService.UpdateTask(r.Context(), spaceID, taskID, userID, &req) if err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(task) } func (h *TaskHandler) DeleteTask(w http.ResponseWriter, r *http.Request) { userID, spaceID, err := parseIDsFromRequest(r) if err != nil { http.Error(w, "invalid request", http.StatusBadRequest) return } taskID, err := bson.ObjectIDFromHex(mux.Vars(r)["taskId"]) if err != nil { http.Error(w, "invalid task id", http.StatusBadRequest) return } if err := h.taskService.DeleteTask(r.Context(), spaceID, taskID, userID); err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } w.WriteHeader(http.StatusNoContent) } func (h *TaskHandler) TransitionTaskStatus(w http.ResponseWriter, r *http.Request) { userID, spaceID, err := parseIDsFromRequest(r) if err != nil { http.Error(w, "invalid request", http.StatusBadRequest) return } taskID, err := bson.ObjectIDFromHex(mux.Vars(r)["taskId"]) if err != nil { http.Error(w, "invalid task id", http.StatusBadRequest) return } var req dto.TaskTransitionRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { http.Error(w, "invalid request body", http.StatusBadRequest) return } task, err := h.taskService.TransitionTaskStatus(r.Context(), spaceID, taskID, userID, req.Direction) if err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(task) } func (h *TaskHandler) LinkTaskNote(w http.ResponseWriter, r *http.Request) { userID, spaceID, err := parseIDsFromRequest(r) if err != nil { http.Error(w, "invalid request", http.StatusBadRequest) return } taskID, err := bson.ObjectIDFromHex(mux.Vars(r)["taskId"]) if err != nil { http.Error(w, "invalid task id", http.StatusBadRequest) return } var req dto.LinkTaskNoteRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { http.Error(w, "invalid request body", http.StatusBadRequest) return } noteID, err := bson.ObjectIDFromHex(req.NoteID) if err != nil { http.Error(w, "invalid note id", http.StatusBadRequest) return } task, err := h.taskService.LinkNoteToTask(r.Context(), spaceID, taskID, noteID, userID) if err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(task) } func (h *TaskHandler) UnlinkTaskNote(w http.ResponseWriter, r *http.Request) { userID, spaceID, err := parseIDsFromRequest(r) if err != nil { http.Error(w, "invalid request", http.StatusBadRequest) return } taskID, err := bson.ObjectIDFromHex(mux.Vars(r)["taskId"]) if err != nil { http.Error(w, "invalid task id", http.StatusBadRequest) return } noteID, err := bson.ObjectIDFromHex(mux.Vars(r)["noteId"]) if err != nil { http.Error(w, "invalid note id", http.StatusBadRequest) return } task, err := h.taskService.UnlinkNoteFromTask(r.Context(), spaceID, taskID, noteID, userID) if err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(task) } func (h *TaskHandler) ListTasksByNote(w http.ResponseWriter, r *http.Request) { userID, spaceID, err := parseIDsFromRequest(r) if err != nil { http.Error(w, "invalid request", http.StatusBadRequest) return } noteID, err := bson.ObjectIDFromHex(mux.Vars(r)["noteId"]) if err != nil { http.Error(w, "invalid note id", http.StatusBadRequest) return } tasks, err := h.taskService.ListTasksLinkedToNote(r.Context(), spaceID, noteID, userID) if err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(tasks) } func (h *TaskHandler) ListStatuses(w http.ResponseWriter, r *http.Request) { userID, spaceID, err := parseIDsFromRequest(r) if err != nil { http.Error(w, "invalid request", http.StatusBadRequest) return } statuses, err := h.taskService.ListStatuses(r.Context(), spaceID, userID) if err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(statuses) } func (h *TaskHandler) CreateStatus(w http.ResponseWriter, r *http.Request) { userID, spaceID, err := parseIDsFromRequest(r) if err != nil { http.Error(w, "invalid request", http.StatusBadRequest) return } var req dto.CreateTaskStatusRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { http.Error(w, "invalid request body", http.StatusBadRequest) return } status, err := h.taskService.CreateStatus(r.Context(), spaceID, userID, &req) if err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusCreated) json.NewEncoder(w).Encode(status) } func (h *TaskHandler) UpdateStatus(w http.ResponseWriter, r *http.Request) { userID, spaceID, err := parseIDsFromRequest(r) if err != nil { http.Error(w, "invalid request", http.StatusBadRequest) return } statusID, err := bson.ObjectIDFromHex(mux.Vars(r)["statusId"]) if err != nil { http.Error(w, "invalid status id", http.StatusBadRequest) return } var req dto.UpdateTaskStatusRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { http.Error(w, "invalid request body", http.StatusBadRequest) return } status, err := h.taskService.UpdateStatus(r.Context(), spaceID, statusID, userID, &req) if err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(status) } func (h *TaskHandler) DeleteStatus(w http.ResponseWriter, r *http.Request) { userID, spaceID, err := parseIDsFromRequest(r) if err != nil { http.Error(w, "invalid request", http.StatusBadRequest) return } statusID, err := bson.ObjectIDFromHex(mux.Vars(r)["statusId"]) if err != nil { http.Error(w, "invalid status id", http.StatusBadRequest) return } if err := h.taskService.DeleteStatus(r.Context(), spaceID, statusID, userID); err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } w.WriteHeader(http.StatusNoContent) } func (h *TaskHandler) ReorderStatuses(w http.ResponseWriter, r *http.Request) { userID, spaceID, err := parseIDsFromRequest(r) if err != nil { http.Error(w, "invalid request", http.StatusBadRequest) return } var req dto.ReorderTaskStatusesRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { http.Error(w, "invalid request body", http.StatusBadRequest) return } statuses, err := h.taskService.ReorderStatuses(r.Context(), spaceID, userID, req.OrderedStatusIDs) if err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(statuses) }