summaryrefslogtreecommitdiff
path: root/internal/handlers
diff options
context:
space:
mode:
authorPeter Stone <thepeterstone@gmail.com>2026-01-20 11:17:19 -1000
committerPeter Stone <thepeterstone@gmail.com>2026-01-20 11:17:19 -1000
commit07ba815e8517ee2d3a5fa531361bbd09bdfcbaa7 (patch)
treeca9d9be0f02d5a724a3646f87d4a9f50203249cc /internal/handlers
parent6a59098c3096f5ebd3a61ef5268cbd480b0f1519 (diff)
Remove Obsidian integration for public server deployment
Obsidian relied on local filesystem access which is incompatible with public server deployment. This removes all Obsidian-related code including: - API client and interface - Store layer methods (SaveNotes, GetNotes, SearchNotes) - Handler methods and routes - UI tab and templates - Configuration fields - Related tests Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Diffstat (limited to 'internal/handlers')
-rw-r--r--internal/handlers/handlers.go114
-rw-r--r--internal/handlers/handlers_test.go35
-rw-r--r--internal/handlers/tab_state_test.go9
-rw-r--r--internal/handlers/tabs.go56
-rw-r--r--internal/handlers/template_test.go40
5 files changed, 25 insertions, 229 deletions
diff --git a/internal/handlers/handlers.go b/internal/handlers/handlers.go
index f53eced..7bb84b9 100644
--- a/internal/handlers/handlers.go
+++ b/internal/handlers/handlers.go
@@ -24,14 +24,13 @@ type Handler struct {
store *store.Store
todoistClient api.TodoistAPI
trelloClient api.TrelloAPI
- obsidianClient api.ObsidianAPI
planToEatClient api.PlanToEatAPI
config *config.Config
templates *template.Template
}
// New creates a new Handler instance
-func New(s *store.Store, todoist api.TodoistAPI, trello api.TrelloAPI, obsidian api.ObsidianAPI, planToEat api.PlanToEatAPI, cfg *config.Config) *Handler {
+func New(s *store.Store, todoist api.TodoistAPI, trello api.TrelloAPI, planToEat api.PlanToEatAPI, cfg *config.Config) *Handler {
// Parse templates including partials
tmpl, err := template.ParseGlob(filepath.Join(cfg.TemplateDir, "*.html"))
if err != nil {
@@ -48,7 +47,6 @@ func New(s *store.Store, todoist api.TodoistAPI, trello api.TrelloAPI, obsidian
store: s,
todoistClient: todoist,
trelloClient: trello,
- obsidianClient: obsidian,
planToEatClient: planToEat,
config: cfg,
templates: tmpl,
@@ -122,18 +120,6 @@ func (h *Handler) HandleGetTasks(w http.ResponseWriter, r *http.Request) {
json.NewEncoder(w).Encode(tasks)
}
-// HandleGetNotes returns notes as JSON
-func (h *Handler) HandleGetNotes(w http.ResponseWriter, r *http.Request) {
- notes, err := h.store.GetNotes(20)
- if err != nil {
- http.Error(w, "Failed to get notes", http.StatusInternalServerError)
- return
- }
-
- w.Header().Set("Content-Type", "application/json")
- json.NewEncoder(w).Encode(notes)
-}
-
// HandleGetMeals returns meals as JSON
func (h *Handler) HandleGetMeals(w http.ResponseWriter, r *http.Request) {
startDate := time.Now()
@@ -178,27 +164,9 @@ func (h *Handler) HandleTasksTab(w http.ResponseWriter, r *http.Request) {
}
}
-// HandleNotesTab renders the notes tab content (Obsidian)
-func (h *Handler) HandleNotesTab(w http.ResponseWriter, r *http.Request) {
- ctx := r.Context()
-
- data, err := h.aggregateData(ctx, false)
- if err != nil {
- http.Error(w, "Failed to load notes", http.StatusInternalServerError)
- log.Printf("Error loading notes tab: %v", err)
- return
- }
-
- if err := h.templates.ExecuteTemplate(w, "notes-tab", data); err != nil {
- http.Error(w, "Failed to render template", http.StatusInternalServerError)
- log.Printf("Error rendering notes tab: %v", err)
- }
-}
-
// HandleRefreshTab refreshes and re-renders the specified tab
func (h *Handler) HandleRefreshTab(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
- tab := r.URL.Query().Get("tab") // "tasks" or "notes"
// Force refresh
data, err := h.aggregateData(ctx, true)
@@ -208,13 +176,7 @@ func (h *Handler) HandleRefreshTab(w http.ResponseWriter, r *http.Request) {
return
}
- // Determine template to render
- templateName := "tasks-tab"
- if tab == "notes" {
- templateName = "notes-tab"
- }
-
- if err := h.templates.ExecuteTemplate(w, templateName, data); err != nil {
+ if err := h.templates.ExecuteTemplate(w, "tasks-tab", data); err != nil {
http.Error(w, "Failed to render template", http.StatusInternalServerError)
log.Printf("Error rendering refreshed tab: %v", err)
}
@@ -254,6 +216,26 @@ func (h *Handler) aggregateData(ctx context.Context, forceRefresh bool) (*models
if err != nil {
data.Errors = append(data.Errors, "Todoist: "+err.Error())
} else {
+ // Sort tasks: earliest due date first, nil last, then by priority (descending)
+ sort.Slice(tasks, func(i, j int) bool {
+ // Handle nil due dates (push to end)
+ if tasks[i].DueDate == nil && tasks[j].DueDate != nil {
+ return false
+ }
+ if tasks[i].DueDate != nil && tasks[j].DueDate == nil {
+ return true
+ }
+
+ // Both have due dates, sort by date
+ 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)
+ }
+ }
+
+ // Same due date (or both nil), sort by priority (descending)
+ return tasks[i].Priority > tasks[j].Priority
+ })
data.Tasks = tasks
}
}()
@@ -272,22 +254,6 @@ func (h *Handler) aggregateData(ctx context.Context, forceRefresh bool) (*models
}
}()
- // Fetch Obsidian notes (if configured)
- if h.obsidianClient != nil {
- wg.Add(1)
- go func() {
- defer wg.Done()
- notes, err := h.fetchNotes(ctx, forceRefresh)
- mu.Lock()
- defer mu.Unlock()
- if err != nil {
- data.Errors = append(data.Errors, "Obsidian: "+err.Error())
- } else {
- data.Notes = notes
- }
- }()
- }
-
// Fetch PlanToEat meals (if configured)
if h.planToEatClient != nil {
wg.Add(1)
@@ -467,42 +433,6 @@ func (h *Handler) convertSyncItemToTask(item api.SyncItemResponse, projectMap ma
return task
}
-// fetchNotes fetches notes from cache or filesystem
-func (h *Handler) fetchNotes(ctx context.Context, forceRefresh bool) ([]models.Note, error) {
- cacheKey := store.CacheKeyObsidianNotes
-
- // Check cache validity
- if !forceRefresh {
- valid, err := h.store.IsCacheValid(cacheKey)
- if err == nil && valid {
- return h.store.GetNotes(20)
- }
- }
-
- // Fetch from filesystem
- notes, err := h.obsidianClient.GetNotes(ctx, 20)
- if err != nil {
- // Try to return cached data even if stale
- cachedNotes, cacheErr := h.store.GetNotes(20)
- if cacheErr == nil && len(cachedNotes) > 0 {
- return cachedNotes, nil
- }
- return nil, err
- }
-
- // Save to cache
- if err := h.store.SaveNotes(notes); err != nil {
- log.Printf("Failed to save notes to cache: %v", err)
- }
-
- // Update cache metadata
- if err := h.store.UpdateCacheMetadata(cacheKey, h.config.CacheTTLMinutes); err != nil {
- log.Printf("Failed to update cache metadata: %v", err)
- }
-
- return notes, nil
-}
-
// fetchMeals fetches meals from cache or API
func (h *Handler) fetchMeals(ctx context.Context, forceRefresh bool) ([]models.Meal, error) {
cacheKey := store.CacheKeyPlanToEatMeals
diff --git a/internal/handlers/handlers_test.go b/internal/handlers/handlers_test.go
index 1aa72cc..6e9346a 100644
--- a/internal/handlers/handlers_test.go
+++ b/internal/handlers/handlers_test.go
@@ -353,41 +353,6 @@ func TestHandleRefresh(t *testing.T) {
// The important thing is the handler doesn't error
}
-// TestHandleGetNotes tests the HandleGetNotes handler
-func TestHandleGetNotes(t *testing.T) {
- db, cleanup := setupTestDB(t)
- defer cleanup()
-
- // Test with nil client should return empty array
- cfg := &config.Config{
- CacheTTLMinutes: 5,
- }
- h := &Handler{
- store: db,
- obsidianClient: nil,
- config: cfg,
- }
-
- req := httptest.NewRequest("GET", "/api/notes", nil)
- w := httptest.NewRecorder()
-
- h.HandleGetNotes(w, req)
-
- if w.Code != http.StatusOK {
- t.Errorf("Expected status 200, got %d", w.Code)
- }
-
- var notes []models.Note
- if err := json.NewDecoder(w.Body).Decode(&notes); err != nil {
- t.Fatalf("Failed to decode response: %v", err)
- }
-
- // Handler returns empty array when client is nil
- if len(notes) != 0 {
- t.Errorf("Expected 0 notes when client is nil, got %d", len(notes))
- }
-}
-
// TestHandleGetMeals tests the HandleGetMeals handler
func TestHandleGetMeals(t *testing.T) {
db, cleanup := setupTestDB(t)
diff --git a/internal/handlers/tab_state_test.go b/internal/handlers/tab_state_test.go
index d3f0fce..a4f6d23 100644
--- a/internal/handlers/tab_state_test.go
+++ b/internal/handlers/tab_state_test.go
@@ -30,7 +30,7 @@ func TestHandleDashboard_TabState(t *testing.T) {
}
// Create handler
- h := New(db, todoistClient, trelloClient, nil, nil, cfg)
+ h := New(db, todoistClient, trelloClient, nil, cfg)
// Skip if templates are not loaded (test environment issue)
if h.templates == nil {
@@ -52,13 +52,6 @@ func TestHandleDashboard_TabState(t *testing.T) {
expectedHxGet: `hx-get="/tabs/tasks"`,
},
{
- name: "notes tab from query param",
- url: "/?tab=notes",
- expectedTab: "notes",
- expectedActive: `class="tab-button tab-button-active"`,
- expectedHxGet: `hx-get="/tabs/notes"`,
- },
- {
name: "planning tab from query param",
url: "/?tab=planning",
expectedTab: "planning",
diff --git a/internal/handlers/tabs.go b/internal/handlers/tabs.go
index 1afdd06..7e0b352 100644
--- a/internal/handlers/tabs.go
+++ b/internal/handlers/tabs.go
@@ -126,7 +126,7 @@ func (h *TabsHandler) HandleTasks(w http.ResponseWriter, r *http.Request) {
}
}
-// HandlePlanning renders the Planning tab (Trello boards + Todoist tasks)
+// HandlePlanning renders the Planning tab (Trello boards)
func (h *TabsHandler) HandlePlanning(w http.ResponseWriter, r *http.Request) {
// Fetch Trello boards
boards, err := h.store.GetBoards()
@@ -136,21 +136,12 @@ func (h *TabsHandler) HandlePlanning(w http.ResponseWriter, r *http.Request) {
return
}
- // Fetch Todoist tasks
- tasks, err := h.store.GetTasks()
- if err != nil {
- log.Printf("Error fetching tasks: %v", err)
- tasks = []models.Task{}
- }
-
data := struct {
Boards []models.Board
- Tasks []models.Task
Projects []models.Project
}{
Boards: boards,
- Tasks: tasks,
- Projects: []models.Project{}, // Empty for now - form won't display but checkboxes will work
+ Projects: []models.Project{}, // Empty for now
}
if err := h.templates.ExecuteTemplate(w, "planning-tab", data); err != nil {
@@ -159,49 +150,6 @@ func (h *TabsHandler) HandlePlanning(w http.ResponseWriter, r *http.Request) {
}
}
-// HandleNotes renders the Notes tab (Obsidian notes)
-func (h *TabsHandler) HandleNotes(w http.ResponseWriter, r *http.Request) {
- // Check for search query parameter
- query := r.URL.Query().Get("q")
-
- var notes []models.Note
- var err error
-
- // If search query is present, search notes; otherwise, get all notes
- if query != "" {
- notes, err = h.store.SearchNotes(query)
- } else {
- notes, err = h.store.GetNotes(20)
- }
-
- if err != nil {
- http.Error(w, "Failed to fetch notes", http.StatusInternalServerError)
- log.Printf("Error fetching notes: %v", err)
- return
- }
-
- data := struct {
- Notes []models.Note
- Errors []string
- }{
- Notes: notes,
- Errors: nil,
- }
-
- // Check HX-Target header for partial update
- hxTarget := r.Header.Get("HX-Target")
- templateName := "notes-tab"
- if hxTarget == "notes-results" {
- // Render only the notes list for HTMX partial update
- templateName = "obsidian-notes"
- }
-
- if err := h.templates.ExecuteTemplate(w, templateName, data); err != nil {
- http.Error(w, "Failed to render template", http.StatusInternalServerError)
- log.Printf("Error rendering notes tab: %v", err)
- }
-}
-
// HandleMeals renders the Meals tab (PlanToEat)
func (h *TabsHandler) HandleMeals(w http.ResponseWriter, r *http.Request) {
// Fetch meals for next 7 days
diff --git a/internal/handlers/template_test.go b/internal/handlers/template_test.go
deleted file mode 100644
index b0b2378..0000000
--- a/internal/handlers/template_test.go
+++ /dev/null
@@ -1,40 +0,0 @@
-package handlers_test
-
-import (
- "html/template"
- "io"
- "testing"
- "task-dashboard/internal/models"
-)
-
-func TestNotesTemplateRendering(t *testing.T) {
- // Parse templates (adjust paths relative to where test runs, usually package root)
- // Since we run 'go test ./...', paths might need to be absolute or relative to project root if we use a helper.
- // But standard 'go test' in a subdir uses that subdir as CWD.
- // We will assume the test runs from 'internal/handlers'.
- // So paths are "../../web/templates/..."
-
- tmpl, err := template.ParseGlob("../../web/templates/*.html")
- if err != nil {
- t.Fatalf("Failed to parse templates: %v", err)
- }
- tmpl, err = tmpl.ParseGlob("../../web/templates/partials/*.html")
- if err != nil {
- t.Fatalf("Failed to parse partials: %v", err)
- }
-
- // Define the data structure we EXPECT to use (with Errors)
- data := struct {
- Notes []models.Note
- Errors []string
- }{
- Notes: []models.Note{},
- Errors: []string{"Test Error"},
- }
-
- // Execute
- err = tmpl.ExecuteTemplate(io.Discard, "notes-tab", data)
- if err != nil {
- t.Errorf("Failed to render notes-tab with corrected data: %v", err)
- }
-}