diff options
Diffstat (limited to 'internal/handlers/widget.go')
| -rw-r--r-- | internal/handlers/widget.go | 169 |
1 files changed, 167 insertions, 2 deletions
diff --git a/internal/handlers/widget.go b/internal/handlers/widget.go index 876aaad..3b94bf1 100644 --- a/internal/handlers/widget.go +++ b/internal/handlers/widget.go @@ -8,6 +8,7 @@ import ( "task-dashboard/internal/config" "task-dashboard/internal/models" + "task-dashboard/internal/store" ) // WidgetAuthMiddleware validates the static bearer token for widget API endpoints. @@ -42,9 +43,12 @@ func TimelineItemToWidgetItem(item models.TimelineItem) models.WidgetItem { wi.Type = "event" case models.TimelineItemTypeMeal: wi.Type = "event" - case models.TimelineItemTypeCard, models.TimelineItemTypeGTask: + case models.TimelineItemTypeCard: wi.Type = "task" - // not completable via widget API + wi.Completable = true + case models.TimelineItemTypeGTask: + wi.Type = "task" + wi.Completable = true default: wi.Type = "task" wi.Completable = item.Source == "doot" @@ -132,6 +136,135 @@ func (h *Handler) HandleWidgetReschedule(w http.ResponseWriter, r *http.Request) w.WriteHeader(http.StatusOK) } +// findGoogleTask looks up a cached Google Task by ID. +func (h *Handler) findGoogleTask(id string) (models.GoogleTask, bool) { + gTasks, err := h.store.GetGoogleTasks() + if err != nil { + return models.GoogleTask{}, false + } + for _, t := range gTasks { + if t.ID == id { + return t, true + } + } + return models.GoogleTask{}, false +} + +// findCard looks up a cached Trello card by ID. +func (h *Handler) findCard(id string) (models.Card, bool) { + boards, err := h.store.GetBoards() + if err != nil { + return models.Card{}, false + } + for _, b := range boards { + for _, c := range b.Cards { + if c.ID == id { + return c, true + } + } + } + return models.Card{}, false +} + +type widgetDetailResponse struct { + Title string `json:"title"` + Description string `json:"description"` + Editable bool `json:"editable"` +} + +// HandleWidgetDetail returns a task's title/description for the widget's edit popup. +func (h *Handler) HandleWidgetDetail(w http.ResponseWriter, r *http.Request) { + id := r.URL.Query().Get("id") + source := r.URL.Query().Get("source") + if id == "" || source == "" { + http.Error(w, "missing id or source", http.StatusBadRequest) + return + } + + var resp widgetDetailResponse + switch source { + case "doot": + tasks, err := h.store.GetNativeTasks() + if err != nil { + http.Error(w, "internal error", http.StatusInternalServerError) + return + } + for _, t := range tasks { + if t.ID == id { + resp = widgetDetailResponse{Title: t.Content, Description: t.Description, Editable: true} + break + } + } + case "gtasks": + if t, ok := h.findGoogleTask(id); ok { + resp = widgetDetailResponse{Title: t.Title, Description: t.Notes, Editable: h.googleTasksClient != nil} + } + case "trello": + if c, ok := h.findCard(id); ok { + resp = widgetDetailResponse{Title: c.Name, Description: c.Description, Editable: h.trelloClient != nil} + } + default: + http.Error(w, "unsupported source", http.StatusBadRequest) + return + } + + w.Header().Set("Content-Type", "application/json") + _ = json.NewEncoder(w).Encode(resp) +} + +type widgetUpdateRequest struct { + ID string `json:"id"` + Source string `json:"source"` + Description string `json:"description"` +} + +// HandleWidgetUpdate saves an edited description from the widget's edit popup. +func (h *Handler) HandleWidgetUpdate(w http.ResponseWriter, r *http.Request) { + var req widgetUpdateRequest + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + http.Error(w, "bad request", http.StatusBadRequest) + return + } + + switch req.Source { + case "doot": + if err := h.store.UpdateNativeTaskDescription(req.ID, req.Description); err != nil { + http.Error(w, "failed to update task", http.StatusInternalServerError) + return + } + case "gtasks": + if h.googleTasksClient == nil { + http.Error(w, "google tasks not configured", http.StatusServiceUnavailable) + return + } + t, ok := h.findGoogleTask(req.ID) + if !ok { + http.Error(w, "task not found", http.StatusNotFound) + return + } + if err := h.googleTasksClient.UpdateTaskNotes(r.Context(), t.ListID, req.ID, req.Description); err != nil { + http.Error(w, "failed to update task", http.StatusInternalServerError) + return + } + _ = h.store.InvalidateCache(store.CacheKeyGoogleTasks) + case "trello": + if h.trelloClient == nil { + http.Error(w, "trello not configured", http.StatusServiceUnavailable) + return + } + if err := h.trelloClient.UpdateCard(r.Context(), req.ID, map[string]interface{}{"desc": req.Description}); err != nil { + http.Error(w, "failed to update task", http.StatusInternalServerError) + return + } + _ = h.store.InvalidateCache(store.CacheKeyTrelloBoards) + default: + http.Error(w, "source not editable", http.StatusBadRequest) + return + } + + w.WriteHeader(http.StatusOK) +} + // HandleWidgetComplete proxies a task completion to the source API or native store. func (h *Handler) HandleWidgetComplete(w http.ResponseWriter, r *http.Request) { var req widgetCompleteRequest @@ -147,6 +280,38 @@ func (h *Handler) HandleWidgetComplete(w http.ResponseWriter, r *http.Request) { return } _ = h.store.SaveCompletedTask("doot", req.ID, "", nil) + case "gtasks": + if h.googleTasksClient == nil { + http.Error(w, "google tasks not configured", http.StatusServiceUnavailable) + return + } + t, ok := h.findGoogleTask(req.ID) + if !ok { + http.Error(w, "task not found", http.StatusNotFound) + return + } + if err := h.googleTasksClient.CompleteTask(r.Context(), t.ListID, req.ID); err != nil { + http.Error(w, "failed to complete task", http.StatusInternalServerError) + return + } + _ = h.store.SaveCompletedTask("gtasks", req.ID, t.Title, t.DueDate) + _ = h.store.InvalidateCache(store.CacheKeyGoogleTasks) + case "trello": + if h.trelloClient == nil { + http.Error(w, "trello not configured", http.StatusServiceUnavailable) + return + } + c, ok := h.findCard(req.ID) + if !ok { + http.Error(w, "task not found", http.StatusNotFound) + return + } + if err := h.trelloClient.UpdateCard(r.Context(), req.ID, map[string]interface{}{"closed": true}); err != nil { + http.Error(w, "failed to complete task", http.StatusInternalServerError) + return + } + _ = h.store.SaveCompletedTask("trello", req.ID, c.Name, c.DueDate) + _ = h.store.DeleteCard(req.ID) default: http.Error(w, "source not completable", http.StatusBadRequest) return |
