POST /api/v1/suggestions/batches enqueues a suggestion_batch job that runs
suggest_side_quests through the harness, grounds the survivors with
recipes.Grounder.GroundMany (dropping those no recipe grounds) and stores
each as a dinner (status suggested, origin side_quest) with a pending
suggestion holding its reason and cited_regular_ids. Only one batch is
pending or running at a time; a POST while one is active returns it.
GET /api/v1/suggestions/deck lists pending suggestions oldest first with
the dinner's name, photo, difficulty and the reason. POST
/api/v1/suggestions/{id}/accept moves the dinner to the Quest log; /decline
records the Pass and archives the dinner, whose name every later batch
reads as declined. Both are idempotent and 409 on the opposite decision.
A decision leaving fewer than 5 in the deck starts a replenish batch
unless one is already pending or running. Batches left unfinished by a
restart are failed at boot so they never block the next.
Task: b4f2558c-227f-4627-852b-be5aa190242a
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
103 lines
3.6 KiB
SQL
103 lines
3.6 KiB
SQL
-- name: CreateSuggestionBatch :one
|
|
INSERT INTO suggestion_batches (id, trigger, status, created_at, updated_at)
|
|
VALUES (?, ?, 'pending', ?, ?)
|
|
RETURNING *;
|
|
|
|
-- name: GetActiveSuggestionBatch :one
|
|
-- The batch still pending or running, oldest first. Returns sql.ErrNoRows
|
|
-- when there is none.
|
|
SELECT * FROM suggestion_batches
|
|
WHERE status IN ('pending', 'running')
|
|
ORDER BY id
|
|
LIMIT 1;
|
|
|
|
-- name: GetSuggestionBatchJobID :one
|
|
-- The id of the job that runs batch batch_id.
|
|
SELECT id FROM jobs
|
|
WHERE kind = sqlc.arg(kind)
|
|
AND json_extract(payload, '$.batch_id') = sqlc.arg(batch_id)
|
|
ORDER BY id
|
|
LIMIT 1;
|
|
|
|
-- name: StartSuggestionBatch :execrows
|
|
-- Marks a pending batch running; a batch in any other status is left as
|
|
-- it is (0 rows).
|
|
UPDATE suggestion_batches
|
|
SET status = 'running', updated_at = ?
|
|
WHERE id = ? AND status = 'pending';
|
|
|
|
-- name: FinishSuggestionBatch :execrows
|
|
-- Records a pending or running batch as done or failed; a batch already
|
|
-- finished is left as it is (0 rows).
|
|
UPDATE suggestion_batches
|
|
SET status = ?, updated_at = ?
|
|
WHERE id = ? AND status IN ('pending', 'running');
|
|
|
|
-- name: FailOrphanedSuggestionBatches :execrows
|
|
-- Fails every batch still pending or running whose job is no longer queued
|
|
-- or running: the process stopped before enqueuing the job or before its
|
|
-- handler recorded a result. Run at boot, after the job runner fails the
|
|
-- jobs a previous process left running.
|
|
UPDATE suggestion_batches
|
|
SET status = 'failed', updated_at = sqlc.arg(updated_at)
|
|
WHERE status IN ('pending', 'running')
|
|
AND NOT EXISTS (
|
|
SELECT 1 FROM jobs
|
|
WHERE jobs.kind = sqlc.arg(kind)
|
|
AND jobs.status IN ('queued', 'running')
|
|
AND json_extract(jobs.payload, '$.batch_id') = suggestion_batches.id
|
|
);
|
|
|
|
-- name: CreateSuggestion :exec
|
|
INSERT INTO suggestions (
|
|
id, dinner_id, batch_id, reason, cited_regular_ids, status, created_at, updated_at
|
|
) VALUES (
|
|
?, ?, ?, ?, ?, 'pending', ?, ?
|
|
);
|
|
|
|
-- name: GetSuggestion :one
|
|
-- A suggestion with its dinner. Returns sql.ErrNoRows for an unknown id.
|
|
SELECT sqlc.embed(suggestions), sqlc.embed(dinners)
|
|
FROM suggestions
|
|
JOIN dinners ON dinners.id = suggestions.dinner_id
|
|
WHERE suggestions.id = ?;
|
|
|
|
-- name: ListDeck :many
|
|
-- The pending suggestions whose dinner is still suggested, oldest first,
|
|
-- with their dinners.
|
|
SELECT sqlc.embed(suggestions), sqlc.embed(dinners)
|
|
FROM suggestions
|
|
JOIN dinners ON dinners.id = suggestions.dinner_id
|
|
WHERE suggestions.status = 'pending' AND dinners.status = 'suggested'
|
|
ORDER BY suggestions.created_at, suggestions.id;
|
|
|
|
-- name: CountDeck :one
|
|
-- How many suggestions ListDeck returns.
|
|
SELECT COUNT(*)
|
|
FROM suggestions
|
|
JOIN dinners ON dinners.id = suggestions.dinner_id
|
|
WHERE suggestions.status = 'pending' AND dinners.status = 'suggested';
|
|
|
|
-- name: ListDeclinedSuggestionNames :many
|
|
-- The name of every declined suggestion's dinner, oldest decision first.
|
|
SELECT dinners.name
|
|
FROM suggestions
|
|
JOIN dinners ON dinners.id = suggestions.dinner_id
|
|
WHERE suggestions.status = 'declined'
|
|
ORDER BY suggestions.decided_at, suggestions.id;
|
|
|
|
-- name: DecideSuggestion :execrows
|
|
-- Records the household's decision on a pending suggestion; one already
|
|
-- decided is left as it is (0 rows).
|
|
UPDATE suggestions
|
|
SET status = sqlc.arg(status), decided_at = sqlc.arg(now), updated_at = sqlc.arg(now)
|
|
WHERE id = sqlc.arg(id) AND status = 'pending';
|
|
|
|
-- name: MoveSuggestedDinner :execrows
|
|
-- Moves a dinner still suggested to status (quest_log or archived),
|
|
-- recording updated_by; a dinner no longer suggested is left as it is
|
|
-- (0 rows).
|
|
UPDATE dinners
|
|
SET status = sqlc.arg(status), updated_by = sqlc.arg(updated_by), updated_at = sqlc.arg(updated_at)
|
|
WHERE id = sqlc.arg(id) AND status = 'suggested';
|