Task 36c40130-a69a-46ed-a047-2cdc5ee2d2e0 (Feature 23, task 1 of 2). A backfill_dinner_photos job grounds, by name with the existing recipes.Grounder.Ground, every unarchived dinner whose grounding failed and that has no photo and no pasted recipe_url. Ground already tries the ranked candidates best first and keeps the first whose photo caches and whose ingredients normalize, reusing stored recipes and photos. Success records the recipe and photo as grounded; a failure leaves the dinner failed with the new grounding_error for the fix-recipe flow, without stopping its siblings. Writes are guarded on status, photo and grounding_revision, so grounded, manual, pending, renamed and archived dinners are never touched. POST /api/v1/settings/photo-backfill starts a full pass and answers 202 with the job id; the kind is service-only. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
883 lines
23 KiB
Go
883 lines
23 KiB
Go
// Code generated by sqlc. DO NOT EDIT.
|
|
// versions:
|
|
// sqlc v1.31.1
|
|
// source: dinners.sql
|
|
|
|
package db
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
)
|
|
|
|
const archiveDinner = `-- name: ArchiveDinner :one
|
|
UPDATE dinners
|
|
SET status = 'archived',
|
|
updated_by = ?,
|
|
updated_at = ?
|
|
WHERE id = ?
|
|
RETURNING id, name, normalized_name, status, origin, method, minutes_total, difficulty, cuisine, is_favorite, recipe_id, photo_id, grounding_status, updated_by, created_at, updated_at, recipe_url, grounding_error, grounding_revision
|
|
`
|
|
|
|
type ArchiveDinnerParams struct {
|
|
UpdatedBy sql.NullString
|
|
UpdatedAt string
|
|
ID string
|
|
}
|
|
|
|
// An unknown id returns sql.ErrNoRows.
|
|
func (q *Queries) ArchiveDinner(ctx context.Context, arg ArchiveDinnerParams) (Dinner, error) {
|
|
row := q.db.QueryRowContext(ctx, archiveDinner, arg.UpdatedBy, arg.UpdatedAt, arg.ID)
|
|
var i Dinner
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.Name,
|
|
&i.NormalizedName,
|
|
&i.Status,
|
|
&i.Origin,
|
|
&i.Method,
|
|
&i.MinutesTotal,
|
|
&i.Difficulty,
|
|
&i.Cuisine,
|
|
&i.IsFavorite,
|
|
&i.RecipeID,
|
|
&i.PhotoID,
|
|
&i.GroundingStatus,
|
|
&i.UpdatedBy,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
&i.RecipeUrl,
|
|
&i.GroundingError,
|
|
&i.GroundingRevision,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const countRegularDinners = `-- name: CountRegularDinners :one
|
|
SELECT COUNT(*) FROM dinners
|
|
WHERE status = 'regular'
|
|
`
|
|
|
|
// How many Regulars the household has; archived dinners are not counted.
|
|
func (q *Queries) CountRegularDinners(ctx context.Context) (int64, error) {
|
|
row := q.db.QueryRowContext(ctx, countRegularDinners)
|
|
var count int64
|
|
err := row.Scan(&count)
|
|
return count, err
|
|
}
|
|
|
|
const createDinner = `-- name: CreateDinner :one
|
|
INSERT INTO dinners (
|
|
id, name, normalized_name, status, origin, method, minutes_total,
|
|
difficulty, cuisine, is_favorite, recipe_id, recipe_url, photo_id,
|
|
grounding_status, updated_by, created_at, updated_at
|
|
) VALUES (
|
|
?, ?, ?, ?, ?, ?, ?,
|
|
?, ?, ?, ?, ?, ?,
|
|
?, ?, ?, ?
|
|
)
|
|
RETURNING id, name, normalized_name, status, origin, method, minutes_total, difficulty, cuisine, is_favorite, recipe_id, photo_id, grounding_status, updated_by, created_at, updated_at, recipe_url, grounding_error, grounding_revision
|
|
`
|
|
|
|
type CreateDinnerParams struct {
|
|
ID string
|
|
Name string
|
|
NormalizedName string
|
|
Status string
|
|
Origin string
|
|
Method sql.NullString
|
|
MinutesTotal sql.NullInt64
|
|
Difficulty sql.NullString
|
|
Cuisine sql.NullString
|
|
IsFavorite int64
|
|
RecipeID sql.NullString
|
|
RecipeUrl sql.NullString
|
|
PhotoID sql.NullString
|
|
GroundingStatus string
|
|
UpdatedBy sql.NullString
|
|
CreatedAt string
|
|
UpdatedAt string
|
|
}
|
|
|
|
func (q *Queries) CreateDinner(ctx context.Context, arg CreateDinnerParams) (Dinner, error) {
|
|
row := q.db.QueryRowContext(ctx, createDinner,
|
|
arg.ID,
|
|
arg.Name,
|
|
arg.NormalizedName,
|
|
arg.Status,
|
|
arg.Origin,
|
|
arg.Method,
|
|
arg.MinutesTotal,
|
|
arg.Difficulty,
|
|
arg.Cuisine,
|
|
arg.IsFavorite,
|
|
arg.RecipeID,
|
|
arg.RecipeUrl,
|
|
arg.PhotoID,
|
|
arg.GroundingStatus,
|
|
arg.UpdatedBy,
|
|
arg.CreatedAt,
|
|
arg.UpdatedAt,
|
|
)
|
|
var i Dinner
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.Name,
|
|
&i.NormalizedName,
|
|
&i.Status,
|
|
&i.Origin,
|
|
&i.Method,
|
|
&i.MinutesTotal,
|
|
&i.Difficulty,
|
|
&i.Cuisine,
|
|
&i.IsFavorite,
|
|
&i.RecipeID,
|
|
&i.PhotoID,
|
|
&i.GroundingStatus,
|
|
&i.UpdatedBy,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
&i.RecipeUrl,
|
|
&i.GroundingError,
|
|
&i.GroundingRevision,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const failOrphanedPendingDinnerGroundings = `-- name: FailOrphanedPendingDinnerGroundings :execrows
|
|
UPDATE dinners
|
|
SET grounding_status = 'failed',
|
|
grounding_error = ?1,
|
|
updated_at = ?2
|
|
WHERE grounding_status = 'pending'
|
|
AND status <> 'archived'
|
|
AND NOT EXISTS (
|
|
SELECT 1 FROM jobs
|
|
WHERE jobs.kind = ?3
|
|
AND jobs.status IN ('queued', 'running')
|
|
AND json_extract(jobs.payload, '$.dinner_id') = dinners.id
|
|
AND json_extract(jobs.payload, '$.revision') = dinners.grounding_revision
|
|
)
|
|
`
|
|
|
|
type FailOrphanedPendingDinnerGroundingsParams struct {
|
|
GroundingError sql.NullString
|
|
UpdatedAt string
|
|
Kind string
|
|
}
|
|
|
|
// 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
|
|
// archived dinner is left as it is; archiving stopped its grounding. Run at
|
|
// boot, after the job runner fails the jobs a previous process left running.
|
|
func (q *Queries) FailOrphanedPendingDinnerGroundings(ctx context.Context, arg FailOrphanedPendingDinnerGroundingsParams) (int64, error) {
|
|
result, err := q.db.ExecContext(ctx, failOrphanedPendingDinnerGroundings, arg.GroundingError, arg.UpdatedAt, arg.Kind)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
return result.RowsAffected()
|
|
}
|
|
|
|
const failPendingDinnerGrounding = `-- name: FailPendingDinnerGrounding :execrows
|
|
UPDATE dinners
|
|
SET grounding_status = 'failed',
|
|
grounding_error = ?,
|
|
updated_at = ?
|
|
WHERE id = ?
|
|
AND status <> 'archived'
|
|
AND grounding_status = 'pending'
|
|
AND grounding_revision = ?
|
|
`
|
|
|
|
type FailPendingDinnerGroundingParams struct {
|
|
GroundingError sql.NullString
|
|
UpdatedAt string
|
|
ID string
|
|
GroundingRevision int64
|
|
}
|
|
|
|
// Records why the background grounding of an unarchived dinner still
|
|
// pending at the grounding_revision it was grounded for failed; a dinner
|
|
// grounded some other way, renamed or archived meanwhile is left as it is
|
|
// (0 rows).
|
|
func (q *Queries) FailPendingDinnerGrounding(ctx context.Context, arg FailPendingDinnerGroundingParams) (int64, error) {
|
|
result, err := q.db.ExecContext(ctx, failPendingDinnerGrounding,
|
|
arg.GroundingError,
|
|
arg.UpdatedAt,
|
|
arg.ID,
|
|
arg.GroundingRevision,
|
|
)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
return result.RowsAffected()
|
|
}
|
|
|
|
const getDinner = `-- name: GetDinner :one
|
|
SELECT id, name, normalized_name, status, origin, method, minutes_total, difficulty, cuisine, is_favorite, recipe_id, photo_id, grounding_status, updated_by, created_at, updated_at, recipe_url, grounding_error, grounding_revision FROM dinners
|
|
WHERE id = ?
|
|
`
|
|
|
|
func (q *Queries) GetDinner(ctx context.Context, id string) (Dinner, error) {
|
|
row := q.db.QueryRowContext(ctx, getDinner, id)
|
|
var i Dinner
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.Name,
|
|
&i.NormalizedName,
|
|
&i.Status,
|
|
&i.Origin,
|
|
&i.Method,
|
|
&i.MinutesTotal,
|
|
&i.Difficulty,
|
|
&i.Cuisine,
|
|
&i.IsFavorite,
|
|
&i.RecipeID,
|
|
&i.PhotoID,
|
|
&i.GroundingStatus,
|
|
&i.UpdatedBy,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
&i.RecipeUrl,
|
|
&i.GroundingError,
|
|
&i.GroundingRevision,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const groundDinner = `-- name: GroundDinner :one
|
|
UPDATE dinners
|
|
SET recipe_id = ?1,
|
|
recipe_url = NULL,
|
|
photo_id = ?2,
|
|
method = CASE WHEN method IS ?3
|
|
THEN ?4 ELSE method END,
|
|
minutes_total = CASE WHEN minutes_total IS ?5
|
|
THEN ?6 ELSE minutes_total END,
|
|
difficulty = CASE WHEN difficulty IS ?7
|
|
THEN ?8 ELSE difficulty END,
|
|
grounding_status = ?9,
|
|
grounding_error = NULL,
|
|
updated_by = ?10,
|
|
updated_at = ?11
|
|
WHERE id = ?12
|
|
AND status <> 'archived'
|
|
AND name = ?13
|
|
AND normalized_name = ?14
|
|
AND grounding_status = ?15
|
|
AND grounding_revision = ?16
|
|
AND recipe_id IS ?17
|
|
RETURNING id, name, normalized_name, status, origin, method, minutes_total, difficulty, cuisine, is_favorite, recipe_id, photo_id, grounding_status, updated_by, created_at, updated_at, recipe_url, grounding_error, grounding_revision
|
|
`
|
|
|
|
type GroundDinnerParams struct {
|
|
RecipeID sql.NullString
|
|
PhotoID sql.NullString
|
|
ObservedMethod sql.NullString
|
|
Method sql.NullString
|
|
ObservedMinutesTotal sql.NullInt64
|
|
MinutesTotal sql.NullInt64
|
|
ObservedDifficulty sql.NullString
|
|
Difficulty sql.NullString
|
|
GroundingStatus string
|
|
UpdatedBy sql.NullString
|
|
UpdatedAt string
|
|
ID string
|
|
ObservedName string
|
|
ObservedNormalizedName string
|
|
ObservedGroundingStatus string
|
|
ObservedGroundingRevision int64
|
|
ObservedRecipeID sql.NullString
|
|
}
|
|
|
|
// Records a dinner's grounding by hand (grounded or manual) and clears any
|
|
// grounding_error and pasted recipe_url: the recipe chosen replaces the
|
|
// link. It writes only an unarchived dinner still as it was observed
|
|
// before grounding (same name, normalized_name, grounding_status,
|
|
// grounding_revision and recipe_id), so a dinner renamed, archived or
|
|
// grounded some other way meanwhile is left as it is. That dinner, or an
|
|
// unknown id, returns sql.ErrNoRows. A method, minutes_total or difficulty
|
|
// no longer as observed is kept: a member edited it while it was grounding.
|
|
func (q *Queries) GroundDinner(ctx context.Context, arg GroundDinnerParams) (Dinner, error) {
|
|
row := q.db.QueryRowContext(ctx, groundDinner,
|
|
arg.RecipeID,
|
|
arg.PhotoID,
|
|
arg.ObservedMethod,
|
|
arg.Method,
|
|
arg.ObservedMinutesTotal,
|
|
arg.MinutesTotal,
|
|
arg.ObservedDifficulty,
|
|
arg.Difficulty,
|
|
arg.GroundingStatus,
|
|
arg.UpdatedBy,
|
|
arg.UpdatedAt,
|
|
arg.ID,
|
|
arg.ObservedName,
|
|
arg.ObservedNormalizedName,
|
|
arg.ObservedGroundingStatus,
|
|
arg.ObservedGroundingRevision,
|
|
arg.ObservedRecipeID,
|
|
)
|
|
var i Dinner
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.Name,
|
|
&i.NormalizedName,
|
|
&i.Status,
|
|
&i.Origin,
|
|
&i.Method,
|
|
&i.MinutesTotal,
|
|
&i.Difficulty,
|
|
&i.Cuisine,
|
|
&i.IsFavorite,
|
|
&i.RecipeID,
|
|
&i.PhotoID,
|
|
&i.GroundingStatus,
|
|
&i.UpdatedBy,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
&i.RecipeUrl,
|
|
&i.GroundingError,
|
|
&i.GroundingRevision,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const groundFailedDinner = `-- name: GroundFailedDinner :execrows
|
|
UPDATE dinners
|
|
SET recipe_id = ?1,
|
|
photo_id = ?2,
|
|
method = COALESCE(method, ?3),
|
|
minutes_total = COALESCE(minutes_total, ?4),
|
|
difficulty = COALESCE(difficulty, ?5),
|
|
grounding_status = 'grounded',
|
|
grounding_error = NULL,
|
|
updated_at = ?6
|
|
WHERE id = ?7
|
|
AND status <> 'archived'
|
|
AND grounding_status = 'failed'
|
|
AND photo_id IS NULL
|
|
AND recipe_url IS NULL
|
|
AND grounding_revision = ?8
|
|
`
|
|
|
|
type GroundFailedDinnerParams struct {
|
|
RecipeID sql.NullString
|
|
PhotoID sql.NullString
|
|
Method sql.NullString
|
|
MinutesTotal sql.NullInt64
|
|
Difficulty sql.NullString
|
|
UpdatedAt string
|
|
ID string
|
|
GroundingRevision int64
|
|
}
|
|
|
|
// Records a photo backfill's grounding of a dinner it listed, while the
|
|
// dinner is still unarchived, failed, photoless, without a recipe_url and at
|
|
// the grounding_revision it was listed at; a dinner grounded by hand,
|
|
// renamed or archived meanwhile is left as it is (0 rows). Like
|
|
// GroundPendingDinner it keeps a method, minutes_total or difficulty a
|
|
// member already set.
|
|
func (q *Queries) GroundFailedDinner(ctx context.Context, arg GroundFailedDinnerParams) (int64, error) {
|
|
result, err := q.db.ExecContext(ctx, groundFailedDinner,
|
|
arg.RecipeID,
|
|
arg.PhotoID,
|
|
arg.Method,
|
|
arg.MinutesTotal,
|
|
arg.Difficulty,
|
|
arg.UpdatedAt,
|
|
arg.ID,
|
|
arg.GroundingRevision,
|
|
)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
return result.RowsAffected()
|
|
}
|
|
|
|
const groundPendingDinner = `-- name: GroundPendingDinner :execrows
|
|
UPDATE dinners
|
|
SET recipe_id = ?1,
|
|
recipe_url = NULL,
|
|
photo_id = ?2,
|
|
method = COALESCE(method, ?3),
|
|
minutes_total = COALESCE(minutes_total, ?4),
|
|
difficulty = COALESCE(difficulty, ?5),
|
|
grounding_status = 'grounded',
|
|
grounding_error = NULL,
|
|
updated_at = ?6
|
|
WHERE id = ?7
|
|
AND status <> 'archived'
|
|
AND grounding_status = 'pending'
|
|
AND grounding_revision = ?8
|
|
`
|
|
|
|
type GroundPendingDinnerParams struct {
|
|
RecipeID sql.NullString
|
|
PhotoID sql.NullString
|
|
Method sql.NullString
|
|
MinutesTotal sql.NullInt64
|
|
Difficulty sql.NullString
|
|
UpdatedAt string
|
|
ID string
|
|
GroundingRevision int64
|
|
}
|
|
|
|
// Records the background grounding of an unarchived dinner still pending at
|
|
// the grounding_revision it was grounded for; a dinner grounded some other
|
|
// way, renamed or archived meanwhile is left as it is (0 rows). A method,
|
|
// minutes_total or difficulty already set is kept: a pending dinner has
|
|
// them only when a member edited them while it was grounding. Like
|
|
// GroundDinner it clears the pasted recipe_url: the recipe replaces the link.
|
|
func (q *Queries) GroundPendingDinner(ctx context.Context, arg GroundPendingDinnerParams) (int64, error) {
|
|
result, err := q.db.ExecContext(ctx, groundPendingDinner,
|
|
arg.RecipeID,
|
|
arg.PhotoID,
|
|
arg.Method,
|
|
arg.MinutesTotal,
|
|
arg.Difficulty,
|
|
arg.UpdatedAt,
|
|
arg.ID,
|
|
arg.GroundingRevision,
|
|
)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
return result.RowsAffected()
|
|
}
|
|
|
|
const listDinnersByStatus = `-- name: ListDinnersByStatus :many
|
|
SELECT id, name, normalized_name, status, origin, method, minutes_total, difficulty, cuisine, is_favorite, recipe_id, photo_id, grounding_status, updated_by, created_at, updated_at, recipe_url, grounding_error, grounding_revision FROM dinners
|
|
WHERE status = ?
|
|
ORDER BY normalized_name, id
|
|
`
|
|
|
|
func (q *Queries) ListDinnersByStatus(ctx context.Context, status string) ([]Dinner, error) {
|
|
rows, err := q.db.QueryContext(ctx, listDinnersByStatus, status)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
var items []Dinner
|
|
for rows.Next() {
|
|
var i Dinner
|
|
if err := rows.Scan(
|
|
&i.ID,
|
|
&i.Name,
|
|
&i.NormalizedName,
|
|
&i.Status,
|
|
&i.Origin,
|
|
&i.Method,
|
|
&i.MinutesTotal,
|
|
&i.Difficulty,
|
|
&i.Cuisine,
|
|
&i.IsFavorite,
|
|
&i.RecipeID,
|
|
&i.PhotoID,
|
|
&i.GroundingStatus,
|
|
&i.UpdatedBy,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
&i.RecipeUrl,
|
|
&i.GroundingError,
|
|
&i.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 listPhotoBackfillDinners = `-- name: ListPhotoBackfillDinners :many
|
|
SELECT id, name, normalized_name, status, origin, method, minutes_total, difficulty, cuisine, is_favorite, recipe_id, photo_id, grounding_status, updated_by, created_at, updated_at, recipe_url, grounding_error, grounding_revision FROM dinners
|
|
WHERE status <> 'archived'
|
|
AND grounding_status = 'failed'
|
|
AND photo_id IS NULL
|
|
AND recipe_url IS NULL
|
|
AND id = COALESCE(?1, id)
|
|
ORDER BY normalized_name, id
|
|
`
|
|
|
|
// The dinners a photo backfill grounds by name: unarchived, failed, with no
|
|
// photo and no pasted recipe_url (the household's own link is never
|
|
// replaced by a search), ordered by normalized name. A non-NULL id narrows
|
|
// the list to that dinner. Grounded, manual and pending dinners are never
|
|
// listed: they have a recipe, or a ground_dinner job grounding them.
|
|
func (q *Queries) ListPhotoBackfillDinners(ctx context.Context, id sql.NullString) ([]Dinner, error) {
|
|
rows, err := q.db.QueryContext(ctx, listPhotoBackfillDinners, id)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
var items []Dinner
|
|
for rows.Next() {
|
|
var i Dinner
|
|
if err := rows.Scan(
|
|
&i.ID,
|
|
&i.Name,
|
|
&i.NormalizedName,
|
|
&i.Status,
|
|
&i.Origin,
|
|
&i.Method,
|
|
&i.MinutesTotal,
|
|
&i.Difficulty,
|
|
&i.Cuisine,
|
|
&i.IsFavorite,
|
|
&i.RecipeID,
|
|
&i.PhotoID,
|
|
&i.GroundingStatus,
|
|
&i.UpdatedBy,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
&i.RecipeUrl,
|
|
&i.GroundingError,
|
|
&i.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 listUnarchivedDinners = `-- name: ListUnarchivedDinners :many
|
|
SELECT 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, recipes.source_url AS recipe_source_url
|
|
FROM dinners
|
|
LEFT JOIN recipes ON recipes.id = dinners.recipe_id
|
|
WHERE dinners.status <> 'archived'
|
|
ORDER BY dinners.id
|
|
`
|
|
|
|
type ListUnarchivedDinnersRow struct {
|
|
Dinner Dinner
|
|
RecipeSourceUrl sql.NullString
|
|
}
|
|
|
|
// Every dinner a new or renamed dinner must not repeat, with the
|
|
// source_url of the recipe it is grounded in (NULL while it has none).
|
|
func (q *Queries) ListUnarchivedDinners(ctx context.Context) ([]ListUnarchivedDinnersRow, error) {
|
|
rows, err := q.db.QueryContext(ctx, listUnarchivedDinners)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
var items []ListUnarchivedDinnersRow
|
|
for rows.Next() {
|
|
var i ListUnarchivedDinnersRow
|
|
if err := rows.Scan(
|
|
&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,
|
|
&i.RecipeSourceUrl,
|
|
); 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 listUnplannedQuestLogDinners = `-- name: ListUnplannedQuestLogDinners :many
|
|
SELECT id, name, normalized_name, status, origin, method, minutes_total, difficulty, cuisine, is_favorite, recipe_id, photo_id, grounding_status, updated_by, created_at, updated_at, recipe_url, grounding_error, grounding_revision FROM dinners
|
|
WHERE status = 'quest_log'
|
|
AND NOT EXISTS (
|
|
SELECT 1 FROM plan_days
|
|
JOIN week_plans ON week_plans.id = plan_days.plan_id
|
|
WHERE plan_days.dinner_id = dinners.id
|
|
AND week_plans.week_start >= ?1
|
|
)
|
|
ORDER BY normalized_name, id
|
|
`
|
|
|
|
// The Quest log: dinners with status quest_log that no plan for the week
|
|
// starting from_week_start or later has on any day.
|
|
func (q *Queries) ListUnplannedQuestLogDinners(ctx context.Context, fromWeekStart string) ([]Dinner, error) {
|
|
rows, err := q.db.QueryContext(ctx, listUnplannedQuestLogDinners, fromWeekStart)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
var items []Dinner
|
|
for rows.Next() {
|
|
var i Dinner
|
|
if err := rows.Scan(
|
|
&i.ID,
|
|
&i.Name,
|
|
&i.NormalizedName,
|
|
&i.Status,
|
|
&i.Origin,
|
|
&i.Method,
|
|
&i.MinutesTotal,
|
|
&i.Difficulty,
|
|
&i.Cuisine,
|
|
&i.IsFavorite,
|
|
&i.RecipeID,
|
|
&i.PhotoID,
|
|
&i.GroundingStatus,
|
|
&i.UpdatedBy,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
&i.RecipeUrl,
|
|
&i.GroundingError,
|
|
&i.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 refailDinnerGrounding = `-- name: RefailDinnerGrounding :execrows
|
|
UPDATE dinners
|
|
SET grounding_error = ?1,
|
|
updated_at = ?2
|
|
WHERE id = ?3
|
|
AND status <> 'archived'
|
|
AND grounding_status = 'failed'
|
|
AND photo_id IS NULL
|
|
AND recipe_url IS NULL
|
|
AND grounding_revision = ?4
|
|
`
|
|
|
|
type RefailDinnerGroundingParams struct {
|
|
GroundingError sql.NullString
|
|
UpdatedAt string
|
|
ID string
|
|
GroundingRevision int64
|
|
}
|
|
|
|
// Records why a photo backfill could not ground a dinner it listed, under
|
|
// the same conditions as GroundFailedDinner (0 rows otherwise).
|
|
func (q *Queries) RefailDinnerGrounding(ctx context.Context, arg RefailDinnerGroundingParams) (int64, error) {
|
|
result, err := q.db.ExecContext(ctx, refailDinnerGrounding,
|
|
arg.GroundingError,
|
|
arg.UpdatedAt,
|
|
arg.ID,
|
|
arg.GroundingRevision,
|
|
)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
return result.RowsAffected()
|
|
}
|
|
|
|
const setDinnerFavorite = `-- name: SetDinnerFavorite :one
|
|
UPDATE dinners
|
|
SET is_favorite = ?,
|
|
updated_by = ?,
|
|
updated_at = ?
|
|
WHERE id = ?
|
|
RETURNING id, name, normalized_name, status, origin, method, minutes_total, difficulty, cuisine, is_favorite, recipe_id, photo_id, grounding_status, updated_by, created_at, updated_at, recipe_url, grounding_error, grounding_revision
|
|
`
|
|
|
|
type SetDinnerFavoriteParams struct {
|
|
IsFavorite int64
|
|
UpdatedBy sql.NullString
|
|
UpdatedAt string
|
|
ID string
|
|
}
|
|
|
|
// An unknown id returns sql.ErrNoRows.
|
|
func (q *Queries) SetDinnerFavorite(ctx context.Context, arg SetDinnerFavoriteParams) (Dinner, error) {
|
|
row := q.db.QueryRowContext(ctx, setDinnerFavorite,
|
|
arg.IsFavorite,
|
|
arg.UpdatedBy,
|
|
arg.UpdatedAt,
|
|
arg.ID,
|
|
)
|
|
var i Dinner
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.Name,
|
|
&i.NormalizedName,
|
|
&i.Status,
|
|
&i.Origin,
|
|
&i.Method,
|
|
&i.MinutesTotal,
|
|
&i.Difficulty,
|
|
&i.Cuisine,
|
|
&i.IsFavorite,
|
|
&i.RecipeID,
|
|
&i.PhotoID,
|
|
&i.GroundingStatus,
|
|
&i.UpdatedBy,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
&i.RecipeUrl,
|
|
&i.GroundingError,
|
|
&i.GroundingRevision,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const toggleDinnerFavorite = `-- name: ToggleDinnerFavorite :one
|
|
UPDATE dinners
|
|
SET is_favorite = 1 - is_favorite,
|
|
updated_by = ?,
|
|
updated_at = ?
|
|
WHERE id = ?
|
|
RETURNING id, name, normalized_name, status, origin, method, minutes_total, difficulty, cuisine, is_favorite, recipe_id, photo_id, grounding_status, updated_by, created_at, updated_at, recipe_url, grounding_error, grounding_revision
|
|
`
|
|
|
|
type ToggleDinnerFavoriteParams struct {
|
|
UpdatedBy sql.NullString
|
|
UpdatedAt string
|
|
ID string
|
|
}
|
|
|
|
// An unknown id returns sql.ErrNoRows.
|
|
func (q *Queries) ToggleDinnerFavorite(ctx context.Context, arg ToggleDinnerFavoriteParams) (Dinner, error) {
|
|
row := q.db.QueryRowContext(ctx, toggleDinnerFavorite, arg.UpdatedBy, arg.UpdatedAt, arg.ID)
|
|
var i Dinner
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.Name,
|
|
&i.NormalizedName,
|
|
&i.Status,
|
|
&i.Origin,
|
|
&i.Method,
|
|
&i.MinutesTotal,
|
|
&i.Difficulty,
|
|
&i.Cuisine,
|
|
&i.IsFavorite,
|
|
&i.RecipeID,
|
|
&i.PhotoID,
|
|
&i.GroundingStatus,
|
|
&i.UpdatedBy,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
&i.RecipeUrl,
|
|
&i.GroundingError,
|
|
&i.GroundingRevision,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const updateDinner = `-- name: UpdateDinner :one
|
|
UPDATE dinners
|
|
SET name = ?,
|
|
normalized_name = ?,
|
|
status = ?,
|
|
origin = ?,
|
|
method = ?,
|
|
minutes_total = ?,
|
|
difficulty = ?,
|
|
cuisine = ?,
|
|
recipe_id = ?,
|
|
recipe_url = ?,
|
|
photo_id = ?,
|
|
grounding_status = ?,
|
|
grounding_revision = ?,
|
|
updated_by = ?,
|
|
updated_at = ?
|
|
WHERE id = ?
|
|
RETURNING id, name, normalized_name, status, origin, method, minutes_total, difficulty, cuisine, is_favorite, recipe_id, photo_id, grounding_status, updated_by, created_at, updated_at, recipe_url, grounding_error, grounding_revision
|
|
`
|
|
|
|
type UpdateDinnerParams struct {
|
|
Name string
|
|
NormalizedName string
|
|
Status string
|
|
Origin string
|
|
Method sql.NullString
|
|
MinutesTotal sql.NullInt64
|
|
Difficulty sql.NullString
|
|
Cuisine sql.NullString
|
|
RecipeID sql.NullString
|
|
RecipeUrl sql.NullString
|
|
PhotoID sql.NullString
|
|
GroundingStatus string
|
|
GroundingRevision int64
|
|
UpdatedBy sql.NullString
|
|
UpdatedAt string
|
|
ID string
|
|
}
|
|
|
|
// An unknown id returns sql.ErrNoRows.
|
|
func (q *Queries) UpdateDinner(ctx context.Context, arg UpdateDinnerParams) (Dinner, error) {
|
|
row := q.db.QueryRowContext(ctx, updateDinner,
|
|
arg.Name,
|
|
arg.NormalizedName,
|
|
arg.Status,
|
|
arg.Origin,
|
|
arg.Method,
|
|
arg.MinutesTotal,
|
|
arg.Difficulty,
|
|
arg.Cuisine,
|
|
arg.RecipeID,
|
|
arg.RecipeUrl,
|
|
arg.PhotoID,
|
|
arg.GroundingStatus,
|
|
arg.GroundingRevision,
|
|
arg.UpdatedBy,
|
|
arg.UpdatedAt,
|
|
arg.ID,
|
|
)
|
|
var i Dinner
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.Name,
|
|
&i.NormalizedName,
|
|
&i.Status,
|
|
&i.Origin,
|
|
&i.Method,
|
|
&i.MinutesTotal,
|
|
&i.Difficulty,
|
|
&i.Cuisine,
|
|
&i.IsFavorite,
|
|
&i.RecipeID,
|
|
&i.PhotoID,
|
|
&i.GroundingStatus,
|
|
&i.UpdatedBy,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
&i.RecipeUrl,
|
|
&i.GroundingError,
|
|
&i.GroundingRevision,
|
|
)
|
|
return i, err
|
|
}
|