summaryrefslogtreecommitdiff
path: root/internal/handlers/handlers.go
diff options
context:
space:
mode:
authorDoot Agent <agent@doot.local>2026-07-06 00:00:59 +0000
committerDoot Agent <agent@doot.local>2026-07-06 00:00:59 +0000
commit945c34590677e161a711ccaff0e1077197b1b178 (patch)
treedf26e8ba5a369b3323649c2fab95bd8e7a49b58e /internal/handlers/handlers.go
parentcd14604bbef17f696475cf8737f1e41c9fa2dd06 (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.go252
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"),
}