Let the lead keep notes on how its user works

This commit is contained in:
Ian Gulliver
2026-10-16 10:50:29 -07:00
parent 919e1f8002
commit d6f73a1560
18 changed files with 759 additions and 208 deletions
+86
View File
@@ -0,0 +1,86 @@
package store
import (
"context"
"database/sql"
"errors"
"fmt"
"time"
"g.fc.run/misc/byre/internal/api"
)
func (s *Store) LeadNotes(ctx context.Context, owner string) ([]api.LeadNote, error) {
rows, err := s.db.QueryContext(ctx, `SELECT id, text, created_at, updated_at FROM lead_notes WHERE owner = ? ORDER BY created_at, id`, owner)
if err != nil {
return nil, err
}
defer rows.Close()
notes := []api.LeadNote{}
for rows.Next() {
n := api.LeadNote{}
createdAt := ""
updatedAt := ""
err = rows.Scan(&n.Id, &n.Text, &createdAt, &updatedAt)
if err != nil {
return nil, err
}
n.CreatedAt, err = parseTime(createdAt)
if err != nil {
return nil, err
}
n.UpdatedAt, err = parseTime(updatedAt)
if err != nil {
return nil, err
}
notes = append(notes, n)
}
return notes, rows.Err()
}
func (s *Store) SaveLeadNote(ctx context.Context, owner, id, text, actor string) (api.LeadNote, error) {
at := time.Now().UTC()
n := api.LeadNote{Id: id, Text: text, CreatedAt: at, UpdatedAt: at}
err := s.write(ctx, func(tx *sql.Tx, emit emitFunc) error {
if id == "" {
n.Id = NewID()
_, err := tx.ExecContext(ctx, `INSERT INTO lead_notes (id, owner, text, created_at, updated_at) VALUES (?, ?, ?, ?, ?)`,
n.Id, owner, text, formatTime(at), formatTime(at))
if err != nil {
return err
}
} else {
createdAt := ""
err := tx.QueryRowContext(ctx, `UPDATE lead_notes SET text = ?, updated_at = ? WHERE id = ? AND owner = ? RETURNING created_at`,
text, formatTime(at), id, owner).Scan(&createdAt)
if errors.Is(err, sql.ErrNoRows) {
return fmt.Errorf("note %s: %w", id, ErrNotFound)
}
if err != nil {
return err
}
n.CreatedAt, err = parseTime(createdAt)
if err != nil {
return err
}
}
return emit(api.Event{Kind: "lead.note_saved", Owner: &owner, Actor: actor, Payload: ToPayload(n)})
})
return n, err
}
func (s *Store) DeleteLeadNote(ctx context.Context, owner, id, actor string) (string, error) {
text := ""
err := s.write(ctx, func(tx *sql.Tx, emit emitFunc) error {
err := tx.QueryRowContext(ctx, `DELETE FROM lead_notes WHERE id = ? AND owner = ? RETURNING text`, id, owner).Scan(&text)
if errors.Is(err, sql.ErrNoRows) {
return fmt.Errorf("note %s: %w", id, ErrNotFound)
}
if err != nil {
return err
}
return emit(api.Event{Kind: "lead.note_deleted", Owner: &owner, Actor: actor, Payload: map[string]any{"id": id, "text": text}})
})
return text, err
}
+9
View File
@@ -429,6 +429,15 @@ CREATE INDEX tasks_parent ON tasks(parent_task_id) WHERE parent_task_id != '';
ALTER TABLE reviews ADD COLUMN finding_states TEXT NOT NULL DEFAULT '{}';
UPDATE events SET payload = json_set(payload, '$.finding_states', json('{}'))
WHERE kind IN ('review.started', 'review.finished') AND json_type(payload, '$.finding_states') IS NULL;
`}, {"add-lead-notes", `
CREATE TABLE lead_notes (
id TEXT PRIMARY KEY,
owner TEXT NOT NULL REFERENCES users(login),
text TEXT NOT NULL,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL
);
CREATE INDEX lead_notes_owner ON lead_notes(owner, created_at);
`}}
const (