summaryrefslogtreecommitdiff
path: root/internal/handlers/ai_handlers.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/handlers/ai_handlers.go')
-rw-r--r--internal/handlers/ai_handlers.go273
1 files changed, 273 insertions, 0 deletions
diff --git a/internal/handlers/ai_handlers.go b/internal/handlers/ai_handlers.go
new file mode 100644
index 0000000..26c945e
--- /dev/null
+++ b/internal/handlers/ai_handlers.go
@@ -0,0 +1,273 @@
+package handlers
+
+import (
+ "encoding/json"
+ "log"
+ "net/http"
+ "time"
+
+ "task-dashboard/internal/models"
+)
+
+// AISnapshotResponse matches the exact format requested by the user
+type AISnapshotResponse struct {
+ GeneratedAt string `json:"generated_at"`
+ Tasks AITasksSection `json:"tasks"`
+ Meals AIMealsSection `json:"meals"`
+ Notes AINotesSection `json:"notes"`
+ TrelloBoards []AITrelloBoard `json:"trello_boards,omitempty"`
+}
+
+type AITasksSection struct {
+ Today []AITask `json:"today"`
+ Overdue []AITask `json:"overdue"`
+ Next7Days []AITask `json:"next_7_days"`
+}
+
+type AITask struct {
+ ID string `json:"id"`
+ Content string `json:"content"`
+ Priority int `json:"priority"`
+ Due *string `json:"due,omitempty"`
+ Project string `json:"project"`
+ Completed bool `json:"completed"`
+}
+
+type AIMealsSection struct {
+ Today AIDayMeals `json:"today"`
+ Next7Days []AIDayMeals `json:"next_7_days"`
+}
+
+type AIDayMeals struct {
+ Date string `json:"date"`
+ Breakfast string `json:"breakfast,omitempty"`
+ Lunch string `json:"lunch,omitempty"`
+ Dinner string `json:"dinner,omitempty"`
+ Snack string `json:"snack,omitempty"`
+}
+
+type AINotesSection struct {
+ Recent []AINote `json:"recent"`
+}
+
+type AINote struct {
+ Title string `json:"title"`
+ Modified string `json:"modified"`
+ Preview string `json:"preview"`
+ Path string `json:"path"`
+}
+
+type AITrelloBoard struct {
+ ID string `json:"id"`
+ Name string `json:"name"`
+ Cards []AITrelloCard `json:"cards"`
+}
+
+type AITrelloCard struct {
+ ID string `json:"id"`
+ Name string `json:"name"`
+ List string `json:"list"`
+ Due *string `json:"due,omitempty"`
+ URL string `json:"url"`
+}
+
+// HandleAISnapshot returns a complete dashboard snapshot optimized for AI consumption
+func (h *Handler) HandleAISnapshot(w http.ResponseWriter, r *http.Request) {
+ ctx := r.Context()
+
+ // Fetch all data (with caching)
+ data, err := h.aggregateData(ctx, false)
+ if err != nil {
+ respondJSON(w, http.StatusInternalServerError, map[string]string{
+ "error": "server_error",
+ "message": "Failed to fetch dashboard data",
+ })
+ log.Printf("AI snapshot error: %v", err)
+ return
+ }
+
+ // Build AI-optimized response
+ response := AISnapshotResponse{
+ GeneratedAt: time.Now().UTC().Format(time.RFC3339),
+ Tasks: buildAITasksSection(data.Tasks),
+ Meals: buildAIMealsSection(data.Meals),
+ Notes: buildAINotesSection(data.Notes),
+ TrelloBoards: buildAITrelloBoardsSection(data.Boards),
+ }
+
+ respondJSON(w, http.StatusOK, response)
+}
+
+// buildAITasksSection organizes tasks by time window
+func buildAITasksSection(tasks []models.Task) AITasksSection {
+ now := time.Now()
+ today := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location())
+ next7Days := today.AddDate(0, 0, 7)
+
+ section := AITasksSection{
+ Today: []AITask{},
+ Overdue: []AITask{},
+ Next7Days: []AITask{},
+ }
+
+ for _, task := range tasks {
+ if task.Completed {
+ continue // Skip completed tasks
+ }
+
+ aiTask := AITask{
+ ID: task.ID,
+ Content: task.Content,
+ Priority: task.Priority,
+ Project: task.ProjectName,
+ Completed: task.Completed,
+ }
+
+ if task.DueDate != nil {
+ dueStr := task.DueDate.UTC().Format(time.RFC3339)
+ aiTask.Due = &dueStr
+
+ taskDay := time.Date(task.DueDate.Year(), task.DueDate.Month(), task.DueDate.Day(), 0, 0, 0, 0, task.DueDate.Location())
+
+ if taskDay.Before(today) {
+ section.Overdue = append(section.Overdue, aiTask)
+ } else if taskDay.Equal(today) {
+ section.Today = append(section.Today, aiTask)
+ } else if taskDay.Before(next7Days) {
+ section.Next7Days = append(section.Next7Days, aiTask)
+ }
+ }
+ }
+
+ return section
+}
+
+// buildAIMealsSection organizes meals by day
+func buildAIMealsSection(meals []models.Meal) AIMealsSection {
+ now := time.Now()
+ today := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location())
+ next7Days := today.AddDate(0, 0, 7)
+
+ section := AIMealsSection{
+ Today: AIDayMeals{Date: today.Format("2006-01-02")},
+ Next7Days: []AIDayMeals{},
+ }
+
+ // Group meals by date
+ mealsByDate := make(map[string]*AIDayMeals)
+
+ for _, meal := range meals {
+ mealDay := time.Date(meal.Date.Year(), meal.Date.Month(), meal.Date.Day(), 0, 0, 0, 0, meal.Date.Location())
+
+ if mealDay.Before(today) || mealDay.After(next7Days) {
+ continue // Skip meals outside our window
+ }
+
+ dateStr := mealDay.Format("2006-01-02")
+
+ if _, exists := mealsByDate[dateStr]; !exists {
+ mealsByDate[dateStr] = &AIDayMeals{Date: dateStr}
+ }
+
+ dayMeals := mealsByDate[dateStr]
+
+ switch meal.MealType {
+ case "breakfast":
+ dayMeals.Breakfast = meal.RecipeName
+ case "lunch":
+ dayMeals.Lunch = meal.RecipeName
+ case "dinner":
+ dayMeals.Dinner = meal.RecipeName
+ case "snack":
+ dayMeals.Snack = meal.RecipeName
+ }
+ }
+
+ // Assign today's meals
+ if todayMeals, exists := mealsByDate[today.Format("2006-01-02")]; exists {
+ section.Today = *todayMeals
+ }
+
+ // Collect next 7 days (excluding today)
+ for i := 1; i <= 7; i++ {
+ day := today.AddDate(0, 0, i)
+ dateStr := day.Format("2006-01-02")
+ if dayMeals, exists := mealsByDate[dateStr]; exists {
+ section.Next7Days = append(section.Next7Days, *dayMeals)
+ }
+ }
+
+ return section
+}
+
+// buildAINotesSection returns the 10 most recent notes with previews
+func buildAINotesSection(notes []models.Note) AINotesSection {
+ section := AINotesSection{
+ Recent: []AINote{},
+ }
+
+ // Limit to 10 most recent
+ limit := 10
+ if len(notes) < limit {
+ limit = len(notes)
+ }
+
+ for i := 0; i < limit; i++ {
+ note := notes[i]
+
+ // Limit preview to 150 chars
+ preview := note.Content
+ if len(preview) > 150 {
+ preview = preview[:150] + "..."
+ }
+
+ section.Recent = append(section.Recent, AINote{
+ Title: note.Title,
+ Modified: note.Modified.UTC().Format(time.RFC3339),
+ Preview: preview,
+ Path: note.Path,
+ })
+ }
+
+ return section
+}
+
+// buildAITrelloBoardsSection formats Trello boards for AI
+func buildAITrelloBoardsSection(boards []models.Board) []AITrelloBoard {
+ aiBoards := []AITrelloBoard{}
+
+ for _, board := range boards {
+ aiBoard := AITrelloBoard{
+ ID: board.ID,
+ Name: board.Name,
+ Cards: []AITrelloCard{},
+ }
+
+ for _, card := range board.Cards {
+ aiCard := AITrelloCard{
+ ID: card.ID,
+ Name: card.Name,
+ List: card.ListName,
+ URL: card.URL,
+ }
+
+ if card.DueDate != nil {
+ dueStr := card.DueDate.UTC().Format(time.RFC3339)
+ aiCard.Due = &dueStr
+ }
+
+ aiBoard.Cards = append(aiBoard.Cards, aiCard)
+ }
+
+ aiBoards = append(aiBoards, aiBoard)
+ }
+
+ return aiBoards
+}
+
+// respondJSON sends a JSON response
+func respondJSON(w http.ResponseWriter, status int, data interface{}) {
+ w.Header().Set("Content-Type", "application/json")
+ w.WriteHeader(status)
+ json.NewEncoder(w).Encode(data)
+}