summaryrefslogtreecommitdiff
path: root/internal
diff options
context:
space:
mode:
Diffstat (limited to 'internal')
-rw-r--r--internal/handlers/widget.go156
1 files changed, 156 insertions, 0 deletions
diff --git a/internal/handlers/widget.go b/internal/handlers/widget.go
index b8c3aa9..1bb706e 100644
--- a/internal/handlers/widget.go
+++ b/internal/handlers/widget.go
@@ -400,6 +400,154 @@ type recurrenceResponse struct {
Weekdays []int `json:"weekdays,omitempty"`
}
+type projectResponse struct {
+ ID string `json:"id"`
+ Name string `json:"name"`
+ Color string `json:"color"`
+ CreatedAt time.Time `json:"created_at"`
+}
+
+func projectToResponse(p models.Project) projectResponse {
+ return projectResponse{ID: p.ID, Name: p.Name, Color: p.Color, CreatedAt: p.CreatedAt}
+}
+
+// HandleWidgetProjectsGet returns all non-archived projects.
+func (h *Handler) HandleWidgetProjectsGet(w http.ResponseWriter, r *http.Request) {
+ projects, err := h.store.GetProjects()
+ if err != nil {
+ http.Error(w, "failed to load projects", http.StatusInternalServerError)
+ return
+ }
+ resp := make([]projectResponse, 0, len(projects))
+ for _, p := range projects {
+ resp = append(resp, projectToResponse(p))
+ }
+ w.Header().Set("Content-Type", "application/json")
+ _ = json.NewEncoder(w).Encode(resp)
+}
+
+type projectCreateRequest struct {
+ Name string `json:"name"`
+ Color string `json:"color"`
+}
+
+// HandleWidgetProjectsCreate creates a new project.
+func (h *Handler) HandleWidgetProjectsCreate(w http.ResponseWriter, r *http.Request) {
+ var req projectCreateRequest
+ if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
+ http.Error(w, "bad request", http.StatusBadRequest)
+ return
+ }
+ if req.Name == "" || req.Color == "" {
+ http.Error(w, "name and color are required", http.StatusBadRequest)
+ return
+ }
+ project, err := h.store.CreateProject(req.Name, req.Color)
+ if err != nil {
+ http.Error(w, "failed to create project", http.StatusInternalServerError)
+ return
+ }
+ w.Header().Set("Content-Type", "application/json")
+ _ = json.NewEncoder(w).Encode(projectToResponse(*project))
+}
+
+type taskProjectRequest struct {
+ ID string `json:"id"`
+ ProjectID string `json:"project_id"`
+}
+
+// HandleWidgetTaskProject sets or clears a task's project.
+func (h *Handler) HandleWidgetTaskProject(w http.ResponseWriter, r *http.Request) {
+ var req taskProjectRequest
+ if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
+ http.Error(w, "bad request", http.StatusBadRequest)
+ return
+ }
+ if req.ID == "" {
+ http.Error(w, "id is required", http.StatusBadRequest)
+ return
+ }
+ if err := h.store.SetTaskProject(req.ID, req.ProjectID); err != nil {
+ if errors.Is(err, store.ErrNativeTaskNotFound) {
+ http.Error(w, "task not found", http.StatusNotFound)
+ return
+ }
+ http.Error(w, "failed to set project", http.StatusInternalServerError)
+ return
+ }
+ w.WriteHeader(http.StatusOK)
+}
+
+type taskLabelsRequest struct {
+ ID string `json:"id"`
+ Labels []string `json:"labels"`
+}
+
+// HandleWidgetTaskLabels replaces a task's label set.
+func (h *Handler) HandleWidgetTaskLabels(w http.ResponseWriter, r *http.Request) {
+ var req taskLabelsRequest
+ if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
+ http.Error(w, "bad request", http.StatusBadRequest)
+ return
+ }
+ if req.ID == "" {
+ http.Error(w, "id is required", http.StatusBadRequest)
+ return
+ }
+ if err := h.store.SetTaskLabels(req.ID, req.Labels); err != nil {
+ if errors.Is(err, store.ErrNativeTaskNotFound) {
+ http.Error(w, "task not found", http.StatusNotFound)
+ return
+ }
+ http.Error(w, "failed to set labels", http.StatusInternalServerError)
+ return
+ }
+ w.WriteHeader(http.StatusOK)
+}
+
+type labelColorResponse struct {
+ Name string `json:"name"`
+ Color string `json:"color"`
+}
+
+// HandleWidgetLabelsGet returns every label that has an assigned color.
+func (h *Handler) HandleWidgetLabelsGet(w http.ResponseWriter, r *http.Request) {
+ colors, err := h.store.GetLabelColors()
+ if err != nil {
+ http.Error(w, "failed to load label colors", http.StatusInternalServerError)
+ return
+ }
+ resp := make([]labelColorResponse, 0, len(colors))
+ for _, c := range colors {
+ resp = append(resp, labelColorResponse{Name: c.Name, Color: c.Color})
+ }
+ w.Header().Set("Content-Type", "application/json")
+ _ = json.NewEncoder(w).Encode(resp)
+}
+
+type labelColorSetRequest struct {
+ Name string `json:"name"`
+ Color string `json:"color"`
+}
+
+// HandleWidgetLabelsColorSet assigns a label's display color.
+func (h *Handler) HandleWidgetLabelsColorSet(w http.ResponseWriter, r *http.Request) {
+ var req labelColorSetRequest
+ if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
+ http.Error(w, "bad request", http.StatusBadRequest)
+ return
+ }
+ if req.Name == "" || req.Color == "" {
+ http.Error(w, "name and color are required", http.StatusBadRequest)
+ return
+ }
+ if err := h.store.SetLabelColor(req.Name, req.Color); err != nil {
+ http.Error(w, "failed to set label color", http.StatusInternalServerError)
+ return
+ }
+ w.WriteHeader(http.StatusOK)
+}
+
type taskDetailResponse struct {
ID string `json:"id"`
Title string `json:"title"`
@@ -408,6 +556,8 @@ type taskDetailResponse struct {
Completed bool `json:"completed"`
Recurrence *recurrenceResponse `json:"recurrence,omitempty"`
NextDate *time.Time `json:"next_date,omitempty"`
+ Project *projectResponse `json:"project,omitempty"`
+ Labels []string `json:"labels,omitempty"`
}
// HandleWidgetTaskDetail returns full detail for a single doot-native task,
@@ -436,6 +586,12 @@ func (h *Handler) HandleWidgetTaskDetail(w http.ResponseWriter, r *http.Request)
Description: task.Description,
DueDate: task.DueDate,
Completed: task.Completed,
+ Labels: task.Labels,
+ }
+ if task.ProjectID != "" {
+ if project, err := h.store.GetProjectByID(task.ProjectID); err == nil {
+ resp.Project = &projectResponse{ID: project.ID, Name: project.Name, Color: project.Color}
+ }
}
if task.RecurrenceSeriesID != "" {
resp.Recurrence = &recurrenceResponse{