home.Service aggregates the Home screen from the services that own each value, at "now" in the household's zone: the date label, tonight's dinner from the current week's plan (null with no plan), the pending Side quest count from the deck, the Supply run status of the upcoming week or else the current one, and recentUpdates.unread. unread is whether a notification was stored in notification_outbox after household_settings.updates_seen_at (or at all while it is null), read through the new notify.StoredSince over the HasNotificationSince query. Nothing writes updates_seen_at yet; the updates task adds that. Tests under America/Chicago show tonight's dinner flips at local midnight, not UTC midnight, and cover no plan, no suggestions, the Supply run choice and unread. Maestro task 9ccfc3a6-cc7b-493a-aa4b-2b455d26f4df. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
280 lines
7.4 KiB
Go
280 lines
7.4 KiB
Go
// Code generated by sqlc. DO NOT EDIT.
|
|
// versions:
|
|
// sqlc v1.31.1
|
|
// source: notifications.sql
|
|
|
|
package db
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
)
|
|
|
|
const claimDueNotification = `-- name: ClaimDueNotification :one
|
|
UPDATE notification_outbox
|
|
SET next_attempt_at = ?1, updated_at = ?2
|
|
WHERE id = (
|
|
SELECT id FROM notification_outbox
|
|
WHERE status = 'pending' AND next_attempt_at <= ?2
|
|
ORDER BY next_attempt_at, id
|
|
LIMIT 1
|
|
)
|
|
RETURNING id, event_key, channel, payload, status, attempts, next_attempt_at, last_error, created_at, updated_at
|
|
`
|
|
|
|
type ClaimDueNotificationParams struct {
|
|
LeaseUntil sql.NullString
|
|
Now string
|
|
}
|
|
|
|
// Leases the pending notification due longest ago by moving its
|
|
// next_attempt_at to lease_until, and returns it. A claimed row is not due
|
|
// again until the lease ends, so no other claim takes it while it is being
|
|
// delivered. Returns sql.ErrNoRows when none is due.
|
|
func (q *Queries) ClaimDueNotification(ctx context.Context, arg ClaimDueNotificationParams) (NotificationOutbox, error) {
|
|
row := q.db.QueryRowContext(ctx, claimDueNotification, arg.LeaseUntil, arg.Now)
|
|
var i NotificationOutbox
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.EventKey,
|
|
&i.Channel,
|
|
&i.Payload,
|
|
&i.Status,
|
|
&i.Attempts,
|
|
&i.NextAttemptAt,
|
|
&i.LastError,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const failNotification = `-- name: FailNotification :execrows
|
|
UPDATE notification_outbox
|
|
SET status = 'failed', attempts = attempts + ?1, next_attempt_at = NULL, last_error = ?2, updated_at = ?3
|
|
WHERE id = ?4 AND status = 'pending' AND next_attempt_at = ?5
|
|
`
|
|
|
|
type FailNotificationParams struct {
|
|
Attempted int64
|
|
LastError sql.NullString
|
|
Now string
|
|
ID string
|
|
LeaseUntil sql.NullString
|
|
}
|
|
|
|
// Gives up on a notification. attempts counts the delivery attempt that
|
|
// failed, if one was made. The lease must still be the claim's.
|
|
func (q *Queries) FailNotification(ctx context.Context, arg FailNotificationParams) (int64, error) {
|
|
result, err := q.db.ExecContext(ctx, failNotification,
|
|
arg.Attempted,
|
|
arg.LastError,
|
|
arg.Now,
|
|
arg.ID,
|
|
arg.LeaseUntil,
|
|
)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
return result.RowsAffected()
|
|
}
|
|
|
|
const getNotificationPref = `-- name: GetNotificationPref :one
|
|
SELECT event, push, chat, created_at, updated_at FROM notification_prefs
|
|
WHERE event = ?
|
|
`
|
|
|
|
func (q *Queries) GetNotificationPref(ctx context.Context, event string) (NotificationPref, error) {
|
|
row := q.db.QueryRowContext(ctx, getNotificationPref, event)
|
|
var i NotificationPref
|
|
err := row.Scan(
|
|
&i.Event,
|
|
&i.Push,
|
|
&i.Chat,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const hasNotificationSince = `-- name: HasNotificationSince :one
|
|
SELECT EXISTS (
|
|
SELECT 1 FROM notification_outbox
|
|
WHERE CAST(?1 AS TEXT) IS NULL OR created_at > CAST(?1 AS TEXT)
|
|
) AS found
|
|
`
|
|
|
|
// Reports whether a notification was stored after since, or at all when
|
|
// since is NULL.
|
|
func (q *Queries) HasNotificationSince(ctx context.Context, since sql.NullString) (bool, error) {
|
|
row := q.db.QueryRowContext(ctx, hasNotificationSince, since)
|
|
var found bool
|
|
err := row.Scan(&found)
|
|
return found, err
|
|
}
|
|
|
|
const insertNotification = `-- name: InsertNotification :execrows
|
|
INSERT INTO notification_outbox (
|
|
id, event_key, channel, payload, status, attempts, next_attempt_at, created_at, updated_at
|
|
) VALUES (
|
|
?1, ?2, ?3, ?4, 'pending', 0, ?5, ?6, ?6
|
|
)
|
|
ON CONFLICT (event_key, channel) DO NOTHING
|
|
`
|
|
|
|
type InsertNotificationParams struct {
|
|
ID string
|
|
EventKey string
|
|
Channel string
|
|
Payload string
|
|
NextAttemptAt sql.NullString
|
|
Now string
|
|
}
|
|
|
|
// Stores a pending notification for one channel, due at once. A row already
|
|
// stored for the event key and channel, in any status, is left unchanged and
|
|
// no row is inserted.
|
|
func (q *Queries) InsertNotification(ctx context.Context, arg InsertNotificationParams) (int64, error) {
|
|
result, err := q.db.ExecContext(ctx, insertNotification,
|
|
arg.ID,
|
|
arg.EventKey,
|
|
arg.Channel,
|
|
arg.Payload,
|
|
arg.NextAttemptAt,
|
|
arg.Now,
|
|
)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
return result.RowsAffected()
|
|
}
|
|
|
|
const listNotificationPrefs = `-- name: ListNotificationPrefs :many
|
|
SELECT event, push, chat, created_at, updated_at FROM notification_prefs
|
|
`
|
|
|
|
func (q *Queries) ListNotificationPrefs(ctx context.Context) ([]NotificationPref, error) {
|
|
rows, err := q.db.QueryContext(ctx, listNotificationPrefs)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
var items []NotificationPref
|
|
for rows.Next() {
|
|
var i NotificationPref
|
|
if err := rows.Scan(
|
|
&i.Event,
|
|
&i.Push,
|
|
&i.Chat,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, i)
|
|
}
|
|
if err := rows.Close(); err != nil {
|
|
return nil, err
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
const markNotificationSent = `-- name: MarkNotificationSent :execrows
|
|
UPDATE notification_outbox
|
|
SET status = 'sent', attempts = attempts + 1, next_attempt_at = NULL, updated_at = ?1
|
|
WHERE id = ?2 AND status = 'pending' AND next_attempt_at = ?3
|
|
`
|
|
|
|
type MarkNotificationSentParams struct {
|
|
Now string
|
|
ID string
|
|
LeaseUntil sql.NullString
|
|
}
|
|
|
|
// Records a delivered notification; updated_at is when it was sent. The
|
|
// lease must still be the claim's.
|
|
func (q *Queries) MarkNotificationSent(ctx context.Context, arg MarkNotificationSentParams) (int64, error) {
|
|
result, err := q.db.ExecContext(ctx, markNotificationSent, arg.Now, arg.ID, arg.LeaseUntil)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
return result.RowsAffected()
|
|
}
|
|
|
|
const nextNotificationDue = `-- name: NextNotificationDue :one
|
|
SELECT next_attempt_at FROM notification_outbox
|
|
WHERE status = 'pending'
|
|
ORDER BY next_attempt_at
|
|
LIMIT 1
|
|
`
|
|
|
|
// Returns when the next pending notification is due. Returns sql.ErrNoRows
|
|
// when none is pending.
|
|
func (q *Queries) NextNotificationDue(ctx context.Context) (sql.NullString, error) {
|
|
row := q.db.QueryRowContext(ctx, nextNotificationDue)
|
|
var next_attempt_at sql.NullString
|
|
err := row.Scan(&next_attempt_at)
|
|
return next_attempt_at, err
|
|
}
|
|
|
|
const retryNotification = `-- name: RetryNotification :execrows
|
|
UPDATE notification_outbox
|
|
SET attempts = attempts + 1, next_attempt_at = ?1, last_error = ?2, updated_at = ?3
|
|
WHERE id = ?4 AND status = 'pending' AND next_attempt_at = ?5
|
|
`
|
|
|
|
type RetryNotificationParams struct {
|
|
NextAttemptAt sql.NullString
|
|
LastError sql.NullString
|
|
Now string
|
|
ID string
|
|
LeaseUntil sql.NullString
|
|
}
|
|
|
|
// Records a failed delivery and when to try again. The lease must still be
|
|
// the claim's.
|
|
func (q *Queries) RetryNotification(ctx context.Context, arg RetryNotificationParams) (int64, error) {
|
|
result, err := q.db.ExecContext(ctx, retryNotification,
|
|
arg.NextAttemptAt,
|
|
arg.LastError,
|
|
arg.Now,
|
|
arg.ID,
|
|
arg.LeaseUntil,
|
|
)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
return result.RowsAffected()
|
|
}
|
|
|
|
const updateNotificationPref = `-- name: UpdateNotificationPref :execrows
|
|
UPDATE notification_prefs
|
|
SET push = ?, chat = ?, updated_at = ?
|
|
WHERE event = ?
|
|
`
|
|
|
|
type UpdateNotificationPrefParams struct {
|
|
Push int64
|
|
Chat int64
|
|
UpdatedAt string
|
|
Event string
|
|
}
|
|
|
|
// Sets which channels an event goes to; affects no row when the event has
|
|
// no preference row.
|
|
func (q *Queries) UpdateNotificationPref(ctx context.Context, arg UpdateNotificationPrefParams) (int64, error) {
|
|
result, err := q.db.ExecContext(ctx, updateNotificationPref,
|
|
arg.Push,
|
|
arg.Chat,
|
|
arg.UpdatedAt,
|
|
arg.Event,
|
|
)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
return result.RowsAffected()
|
|
}
|