From a38ce269cc9283556621b5447eb3a0caf697eae9 Mon Sep 17 00:00:00 2001 From: Peter Stone Date: Tue, 20 Jan 2026 21:07:36 -1000 Subject: Add in-app bug reporting feature - New bugs table in SQLite (migration 005) - Store methods for saving and retrieving bugs - Handlers for GET/POST /bugs - Floating bug button with modal UI - Shows recent bug reports in modal Co-Authored-By: Claude Opus 4.5 --- internal/handlers/handlers.go | 46 +++++++++++++++++++++++++++++++++++++++++++ internal/store/sqlite.go | 32 ++++++++++++++++++++++++++++++ 2 files changed, 78 insertions(+) (limited to 'internal') diff --git a/internal/handlers/handlers.go b/internal/handlers/handlers.go index d52e786..8d809ae 100644 --- a/internal/handlers/handlers.go +++ b/internal/handlers/handlers.go @@ -829,3 +829,49 @@ func (h *Handler) HandleGetListsOptions(w http.ResponseWriter, r *http.Request) fmt.Fprintf(w, ``, list.ID, list.Name) } } + +// HandleGetBugs returns the list of reported bugs +func (h *Handler) HandleGetBugs(w http.ResponseWriter, r *http.Request) { + bugs, err := h.store.GetBugs() + if err != nil { + http.Error(w, "Failed to fetch bugs", http.StatusInternalServerError) + log.Printf("Error fetching bugs: %v", err) + return + } + + w.Header().Set("Content-Type", "text/html") + if len(bugs) == 0 { + fmt.Fprint(w, `

No bugs reported yet.

`) + return + } + + for _, bug := range bugs { + fmt.Fprintf(w, `
+

%s

+

%s

+
`, template.HTMLEscapeString(bug.Description), bug.CreatedAt.Format("Jan 2, 3:04 PM")) + } +} + +// HandleReportBug saves a new bug report +func (h *Handler) HandleReportBug(w http.ResponseWriter, r *http.Request) { + if err := r.ParseForm(); err != nil { + http.Error(w, "Invalid form data", http.StatusBadRequest) + return + } + + description := strings.TrimSpace(r.FormValue("description")) + if description == "" { + http.Error(w, "Description is required", http.StatusBadRequest) + return + } + + if err := h.store.SaveBug(description); err != nil { + http.Error(w, "Failed to save bug", http.StatusInternalServerError) + log.Printf("Error saving bug: %v", err) + return + } + + // Return updated bug list + h.HandleGetBugs(w, r) +} diff --git a/internal/store/sqlite.go b/internal/store/sqlite.go index 7961f35..8640b9b 100644 --- a/internal/store/sqlite.go +++ b/internal/store/sqlite.go @@ -541,3 +541,35 @@ func (s *Store) ClearSyncToken(service string) error { _, err := s.db.Exec(`DELETE FROM sync_tokens WHERE service = ?`, service) return err } + +// Bug represents a user-reported bug +type Bug struct { + ID int64 + Description string + CreatedAt time.Time +} + +// SaveBug saves a new bug report +func (s *Store) SaveBug(description string) error { + _, err := s.db.Exec(`INSERT INTO bugs (description) VALUES (?)`, description) + return err +} + +// GetBugs retrieves all bugs, newest first +func (s *Store) GetBugs() ([]Bug, error) { + rows, err := s.db.Query(`SELECT id, description, created_at FROM bugs ORDER BY created_at DESC`) + if err != nil { + return nil, err + } + defer rows.Close() + + var bugs []Bug + for rows.Next() { + var b Bug + if err := rows.Scan(&b.ID, &b.Description, &b.CreatedAt); err != nil { + return nil, err + } + bugs = append(bugs, b) + } + return bugs, rows.Err() +} -- cgit v1.2.3