Grounding pipeline and recipe-search settings API
recipes.Grounder ties search, fetch/extract, Rank, the photo cache and
normalize_recipe together. Ground(ctx, dishName, query) reuses a stored
source_url among the top candidates without fetching, otherwise tries the
ranked recipes best first and stores the photo, recipe, ingredients and
steps in one transaction, returning Grounded{recipe_id, photo_id, method,
minutes_total, difficulty, source_url, site_name} or a typed
*ErrUngroundable listing every reason. GroundURL grounds a pasted URL the
same way; GroundMany grounds items 3 at a time with per-item results.
PhotoCache is split into DownloadPhoto (fetch, decode, size check, no
writes) and CachePhoto(ctx, tx, photo), which records the row in the
caller's transaction; RemovePhotoFiles cleans up after a rollback.
Migration 0007 adds photo_id, method, difficulty and minutes_total to
recipes, and api_key_last4 to recipe_search_settings. SearXNG is now
called at recipe_search_settings.base_url.
GET/PATCH /api/v1/settings/recipe-search (Brave key sealed with
secretbox, only api_key_set and api_key_last4 returned) and POST
/api/v1/settings/recipe-search/test (searches "chicken tikka masala",
returns the count, 409 not_configured or 502 search_failed).
Maestro task 53afd59c-6c35-4af7-904c-6c167d1ad69d.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+11
-6
@@ -181,6 +181,10 @@ type Recipe struct {
|
||||
FetchedAt string
|
||||
CreatedAt string
|
||||
UpdatedAt string
|
||||
PhotoID sql.NullString
|
||||
Method sql.NullString
|
||||
Difficulty sql.NullString
|
||||
MinutesTotal sql.NullInt64
|
||||
}
|
||||
|
||||
type RecipeIngredient struct {
|
||||
@@ -197,12 +201,13 @@ type RecipeIngredient struct {
|
||||
}
|
||||
|
||||
type RecipeSearchSetting struct {
|
||||
ID int64
|
||||
Provider string
|
||||
BaseUrl sql.NullString
|
||||
ApiKeyCt []byte
|
||||
CreatedAt string
|
||||
UpdatedAt string
|
||||
ID int64
|
||||
Provider string
|
||||
BaseUrl sql.NullString
|
||||
ApiKeyCt []byte
|
||||
CreatedAt string
|
||||
UpdatedAt string
|
||||
ApiKeyLast4 sql.NullString
|
||||
}
|
||||
|
||||
type RecipeStep struct {
|
||||
|
||||
@@ -99,7 +99,7 @@ func (q *Queries) DeleteRecipeSteps(ctx context.Context, recipeID string) error
|
||||
}
|
||||
|
||||
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 FROM recipes
|
||||
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 = ?
|
||||
`
|
||||
|
||||
@@ -119,12 +119,16 @@ func (q *Queries) GetRecipe(ctx context.Context, id string) (Recipe, error) {
|
||||
&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 FROM recipes
|
||||
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 = ?
|
||||
`
|
||||
|
||||
@@ -144,6 +148,10 @@ func (q *Queries) GetRecipeBySourceURL(ctx context.Context, sourceUrl string) (R
|
||||
&i.FetchedAt,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
&i.PhotoID,
|
||||
&i.Method,
|
||||
&i.Difficulty,
|
||||
&i.MinutesTotal,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
@@ -227,10 +235,12 @@ func (q *Queries) ListRecipeSteps(ctx context.Context, recipeID string) ([]Recip
|
||||
const upsertRecipe = `-- name: UpsertRecipe :one
|
||||
INSERT INTO recipes (
|
||||
id, source_url, site_name, title, total_minutes, prep_minutes,
|
||||
cook_minutes, yield, raw_jsonld, fetched_at, created_at, updated_at
|
||||
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,
|
||||
@@ -240,9 +250,13 @@ ON CONFLICT (source_url) DO UPDATE SET
|
||||
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
|
||||
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 {
|
||||
@@ -255,6 +269,10 @@ type UpsertRecipeParams struct {
|
||||
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
|
||||
@@ -275,6 +293,10 @@ func (q *Queries) UpsertRecipe(ctx context.Context, arg UpsertRecipeParams) (Rec
|
||||
arg.CookMinutes,
|
||||
arg.Yield,
|
||||
arg.RawJsonld,
|
||||
arg.PhotoID,
|
||||
arg.Method,
|
||||
arg.Difficulty,
|
||||
arg.MinutesTotal,
|
||||
arg.FetchedAt,
|
||||
arg.CreatedAt,
|
||||
arg.UpdatedAt,
|
||||
@@ -293,6 +315,10 @@ func (q *Queries) UpsertRecipe(ctx context.Context, arg UpsertRecipeParams) (Rec
|
||||
&i.FetchedAt,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
&i.PhotoID,
|
||||
&i.Method,
|
||||
&i.Difficulty,
|
||||
&i.MinutesTotal,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
@@ -109,7 +109,7 @@ func (q *Queries) GetIntegrationsGoogle(ctx context.Context) (IntegrationsGoogle
|
||||
}
|
||||
|
||||
const getRecipeSearchSettings = `-- name: GetRecipeSearchSettings :one
|
||||
SELECT id, provider, base_url, api_key_ct, created_at, updated_at FROM recipe_search_settings
|
||||
SELECT id, provider, base_url, api_key_ct, created_at, updated_at, api_key_last4 FROM recipe_search_settings
|
||||
WHERE id = 1
|
||||
`
|
||||
|
||||
@@ -124,6 +124,7 @@ func (q *Queries) GetRecipeSearchSettings(ctx context.Context) (RecipeSearchSett
|
||||
&i.ApiKeyCt,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
&i.ApiKeyLast4,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
@@ -390,24 +391,26 @@ func (q *Queries) UpsertIntegrationsGoogle(ctx context.Context, arg UpsertIntegr
|
||||
|
||||
const upsertRecipeSearchSettings = `-- name: UpsertRecipeSearchSettings :one
|
||||
INSERT INTO recipe_search_settings (
|
||||
id, provider, base_url, api_key_ct, created_at, updated_at
|
||||
id, provider, base_url, api_key_ct, api_key_last4, created_at, updated_at
|
||||
) VALUES (
|
||||
1, ?, ?, ?, ?, ?
|
||||
1, ?, ?, ?, ?, ?, ?
|
||||
)
|
||||
ON CONFLICT (id) DO UPDATE SET
|
||||
provider = excluded.provider,
|
||||
base_url = excluded.base_url,
|
||||
api_key_ct = excluded.api_key_ct,
|
||||
api_key_last4 = excluded.api_key_last4,
|
||||
updated_at = excluded.updated_at
|
||||
RETURNING id, provider, base_url, api_key_ct, created_at, updated_at
|
||||
RETURNING id, provider, base_url, api_key_ct, created_at, updated_at, api_key_last4
|
||||
`
|
||||
|
||||
type UpsertRecipeSearchSettingsParams struct {
|
||||
Provider string
|
||||
BaseUrl sql.NullString
|
||||
ApiKeyCt []byte
|
||||
CreatedAt string
|
||||
UpdatedAt string
|
||||
Provider string
|
||||
BaseUrl sql.NullString
|
||||
ApiKeyCt []byte
|
||||
ApiKeyLast4 sql.NullString
|
||||
CreatedAt string
|
||||
UpdatedAt string
|
||||
}
|
||||
|
||||
// Creates the single row (id 1) or replaces every column except id and
|
||||
@@ -417,6 +420,7 @@ func (q *Queries) UpsertRecipeSearchSettings(ctx context.Context, arg UpsertReci
|
||||
arg.Provider,
|
||||
arg.BaseUrl,
|
||||
arg.ApiKeyCt,
|
||||
arg.ApiKeyLast4,
|
||||
arg.CreatedAt,
|
||||
arg.UpdatedAt,
|
||||
)
|
||||
@@ -428,6 +432,7 @@ func (q *Queries) UpsertRecipeSearchSettings(ctx context.Context, arg UpsertReci
|
||||
&i.ApiKeyCt,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
&i.ApiKeyLast4,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user