Feature 19: Notifications - Web Push and Google Chat (#169)
CI / ci (push) Successful in 8m31s
CI / ci (push) Successful in 8m31s
Co-authored-by: orchestrator-agent <20+orchestrator-agent@code.cubecraftcreations.com>
This commit was merged in pull request #169.
This commit is contained in:
+10
-9
@@ -276,15 +276,16 @@ type ShoppingItem struct {
|
||||
}
|
||||
|
||||
type ShoppingList struct {
|
||||
ID string
|
||||
WeekStart string
|
||||
PlanID string
|
||||
Title sql.NullString
|
||||
Status string
|
||||
SentToChatAt sql.NullString
|
||||
FrozenAt sql.NullString
|
||||
CreatedAt string
|
||||
UpdatedAt string
|
||||
ID string
|
||||
WeekStart string
|
||||
PlanID string
|
||||
Title sql.NullString
|
||||
Status string
|
||||
SentToChatAt sql.NullString
|
||||
FrozenAt sql.NullString
|
||||
CreatedAt string
|
||||
UpdatedAt string
|
||||
ChatSendUnconfirmedAt sql.NullString
|
||||
}
|
||||
|
||||
type Suggestion struct {
|
||||
|
||||
@@ -0,0 +1,263 @@
|
||||
// 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 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()
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
// Code generated by sqlc. DO NOT EDIT.
|
||||
// versions:
|
||||
// sqlc v1.31.1
|
||||
// source: push_subscriptions.sql
|
||||
|
||||
package db
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
)
|
||||
|
||||
const deletePushSubscription = `-- name: DeletePushSubscription :execrows
|
||||
DELETE FROM push_subscriptions
|
||||
WHERE endpoint = ?
|
||||
`
|
||||
|
||||
func (q *Queries) DeletePushSubscription(ctx context.Context, endpoint string) (int64, error) {
|
||||
result, err := q.db.ExecContext(ctx, deletePushSubscription, endpoint)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return result.RowsAffected()
|
||||
}
|
||||
|
||||
const deletePushSubscriptionByID = `-- name: DeletePushSubscriptionByID :exec
|
||||
DELETE FROM push_subscriptions
|
||||
WHERE id = ?
|
||||
`
|
||||
|
||||
// Removes a subscription the push service reports gone (404 or 410).
|
||||
func (q *Queries) DeletePushSubscriptionByID(ctx context.Context, id string) error {
|
||||
_, err := q.db.ExecContext(ctx, deletePushSubscriptionByID, id)
|
||||
return err
|
||||
}
|
||||
|
||||
const listPushSubscriptions = `-- name: ListPushSubscriptions :many
|
||||
SELECT id, endpoint, p256dh, auth
|
||||
FROM push_subscriptions
|
||||
ORDER BY created_at, id
|
||||
`
|
||||
|
||||
type ListPushSubscriptionsRow struct {
|
||||
ID string
|
||||
Endpoint string
|
||||
P256dh string
|
||||
Auth string
|
||||
}
|
||||
|
||||
// Every stored subscription with its keys, for the push sender, oldest
|
||||
// first.
|
||||
func (q *Queries) ListPushSubscriptions(ctx context.Context) ([]ListPushSubscriptionsRow, error) {
|
||||
rows, err := q.db.QueryContext(ctx, listPushSubscriptions)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var items []ListPushSubscriptionsRow
|
||||
for rows.Next() {
|
||||
var i ListPushSubscriptionsRow
|
||||
if err := rows.Scan(
|
||||
&i.ID,
|
||||
&i.Endpoint,
|
||||
&i.P256dh,
|
||||
&i.Auth,
|
||||
); 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 recordPushFailure = `-- name: RecordPushFailure :exec
|
||||
UPDATE push_subscriptions
|
||||
SET failure_count = failure_count + 1
|
||||
WHERE id = ?
|
||||
`
|
||||
|
||||
// Counts a delivery to a subscription that the push service did not accept.
|
||||
func (q *Queries) RecordPushFailure(ctx context.Context, id string) error {
|
||||
_, err := q.db.ExecContext(ctx, recordPushFailure, id)
|
||||
return err
|
||||
}
|
||||
|
||||
const recordPushSuccess = `-- name: RecordPushSuccess :exec
|
||||
UPDATE push_subscriptions
|
||||
SET last_success_at = ?
|
||||
WHERE id = ?
|
||||
`
|
||||
|
||||
type RecordPushSuccessParams struct {
|
||||
LastSuccessAt sql.NullString
|
||||
ID string
|
||||
}
|
||||
|
||||
// Records that the push service accepted a notification for a subscription.
|
||||
func (q *Queries) RecordPushSuccess(ctx context.Context, arg RecordPushSuccessParams) error {
|
||||
_, err := q.db.ExecContext(ctx, recordPushSuccess, arg.LastSuccessAt, arg.ID)
|
||||
return err
|
||||
}
|
||||
|
||||
const upsertPushSubscription = `-- name: UpsertPushSubscription :one
|
||||
INSERT INTO push_subscriptions (
|
||||
id, endpoint, p256dh, auth, user_agent, failure_count, created_at, updated_at
|
||||
) VALUES (
|
||||
?, ?, ?, ?, ?, 0, ?, ?
|
||||
)
|
||||
ON CONFLICT (endpoint) DO UPDATE SET
|
||||
p256dh = excluded.p256dh,
|
||||
auth = excluded.auth,
|
||||
user_agent = excluded.user_agent,
|
||||
failure_count = 0,
|
||||
updated_at = excluded.updated_at
|
||||
RETURNING id, endpoint, p256dh, auth, user_agent, failure_count, last_success_at, created_at, updated_at
|
||||
`
|
||||
|
||||
type UpsertPushSubscriptionParams struct {
|
||||
ID string
|
||||
Endpoint string
|
||||
P256dh string
|
||||
Auth string
|
||||
UserAgent sql.NullString
|
||||
CreatedAt string
|
||||
UpdatedAt string
|
||||
}
|
||||
|
||||
// Stores a browser's push subscription, keyed by its endpoint. Subscribing
|
||||
// an endpoint already stored replaces its keys and user agent and clears
|
||||
// its failure count, keeping its id, created_at and last_success_at.
|
||||
func (q *Queries) UpsertPushSubscription(ctx context.Context, arg UpsertPushSubscriptionParams) (PushSubscription, error) {
|
||||
row := q.db.QueryRowContext(ctx, upsertPushSubscription,
|
||||
arg.ID,
|
||||
arg.Endpoint,
|
||||
arg.P256dh,
|
||||
arg.Auth,
|
||||
arg.UserAgent,
|
||||
arg.CreatedAt,
|
||||
arg.UpdatedAt,
|
||||
)
|
||||
var i PushSubscription
|
||||
err := row.Scan(
|
||||
&i.ID,
|
||||
&i.Endpoint,
|
||||
&i.P256dh,
|
||||
&i.Auth,
|
||||
&i.UserAgent,
|
||||
&i.FailureCount,
|
||||
&i.LastSuccessAt,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
@@ -12,6 +12,15 @@ import (
|
||||
type Querier interface {
|
||||
// An unknown id returns sql.ErrNoRows.
|
||||
ArchiveDinner(ctx context.Context, arg ArchiveDinnerParams) (Dinner, error)
|
||||
// Records a send of list id to Google Chat begun at
|
||||
// chat_send_unconfirmed_at, before it is posted, unless the list is frozen
|
||||
// into the Supply archive.
|
||||
BeginShoppingListChatSend(ctx context.Context, arg BeginShoppingListChatSendParams) (int64, error)
|
||||
// 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.
|
||||
ClaimDueNotification(ctx context.Context, arg ClaimDueNotificationParams) (NotificationOutbox, error)
|
||||
// Marks the oldest queued job running and returns it. Returns sql.ErrNoRows
|
||||
// when no job is queued.
|
||||
ClaimNextJob(ctx context.Context, now sql.NullString) (Job, error)
|
||||
@@ -21,6 +30,8 @@ type Querier interface {
|
||||
// sets last_sync_error, keeping the chosen calendar. Affects no row when
|
||||
// nothing is connected.
|
||||
ClearIntegrationsGoogleToken(ctx context.Context, arg ClearIntegrationsGoogleTokenParams) (int64, error)
|
||||
// Clears the send of list id begun, once Google Chat refused it.
|
||||
ClearShoppingListChatSend(ctx context.Context, arg ClearShoppingListChatSendParams) (int64, error)
|
||||
// Drops references to the runs created before the cutoff, so the purge
|
||||
// can delete them.
|
||||
ClearSuggestionBatchAIRunsBefore(ctx context.Context, cutoff string) (int64, error)
|
||||
@@ -62,11 +73,17 @@ type Querier interface {
|
||||
DecideSuggestion(ctx context.Context, arg DecideSuggestionParams) (int64, error)
|
||||
DeleteAIRunsBefore(ctx context.Context, cutoff string) (int64, error)
|
||||
DeleteIntegrationsGoogle(ctx context.Context) error
|
||||
DeletePushSubscription(ctx context.Context, endpoint string) (int64, error)
|
||||
// Removes a subscription the push service reports gone (404 or 410).
|
||||
DeletePushSubscriptionByID(ctx context.Context, id string) error
|
||||
DeleteRecipeIngredients(ctx context.Context, recipeID string) error
|
||||
DeleteRecipeSteps(ctx context.Context, recipeID string) error
|
||||
DeleteShoppingGroup(ctx context.Context, id string) error
|
||||
DeleteShoppingItem(ctx context.Context, id string) error
|
||||
FailJob(ctx context.Context, arg FailJobParams) (int64, error)
|
||||
// Gives up on a notification. attempts counts the delivery attempt that
|
||||
// failed, if one was made. The lease must still be the claim's.
|
||||
FailNotification(ctx context.Context, arg FailNotificationParams) (int64, error)
|
||||
// Fails every unarchived dinner still pending whose grounding job for its
|
||||
// current grounding_revision is no longer queued or running: the process
|
||||
// stopped before the job, or before its handler recorded a result. An
|
||||
@@ -118,6 +135,7 @@ type Querier interface {
|
||||
// The newest batch, whatever its status. Returns sql.ErrNoRows when there
|
||||
// is none.
|
||||
GetLatestSuggestionBatch(ctx context.Context) (SuggestionBatch, error)
|
||||
GetNotificationPref(ctx context.Context, event string) (NotificationPref, error)
|
||||
// The pending Rotation check of dinner_id. MarkTried makes one only for a
|
||||
// dinner in the Quest log, which it moves to tried, so a dinner has at
|
||||
// most one. Returns sql.ErrNoRows when there is none.
|
||||
@@ -138,6 +156,8 @@ type Querier interface {
|
||||
GetShoppingList(ctx context.Context, weekStart string) (ShoppingList, error)
|
||||
// A suggestion with its dinner. Returns sql.ErrNoRows for an unknown id.
|
||||
GetSuggestion(ctx context.Context, id string) (GetSuggestionRow, error)
|
||||
// Batch id. Returns sql.ErrNoRows when there is none.
|
||||
GetSuggestionBatch(ctx context.Context, id string) (SuggestionBatch, error)
|
||||
// The id of the job that runs batch batch_id.
|
||||
GetSuggestionBatchJobID(ctx context.Context, arg GetSuggestionBatchJobIDParams) (string, error)
|
||||
// The plan of the week starting week_start. Returns sql.ErrNoRows when
|
||||
@@ -159,6 +179,10 @@ type Querier interface {
|
||||
// them only when a member edited them while it was grounding. Like
|
||||
// GroundDinner it clears the pasted recipe_url: the recipe replaces the link.
|
||||
GroundPendingDinner(ctx context.Context, arg GroundPendingDinnerParams) (int64, error)
|
||||
// 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.
|
||||
InsertNotification(ctx context.Context, arg InsertNotificationParams) (int64, error)
|
||||
// The pending suggestions whose dinner is still suggested, oldest first,
|
||||
// with their dinners.
|
||||
ListDeck(ctx context.Context) ([]ListDeckRow, error)
|
||||
@@ -167,6 +191,7 @@ type Querier interface {
|
||||
// Regular, and every Quest log dinner no other plan for the week starting
|
||||
// from_week_start or later has on any day.
|
||||
ListDraftPool(ctx context.Context, arg ListDraftPoolParams) ([]Dinner, error)
|
||||
ListNotificationPrefs(ctx context.Context) ([]NotificationPref, error)
|
||||
// Every name the household turned down, oldest decision first: the name
|
||||
// each declined suggestion was suggested under, which renaming its dinner
|
||||
// does not change, and the name each dinner a Rotation check dropped
|
||||
@@ -181,6 +206,9 @@ type Querier interface {
|
||||
// The planned dinners dated from from_date up to but not including
|
||||
// to_date, oldest first. Days with no dinner are left out.
|
||||
ListPlannedDinners(ctx context.Context, arg ListPlannedDinnersParams) ([]ListPlannedDinnersRow, error)
|
||||
// Every stored subscription with its keys, for the push sender, oldest
|
||||
// first.
|
||||
ListPushSubscriptions(ctx context.Context) ([]ListPushSubscriptionsRow, error)
|
||||
// Reads for the AI household context block (internal/ai/harness).
|
||||
// The most recent names the household turned down, newest first, as
|
||||
// ListPassedNames has them.
|
||||
@@ -217,6 +245,12 @@ type Querier interface {
|
||||
// Moves a dinner still in the Quest log to tried; a dinner with any other
|
||||
// status is left as it is (0 rows).
|
||||
MarkDinnerTried(ctx context.Context, arg MarkDinnerTriedParams) (int64, error)
|
||||
// Records a delivered notification; updated_at is when it was sent. The
|
||||
// lease must still be the claim's.
|
||||
MarkNotificationSent(ctx context.Context, arg MarkNotificationSentParams) (int64, error)
|
||||
// Records list id sent to Google Chat at sent_to_chat_at, its send
|
||||
// confirmed, unless it is frozen into the Supply archive.
|
||||
MarkShoppingListSentToChat(ctx context.Context, arg MarkShoppingListSentToChatParams) (int64, error)
|
||||
// Records an edit of plan id, raising its revision: a synced plan becomes
|
||||
// changed, any other keeps its status.
|
||||
MarkWeekPlanEdited(ctx context.Context, arg MarkWeekPlanEditedParams) error
|
||||
@@ -236,17 +270,31 @@ type Querier interface {
|
||||
// by a Rotation check decision, recording updated_by; its origin is kept.
|
||||
// A dinner no longer tried is left as it is (0 rows).
|
||||
MoveTriedDinner(ctx context.Context, arg MoveTriedDinnerParams) (int64, error)
|
||||
// Returns when the next pending notification is due. Returns sql.ErrNoRows
|
||||
// when none is pending.
|
||||
NextNotificationDue(ctx context.Context) (sql.NullString, error)
|
||||
// Stores a partial result of a running job.
|
||||
PublishJobResult(ctx context.Context, arg PublishJobResultParams) (int64, error)
|
||||
// Plans dinner_id on date of plan_id. An existing day keeps its calendar
|
||||
// event id while its dinner stays the same; a new dinner clears it, since
|
||||
// Calendar no longer shows the day as planned.
|
||||
PutPlanDay(ctx context.Context, arg PutPlanDayParams) error
|
||||
// Records the outcome of a test message sent to the webhook sealed in
|
||||
// webhook_url_ct; affects no row when the webhook has been replaced or
|
||||
// removed since the test began.
|
||||
RecordChatTest(ctx context.Context, arg RecordChatTestParams) (int64, error)
|
||||
// Counts a delivery to a subscription that the push service did not accept.
|
||||
RecordPushFailure(ctx context.Context, id string) error
|
||||
// Records that the push service accepted a notification for a subscription.
|
||||
RecordPushSuccess(ctx context.Context, arg RecordPushSuccessParams) error
|
||||
// Records a new draft over plan id: its status and note, raising its
|
||||
// revision.
|
||||
RedraftWeekPlan(ctx context.Context, arg RedraftWeekPlanParams) error
|
||||
// Records a generation of list id from plan plan_id.
|
||||
RegenerateShoppingList(ctx context.Context, arg RegenerateShoppingListParams) error
|
||||
// Records a failed delivery and when to try again. The lease must still be
|
||||
// the claim's.
|
||||
RetryNotification(ctx context.Context, arg RetryNotificationParams) (int64, error)
|
||||
// An unknown id returns sql.ErrNoRows.
|
||||
SetDinnerFavorite(ctx context.Context, arg SetDinnerFavoriteParams) (Dinner, error)
|
||||
// Sets the chosen calendar; affects no row when nothing is connected.
|
||||
@@ -271,6 +319,9 @@ type Querier interface {
|
||||
ToggleDinnerFavorite(ctx context.Context, arg ToggleDinnerFavoriteParams) (Dinner, error)
|
||||
// An unknown id returns sql.ErrNoRows.
|
||||
UpdateDinner(ctx context.Context, arg UpdateDinnerParams) (Dinner, error)
|
||||
// Sets which channels an event goes to; affects no row when the event has
|
||||
// no preference row.
|
||||
UpdateNotificationPref(ctx context.Context, arg UpdateNotificationPrefParams) (int64, error)
|
||||
// Creates the single row (id 1) or replaces every column except id and
|
||||
// created_at.
|
||||
UpsertAISettings(ctx context.Context, arg UpsertAISettingsParams) (AiSetting, error)
|
||||
@@ -283,6 +334,10 @@ type Querier interface {
|
||||
// Creates the single row (id 1) or replaces every column except id and
|
||||
// created_at.
|
||||
UpsertIntegrationsGoogle(ctx context.Context, arg UpsertIntegrationsGoogleParams) (IntegrationsGoogle, error)
|
||||
// Stores a browser's push subscription, keyed by its endpoint. Subscribing
|
||||
// an endpoint already stored replaces its keys and user agent and clears
|
||||
// its failure count, keeping its id, created_at and last_success_at.
|
||||
UpsertPushSubscription(ctx context.Context, arg UpsertPushSubscriptionParams) (PushSubscription, error)
|
||||
// A recipe is keyed by source_url: re-fetching the same URL updates the
|
||||
// existing row and keeps its id and created_at, which the returned row
|
||||
// carries. Replace its ingredients and steps in the same transaction with
|
||||
|
||||
@@ -161,6 +161,35 @@ func (q *Queries) GetRecipeSearchSettings(ctx context.Context) (RecipeSearchSett
|
||||
return i, err
|
||||
}
|
||||
|
||||
const recordChatTest = `-- name: RecordChatTest :execrows
|
||||
UPDATE chat_settings
|
||||
SET last_test_at = ?, last_test_status = ?, updated_at = ?
|
||||
WHERE id = 1 AND webhook_url_ct = ?
|
||||
`
|
||||
|
||||
type RecordChatTestParams struct {
|
||||
LastTestAt sql.NullString
|
||||
LastTestStatus sql.NullString
|
||||
UpdatedAt string
|
||||
WebhookUrlCt []byte
|
||||
}
|
||||
|
||||
// Records the outcome of a test message sent to the webhook sealed in
|
||||
// webhook_url_ct; affects no row when the webhook has been replaced or
|
||||
// removed since the test began.
|
||||
func (q *Queries) RecordChatTest(ctx context.Context, arg RecordChatTestParams) (int64, error) {
|
||||
result, err := q.db.ExecContext(ctx, recordChatTest,
|
||||
arg.LastTestAt,
|
||||
arg.LastTestStatus,
|
||||
arg.UpdatedAt,
|
||||
arg.WebhookUrlCt,
|
||||
)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return result.RowsAffected()
|
||||
}
|
||||
|
||||
const setIntegrationsGoogleCalendar = `-- name: SetIntegrationsGoogleCalendar :execrows
|
||||
UPDATE integrations_google
|
||||
SET calendar_id = ?, calendar_name = ?, updated_at = ?
|
||||
|
||||
@@ -10,6 +10,49 @@ import (
|
||||
"database/sql"
|
||||
)
|
||||
|
||||
const beginShoppingListChatSend = `-- name: BeginShoppingListChatSend :execrows
|
||||
UPDATE shopping_lists
|
||||
SET chat_send_unconfirmed_at = ?, updated_at = ?
|
||||
WHERE id = ? AND status = 'current' AND frozen_at IS NULL
|
||||
`
|
||||
|
||||
type BeginShoppingListChatSendParams struct {
|
||||
ChatSendUnconfirmedAt sql.NullString
|
||||
UpdatedAt string
|
||||
ID string
|
||||
}
|
||||
|
||||
// Records a send of list id to Google Chat begun at
|
||||
// chat_send_unconfirmed_at, before it is posted, unless the list is frozen
|
||||
// into the Supply archive.
|
||||
func (q *Queries) BeginShoppingListChatSend(ctx context.Context, arg BeginShoppingListChatSendParams) (int64, error) {
|
||||
result, err := q.db.ExecContext(ctx, beginShoppingListChatSend, arg.ChatSendUnconfirmedAt, arg.UpdatedAt, arg.ID)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return result.RowsAffected()
|
||||
}
|
||||
|
||||
const clearShoppingListChatSend = `-- name: ClearShoppingListChatSend :execrows
|
||||
UPDATE shopping_lists
|
||||
SET chat_send_unconfirmed_at = NULL, updated_at = ?
|
||||
WHERE id = ?
|
||||
`
|
||||
|
||||
type ClearShoppingListChatSendParams struct {
|
||||
UpdatedAt string
|
||||
ID string
|
||||
}
|
||||
|
||||
// Clears the send of list id begun, once Google Chat refused it.
|
||||
func (q *Queries) ClearShoppingListChatSend(ctx context.Context, arg ClearShoppingListChatSendParams) (int64, error) {
|
||||
result, err := q.db.ExecContext(ctx, clearShoppingListChatSend, arg.UpdatedAt, arg.ID)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return result.RowsAffected()
|
||||
}
|
||||
|
||||
const createShoppingGroup = `-- name: CreateShoppingGroup :exec
|
||||
INSERT INTO shopping_groups (id, list_id, dinner_id, date, position, created_at, updated_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)
|
||||
@@ -135,7 +178,7 @@ func (q *Queries) FreezeShoppingList(ctx context.Context, arg FreezeShoppingList
|
||||
}
|
||||
|
||||
const getShoppingItemList = `-- name: GetShoppingItemList :one
|
||||
SELECT shopping_lists.id, shopping_lists.week_start, shopping_lists.plan_id, shopping_lists.title, shopping_lists.status, shopping_lists.sent_to_chat_at, shopping_lists.frozen_at, shopping_lists.created_at, shopping_lists.updated_at
|
||||
SELECT shopping_lists.id, shopping_lists.week_start, shopping_lists.plan_id, shopping_lists.title, shopping_lists.status, shopping_lists.sent_to_chat_at, shopping_lists.frozen_at, shopping_lists.created_at, shopping_lists.updated_at, shopping_lists.chat_send_unconfirmed_at
|
||||
FROM shopping_items
|
||||
JOIN shopping_groups ON shopping_groups.id = shopping_items.group_id
|
||||
JOIN shopping_lists ON shopping_lists.id = shopping_groups.list_id
|
||||
@@ -161,12 +204,13 @@ func (q *Queries) GetShoppingItemList(ctx context.Context, id string) (GetShoppi
|
||||
&i.ShoppingList.FrozenAt,
|
||||
&i.ShoppingList.CreatedAt,
|
||||
&i.ShoppingList.UpdatedAt,
|
||||
&i.ShoppingList.ChatSendUnconfirmedAt,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const getShoppingList = `-- name: GetShoppingList :one
|
||||
SELECT id, week_start, plan_id, title, status, sent_to_chat_at, frozen_at, created_at, updated_at FROM shopping_lists
|
||||
SELECT id, week_start, plan_id, title, status, sent_to_chat_at, frozen_at, created_at, updated_at, chat_send_unconfirmed_at FROM shopping_lists
|
||||
WHERE week_start = ?
|
||||
`
|
||||
|
||||
@@ -185,6 +229,7 @@ func (q *Queries) GetShoppingList(ctx context.Context, weekStart string) (Shoppi
|
||||
&i.FrozenAt,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
&i.ChatSendUnconfirmedAt,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
@@ -322,7 +367,7 @@ func (q *Queries) ListShoppingItems(ctx context.Context, listID string) ([]ListS
|
||||
}
|
||||
|
||||
const listShoppingListsThrough = `-- name: ListShoppingListsThrough :many
|
||||
SELECT id, week_start, plan_id, title, status, sent_to_chat_at, frozen_at, created_at, updated_at FROM shopping_lists
|
||||
SELECT id, week_start, plan_id, title, status, sent_to_chat_at, frozen_at, created_at, updated_at, chat_send_unconfirmed_at FROM shopping_lists
|
||||
WHERE week_start <= ?
|
||||
ORDER BY week_start DESC
|
||||
`
|
||||
@@ -348,6 +393,7 @@ func (q *Queries) ListShoppingListsThrough(ctx context.Context, weekStart string
|
||||
&i.FrozenAt,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
&i.ChatSendUnconfirmedAt,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -362,6 +408,28 @@ func (q *Queries) ListShoppingListsThrough(ctx context.Context, weekStart string
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const markShoppingListSentToChat = `-- name: MarkShoppingListSentToChat :execrows
|
||||
UPDATE shopping_lists
|
||||
SET sent_to_chat_at = ?, chat_send_unconfirmed_at = NULL, updated_at = ?
|
||||
WHERE id = ? AND status = 'current' AND frozen_at IS NULL
|
||||
`
|
||||
|
||||
type MarkShoppingListSentToChatParams struct {
|
||||
SentToChatAt sql.NullString
|
||||
UpdatedAt string
|
||||
ID string
|
||||
}
|
||||
|
||||
// Records list id sent to Google Chat at sent_to_chat_at, its send
|
||||
// confirmed, unless it is frozen into the Supply archive.
|
||||
func (q *Queries) MarkShoppingListSentToChat(ctx context.Context, arg MarkShoppingListSentToChatParams) (int64, error) {
|
||||
result, err := q.db.ExecContext(ctx, markShoppingListSentToChat, arg.SentToChatAt, arg.UpdatedAt, arg.ID)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return result.RowsAffected()
|
||||
}
|
||||
|
||||
const moveShoppingGroup = `-- name: MoveShoppingGroup :exec
|
||||
UPDATE shopping_groups
|
||||
SET position = ?, updated_at = ?
|
||||
|
||||
@@ -294,6 +294,26 @@ func (q *Queries) GetSuggestion(ctx context.Context, id string) (GetSuggestionRo
|
||||
return i, err
|
||||
}
|
||||
|
||||
const getSuggestionBatch = `-- name: GetSuggestionBatch :one
|
||||
SELECT id, "trigger", status, ai_run_id, created_at, updated_at FROM suggestion_batches
|
||||
WHERE id = ?
|
||||
`
|
||||
|
||||
// Batch id. Returns sql.ErrNoRows when there is none.
|
||||
func (q *Queries) GetSuggestionBatch(ctx context.Context, id string) (SuggestionBatch, error) {
|
||||
row := q.db.QueryRowContext(ctx, getSuggestionBatch, id)
|
||||
var i SuggestionBatch
|
||||
err := row.Scan(
|
||||
&i.ID,
|
||||
&i.Trigger,
|
||||
&i.Status,
|
||||
&i.AiRunID,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const getSuggestionBatchJobID = `-- name: GetSuggestionBatchJobID :one
|
||||
SELECT id FROM jobs
|
||||
WHERE kind = ?1
|
||||
|
||||
Reference in New Issue
Block a user