summaryrefslogtreecommitdiff
path: root/internal/handlers/handlers.go
diff options
context:
space:
mode:
authorPeter Stone <thepeterstone@gmail.com>2026-06-29 08:53:48 +0000
committerPeter Stone <thepeterstone@gmail.com>2026-06-29 08:53:48 +0000
commit3fbfdd1f9441299eb146bf215449f238977deb66 (patch)
treea01d0cd35309afbab6cc8989e26e2e824442c898 /internal/handlers/handlers.go
parentde3e37cca4f34a30f9c0beef05f67c3d515d64c2 (diff)
feat: native task management + Todoist migration
Adds doot-owned task storage (native_tasks table) so tasks can be managed without Todoist. CompleteTask for 'doot' source just updates the DB — no external API call, no token dependency. Migration path: POST /settings/import-from-todoist — copies Todoist cache → native_tasks Then remove TODOIST_TOKEN from .env to disable Todoist Changes: - migration 020: native_tasks table - store: GetNativeTasks, GetNativeTasksByDateRange, GetUndatedNativeTasks, CreateNativeTask, CompleteNativeTask, UncompleteNativeTask, UpdateNativeTask, ImportFromTodoist - timeline: native tasks appear as source="doot" (teal) - handleAtomToggle: "doot" case — no external API needed - HandleWidgetComplete: method on Handler, handles "doot" natively - HandleUnifiedAdd: "doot" source creates in native_tasks - widget: "doot" tasks are completable, teal color indicator Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Diffstat (limited to 'internal/handlers/handlers.go')
-rw-r--r--internal/handlers/handlers.go60
1 files changed, 59 insertions, 1 deletions
diff --git a/internal/handlers/handlers.go b/internal/handlers/handlers.go
index 0903cf3..877293a 100644
--- a/internal/handlers/handlers.go
+++ b/internal/handlers/handlers.go
@@ -2,6 +2,7 @@ package handlers
import (
"context"
+ "crypto/rand"
"fmt"
"html/template"
"log"
@@ -19,6 +20,13 @@ import (
"task-dashboard/internal/store"
)
+// newID generates a random hex ID for native tasks.
+func newID() string {
+ b := make([]byte, 12)
+ _, _ = rand.Read(b)
+ return fmt.Sprintf("%x", b)
+}
+
// Handler holds dependencies for HTTP handlers
type Handler struct {
store *store.Store
@@ -597,6 +605,12 @@ func (h *Handler) handleAtomToggle(w http.ResponseWriter, r *http.Request, compl
var err error
switch source {
+ case "doot":
+ if complete {
+ err = h.store.CompleteNativeTask(id)
+ } else {
+ err = h.store.UncompleteNativeTask(id)
+ }
case "todoist":
if complete {
err = h.todoistClient.CompleteTask(ctx, id)
@@ -648,6 +662,8 @@ func (h *Handler) handleAtomToggle(w http.ResponseWriter, r *http.Request, compl
_ = h.store.DeleteTask(id)
case "trello":
_ = h.store.DeleteCard(id)
+ case "doot":
+ // native tasks stay in DB marked completed; no cache to invalidate
}
// Return completed task HTML with uncomplete option
@@ -675,6 +691,14 @@ func (h *Handler) handleAtomToggle(w http.ResponseWriter, r *http.Request, compl
// getAtomDetails retrieves title and due date for a task/card/bug from the store
func (h *Handler) getAtomDetails(id, source string) (string, *time.Time) {
switch source {
+ case "doot":
+ if tasks, err := h.store.GetNativeTasks(); err == nil {
+ for _, t := range tasks {
+ if t.ID == id {
+ return t.Content, t.DueDate
+ }
+ }
+ }
case "todoist":
if tasks, err := h.store.GetTasks(); err == nil {
for _, t := range tasks {
@@ -694,7 +718,6 @@ func (h *Handler) getAtomDetails(id, source string) (string, *time.Time) {
}
}
case "gtasks":
- // Google Tasks don't have local cache, return generic title
return "Google Task", nil
}
return "Task", nil
@@ -726,6 +749,19 @@ 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,
+ }
+ 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 {
@@ -826,6 +862,15 @@ func (h *Handler) HandleTaskDetailPage(w http.ResponseWriter, r *http.Request) {
var title, description string
switch source {
+ case "doot":
+ if tasks, err := h.store.GetNativeTasks(); err == nil {
+ for _, t := range tasks {
+ if t.ID == id {
+ title, description = t.Content, t.Description
+ break
+ }
+ }
+ }
case "todoist":
if tasks, err := h.store.GetTasks(); err == nil {
for _, t := range tasks {
@@ -884,6 +929,8 @@ func (h *Handler) HandleUpdateTask(w http.ResponseWriter, r *http.Request) {
var err error
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":
@@ -905,6 +952,17 @@ 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)
func (h *Handler) HandleTabTasks(w http.ResponseWriter, r *http.Request) {
atoms, boards, err := BuildUnifiedAtomList(h.store, h.claudomatorClient)