summaryrefslogtreecommitdiff
path: root/internal/handlers/agent.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/handlers/agent.go')
-rw-r--r--internal/handlers/agent.go271
1 files changed, 271 insertions, 0 deletions
diff --git a/internal/handlers/agent.go b/internal/handlers/agent.go
index aa3f000..6d6079f 100644
--- a/internal/handlers/agent.go
+++ b/internal/handlers/agent.go
@@ -9,8 +9,11 @@ import (
"net/http"
"time"
+ "github.com/go-chi/chi/v5"
+
"task-dashboard/internal/config"
"task-dashboard/internal/models"
+ "task-dashboard/internal/store"
)
// -----------------------------------------------------------------------------
@@ -385,6 +388,274 @@ func (h *Handler) buildCompletedLog(limit int) []agentCompletedItem {
}
// -----------------------------------------------------------------------------
+// Write Handlers
+// -----------------------------------------------------------------------------
+
+// HandleAgentTaskComplete handles POST /agent/tasks/{id}/complete
+func (h *Handler) HandleAgentTaskComplete(w http.ResponseWriter, r *http.Request) {
+ h.handleAgentTaskToggle(w, r, true)
+}
+
+// HandleAgentTaskUncomplete handles POST /agent/tasks/{id}/uncomplete
+func (h *Handler) HandleAgentTaskUncomplete(w http.ResponseWriter, r *http.Request) {
+ h.handleAgentTaskToggle(w, r, false)
+}
+
+// handleAgentTaskToggle handles both complete and uncomplete operations for agents
+func (h *Handler) handleAgentTaskToggle(w http.ResponseWriter, r *http.Request, complete bool) {
+ id := chi.URLParam(r, "id")
+ source := r.URL.Query().Get("source")
+
+ if id == "" || source == "" {
+ http.Error(w, "id and source are required", http.StatusBadRequest)
+ return
+ }
+
+ var err error
+ ctx := r.Context()
+ switch source {
+ case "todoist":
+ if complete {
+ err = h.todoistClient.CompleteTask(ctx, id)
+ } else {
+ err = h.todoistClient.ReopenTask(ctx, id)
+ }
+ case "trello":
+ err = h.trelloClient.UpdateCard(ctx, id, map[string]interface{}{"closed": complete})
+ case "gtasks":
+ listID := r.URL.Query().Get("listId")
+ if listID == "" {
+ listID = "@default"
+ }
+ if h.googleTasksClient != nil {
+ if complete {
+ err = h.googleTasksClient.CompleteTask(ctx, listID, id)
+ } else {
+ err = h.googleTasksClient.UncompleteTask(ctx, listID, id)
+ }
+ } else {
+ http.Error(w, "Google Tasks not configured", http.StatusServiceUnavailable)
+ return
+ }
+ default:
+ http.Error(w, "Unknown source: "+source, http.StatusBadRequest)
+ return
+ }
+
+ if err != nil {
+ http.Error(w, "Failed to toggle task: "+err.Error(), http.StatusInternalServerError)
+ return
+ }
+
+ if complete {
+ title, dueDate := h.getAtomDetails(id, source)
+ _ = h.store.SaveCompletedTask(source, id, title, dueDate)
+ switch source {
+ case "todoist":
+ _ = h.store.DeleteTask(id)
+ case "trello":
+ _ = h.store.DeleteCard(id)
+ }
+ } else {
+ switch source {
+ case "todoist":
+ _ = h.store.InvalidateCache(store.CacheKeyTodoistTasks)
+ case "trello":
+ _ = h.store.InvalidateCache(store.CacheKeyTrelloBoards)
+ }
+ }
+
+ w.Header().Set("Content-Type", "application/json")
+ json.NewEncoder(w).Encode(map[string]string{"status": "success"})
+}
+
+// HandleAgentTaskUpdateDue handles PATCH /agent/tasks/{id}/due
+func (h *Handler) HandleAgentTaskUpdateDue(w http.ResponseWriter, r *http.Request) {
+ id := chi.URLParam(r, "id")
+ source := r.URL.Query().Get("source")
+
+ var req struct {
+ Due *time.Time `json:"due"`
+ }
+ if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
+ http.Error(w, "Invalid request body", http.StatusBadRequest)
+ return
+ }
+
+ var err error
+ ctx := r.Context()
+ switch source {
+ case "todoist":
+ err = h.todoistClient.UpdateTask(ctx, id, map[string]interface{}{"due_datetime": req.Due})
+ case "trello":
+ err = h.trelloClient.UpdateCard(ctx, id, map[string]interface{}{"due": req.Due})
+ default:
+ http.Error(w, "Source does not support due date updates via this endpoint", http.StatusBadRequest)
+ return
+ }
+
+ if err != nil {
+ http.Error(w, "Failed to update due date: "+err.Error(), http.StatusInternalServerError)
+ return
+ }
+
+ // Invalidate cache
+ switch source {
+ case "todoist":
+ _ = h.store.InvalidateCache(store.CacheKeyTodoistTasks)
+ case "trello":
+ _ = h.store.InvalidateCache(store.CacheKeyTrelloBoards)
+ }
+
+ w.Header().Set("Content-Type", "application/json")
+ json.NewEncoder(w).Encode(map[string]string{"status": "success"})
+}
+
+// HandleAgentTaskUpdate handles PATCH /agent/tasks/{id}
+func (h *Handler) HandleAgentTaskUpdate(w http.ResponseWriter, r *http.Request) {
+ id := chi.URLParam(r, "id")
+ source := r.URL.Query().Get("source")
+
+ var updates map[string]interface{}
+ if err := json.NewDecoder(r.Body).Decode(&updates); err != nil {
+ http.Error(w, "Invalid request body", http.StatusBadRequest)
+ return
+ }
+
+ var err error
+ ctx := r.Context()
+ switch source {
+ case "todoist":
+ // Map generic updates to Todoist specific ones if needed
+ if title, ok := updates["title"].(string); ok {
+ updates["content"] = title
+ delete(updates, "title")
+ }
+ err = h.todoistClient.UpdateTask(ctx, id, updates)
+ case "trello":
+ if title, ok := updates["title"].(string); ok {
+ updates["name"] = title
+ delete(updates, "title")
+ }
+ if desc, ok := updates["description"].(string); ok {
+ updates["desc"] = desc
+ delete(updates, "description")
+ }
+ err = h.trelloClient.UpdateCard(ctx, id, updates)
+ default:
+ http.Error(w, "Source does not support updates via this endpoint", http.StatusBadRequest)
+ return
+ }
+
+ if err != nil {
+ http.Error(w, "Failed to update task: "+err.Error(), http.StatusInternalServerError)
+ return
+ }
+
+ // Invalidate cache
+ switch source {
+ case "todoist":
+ _ = h.store.InvalidateCache(store.CacheKeyTodoistTasks)
+ case "trello":
+ _ = h.store.InvalidateCache(store.CacheKeyTrelloBoards)
+ }
+
+ w.Header().Set("Content-Type", "application/json")
+ json.NewEncoder(w).Encode(map[string]string{"status": "success"})
+}
+
+// HandleAgentTaskCreate handles POST /agent/tasks
+func (h *Handler) HandleAgentTaskCreate(w http.ResponseWriter, r *http.Request) {
+ var req struct {
+ Title string `json:"title"`
+ Source string `json:"source"`
+ DueDate *time.Time `json:"due_date"`
+ ProjectID string `json:"project_id"`
+ ListID string `json:"list_id"`
+ Priority int `json:"priority"`
+ }
+ if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
+ http.Error(w, "Invalid request body", http.StatusBadRequest)
+ return
+ }
+
+ if req.Title == "" || req.Source == "" {
+ http.Error(w, "title and source are required", http.StatusBadRequest)
+ return
+ }
+
+ var err error
+ ctx := r.Context()
+ switch req.Source {
+ case "todoist":
+ _, err = h.todoistClient.CreateTask(ctx, req.Title, req.ProjectID, req.DueDate, req.Priority)
+ _ = h.store.InvalidateCache(store.CacheKeyTodoistTasks)
+ case "trello":
+ if req.ListID == "" {
+ http.Error(w, "list_id is required for Trello", http.StatusBadRequest)
+ return
+ }
+ _, err = h.trelloClient.CreateCard(ctx, req.ListID, req.Title, "", req.DueDate)
+ _ = h.store.InvalidateCache(store.CacheKeyTrelloBoards)
+ default:
+ http.Error(w, "Unsupported source for task creation", http.StatusBadRequest)
+ return
+ }
+
+ if err != nil {
+ http.Error(w, "Failed to create task: "+err.Error(), http.StatusInternalServerError)
+ return
+ }
+
+ w.Header().Set("Content-Type", "application/json")
+ json.NewEncoder(w).Encode(map[string]string{"status": "success"})
+}
+
+// HandleAgentShoppingAdd handles POST /agent/shopping
+func (h *Handler) HandleAgentShoppingAdd(w http.ResponseWriter, r *http.Request) {
+ var req struct {
+ Name string `json:"name"`
+ Store string `json:"store"`
+ }
+ if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
+ http.Error(w, "Invalid request body", http.StatusBadRequest)
+ return
+ }
+
+ if req.Name == "" || req.Store == "" {
+ http.Error(w, "name and store are required", http.StatusBadRequest)
+ return
+ }
+
+ if err := h.store.SaveUserShoppingItem(req.Name, req.Store); err != nil {
+ http.Error(w, "Failed to save shopping item: "+err.Error(), http.StatusInternalServerError)
+ return
+ }
+
+ w.Header().Set("Content-Type", "application/json")
+ json.NewEncoder(w).Encode(map[string]string{"status": "success"})
+}
+
+// HandleDeleteAgent handles DELETE /settings/agents/{id}
+func (h *Handler) HandleDeleteAgent(w http.ResponseWriter, r *http.Request) {
+ agentID := chi.URLParam(r, "id")
+ if agentID == "" {
+ http.Error(w, "agent ID is required", http.StatusBadRequest)
+ return
+ }
+
+ if err := h.store.RevokeAgent(agentID); err != nil {
+ http.Error(w, "Failed to revoke agent: "+err.Error(), http.StatusInternalServerError)
+ return
+ }
+
+ // Also invalidate their sessions
+ _ = h.store.InvalidatePreviousAgentSessions(agentID)
+
+ w.WriteHeader(http.StatusOK)
+}
+
+// -----------------------------------------------------------------------------
// Middleware
// -----------------------------------------------------------------------------