Returns a dinner with its recipe for the recipe detail screen and Kitchen reading mode: photo URLs at both cached sizes (400/1200) or null, ingredients and steps in recipe position order, the source page for a fetched recipe, grounding status and error, and why_picked, the reason of the suggestion that created the dinner (null when none). Unknown and archived dinners are 404. Read in one transaction. Adds the GetDinnerSuggestionReason query (sqlc regenerated). The dinners test env now serves photos from the grounder's own store and data directory so photo URLs can be fetched in tests. Maestro task 099e9952-7f98-4700-8369-241c589a1e9d. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
456 lines
13 KiB
Go
456 lines
13 KiB
Go
// Code generated by sqlc. DO NOT EDIT.
|
|
// versions:
|
|
// sqlc v1.31.1
|
|
// source: suggestions.sql
|
|
|
|
package db
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
)
|
|
|
|
const countDeck = `-- name: CountDeck :one
|
|
SELECT COUNT(*)
|
|
FROM suggestions
|
|
JOIN dinners ON dinners.id = suggestions.dinner_id
|
|
WHERE suggestions.status = 'pending' AND dinners.status = 'suggested'
|
|
`
|
|
|
|
// How many suggestions ListDeck returns.
|
|
func (q *Queries) CountDeck(ctx context.Context) (int64, error) {
|
|
row := q.db.QueryRowContext(ctx, countDeck)
|
|
var count int64
|
|
err := row.Scan(&count)
|
|
return count, err
|
|
}
|
|
|
|
const createSuggestion = `-- name: CreateSuggestion :exec
|
|
INSERT INTO suggestions (
|
|
id, dinner_id, batch_id, name, reason, cited_regular_ids, status, created_at, updated_at
|
|
) VALUES (
|
|
?, ?, ?, ?, ?, ?, 'pending', ?, ?
|
|
)
|
|
`
|
|
|
|
type CreateSuggestionParams struct {
|
|
ID string
|
|
DinnerID string
|
|
BatchID string
|
|
Name string
|
|
Reason string
|
|
CitedRegularIds string
|
|
CreatedAt string
|
|
UpdatedAt string
|
|
}
|
|
|
|
// name is the name the dish is suggested under, kept whatever its dinner is
|
|
// later renamed.
|
|
func (q *Queries) CreateSuggestion(ctx context.Context, arg CreateSuggestionParams) error {
|
|
_, err := q.db.ExecContext(ctx, createSuggestion,
|
|
arg.ID,
|
|
arg.DinnerID,
|
|
arg.BatchID,
|
|
arg.Name,
|
|
arg.Reason,
|
|
arg.CitedRegularIds,
|
|
arg.CreatedAt,
|
|
arg.UpdatedAt,
|
|
)
|
|
return err
|
|
}
|
|
|
|
const createSuggestionBatch = `-- name: CreateSuggestionBatch :one
|
|
INSERT INTO suggestion_batches (id, trigger, status, created_at, updated_at)
|
|
VALUES (?, ?, 'pending', ?, ?)
|
|
RETURNING id, "trigger", status, ai_run_id, created_at, updated_at
|
|
`
|
|
|
|
type CreateSuggestionBatchParams struct {
|
|
ID string
|
|
Trigger string
|
|
CreatedAt string
|
|
UpdatedAt string
|
|
}
|
|
|
|
func (q *Queries) CreateSuggestionBatch(ctx context.Context, arg CreateSuggestionBatchParams) (SuggestionBatch, error) {
|
|
row := q.db.QueryRowContext(ctx, createSuggestionBatch,
|
|
arg.ID,
|
|
arg.Trigger,
|
|
arg.CreatedAt,
|
|
arg.UpdatedAt,
|
|
)
|
|
var i SuggestionBatch
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.Trigger,
|
|
&i.Status,
|
|
&i.AiRunID,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const decideSuggestion = `-- name: DecideSuggestion :execrows
|
|
UPDATE suggestions
|
|
SET status = ?1, decided_at = ?2, updated_at = ?2
|
|
WHERE id = ?3 AND status = 'pending'
|
|
`
|
|
|
|
type DecideSuggestionParams struct {
|
|
Status string
|
|
Now sql.NullString
|
|
ID string
|
|
}
|
|
|
|
// Records the household's decision on a pending suggestion; one already
|
|
// decided is left as it is (0 rows).
|
|
func (q *Queries) DecideSuggestion(ctx context.Context, arg DecideSuggestionParams) (int64, error) {
|
|
result, err := q.db.ExecContext(ctx, decideSuggestion, arg.Status, arg.Now, arg.ID)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
return result.RowsAffected()
|
|
}
|
|
|
|
const failOrphanedSuggestionBatches = `-- name: FailOrphanedSuggestionBatches :execrows
|
|
UPDATE suggestion_batches
|
|
SET status = 'failed', updated_at = ?1
|
|
WHERE status IN ('pending', 'running')
|
|
AND NOT EXISTS (
|
|
SELECT 1 FROM jobs
|
|
WHERE jobs.kind = ?2
|
|
AND jobs.status IN ('queued', 'running')
|
|
AND json_extract(jobs.payload, '$.batch_id') = suggestion_batches.id
|
|
)
|
|
`
|
|
|
|
type FailOrphanedSuggestionBatchesParams struct {
|
|
UpdatedAt string
|
|
Kind string
|
|
}
|
|
|
|
// Fails every batch still pending or running whose job is no longer queued
|
|
// or running: the process stopped, or recording the batch's end failed,
|
|
// before its handler recorded a result. Run before reading the active batch.
|
|
func (q *Queries) FailOrphanedSuggestionBatches(ctx context.Context, arg FailOrphanedSuggestionBatchesParams) (int64, error) {
|
|
result, err := q.db.ExecContext(ctx, failOrphanedSuggestionBatches, arg.UpdatedAt, arg.Kind)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
return result.RowsAffected()
|
|
}
|
|
|
|
const finishSuggestionBatch = `-- name: FinishSuggestionBatch :execrows
|
|
UPDATE suggestion_batches
|
|
SET status = ?, updated_at = ?
|
|
WHERE id = ? AND status IN ('pending', 'running')
|
|
`
|
|
|
|
type FinishSuggestionBatchParams struct {
|
|
Status string
|
|
UpdatedAt string
|
|
ID string
|
|
}
|
|
|
|
// Records a pending or running batch as done or failed; a batch already
|
|
// finished is left as it is (0 rows).
|
|
func (q *Queries) FinishSuggestionBatch(ctx context.Context, arg FinishSuggestionBatchParams) (int64, error) {
|
|
result, err := q.db.ExecContext(ctx, finishSuggestionBatch, arg.Status, arg.UpdatedAt, arg.ID)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
return result.RowsAffected()
|
|
}
|
|
|
|
const getActiveSuggestionBatch = `-- name: GetActiveSuggestionBatch :one
|
|
SELECT id, "trigger", status, ai_run_id, created_at, updated_at FROM suggestion_batches
|
|
WHERE status IN ('pending', 'running')
|
|
ORDER BY id
|
|
LIMIT 1
|
|
`
|
|
|
|
// The batch still pending or running, oldest first. Returns sql.ErrNoRows
|
|
// when there is none.
|
|
func (q *Queries) GetActiveSuggestionBatch(ctx context.Context) (SuggestionBatch, error) {
|
|
row := q.db.QueryRowContext(ctx, getActiveSuggestionBatch)
|
|
var i SuggestionBatch
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.Trigger,
|
|
&i.Status,
|
|
&i.AiRunID,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const getDinnerSuggestionReason = `-- name: GetDinnerSuggestionReason :one
|
|
SELECT reason FROM suggestions
|
|
WHERE dinner_id = ?
|
|
ORDER BY created_at, id
|
|
LIMIT 1
|
|
`
|
|
|
|
// The reason of the suggestion that created dinner_id, its earliest.
|
|
// Returns sql.ErrNoRows for a dinner no suggestion created.
|
|
func (q *Queries) GetDinnerSuggestionReason(ctx context.Context, dinnerID string) (string, error) {
|
|
row := q.db.QueryRowContext(ctx, getDinnerSuggestionReason, dinnerID)
|
|
var reason string
|
|
err := row.Scan(&reason)
|
|
return reason, err
|
|
}
|
|
|
|
const getLatestSuggestionBatch = `-- name: GetLatestSuggestionBatch :one
|
|
SELECT id, "trigger", status, ai_run_id, created_at, updated_at FROM suggestion_batches
|
|
ORDER BY id DESC
|
|
LIMIT 1
|
|
`
|
|
|
|
// The newest batch, whatever its status. Returns sql.ErrNoRows when there
|
|
// is none.
|
|
func (q *Queries) GetLatestSuggestionBatch(ctx context.Context) (SuggestionBatch, error) {
|
|
row := q.db.QueryRowContext(ctx, getLatestSuggestionBatch)
|
|
var i SuggestionBatch
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.Trigger,
|
|
&i.Status,
|
|
&i.AiRunID,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const getSuggestion = `-- name: GetSuggestion :one
|
|
SELECT suggestions.id, suggestions.dinner_id, suggestions.batch_id, suggestions.name, suggestions.reason, suggestions.cited_regular_ids, suggestions.status, suggestions.decided_at, suggestions.created_at, suggestions.updated_at, dinners.id, dinners.name, dinners.normalized_name, dinners.status, dinners.origin, dinners.method, dinners.minutes_total, dinners.difficulty, dinners.cuisine, dinners.is_favorite, dinners.recipe_id, dinners.photo_id, dinners.grounding_status, dinners.updated_by, dinners.created_at, dinners.updated_at, dinners.recipe_url, dinners.grounding_error, dinners.grounding_revision
|
|
FROM suggestions
|
|
JOIN dinners ON dinners.id = suggestions.dinner_id
|
|
WHERE suggestions.id = ?
|
|
`
|
|
|
|
type GetSuggestionRow struct {
|
|
Suggestion Suggestion
|
|
Dinner Dinner
|
|
}
|
|
|
|
// A suggestion with its dinner. Returns sql.ErrNoRows for an unknown id.
|
|
func (q *Queries) GetSuggestion(ctx context.Context, id string) (GetSuggestionRow, error) {
|
|
row := q.db.QueryRowContext(ctx, getSuggestion, id)
|
|
var i GetSuggestionRow
|
|
err := row.Scan(
|
|
&i.Suggestion.ID,
|
|
&i.Suggestion.DinnerID,
|
|
&i.Suggestion.BatchID,
|
|
&i.Suggestion.Name,
|
|
&i.Suggestion.Reason,
|
|
&i.Suggestion.CitedRegularIds,
|
|
&i.Suggestion.Status,
|
|
&i.Suggestion.DecidedAt,
|
|
&i.Suggestion.CreatedAt,
|
|
&i.Suggestion.UpdatedAt,
|
|
&i.Dinner.ID,
|
|
&i.Dinner.Name,
|
|
&i.Dinner.NormalizedName,
|
|
&i.Dinner.Status,
|
|
&i.Dinner.Origin,
|
|
&i.Dinner.Method,
|
|
&i.Dinner.MinutesTotal,
|
|
&i.Dinner.Difficulty,
|
|
&i.Dinner.Cuisine,
|
|
&i.Dinner.IsFavorite,
|
|
&i.Dinner.RecipeID,
|
|
&i.Dinner.PhotoID,
|
|
&i.Dinner.GroundingStatus,
|
|
&i.Dinner.UpdatedBy,
|
|
&i.Dinner.CreatedAt,
|
|
&i.Dinner.UpdatedAt,
|
|
&i.Dinner.RecipeUrl,
|
|
&i.Dinner.GroundingError,
|
|
&i.Dinner.GroundingRevision,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const getSuggestionBatchJobID = `-- name: GetSuggestionBatchJobID :one
|
|
SELECT id FROM jobs
|
|
WHERE kind = ?1
|
|
AND json_extract(payload, '$.batch_id') = ?2
|
|
ORDER BY id
|
|
LIMIT 1
|
|
`
|
|
|
|
type GetSuggestionBatchJobIDParams struct {
|
|
Kind string
|
|
BatchID string
|
|
}
|
|
|
|
// The id of the job that runs batch batch_id.
|
|
func (q *Queries) GetSuggestionBatchJobID(ctx context.Context, arg GetSuggestionBatchJobIDParams) (string, error) {
|
|
row := q.db.QueryRowContext(ctx, getSuggestionBatchJobID, arg.Kind, arg.BatchID)
|
|
var id string
|
|
err := row.Scan(&id)
|
|
return id, err
|
|
}
|
|
|
|
const listDeck = `-- name: ListDeck :many
|
|
SELECT suggestions.id, suggestions.dinner_id, suggestions.batch_id, suggestions.name, suggestions.reason, suggestions.cited_regular_ids, suggestions.status, suggestions.decided_at, suggestions.created_at, suggestions.updated_at, dinners.id, dinners.name, dinners.normalized_name, dinners.status, dinners.origin, dinners.method, dinners.minutes_total, dinners.difficulty, dinners.cuisine, dinners.is_favorite, dinners.recipe_id, dinners.photo_id, dinners.grounding_status, dinners.updated_by, dinners.created_at, dinners.updated_at, dinners.recipe_url, dinners.grounding_error, dinners.grounding_revision
|
|
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
|
|
`
|
|
|
|
type ListDeckRow struct {
|
|
Suggestion Suggestion
|
|
Dinner Dinner
|
|
}
|
|
|
|
// The pending suggestions whose dinner is still suggested, oldest first,
|
|
// with their dinners.
|
|
func (q *Queries) ListDeck(ctx context.Context) ([]ListDeckRow, error) {
|
|
rows, err := q.db.QueryContext(ctx, listDeck)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
var items []ListDeckRow
|
|
for rows.Next() {
|
|
var i ListDeckRow
|
|
if err := rows.Scan(
|
|
&i.Suggestion.ID,
|
|
&i.Suggestion.DinnerID,
|
|
&i.Suggestion.BatchID,
|
|
&i.Suggestion.Name,
|
|
&i.Suggestion.Reason,
|
|
&i.Suggestion.CitedRegularIds,
|
|
&i.Suggestion.Status,
|
|
&i.Suggestion.DecidedAt,
|
|
&i.Suggestion.CreatedAt,
|
|
&i.Suggestion.UpdatedAt,
|
|
&i.Dinner.ID,
|
|
&i.Dinner.Name,
|
|
&i.Dinner.NormalizedName,
|
|
&i.Dinner.Status,
|
|
&i.Dinner.Origin,
|
|
&i.Dinner.Method,
|
|
&i.Dinner.MinutesTotal,
|
|
&i.Dinner.Difficulty,
|
|
&i.Dinner.Cuisine,
|
|
&i.Dinner.IsFavorite,
|
|
&i.Dinner.RecipeID,
|
|
&i.Dinner.PhotoID,
|
|
&i.Dinner.GroundingStatus,
|
|
&i.Dinner.UpdatedBy,
|
|
&i.Dinner.CreatedAt,
|
|
&i.Dinner.UpdatedAt,
|
|
&i.Dinner.RecipeUrl,
|
|
&i.Dinner.GroundingError,
|
|
&i.Dinner.GroundingRevision,
|
|
); 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 listPassedNames = `-- name: ListPassedNames :many
|
|
SELECT name FROM (
|
|
SELECT name, decided_at, id
|
|
FROM suggestions
|
|
WHERE status = 'declined'
|
|
UNION ALL
|
|
SELECT name, decided_at, id
|
|
FROM rotation_reviews
|
|
WHERE decision = 'drop'
|
|
)
|
|
ORDER BY decided_at, id
|
|
`
|
|
|
|
// 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
|
|
// ("Not for us") had when dropped, which renaming it does not change.
|
|
func (q *Queries) ListPassedNames(ctx context.Context) ([]string, error) {
|
|
rows, err := q.db.QueryContext(ctx, listPassedNames)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
var items []string
|
|
for rows.Next() {
|
|
var name string
|
|
if err := rows.Scan(&name); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, name)
|
|
}
|
|
if err := rows.Close(); err != nil {
|
|
return nil, err
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
const moveSuggestedDinner = `-- name: MoveSuggestedDinner :execrows
|
|
UPDATE dinners
|
|
SET status = ?1, updated_by = ?2, updated_at = ?3
|
|
WHERE id = ?4 AND status = 'suggested'
|
|
`
|
|
|
|
type MoveSuggestedDinnerParams struct {
|
|
Status string
|
|
UpdatedBy sql.NullString
|
|
UpdatedAt string
|
|
ID string
|
|
}
|
|
|
|
// 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).
|
|
func (q *Queries) MoveSuggestedDinner(ctx context.Context, arg MoveSuggestedDinnerParams) (int64, error) {
|
|
result, err := q.db.ExecContext(ctx, moveSuggestedDinner,
|
|
arg.Status,
|
|
arg.UpdatedBy,
|
|
arg.UpdatedAt,
|
|
arg.ID,
|
|
)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
return result.RowsAffected()
|
|
}
|
|
|
|
const startSuggestionBatch = `-- name: StartSuggestionBatch :execrows
|
|
UPDATE suggestion_batches
|
|
SET status = 'running', updated_at = ?
|
|
WHERE id = ? AND status = 'pending'
|
|
`
|
|
|
|
type StartSuggestionBatchParams struct {
|
|
UpdatedAt string
|
|
ID string
|
|
}
|
|
|
|
// Marks a pending batch running; a batch in any other status is left as
|
|
// it is (0 rows).
|
|
func (q *Queries) StartSuggestionBatch(ctx context.Context, arg StartSuggestionBatchParams) (int64, error) {
|
|
result, err := q.db.ExecContext(ctx, startSuggestionBatch, arg.UpdatedAt, arg.ID)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
return result.RowsAffected()
|
|
}
|