summaryrefslogtreecommitdiff
path: root/internal/handlers
diff options
context:
space:
mode:
Diffstat (limited to 'internal/handlers')
-rw-r--r--internal/handlers/handlers.go60
-rw-r--r--internal/handlers/timeline_logic.go45
-rw-r--r--internal/handlers/widget.go37
3 files changed, 126 insertions, 16 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)
diff --git a/internal/handlers/timeline_logic.go b/internal/handlers/timeline_logic.go
index 8430ee9..c9510bf 100644
--- a/internal/handlers/timeline_logic.go
+++ b/internal/handlers/timeline_logic.go
@@ -172,6 +172,51 @@ func BuildTimeline(ctx context.Context, s *store.Store, start, end time.Time) ([
}
}
+ // 6. Fetch native (doot-owned) tasks
+ nativeDated, err := s.GetNativeTasksByDateRange(start, end)
+ if err != nil {
+ log.Printf("Warning: failed to read native tasks: %v", err)
+ } else {
+ for _, task := range nativeDated {
+ if task.DueDate == nil {
+ continue
+ }
+ item := models.TimelineItem{
+ ID: task.ID,
+ Type: models.TimelineItemTypeTask,
+ Title: task.Content,
+ Time: *task.DueDate,
+ Description: task.Description,
+ OriginalItem: task,
+ IsCompleted: task.Completed,
+ Source: "doot",
+ }
+ item.ComputeDaySection(now)
+ items = append(items, item)
+ }
+ }
+
+ nativeUndated, err := s.GetUndatedNativeTasks()
+ if err != nil {
+ log.Printf("Warning: failed to read undated native tasks: %v", err)
+ } else {
+ for _, task := range nativeUndated {
+ item := models.TimelineItem{
+ ID: task.ID,
+ Type: models.TimelineItemTypeTask,
+ Title: task.Content,
+ Time: now,
+ Description: task.Description,
+ OriginalItem: task,
+ IsCompleted: task.Completed,
+ Source: "doot",
+ IsAllDay: true,
+ }
+ item.ComputeDaySection(now)
+ items = append(items, item)
+ }
+ }
+
// Sort items by Time
sort.Slice(items, func(i, j int) bool {
return items[i].Time.Before(items[j].Time)
diff --git a/internal/handlers/widget.go b/internal/handlers/widget.go
index 00efc75..360ccc5 100644
--- a/internal/handlers/widget.go
+++ b/internal/handlers/widget.go
@@ -6,7 +6,6 @@ import (
"strings"
"time"
- "task-dashboard/internal/api"
"task-dashboard/internal/config"
"task-dashboard/internal/models"
)
@@ -48,7 +47,7 @@ func TimelineItemToWidgetItem(item models.TimelineItem) models.WidgetItem {
// not completable via widget API
default:
wi.Type = "task"
- wi.Completable = item.Source == "todoist"
+ wi.Completable = item.Source == "todoist" || item.Source == "doot"
}
// Only populate Start/End for items with a real time (non-all-day, non-zero)
@@ -102,26 +101,34 @@ type widgetCompleteRequest struct {
Source string `json:"source"`
}
-// HandleWidgetComplete proxies a task completion to the source API.
-func HandleWidgetComplete(todoistClient api.TodoistAPI) http.HandlerFunc {
- return func(w http.ResponseWriter, r *http.Request) {
- var req widgetCompleteRequest
- if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
- http.Error(w, "bad request", http.StatusBadRequest)
- return
- }
- if req.Source != "todoist" {
- http.Error(w, "source not completable", http.StatusBadRequest)
+// 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
+ 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.CompleteNativeTask(req.ID); err != nil {
+ http.Error(w, "failed to complete task", http.StatusInternalServerError)
return
}
- if todoistClient == nil {
+ _ = h.store.SaveCompletedTask("doot", req.ID, "", nil)
+ case "todoist":
+ if h.todoistClient == nil {
http.Error(w, "todoist not configured", http.StatusServiceUnavailable)
return
}
- if err := todoistClient.CompleteTask(r.Context(), req.ID); err != nil {
+ if err := h.todoistClient.CompleteTask(r.Context(), req.ID); err != nil {
http.Error(w, "failed to complete task", http.StatusInternalServerError)
return
}
- w.WriteHeader(http.StatusOK)
+ default:
+ http.Error(w, "source not completable", http.StatusBadRequest)
+ return
}
+
+ w.WriteHeader(http.StatusOK)
}