// Package handlers provides HTTP request handlers for NextReceipt. // // This file implements the dashboard handler showing all purchases // for the authenticated user. package handlers import ( "database/sql" "fmt" "log" "net/http" "time" "github.com/cclohmar/NextReceipt/internal/database" ) // --------------------------------------------------------------------------- // DashboardHandler // --------------------------------------------------------------------------- // DashboardHandler groups HTTP handlers related to the main dashboard view. type DashboardHandler struct { DB *sql.DB } // NewDashboardHandler creates a new DashboardHandler with the given database handle. func NewDashboardHandler(db *sql.DB) *DashboardHandler { return &DashboardHandler{DB: db} } // --------------------------------------------------------------------------- // GET /dashboard — Dashboard // --------------------------------------------------------------------------- // Dashboard renders the main dashboard page showing all purchases belonging // to the authenticated user, along with the receipt upload controls. func (h *DashboardHandler) Dashboard(w http.ResponseWriter, r *http.Request) { userID := getUserID(r) if userID == "" { log.Printf("ERROR [%s] handlers: Dashboard: missing user ID", time.Now().Format(time.RFC3339)) http.Error(w, "Unauthorized", http.StatusUnauthorized) return } purchases, err := database.GetPurchasesByUser(h.DB, userID) if err != nil { log.Printf("ERROR [%s] handlers: Dashboard: GetPurchasesByUser: %v", time.Now().Format(time.RFC3339), err) http.Error(w, "Failed to load purchases", http.StatusInternalServerError) return } // Normalize ImagePath for all purchases. for i := range purchases { purchases[i].ImagePath = normalizeImagePath(purchases[i].ImagePath) } tmpl := getTemplate("dashboard.html") data := map[string]interface{}{ "Purchases": purchases, "Query": "", } w.Header().Set("Content-Type", "text/html; charset=utf-8") if err := tmpl.Execute(w, data); err != nil { log.Printf("ERROR [%s] handlers: Dashboard: execute template: %v", time.Now().Format(time.RFC3339), err) } } // --------------------------------------------------------------------------- // GET /search — SearchPurchases // --------------------------------------------------------------------------- // SearchPurchases handles the HTMX search request, returning only the purchase // list fragment filtered by the search query. func (h *DashboardHandler) SearchPurchases(w http.ResponseWriter, r *http.Request) { userID := getUserID(r) if userID == "" { http.Error(w, "Unauthorized", http.StatusUnauthorized) return } query := r.URL.Query().Get("q") purchases, err := database.SearchPurchases(h.DB, userID, query) if err != nil { log.Printf("ERROR [%s] handlers: SearchPurchases: %v", time.Now().Format(time.RFC3339), err) http.Error(w, "Search failed", http.StatusInternalServerError) return } // Normalize ImagePath for all purchases. for i := range purchases { purchases[i].ImagePath = normalizeImagePath(purchases[i].ImagePath) } w.Header().Set("Content-Type", "text/html; charset=utf-8") fmt.Fprint(w, renderPurchaseList(purchases)) }