diff options
| author | Doot Agent <agent@doot.local> | 2026-07-06 00:00:59 +0000 |
|---|---|---|
| committer | Doot Agent <agent@doot.local> | 2026-07-06 00:00:59 +0000 |
| commit | 945c34590677e161a711ccaff0e1077197b1b178 (patch) | |
| tree | df26e8ba5a369b3323649c2fab95bd8e7a49b58e /internal/handlers/handlers.go | |
| parent | cd14604bbef17f696475cf8737f1e41c9fa2dd06 (diff) | |
feat: remove Todoist integration entirely
Native tasks (native_tasks table) fully replace Todoist. All Todoist
API code, store functions, handlers, routes, templates, and tests have
been removed. Migration 021 drops the now-unused tasks cache table.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Diffstat (limited to 'internal/handlers/handlers.go')
| -rw-r--r-- | internal/handlers/handlers.go | 252 |
1 files changed, 9 insertions, 243 deletions
diff --git a/internal/handlers/handlers.go b/internal/handlers/handlers.go index 6c74fff..d1a7512 100644 --- a/internal/handlers/handlers.go +++ b/internal/handlers/handlers.go @@ -30,7 +30,6 @@ func newID() string { // Handler holds dependencies for HTTP handlers type Handler struct { store *store.Store - todoistClient api.TodoistAPI trelloClient api.TrelloAPI planToEatClient api.PlanToEatAPI googleCalendarClient api.GoogleCalendarAPI @@ -43,7 +42,7 @@ type Handler struct { } // New creates a new Handler instance -func New(s *store.Store, todoist api.TodoistAPI, trello api.TrelloAPI, planToEat api.PlanToEatAPI, googleCalendar api.GoogleCalendarAPI, googleTasks api.GoogleTasksAPI, claudomator api.ClaudomatorClient, cfg *config.Config, buildVersion string, webAuthnEnabled bool) *Handler { +func New(s *store.Store, trello api.TrelloAPI, planToEat api.PlanToEatAPI, googleCalendar api.GoogleCalendarAPI, googleTasks api.GoogleTasksAPI, claudomator api.ClaudomatorClient, cfg *config.Config, buildVersion string, webAuthnEnabled bool) *Handler { // Template functions funcMap := template.FuncMap{ "subtract": func(a, b int) int { return a - b }, @@ -63,7 +62,6 @@ func New(s *store.Store, todoist api.TodoistAPI, trello api.TrelloAPI, planToEat return &Handler{ store: s, - todoistClient: todoist, trelloClient: trello, planToEatClient: planToEat, googleCalendarClient: googleCalendar, @@ -134,16 +132,6 @@ func (h *Handler) HandleRefresh(w http.ResponseWriter, r *http.Request) { JSONResponse(w, data) } -// HandleGetTasks returns tasks as JSON -func (h *Handler) HandleGetTasks(w http.ResponseWriter, r *http.Request) { - tasks, err := h.store.GetTasks() - if err != nil { - JSONError(w, http.StatusInternalServerError, "Failed to get tasks", err) - return - } - JSONResponse(w, tasks) -} - // HandleGetMeals returns meals as JSON func (h *Handler) HandleGetMeals(w http.ResponseWriter, r *http.Request) { startDate := config.Now() @@ -221,17 +209,6 @@ func (h *Handler) aggregateData(ctx context.Context, forceRefresh bool) (*models return err }) - fetch("Todoist", func() error { - tasks, err := h.fetchTasks(ctx, forceRefresh) - if err == nil { - sortTasksByUrgency(tasks) - mu.Lock() - data.Tasks = tasks - mu.Unlock() - } - return err - }) - if h.planToEatClient != nil { fetch("PlanToEat", func() error { meals, err := h.fetchMeals(ctx, forceRefresh) @@ -270,35 +247,12 @@ func (h *Handler) aggregateData(ctx context.Context, forceRefresh bool) (*models wg.Wait() - // Populate projects from cached tasks (avoids deprecated REST API) - if projects, err := h.store.GetProjectsFromTasks(); err == nil { - data.Projects = projects - } - // Extract and sort Trello tasks data.TrelloTasks = filterAndSortTrelloTasks(data.Boards) return data, nil } -// sortTasksByUrgency sorts tasks by due date then priority -func sortTasksByUrgency(tasks []models.Task) { - sort.Slice(tasks, func(i, j int) bool { - if tasks[i].DueDate == nil && tasks[j].DueDate != nil { - return false - } - if tasks[i].DueDate != nil && tasks[j].DueDate == nil { - return true - } - if tasks[i].DueDate != nil && tasks[j].DueDate != nil { - if !tasks[i].DueDate.Equal(*tasks[j].DueDate) { - return tasks[i].DueDate.Before(*tasks[j].DueDate) - } - } - return tasks[i].Priority > tasks[j].Priority - }) -} - // filterAndSortTrelloTasks extracts task-like cards from boards func filterAndSortTrelloTasks(boards []models.Board) []models.Card { var tasks []models.Card @@ -328,39 +282,6 @@ func filterAndSortTrelloTasks(boards []models.Board) []models.Card { return tasks } -// fetchTasks fetches tasks from cache or API -func (h *Handler) fetchTasks(ctx context.Context, forceRefresh bool) ([]models.Task, error) { - cacheKey := store.CacheKeyTodoistTasks - - // Check cache validity - if !forceRefresh { - valid, err := h.store.IsCacheValid(cacheKey) - if err == nil && valid { - return h.store.GetTasks() - } - } - - tasks, err := h.todoistClient.GetTasks(ctx) - if err != nil { - // Fall back to cached data if available - cachedTasks, cacheErr := h.store.GetTasks() - if cacheErr == nil && len(cachedTasks) > 0 { - return cachedTasks, nil - } - return nil, err - } - - if err := h.store.SaveTasks(tasks); err != nil { - log.Printf("Failed to save tasks to cache: %v", err) - } - - if err := h.store.UpdateCacheMetadata(cacheKey, h.config.CacheTTLMinutes); err != nil { - log.Printf("Failed to update cache metadata: %v", err) - } - - return tasks, nil -} - // fetchMeals fetches meals from cache or API func (h *Handler) fetchMeals(ctx context.Context, forceRefresh bool) ([]models.Meal, error) { startDate := config.Now() @@ -534,52 +455,6 @@ func (h *Handler) HandleCompleteCard(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) } -// HandleCreateTask creates a new Todoist task -func (h *Handler) HandleCreateTask(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - - content, ok := requireFormValue(w, r, "content") - if !ok { - return - } - projectID := r.FormValue("project_id") - - if _, err := h.todoistClient.CreateTask(ctx, content, projectID, nil, 0); err != nil { - JSONError(w, http.StatusInternalServerError, "Failed to create task", err) - return - } - - tasks, err := h.fetchTasks(ctx, true) - if err != nil { - JSONError(w, http.StatusInternalServerError, "Failed to refresh tasks", err) - return - } - - projects, _ := h.store.GetProjectsFromTasks() - - data := struct { - Tasks []models.Task - Projects []models.Project - }{Tasks: tasks, Projects: projects} - - HTMLResponse(w, h.renderer, "todoist-tasks", data) -} - -// HandleCompleteTask marks a Todoist task as complete -func (h *Handler) HandleCompleteTask(w http.ResponseWriter, r *http.Request) { - taskID, ok := requireFormValue(w, r, "task_id") - if !ok { - return - } - - if err := h.todoistClient.CompleteTask(r.Context(), taskID); err != nil { - JSONError(w, http.StatusInternalServerError, "Failed to complete task", err) - return - } - - w.WriteHeader(http.StatusOK) -} - // HandleCompleteAtom handles completion of a unified task (Atom) func (h *Handler) HandleCompleteAtom(w http.ResponseWriter, r *http.Request) { h.handleAtomToggle(w, r, true) @@ -623,16 +498,6 @@ func (h *Handler) handleAtomToggle(w http.ResponseWriter, r *http.Request, compl } else { err = h.store.UncompleteNativeTask(id) } - case "todoist": - if h.todoistClient == nil { - JSONError(w, http.StatusServiceUnavailable, "Todoist not configured", nil) - return - } - 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": @@ -674,8 +539,6 @@ func (h *Handler) handleAtomToggle(w http.ResponseWriter, r *http.Request, compl // Remove from local cache switch source { - case "todoist": - _ = h.store.DeleteTask(id) case "trello": _ = h.store.DeleteCard(id) case "doot": @@ -691,10 +554,7 @@ func (h *Handler) handleAtomToggle(w http.ResponseWriter, r *http.Request, compl HTMLResponse(w, h.renderer, "completed-atom", data) } else { // Invalidate cache to force refresh - switch source { - case "todoist": - _ = h.store.InvalidateCache(store.CacheKeyTodoistTasks) - case "trello": + if source == "trello" { _ = h.store.InvalidateCache(store.CacheKeyTrelloBoards) } // Don't swap empty response - just trigger refresh @@ -715,14 +575,6 @@ func (h *Handler) getAtomDetails(id, source string) (string, *time.Time) { } } } - case "todoist": - if tasks, err := h.store.GetTasks(); err == nil { - for _, t := range tasks { - if t.ID == id { - return t.Content, t.DueDate - } - } - } case "trello": if boards, err := h.store.GetBoards(); err == nil { for _, b := range boards { @@ -739,7 +591,7 @@ func (h *Handler) getAtomDetails(id, source string) (string, *time.Time) { return "Task", nil } -// HandleUnifiedAdd creates a task in Todoist or a card in Trello from the Quick Add form +// HandleUnifiedAdd creates a native task or a card in Trello from the Quick Add form func (h *Handler) HandleUnifiedAdd(w http.ResponseWriter, r *http.Request) { ctx := r.Context() @@ -767,25 +619,17 @@ func (h *Handler) HandleUnifiedAdd(w http.ResponseWriter, r *http.Request) { switch source { case "doot": task := models.Task{ - ID: newID(), - Content: title, - DueDate: dueDate, - Priority: 1, - Labels: nil, + ID: newID(), + Content: title, + DueDate: dueDate, + Priority: 1, + Labels: nil, } if err := h.store.CreateNativeTask(task); err != nil { JSONError(w, http.StatusInternalServerError, "Failed to create task", err) return } - case "todoist": - projectID := r.FormValue("project_id") - if _, err := h.todoistClient.CreateTask(ctx, title, projectID, dueDate, 1); err != nil { - JSONError(w, http.StatusInternalServerError, "Failed to create Todoist task", err) - return - } - _ = h.store.InvalidateCache(store.CacheKeyTodoistTasks) - case "trello": listID := r.FormValue("list_id") if listID == "" { @@ -836,15 +680,6 @@ func (h *Handler) HandleGetTaskDetail(w http.ResponseWriter, r *http.Request) { var title, description string switch source { - case "todoist": - if tasks, err := h.store.GetTasks(); err == nil { - for _, t := range tasks { - if t.ID == id { - title, description = t.Content, t.Description - break - } - } - } case "trello": if boards, err := h.store.GetBoards(); err == nil { for _, b := range boards { @@ -887,15 +722,6 @@ func (h *Handler) HandleTaskDetailPage(w http.ResponseWriter, r *http.Request) { } } } - case "todoist": - if tasks, err := h.store.GetTasks(); err == nil { - for _, t := range tasks { - if t.ID == id { - title, description = t.Content, t.Description - break - } - } - } case "trello": if boards, err := h.store.GetBoards(); err == nil { for _, b := range boards { @@ -947,8 +773,6 @@ func (h *Handler) HandleUpdateTask(w http.ResponseWriter, r *http.Request) { switch source { case "doot": err = h.store.UpdateNativeTask(id, "", description) - case "todoist": - err = h.todoistClient.UpdateTask(r.Context(), id, map[string]interface{}{"description": description}) case "trello": err = h.trelloClient.UpdateCard(r.Context(), id, map[string]interface{}{"desc": description}) default: @@ -968,18 +792,7 @@ func (h *Handler) HandleUpdateTask(w http.ResponseWriter, r *http.Request) { } } -// HandleImportFromTodoist copies the current Todoist task cache into native_tasks. -// One-time migration action. Safe to run multiple times (INSERT OR IGNORE by ID). -func (h *Handler) HandleImportFromTodoist(w http.ResponseWriter, r *http.Request) { - count, err := h.store.ImportFromTodoist() - if err != nil { - http.Error(w, "Import failed: "+err.Error(), http.StatusInternalServerError) - return - } - fmt.Fprintf(w, "Imported %d tasks from Todoist. You can now remove TODOIST_TOKEN from your .env to disable Todoist.", count) -} - -// HandleTabTasks renders the unified Tasks tab (Todoist + Trello cards with due dates + Bugs + Google Tasks) +// HandleTabTasks renders the unified Tasks tab (native tasks + Trello cards with due dates + Google Tasks) func (h *Handler) HandleTabTasks(w http.ResponseWriter, r *http.Request) { atoms, boards, err := BuildUnifiedAtomList(h.store, h.claudomatorClient) if err != nil { @@ -990,19 +803,15 @@ func (h *Handler) HandleTabTasks(w http.ResponseWriter, r *http.Request) { SortAtomsByUrgency(atoms) currentAtoms, futureAtoms := PartitionAtomsByTime(atoms) - projects, _ := h.store.GetProjectsFromTasks() - data := struct { Atoms []models.Atom FutureAtoms []models.Atom Boards []models.Board - Projects []models.Project Today string }{ Atoms: currentAtoms, FutureAtoms: futureAtoms, Boards: boards, - Projects: projects, Today: config.Now().Format("2006-01-02"), } @@ -1016,7 +825,6 @@ func (h *Handler) HandleTabPlanning(w http.ResponseWriter, r *http.Request) { in3Days := today.AddDate(0, 0, 4) boards, _ := h.store.GetBoards() - tasks, _ := h.store.GetTasks() gTasks, _ := h.store.GetGoogleTasks() events, _ := h.fetchCalendarEvents(r.Context(), false) @@ -1044,44 +852,6 @@ func (h *Handler) HandleTabPlanning(w http.ResponseWriter, r *http.Request) { } } - for _, task := range tasks { - if task.Completed || task.DueDate == nil { - continue - } - dueDate := *task.DueDate - hasTime := dueDate.Hour() != 0 || dueDate.Minute() != 0 - - if dueDate.Before(tomorrow) { - if hasTime { - scheduled = append(scheduled, ScheduledItem{ - Type: "task", - ID: task.ID, - Title: task.Content, - Start: dueDate, - URL: task.URL, - Source: "todoist", - SourceIcon: "✓", - Priority: task.Priority, - }) - } else { - atom := models.TaskToAtom(task) - atom.ComputeUIFields() - unscheduled = append(unscheduled, atom) - } - } else if dueDate.Before(in3Days) { - upcoming = append(upcoming, ScheduledItem{ - Type: "task", - ID: task.ID, - Title: task.Content, - Start: dueDate, - URL: task.URL, - Source: "todoist", - SourceIcon: "✓", - Priority: task.Priority, - }) - } - } - for _, board := range boards { for _, card := range board.Cards { if card.DueDate == nil { @@ -1171,21 +941,17 @@ func (h *Handler) HandleTabPlanning(w http.ResponseWriter, r *http.Request) { sort.Slice(unscheduled, func(i, j int) bool { return unscheduled[i].Priority > unscheduled[j].Priority }) sort.Slice(upcoming, func(i, j int) bool { return upcoming[i].Start.Before(upcoming[j].Start) }) - projects, _ := h.store.GetProjectsFromTasks() - data := struct { Scheduled []ScheduledItem Unscheduled []models.Atom Upcoming []ScheduledItem Boards []models.Board - Projects []models.Project Today string }{ Scheduled: scheduled, Unscheduled: unscheduled, Upcoming: upcoming, Boards: boards, - Projects: projects, Today: today.Format("2006-01-02"), } |
