summaryrefslogtreecommitdiff
path: root/internal
diff options
context:
space:
mode:
authorPeter Stone <thepeterstone@gmail.com>2026-01-20 21:07:36 -1000
committerPeter Stone <thepeterstone@gmail.com>2026-01-20 21:07:36 -1000
commita38ce269cc9283556621b5447eb3a0caf697eae9 (patch)
tree28727aada058e8ec4c01d170f79bf7aaa2ab1201 /internal
parent093ad56d8cb7274627a43004c95f4a5dd6b94fb7 (diff)
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 <noreply@anthropic.com>
Diffstat (limited to 'internal')
-rw-r--r--internal/handlers/handlers.go46
-rw-r--r--internal/store/sqlite.go32
2 files changed, 78 insertions, 0 deletions
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, `<option value="%s">%s</option>`, 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, `<p class="text-gray-500 text-sm">No bugs reported yet.</p>`)
+ return
+ }
+
+ for _, bug := range bugs {
+ fmt.Fprintf(w, `<div class="text-sm border-b border-gray-100 py-2">
+ <p class="text-gray-900">%s</p>
+ <p class="text-gray-400 text-xs">%s</p>
+ </div>`, 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()
+}