Add the Recent updates sheet behind the Home bell (Feature 21)
GET /api/v1/updates lists the last 10 distinct notification events from
the outbox, newest first and one per event key whatever its channels.
POST /api/v1/updates/seen {"through": created_at} moves
household_settings.updates_seen_at forward, so /api/v1/home's
recentUpdates.unread is true only for an event stored since. The Home
bell opens a Sheet listing the updates with relative times; a row opens
its URL, and opening the sheet marks the updates seen, clearing the
grape dot optimistically and putting it back if the mark fails.
Maestro task 96e2dc3f-1f8a-4114-bcb5-e0f93c349165.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -205,6 +205,45 @@ func (s *Service) StoredSince(ctx context.Context, since *time.Time) (bool, erro
|
||||
return found, nil
|
||||
}
|
||||
|
||||
// Stored is one event as it was stored, whatever channels it went to.
|
||||
type Stored struct {
|
||||
Type EventType
|
||||
Title string
|
||||
Body string
|
||||
// URL is the path-absolute, same-origin path the event opens.
|
||||
URL string
|
||||
// CreatedAt is when the event was last stored, for any channel: the
|
||||
// instant StoredSince must be given for it not to count.
|
||||
CreatedAt time.Time
|
||||
}
|
||||
|
||||
// Recent returns the last n distinct events stored, newest first. An event
|
||||
// stored for more than one channel is one event, as new as its newest row;
|
||||
// one whose preference switches every channel off is never stored and
|
||||
// never listed.
|
||||
func (s *Service) Recent(ctx context.Context, n int) ([]Stored, error) {
|
||||
if n <= 0 {
|
||||
return nil, fmt.Errorf("notify: list %d recent events: want a positive count", n)
|
||||
}
|
||||
rows, err := db.New(s.st.Reader).ListRecentNotifications(ctx, int64(n))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("notify: list recent events: %w", err)
|
||||
}
|
||||
out := make([]Stored, 0, len(rows))
|
||||
for _, r := range rows {
|
||||
var p Payload
|
||||
if err := json.Unmarshal([]byte(r.Payload), &p); err != nil {
|
||||
return nil, fmt.Errorf("notify: decode payload of %s: %w", r.EventKey, err)
|
||||
}
|
||||
created, err := parseTimestamp(r.CreatedAt)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("notify: created_at of %s: %w", r.EventKey, err)
|
||||
}
|
||||
out = append(out, Stored{Type: p.Event, Title: p.Title, Body: p.Body, URL: p.URL, CreatedAt: created})
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// emitTx stores ev in tx for each channel that channels chooses from the
|
||||
// preference of ev's type.
|
||||
func (s *Service) emitTx(ctx context.Context, tx *sql.Tx, ev Event, channels func(db.NotificationPref) ([]Channel, error)) error {
|
||||
|
||||
Reference in New Issue
Block a user