diff options
Diffstat (limited to 'internal/store/store.go')
| -rw-r--r-- | internal/store/store.go | 54 |
1 files changed, 45 insertions, 9 deletions
diff --git a/internal/store/store.go b/internal/store/store.go index d04887b..4de4fa1 100644 --- a/internal/store/store.go +++ b/internal/store/store.go @@ -33,7 +33,8 @@ CREATE TABLE IF NOT EXISTS history ( request_exact INTEGER NOT NULL, response_exact INTEGER NOT NULL, error TEXT NOT NULL DEFAULT '', - source TEXT NOT NULL DEFAULT 'proxy' + source TEXT NOT NULL DEFAULT 'proxy', + flagged INTEGER NOT NULL DEFAULT 0 ); CREATE VIRTUAL TABLE IF NOT EXISTS history_fts USING fts5( @@ -87,6 +88,7 @@ func Open(path string) (*Store, error) { // Added after the initial schema; ignore the "duplicate column" error // on databases that already have it. db.Exec("ALTER TABLE history ADD COLUMN source TEXT NOT NULL DEFAULT 'proxy'") + db.Exec("ALTER TABLE history ADD COLUMN flagged INTEGER NOT NULL DEFAULT 0") // Backfill history_fts for rows inserted before it existed. A no-op // once caught up, since every Insert keeps both tables in sync. if _, err := db.Exec(` @@ -121,6 +123,7 @@ type Entry struct { ResponseExact bool Error string // network/transport error, if the request never got a response Source string // "proxy" or "repeater" + Flagged bool } // Summary is the lightweight metadata used for the history list view - @@ -138,6 +141,7 @@ type Summary struct { RespSize int Error string Source string + Flagged bool } // Insert stores e (and indexes it for search) and returns its assigned ID. @@ -207,7 +211,7 @@ func (s *Store) List(limit int, beforeID int64) ([]Summary, error) { } rows, err := s.db.Query( `SELECT id, started_at, duration_ms, method, scheme, host, path, - COALESCE(status_code, 0), length(request_raw), COALESCE(length(response_raw), 0), error, source + COALESCE(status_code, 0), length(request_raw), COALESCE(length(response_raw), 0), error, source, flagged FROM history WHERE id < ? ORDER BY id DESC LIMIT ?`, beforeID, limit, ) @@ -220,10 +224,12 @@ func (s *Store) List(limit int, beforeID int64) ([]Summary, error) { for rows.Next() { var sum Summary var startedAt, durationMs int64 + var flagged int if err := rows.Scan(&sum.ID, &startedAt, &durationMs, &sum.Method, &sum.Scheme, &sum.Host, &sum.Path, - &sum.StatusCode, &sum.ReqSize, &sum.RespSize, &sum.Error, &sum.Source); err != nil { + &sum.StatusCode, &sum.ReqSize, &sum.RespSize, &sum.Error, &sum.Source, &flagged); err != nil { return nil, fmt.Errorf("scan history row: %w", err) } + sum.Flagged = flagged != 0 sum.StartedAt = time.UnixMilli(startedAt) sum.Duration = time.Duration(durationMs) * time.Millisecond out = append(out, sum) @@ -267,6 +273,10 @@ func (s *Store) Search(query string, limit int, beforeID int64) ([]Summary, erro where = append(where, "h.source = ?") args = append(args, pred.source) } + if pred.flagged != nil { + where = append(where, "h.flagged = ?") + args = append(args, boolToInt(*pred.flagged)) + } var q string if remaining == "" { @@ -274,7 +284,7 @@ func (s *Store) Search(query string, limit int, beforeID int64) ([]Summary, erro // FTS5 join or ranking needed. q = `SELECT h.id, h.started_at, h.duration_ms, h.method, h.scheme, h.host, h.path, COALESCE(h.status_code, 0), length(h.request_raw), COALESCE(length(h.response_raw), 0), - h.error, h.source + h.error, h.source, h.flagged FROM history h WHERE ` + strings.Join(where, " AND ") + ` ORDER BY h.id DESC LIMIT ?` @@ -287,7 +297,7 @@ func (s *Store) Search(query string, limit int, beforeID int64) ([]Summary, erro args = append([]any{prepareFTSQuery(remaining)}, args...) q = `SELECT h.id, h.started_at, h.duration_ms, h.method, h.scheme, h.host, h.path, COALESCE(h.status_code, 0), length(h.request_raw), COALESCE(length(h.response_raw), 0), - h.error, h.source + h.error, h.source, h.flagged FROM history_fts JOIN history h ON h.id = history_fts.rowid WHERE ` + strings.Join(where, " AND ") + ` @@ -305,10 +315,12 @@ func (s *Store) Search(query string, limit int, beforeID int64) ([]Summary, erro for rows.Next() { var sum Summary var startedAt, durationMs int64 + var flagged int if err := rows.Scan(&sum.ID, &startedAt, &durationMs, &sum.Method, &sum.Scheme, &sum.Host, &sum.Path, - &sum.StatusCode, &sum.ReqSize, &sum.RespSize, &sum.Error, &sum.Source); err != nil { + &sum.StatusCode, &sum.ReqSize, &sum.RespSize, &sum.Error, &sum.Source, &flagged); err != nil { return nil, fmt.Errorf("scan search row: %w", err) } + sum.Flagged = flagged != 0 sum.StartedAt = time.UnixMilli(startedAt) sum.Duration = time.Duration(durationMs) * time.Millisecond out = append(out, sum) @@ -322,6 +334,7 @@ type structuredPredicate struct { statusSQL string statusArgs []any source string + flagged *bool } var ( @@ -358,6 +371,18 @@ func extractStructured(query string) (remaining string, pred structuredPredicate case strings.HasPrefix(lower, "source:"): pred.source = strings.ToLower(strings.TrimPrefix(f, "source:")) continue + case strings.HasPrefix(lower, "flagged:"): + val := strings.ToLower(strings.TrimPrefix(lower, "flagged:")) + switch val { + case "true", "1", "yes": + b := true + pred.flagged = &b + continue + case "false", "0", "no": + b := false + pred.flagged = &b + continue + } } kept = append(kept, f) } @@ -369,24 +394,35 @@ func (s *Store) Get(id int64) (*Entry, error) { row := s.db.QueryRow( `SELECT id, started_at, duration_ms, method, scheme, host, path, COALESCE(status_code, 0), request_raw, response_raw, - request_exact, response_exact, error, source + request_exact, response_exact, error, source, flagged FROM history WHERE id = ?`, id, ) var e Entry var startedAt, durationMs int64 - var reqExact, respExact int + var reqExact, respExact, flagged int if err := row.Scan(&e.ID, &startedAt, &durationMs, &e.Method, &e.Scheme, &e.Host, &e.Path, - &e.StatusCode, &e.RequestRaw, &e.ResponseRaw, &reqExact, &respExact, &e.Error, &e.Source); err != nil { + &e.StatusCode, &e.RequestRaw, &e.ResponseRaw, &reqExact, &respExact, &e.Error, &e.Source, &flagged); err != nil { return nil, fmt.Errorf("get history entry %d: %w", id, err) } e.StartedAt = time.UnixMilli(startedAt) e.Duration = time.Duration(durationMs) * time.Millisecond e.RequestExact = reqExact != 0 e.ResponseExact = respExact != 0 + e.Flagged = flagged != 0 return &e, nil } +// SetFlagged toggles the flagged marker on a history entry - a simple +// "mark this, revisit later" bit, filterable via flagged:true/false in +// Search. Doesn't touch anything else about the entry. +func (s *Store) SetFlagged(id int64, flagged bool) error { + if _, err := s.db.Exec(`UPDATE history SET flagged = ? WHERE id = ?`, boolToInt(flagged), id); err != nil { + return fmt.Errorf("set flagged on entry %d: %w", id, err) + } + return nil +} + // prepareFTSQuery makes free text safe to hand to FTS5's MATCH, whose // query grammar reserves a wide range of punctuation ('.', '-', '/', // '@', '(', ')', and more - confirmed empirically, not just from docs) |