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) }