Creating a Regular now enqueues a ground_dinner job and returns at once
with its grounding_job_id. The job grounds the dinner's recipe_url with
GroundURL, else its name with Ground, and sets grounding_status grounded
(recipe, photo, method, minutes, difficulty) or failed with a
grounding_error reason code (migration 0009). It writes only a dinner
still pending, so a fix made meanwhile wins.
GET /api/v1/dinners/{id}/recipe-candidates returns up to 3 ranked
candidates {candidate_id, title, site_name, photo_url, minutes} without
downloading photos or running the model. POST /api/v1/dinners/{id}/ground
takes exactly one of {candidate_id}, {recipe_url} or {manual: {...}} and
sets grounded or manual.
PhotoCache takes an ImageFetcher interface and recipestest provides fakes
so other packages can test grounding with fake search, fetch and model.
main wires the Grounder with normalize_recipe over the AI settings.
Maestro task: 0cbaa5bf-8be4-4cbd-aab2-891e7f1bc2b5
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
386 lines
8.9 KiB
Go
386 lines
8.9 KiB
Go
// Code generated by sqlc. DO NOT EDIT.
|
|
// versions:
|
|
// sqlc v1.31.1
|
|
// source: recipes.sql
|
|
|
|
package db
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
)
|
|
|
|
const createManualRecipe = `-- name: CreateManualRecipe :one
|
|
INSERT INTO recipes (
|
|
id, source_url, title, total_minutes, raw_jsonld, method,
|
|
minutes_total, fetched_at, created_at, updated_at
|
|
) VALUES (
|
|
?, ?, ?, ?, ?, ?,
|
|
?, ?, ?, ?
|
|
)
|
|
RETURNING id, source_url, site_name, title, total_minutes, prep_minutes, cook_minutes, yield, raw_jsonld, fetched_at, created_at, updated_at, photo_id, method, difficulty, minutes_total
|
|
`
|
|
|
|
type CreateManualRecipeParams struct {
|
|
ID string
|
|
SourceUrl string
|
|
Title string
|
|
TotalMinutes sql.NullInt64
|
|
RawJsonld string
|
|
Method sql.NullString
|
|
MinutesTotal sql.NullInt64
|
|
FetchedAt string
|
|
CreatedAt string
|
|
UpdatedAt string
|
|
}
|
|
|
|
// A recipe the household typed in. Its source_url is a mealquest:manual:
|
|
// URN rather than a page, so it never matches a fetched page.
|
|
func (q *Queries) CreateManualRecipe(ctx context.Context, arg CreateManualRecipeParams) (Recipe, error) {
|
|
row := q.db.QueryRowContext(ctx, createManualRecipe,
|
|
arg.ID,
|
|
arg.SourceUrl,
|
|
arg.Title,
|
|
arg.TotalMinutes,
|
|
arg.RawJsonld,
|
|
arg.Method,
|
|
arg.MinutesTotal,
|
|
arg.FetchedAt,
|
|
arg.CreatedAt,
|
|
arg.UpdatedAt,
|
|
)
|
|
var i Recipe
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.SourceUrl,
|
|
&i.SiteName,
|
|
&i.Title,
|
|
&i.TotalMinutes,
|
|
&i.PrepMinutes,
|
|
&i.CookMinutes,
|
|
&i.Yield,
|
|
&i.RawJsonld,
|
|
&i.FetchedAt,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
&i.PhotoID,
|
|
&i.Method,
|
|
&i.Difficulty,
|
|
&i.MinutesTotal,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const createRecipeIngredient = `-- name: CreateRecipeIngredient :exec
|
|
INSERT INTO recipe_ingredients (
|
|
id, recipe_id, position, raw_text, name, qty, unit, note,
|
|
created_at, updated_at
|
|
) VALUES (
|
|
?, ?, ?, ?, ?, ?, ?, ?,
|
|
?, ?
|
|
)
|
|
`
|
|
|
|
type CreateRecipeIngredientParams struct {
|
|
ID string
|
|
RecipeID string
|
|
Position int64
|
|
RawText string
|
|
Name sql.NullString
|
|
Qty sql.NullString
|
|
Unit sql.NullString
|
|
Note sql.NullString
|
|
CreatedAt string
|
|
UpdatedAt string
|
|
}
|
|
|
|
func (q *Queries) CreateRecipeIngredient(ctx context.Context, arg CreateRecipeIngredientParams) error {
|
|
_, err := q.db.ExecContext(ctx, createRecipeIngredient,
|
|
arg.ID,
|
|
arg.RecipeID,
|
|
arg.Position,
|
|
arg.RawText,
|
|
arg.Name,
|
|
arg.Qty,
|
|
arg.Unit,
|
|
arg.Note,
|
|
arg.CreatedAt,
|
|
arg.UpdatedAt,
|
|
)
|
|
return err
|
|
}
|
|
|
|
const createRecipeStep = `-- name: CreateRecipeStep :exec
|
|
INSERT INTO recipe_steps (
|
|
id, recipe_id, position, text, created_at, updated_at
|
|
) VALUES (
|
|
?, ?, ?, ?, ?, ?
|
|
)
|
|
`
|
|
|
|
type CreateRecipeStepParams struct {
|
|
ID string
|
|
RecipeID string
|
|
Position int64
|
|
Text string
|
|
CreatedAt string
|
|
UpdatedAt string
|
|
}
|
|
|
|
func (q *Queries) CreateRecipeStep(ctx context.Context, arg CreateRecipeStepParams) error {
|
|
_, err := q.db.ExecContext(ctx, createRecipeStep,
|
|
arg.ID,
|
|
arg.RecipeID,
|
|
arg.Position,
|
|
arg.Text,
|
|
arg.CreatedAt,
|
|
arg.UpdatedAt,
|
|
)
|
|
return err
|
|
}
|
|
|
|
const deleteRecipeIngredients = `-- name: DeleteRecipeIngredients :exec
|
|
DELETE FROM recipe_ingredients
|
|
WHERE recipe_id = ?
|
|
`
|
|
|
|
func (q *Queries) DeleteRecipeIngredients(ctx context.Context, recipeID string) error {
|
|
_, err := q.db.ExecContext(ctx, deleteRecipeIngredients, recipeID)
|
|
return err
|
|
}
|
|
|
|
const deleteRecipeSteps = `-- name: DeleteRecipeSteps :exec
|
|
DELETE FROM recipe_steps
|
|
WHERE recipe_id = ?
|
|
`
|
|
|
|
func (q *Queries) DeleteRecipeSteps(ctx context.Context, recipeID string) error {
|
|
_, err := q.db.ExecContext(ctx, deleteRecipeSteps, recipeID)
|
|
return err
|
|
}
|
|
|
|
const getRecipe = `-- name: GetRecipe :one
|
|
SELECT id, source_url, site_name, title, total_minutes, prep_minutes, cook_minutes, yield, raw_jsonld, fetched_at, created_at, updated_at, photo_id, method, difficulty, minutes_total FROM recipes
|
|
WHERE id = ?
|
|
`
|
|
|
|
func (q *Queries) GetRecipe(ctx context.Context, id string) (Recipe, error) {
|
|
row := q.db.QueryRowContext(ctx, getRecipe, id)
|
|
var i Recipe
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.SourceUrl,
|
|
&i.SiteName,
|
|
&i.Title,
|
|
&i.TotalMinutes,
|
|
&i.PrepMinutes,
|
|
&i.CookMinutes,
|
|
&i.Yield,
|
|
&i.RawJsonld,
|
|
&i.FetchedAt,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
&i.PhotoID,
|
|
&i.Method,
|
|
&i.Difficulty,
|
|
&i.MinutesTotal,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const getRecipeBySourceURL = `-- name: GetRecipeBySourceURL :one
|
|
SELECT id, source_url, site_name, title, total_minutes, prep_minutes, cook_minutes, yield, raw_jsonld, fetched_at, created_at, updated_at, photo_id, method, difficulty, minutes_total FROM recipes
|
|
WHERE source_url = ?
|
|
`
|
|
|
|
func (q *Queries) GetRecipeBySourceURL(ctx context.Context, sourceUrl string) (Recipe, error) {
|
|
row := q.db.QueryRowContext(ctx, getRecipeBySourceURL, sourceUrl)
|
|
var i Recipe
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.SourceUrl,
|
|
&i.SiteName,
|
|
&i.Title,
|
|
&i.TotalMinutes,
|
|
&i.PrepMinutes,
|
|
&i.CookMinutes,
|
|
&i.Yield,
|
|
&i.RawJsonld,
|
|
&i.FetchedAt,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
&i.PhotoID,
|
|
&i.Method,
|
|
&i.Difficulty,
|
|
&i.MinutesTotal,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const listRecipeIngredients = `-- name: ListRecipeIngredients :many
|
|
SELECT id, recipe_id, position, raw_text, name, qty, unit, note, created_at, updated_at FROM recipe_ingredients
|
|
WHERE recipe_id = ?
|
|
ORDER BY position, id
|
|
`
|
|
|
|
func (q *Queries) ListRecipeIngredients(ctx context.Context, recipeID string) ([]RecipeIngredient, error) {
|
|
rows, err := q.db.QueryContext(ctx, listRecipeIngredients, recipeID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
var items []RecipeIngredient
|
|
for rows.Next() {
|
|
var i RecipeIngredient
|
|
if err := rows.Scan(
|
|
&i.ID,
|
|
&i.RecipeID,
|
|
&i.Position,
|
|
&i.RawText,
|
|
&i.Name,
|
|
&i.Qty,
|
|
&i.Unit,
|
|
&i.Note,
|
|
&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 listRecipeSteps = `-- name: ListRecipeSteps :many
|
|
SELECT id, recipe_id, position, text, created_at, updated_at FROM recipe_steps
|
|
WHERE recipe_id = ?
|
|
ORDER BY position, id
|
|
`
|
|
|
|
func (q *Queries) ListRecipeSteps(ctx context.Context, recipeID string) ([]RecipeStep, error) {
|
|
rows, err := q.db.QueryContext(ctx, listRecipeSteps, recipeID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
var items []RecipeStep
|
|
for rows.Next() {
|
|
var i RecipeStep
|
|
if err := rows.Scan(
|
|
&i.ID,
|
|
&i.RecipeID,
|
|
&i.Position,
|
|
&i.Text,
|
|
&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 upsertRecipe = `-- name: UpsertRecipe :one
|
|
INSERT INTO recipes (
|
|
id, source_url, site_name, title, total_minutes, prep_minutes,
|
|
cook_minutes, yield, raw_jsonld, photo_id, method, difficulty,
|
|
minutes_total, fetched_at, created_at, updated_at
|
|
) VALUES (
|
|
?, ?, ?, ?, ?, ?,
|
|
?, ?, ?, ?, ?, ?,
|
|
?, ?, ?, ?
|
|
)
|
|
ON CONFLICT (source_url) DO UPDATE SET
|
|
site_name = excluded.site_name,
|
|
title = excluded.title,
|
|
total_minutes = excluded.total_minutes,
|
|
prep_minutes = excluded.prep_minutes,
|
|
cook_minutes = excluded.cook_minutes,
|
|
yield = excluded.yield,
|
|
raw_jsonld = excluded.raw_jsonld,
|
|
photo_id = excluded.photo_id,
|
|
method = excluded.method,
|
|
difficulty = excluded.difficulty,
|
|
minutes_total = excluded.minutes_total,
|
|
fetched_at = excluded.fetched_at,
|
|
updated_at = excluded.updated_at
|
|
RETURNING id, source_url, site_name, title, total_minutes, prep_minutes, cook_minutes, yield, raw_jsonld, fetched_at, created_at, updated_at, photo_id, method, difficulty, minutes_total
|
|
`
|
|
|
|
type UpsertRecipeParams struct {
|
|
ID string
|
|
SourceUrl string
|
|
SiteName sql.NullString
|
|
Title string
|
|
TotalMinutes sql.NullInt64
|
|
PrepMinutes sql.NullInt64
|
|
CookMinutes sql.NullInt64
|
|
Yield sql.NullString
|
|
RawJsonld string
|
|
PhotoID sql.NullString
|
|
Method sql.NullString
|
|
Difficulty sql.NullString
|
|
MinutesTotal sql.NullInt64
|
|
FetchedAt string
|
|
CreatedAt string
|
|
UpdatedAt string
|
|
}
|
|
|
|
// 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
|
|
// DeleteRecipeIngredients/DeleteRecipeSteps then the Create queries.
|
|
func (q *Queries) UpsertRecipe(ctx context.Context, arg UpsertRecipeParams) (Recipe, error) {
|
|
row := q.db.QueryRowContext(ctx, upsertRecipe,
|
|
arg.ID,
|
|
arg.SourceUrl,
|
|
arg.SiteName,
|
|
arg.Title,
|
|
arg.TotalMinutes,
|
|
arg.PrepMinutes,
|
|
arg.CookMinutes,
|
|
arg.Yield,
|
|
arg.RawJsonld,
|
|
arg.PhotoID,
|
|
arg.Method,
|
|
arg.Difficulty,
|
|
arg.MinutesTotal,
|
|
arg.FetchedAt,
|
|
arg.CreatedAt,
|
|
arg.UpdatedAt,
|
|
)
|
|
var i Recipe
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.SourceUrl,
|
|
&i.SiteName,
|
|
&i.Title,
|
|
&i.TotalMinutes,
|
|
&i.PrepMinutes,
|
|
&i.CookMinutes,
|
|
&i.Yield,
|
|
&i.RawJsonld,
|
|
&i.FetchedAt,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
&i.PhotoID,
|
|
&i.Method,
|
|
&i.Difficulty,
|
|
&i.MinutesTotal,
|
|
)
|
|
return i, err
|
|
}
|