shopping.Freeze(weekStart) archives a week's Supply run once its week has
ended in the household's time zone, setting status archived and frozen_at;
it is idempotent, and a week not yet ended (ErrWeekNotEnded) or with no
list (ErrListNotFound) is an explicit error. A list still untitled is
titled first with archive_title and the same conditional write the
title_supply_run job uses; a failed title is logged and the list is
frozen untitled, keeping its week label.
GET /api/v1/shopping/archive?by=week|month|year returns the Supply runs
up to the household's current week, newest first, grouped by the
Monday's ISO week, month or year, each post with its week label, title or
null, dinner, item and on-hand counts, dinner names and current flag. Any
other by is 400 invalid_request. Frozen lists stay readable through
GET /shopping/{weekStart} and reject item PATCHes with 409 archived.
Maestro task 7ac36526-cb51-4dc7-bbad-6ebbee481942.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
466 lines
12 KiB
Go
466 lines
12 KiB
Go
// Code generated by sqlc. DO NOT EDIT.
|
|
// versions:
|
|
// sqlc v1.31.1
|
|
// source: shopping.sql
|
|
|
|
package db
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
)
|
|
|
|
const createShoppingGroup = `-- name: CreateShoppingGroup :exec
|
|
INSERT INTO shopping_groups (id, list_id, dinner_id, date, position, created_at, updated_at)
|
|
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
`
|
|
|
|
type CreateShoppingGroupParams struct {
|
|
ID string
|
|
ListID string
|
|
DinnerID string
|
|
Date string
|
|
Position int64
|
|
CreatedAt string
|
|
UpdatedAt string
|
|
}
|
|
|
|
func (q *Queries) CreateShoppingGroup(ctx context.Context, arg CreateShoppingGroupParams) error {
|
|
_, err := q.db.ExecContext(ctx, createShoppingGroup,
|
|
arg.ID,
|
|
arg.ListID,
|
|
arg.DinnerID,
|
|
arg.Date,
|
|
arg.Position,
|
|
arg.CreatedAt,
|
|
arg.UpdatedAt,
|
|
)
|
|
return err
|
|
}
|
|
|
|
const createShoppingItem = `-- name: CreateShoppingItem :exec
|
|
INSERT INTO shopping_items (id, group_id, name, qty, needed, position, created_at, updated_at)
|
|
VALUES (?, ?, ?, ?, 1, ?, ?, ?)
|
|
`
|
|
|
|
type CreateShoppingItemParams struct {
|
|
ID string
|
|
GroupID string
|
|
Name string
|
|
Qty sql.NullString
|
|
Position int64
|
|
CreatedAt string
|
|
UpdatedAt string
|
|
}
|
|
|
|
// A new item is needed until the household marks it "have it".
|
|
func (q *Queries) CreateShoppingItem(ctx context.Context, arg CreateShoppingItemParams) error {
|
|
_, err := q.db.ExecContext(ctx, createShoppingItem,
|
|
arg.ID,
|
|
arg.GroupID,
|
|
arg.Name,
|
|
arg.Qty,
|
|
arg.Position,
|
|
arg.CreatedAt,
|
|
arg.UpdatedAt,
|
|
)
|
|
return err
|
|
}
|
|
|
|
const createShoppingList = `-- name: CreateShoppingList :exec
|
|
INSERT INTO shopping_lists (id, week_start, plan_id, status, created_at, updated_at)
|
|
VALUES (?, ?, ?, 'current', ?, ?)
|
|
`
|
|
|
|
type CreateShoppingListParams struct {
|
|
ID string
|
|
WeekStart string
|
|
PlanID string
|
|
CreatedAt string
|
|
UpdatedAt string
|
|
}
|
|
|
|
// A new, untitled Supply run: the title is written later, by the AI.
|
|
func (q *Queries) CreateShoppingList(ctx context.Context, arg CreateShoppingListParams) error {
|
|
_, err := q.db.ExecContext(ctx, createShoppingList,
|
|
arg.ID,
|
|
arg.WeekStart,
|
|
arg.PlanID,
|
|
arg.CreatedAt,
|
|
arg.UpdatedAt,
|
|
)
|
|
return err
|
|
}
|
|
|
|
const deleteShoppingGroup = `-- name: DeleteShoppingGroup :exec
|
|
DELETE FROM shopping_groups
|
|
WHERE id = ?
|
|
`
|
|
|
|
func (q *Queries) DeleteShoppingGroup(ctx context.Context, id string) error {
|
|
_, err := q.db.ExecContext(ctx, deleteShoppingGroup, id)
|
|
return err
|
|
}
|
|
|
|
const deleteShoppingItem = `-- name: DeleteShoppingItem :exec
|
|
DELETE FROM shopping_items
|
|
WHERE id = ?
|
|
`
|
|
|
|
func (q *Queries) DeleteShoppingItem(ctx context.Context, id string) error {
|
|
_, err := q.db.ExecContext(ctx, deleteShoppingItem, id)
|
|
return err
|
|
}
|
|
|
|
const freezeShoppingList = `-- name: FreezeShoppingList :execrows
|
|
UPDATE shopping_lists
|
|
SET status = 'archived', frozen_at = ?, updated_at = ?
|
|
WHERE id = ? AND frozen_at IS NULL
|
|
`
|
|
|
|
type FreezeShoppingListParams struct {
|
|
FrozenAt sql.NullString
|
|
UpdatedAt string
|
|
ID string
|
|
}
|
|
|
|
// Freezes list id into the Supply archive at frozen_at, unless it is
|
|
// frozen already.
|
|
func (q *Queries) FreezeShoppingList(ctx context.Context, arg FreezeShoppingListParams) (int64, error) {
|
|
result, err := q.db.ExecContext(ctx, freezeShoppingList, arg.FrozenAt, arg.UpdatedAt, arg.ID)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
return result.RowsAffected()
|
|
}
|
|
|
|
const getShoppingItemList = `-- name: GetShoppingItemList :one
|
|
SELECT shopping_lists.id, shopping_lists.week_start, shopping_lists.plan_id, shopping_lists.title, shopping_lists.status, shopping_lists.sent_to_chat_at, shopping_lists.frozen_at, shopping_lists.created_at, shopping_lists.updated_at
|
|
FROM shopping_items
|
|
JOIN shopping_groups ON shopping_groups.id = shopping_items.group_id
|
|
JOIN shopping_lists ON shopping_lists.id = shopping_groups.list_id
|
|
WHERE shopping_items.id = ?
|
|
`
|
|
|
|
type GetShoppingItemListRow struct {
|
|
ShoppingList ShoppingList
|
|
}
|
|
|
|
// The Supply run holding item id. Returns sql.ErrNoRows when there is no
|
|
// such item.
|
|
func (q *Queries) GetShoppingItemList(ctx context.Context, id string) (GetShoppingItemListRow, error) {
|
|
row := q.db.QueryRowContext(ctx, getShoppingItemList, id)
|
|
var i GetShoppingItemListRow
|
|
err := row.Scan(
|
|
&i.ShoppingList.ID,
|
|
&i.ShoppingList.WeekStart,
|
|
&i.ShoppingList.PlanID,
|
|
&i.ShoppingList.Title,
|
|
&i.ShoppingList.Status,
|
|
&i.ShoppingList.SentToChatAt,
|
|
&i.ShoppingList.FrozenAt,
|
|
&i.ShoppingList.CreatedAt,
|
|
&i.ShoppingList.UpdatedAt,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const getShoppingList = `-- name: GetShoppingList :one
|
|
SELECT id, week_start, plan_id, title, status, sent_to_chat_at, frozen_at, created_at, updated_at FROM shopping_lists
|
|
WHERE week_start = ?
|
|
`
|
|
|
|
// The Supply run of the week starting week_start. Returns sql.ErrNoRows
|
|
// when there is none.
|
|
func (q *Queries) GetShoppingList(ctx context.Context, weekStart string) (ShoppingList, error) {
|
|
row := q.db.QueryRowContext(ctx, getShoppingList, weekStart)
|
|
var i ShoppingList
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.WeekStart,
|
|
&i.PlanID,
|
|
&i.Title,
|
|
&i.Status,
|
|
&i.SentToChatAt,
|
|
&i.FrozenAt,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const listShoppingGroupDinners = `-- name: ListShoppingGroupDinners :many
|
|
SELECT shopping_groups.id, shopping_groups.list_id, shopping_groups.dinner_id, shopping_groups.date, shopping_groups.position, shopping_groups.created_at, shopping_groups.updated_at, dinners.name AS dinner_name
|
|
FROM shopping_groups
|
|
JOIN dinners ON dinners.id = shopping_groups.dinner_id
|
|
WHERE shopping_groups.list_id = ?
|
|
ORDER BY shopping_groups.position, shopping_groups.id
|
|
`
|
|
|
|
type ListShoppingGroupDinnersRow struct {
|
|
ShoppingGroup ShoppingGroup
|
|
DinnerName string
|
|
}
|
|
|
|
// The groups of list list_id with their dinner's name, in list order.
|
|
func (q *Queries) ListShoppingGroupDinners(ctx context.Context, listID string) ([]ListShoppingGroupDinnersRow, error) {
|
|
rows, err := q.db.QueryContext(ctx, listShoppingGroupDinners, listID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
var items []ListShoppingGroupDinnersRow
|
|
for rows.Next() {
|
|
var i ListShoppingGroupDinnersRow
|
|
if err := rows.Scan(
|
|
&i.ShoppingGroup.ID,
|
|
&i.ShoppingGroup.ListID,
|
|
&i.ShoppingGroup.DinnerID,
|
|
&i.ShoppingGroup.Date,
|
|
&i.ShoppingGroup.Position,
|
|
&i.ShoppingGroup.CreatedAt,
|
|
&i.ShoppingGroup.UpdatedAt,
|
|
&i.DinnerName,
|
|
); 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 listShoppingGroups = `-- name: ListShoppingGroups :many
|
|
SELECT id, list_id, dinner_id, date, position, created_at, updated_at FROM shopping_groups
|
|
WHERE list_id = ?
|
|
ORDER BY position, id
|
|
`
|
|
|
|
// The groups of list list_id, in list order.
|
|
func (q *Queries) ListShoppingGroups(ctx context.Context, listID string) ([]ShoppingGroup, error) {
|
|
rows, err := q.db.QueryContext(ctx, listShoppingGroups, listID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
var items []ShoppingGroup
|
|
for rows.Next() {
|
|
var i ShoppingGroup
|
|
if err := rows.Scan(
|
|
&i.ID,
|
|
&i.ListID,
|
|
&i.DinnerID,
|
|
&i.Date,
|
|
&i.Position,
|
|
&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 listShoppingItems = `-- name: ListShoppingItems :many
|
|
SELECT shopping_items.id, shopping_items.group_id, shopping_items.name, shopping_items.qty, shopping_items.needed, shopping_items.position, shopping_items.created_at, shopping_items.updated_at, shopping_groups.dinner_id
|
|
FROM shopping_items
|
|
JOIN shopping_groups ON shopping_groups.id = shopping_items.group_id
|
|
WHERE shopping_groups.list_id = ?
|
|
ORDER BY shopping_groups.position, shopping_items.position, shopping_items.id
|
|
`
|
|
|
|
type ListShoppingItemsRow struct {
|
|
ShoppingItem ShoppingItem
|
|
DinnerID string
|
|
}
|
|
|
|
// The items of list list_id with their group's dinner, in list order: by
|
|
// group, then by item.
|
|
func (q *Queries) ListShoppingItems(ctx context.Context, listID string) ([]ListShoppingItemsRow, error) {
|
|
rows, err := q.db.QueryContext(ctx, listShoppingItems, listID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
var items []ListShoppingItemsRow
|
|
for rows.Next() {
|
|
var i ListShoppingItemsRow
|
|
if err := rows.Scan(
|
|
&i.ShoppingItem.ID,
|
|
&i.ShoppingItem.GroupID,
|
|
&i.ShoppingItem.Name,
|
|
&i.ShoppingItem.Qty,
|
|
&i.ShoppingItem.Needed,
|
|
&i.ShoppingItem.Position,
|
|
&i.ShoppingItem.CreatedAt,
|
|
&i.ShoppingItem.UpdatedAt,
|
|
&i.DinnerID,
|
|
); 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 listShoppingListsThrough = `-- name: ListShoppingListsThrough :many
|
|
SELECT id, week_start, plan_id, title, status, sent_to_chat_at, frozen_at, created_at, updated_at FROM shopping_lists
|
|
WHERE week_start <= ?
|
|
ORDER BY week_start DESC
|
|
`
|
|
|
|
// The Supply runs of the weeks starting on or before week_start, newest
|
|
// week first.
|
|
func (q *Queries) ListShoppingListsThrough(ctx context.Context, weekStart string) ([]ShoppingList, error) {
|
|
rows, err := q.db.QueryContext(ctx, listShoppingListsThrough, weekStart)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
var items []ShoppingList
|
|
for rows.Next() {
|
|
var i ShoppingList
|
|
if err := rows.Scan(
|
|
&i.ID,
|
|
&i.WeekStart,
|
|
&i.PlanID,
|
|
&i.Title,
|
|
&i.Status,
|
|
&i.SentToChatAt,
|
|
&i.FrozenAt,
|
|
&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 moveShoppingGroup = `-- name: MoveShoppingGroup :exec
|
|
UPDATE shopping_groups
|
|
SET position = ?, updated_at = ?
|
|
WHERE id = ?
|
|
`
|
|
|
|
type MoveShoppingGroupParams struct {
|
|
Position int64
|
|
UpdatedAt string
|
|
ID string
|
|
}
|
|
|
|
func (q *Queries) MoveShoppingGroup(ctx context.Context, arg MoveShoppingGroupParams) error {
|
|
_, err := q.db.ExecContext(ctx, moveShoppingGroup, arg.Position, arg.UpdatedAt, arg.ID)
|
|
return err
|
|
}
|
|
|
|
const moveShoppingItem = `-- name: MoveShoppingItem :exec
|
|
UPDATE shopping_items
|
|
SET group_id = ?, qty = ?, position = ?, updated_at = ?
|
|
WHERE id = ?
|
|
`
|
|
|
|
type MoveShoppingItemParams struct {
|
|
GroupID string
|
|
Qty sql.NullString
|
|
Position int64
|
|
UpdatedAt string
|
|
ID string
|
|
}
|
|
|
|
// Places item id in group group_id at position with quantity qty; its
|
|
// name and needed flag are kept.
|
|
func (q *Queries) MoveShoppingItem(ctx context.Context, arg MoveShoppingItemParams) error {
|
|
_, err := q.db.ExecContext(ctx, moveShoppingItem,
|
|
arg.GroupID,
|
|
arg.Qty,
|
|
arg.Position,
|
|
arg.UpdatedAt,
|
|
arg.ID,
|
|
)
|
|
return err
|
|
}
|
|
|
|
const regenerateShoppingList = `-- name: RegenerateShoppingList :exec
|
|
UPDATE shopping_lists
|
|
SET plan_id = ?, updated_at = ?
|
|
WHERE id = ?
|
|
`
|
|
|
|
type RegenerateShoppingListParams struct {
|
|
PlanID string
|
|
UpdatedAt string
|
|
ID string
|
|
}
|
|
|
|
// Records a generation of list id from plan plan_id.
|
|
func (q *Queries) RegenerateShoppingList(ctx context.Context, arg RegenerateShoppingListParams) error {
|
|
_, err := q.db.ExecContext(ctx, regenerateShoppingList, arg.PlanID, arg.UpdatedAt, arg.ID)
|
|
return err
|
|
}
|
|
|
|
const setShoppingItemNeeded = `-- name: SetShoppingItemNeeded :exec
|
|
UPDATE shopping_items
|
|
SET needed = ?, updated_at = ?
|
|
WHERE id = ?
|
|
`
|
|
|
|
type SetShoppingItemNeededParams struct {
|
|
Needed int64
|
|
UpdatedAt string
|
|
ID string
|
|
}
|
|
|
|
// Marks item id needed (1) or "have it" (0).
|
|
func (q *Queries) SetShoppingItemNeeded(ctx context.Context, arg SetShoppingItemNeededParams) error {
|
|
_, err := q.db.ExecContext(ctx, setShoppingItemNeeded, arg.Needed, arg.UpdatedAt, arg.ID)
|
|
return err
|
|
}
|
|
|
|
const setShoppingListTitle = `-- name: SetShoppingListTitle :execrows
|
|
UPDATE shopping_lists
|
|
SET title = ?, updated_at = ?
|
|
WHERE id = ? AND title IS NULL AND status = 'current' AND frozen_at IS NULL
|
|
`
|
|
|
|
type SetShoppingListTitleParams struct {
|
|
Title sql.NullString
|
|
UpdatedAt string
|
|
ID string
|
|
}
|
|
|
|
// Titles list id, unless it has a title already or is frozen into the
|
|
// Supply archive.
|
|
func (q *Queries) SetShoppingListTitle(ctx context.Context, arg SetShoppingListTitleParams) (int64, error) {
|
|
result, err := q.db.ExecContext(ctx, setShoppingListTitle, arg.Title, arg.UpdatedAt, arg.ID)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
return result.RowsAffected()
|
|
}
|