Disconnect deleted the connection but left every pushed plan synced with its stored event ids, though no calendar was chosen any more. It now deletes the connection and runs the same invalidation as choosing another calendar (forgetEvents: MarkCalendarPlansEdited, ClearCalendarEvents) in one transaction, under the account lock. A refused revoke still changes nothing. Tests push three weeks, disconnect, and check each is changed with no event id; then connect again, choose a calendar and push the whole week to it. Also: a refused revoke keeps every week synced, and a disconnect during a push waits for its record and then forgets it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
451 lines
12 KiB
Go
451 lines
12 KiB
Go
// Code generated by sqlc. DO NOT EDIT.
|
|
// versions:
|
|
// sqlc v1.31.1
|
|
// source: plans.sql
|
|
|
|
package db
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
)
|
|
|
|
const clearCalendarEvents = `-- name: ClearCalendarEvents :exec
|
|
UPDATE plan_days
|
|
SET calendar_event_id = NULL, updated_at = ?
|
|
WHERE calendar_event_id IS NOT NULL
|
|
`
|
|
|
|
// Clears every day's calendar event id: none is on the chosen calendar.
|
|
func (q *Queries) ClearCalendarEvents(ctx context.Context, updatedAt string) error {
|
|
_, err := q.db.ExecContext(ctx, clearCalendarEvents, updatedAt)
|
|
return err
|
|
}
|
|
|
|
const createWeekPlan = `-- name: CreateWeekPlan :exec
|
|
INSERT INTO week_plans (id, week_start, status, ai_note, created_at, updated_at)
|
|
VALUES (?, ?, 'draft', ?, ?, ?)
|
|
`
|
|
|
|
type CreateWeekPlanParams struct {
|
|
ID string
|
|
WeekStart string
|
|
AiNote sql.NullString
|
|
CreatedAt string
|
|
UpdatedAt string
|
|
}
|
|
|
|
func (q *Queries) CreateWeekPlan(ctx context.Context, arg CreateWeekPlanParams) error {
|
|
_, err := q.db.ExecContext(ctx, createWeekPlan,
|
|
arg.ID,
|
|
arg.WeekStart,
|
|
arg.AiNote,
|
|
arg.CreatedAt,
|
|
arg.UpdatedAt,
|
|
)
|
|
return err
|
|
}
|
|
|
|
const getActiveDraftJob = `-- name: GetActiveDraftJob :one
|
|
SELECT id, CAST(json_extract(payload, '$.confirm') = 1 AS INTEGER) AS confirmed
|
|
FROM jobs
|
|
WHERE kind = ?1
|
|
AND status IN ('queued', 'running')
|
|
AND json_extract(payload, '$.week_start') = ?2
|
|
ORDER BY id
|
|
LIMIT 1
|
|
`
|
|
|
|
type GetActiveDraftJobParams struct {
|
|
Kind string
|
|
WeekStart string
|
|
}
|
|
|
|
type GetActiveDraftJobRow struct {
|
|
ID string
|
|
Confirmed int64
|
|
}
|
|
|
|
// The job of kind queued or running for the week starting week_start,
|
|
// with whether its payload has confirm. StartDraft enqueues a draft only
|
|
// when there is none, so there is at most one. Returns sql.ErrNoRows when
|
|
// there is none.
|
|
func (q *Queries) GetActiveDraftJob(ctx context.Context, arg GetActiveDraftJobParams) (GetActiveDraftJobRow, error) {
|
|
row := q.db.QueryRowContext(ctx, getActiveDraftJob, arg.Kind, arg.WeekStart)
|
|
var i GetActiveDraftJobRow
|
|
err := row.Scan(&i.ID, &i.Confirmed)
|
|
return i, err
|
|
}
|
|
|
|
const getWeekPlan = `-- name: GetWeekPlan :one
|
|
SELECT id, week_start, status, ai_note, ai_run_id, calendar_synced_at, created_at, updated_at, revision FROM week_plans
|
|
WHERE week_start = ?
|
|
`
|
|
|
|
// The plan of the week starting week_start. Returns sql.ErrNoRows when
|
|
// there is none.
|
|
func (q *Queries) GetWeekPlan(ctx context.Context, weekStart string) (WeekPlan, error) {
|
|
row := q.db.QueryRowContext(ctx, getWeekPlan, weekStart)
|
|
var i WeekPlan
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.WeekStart,
|
|
&i.Status,
|
|
&i.AiNote,
|
|
&i.AiRunID,
|
|
&i.CalendarSyncedAt,
|
|
&i.CreatedAt,
|
|
&i.UpdatedAt,
|
|
&i.Revision,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const listDraftPool = `-- name: ListDraftPool :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 = 'regular'
|
|
OR (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
|
|
AND week_plans.week_start <> ?2
|
|
))
|
|
ORDER BY normalized_name, id
|
|
`
|
|
|
|
type ListDraftPoolParams struct {
|
|
FromWeekStart string
|
|
WeekStart string
|
|
}
|
|
|
|
// The dinners a draft of the week starting week_start may plan: every
|
|
// Regular, and every Quest log dinner no other plan for the week starting
|
|
// from_week_start or later has on any day.
|
|
func (q *Queries) ListDraftPool(ctx context.Context, arg ListDraftPoolParams) ([]Dinner, error) {
|
|
rows, err := q.db.QueryContext(ctx, listDraftPool, arg.FromWeekStart, arg.WeekStart)
|
|
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 listPlanDays = `-- name: ListPlanDays :many
|
|
SELECT plan_days.date, plan_days.is_new, plan_days.calendar_event_id, 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 plan_days
|
|
JOIN dinners ON dinners.id = plan_days.dinner_id
|
|
WHERE plan_days.plan_id = ?
|
|
ORDER BY plan_days.date
|
|
`
|
|
|
|
type ListPlanDaysRow struct {
|
|
Date string
|
|
IsNew int64
|
|
CalendarEventID sql.NullString
|
|
Dinner Dinner
|
|
}
|
|
|
|
// The days of plan plan_id with their dinners, Monday first. A day with
|
|
// no dinner is left out.
|
|
func (q *Queries) ListPlanDays(ctx context.Context, planID string) ([]ListPlanDaysRow, error) {
|
|
rows, err := q.db.QueryContext(ctx, listPlanDays, planID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
var items []ListPlanDaysRow
|
|
for rows.Next() {
|
|
var i ListPlanDaysRow
|
|
if err := rows.Scan(
|
|
&i.Date,
|
|
&i.IsNew,
|
|
&i.CalendarEventID,
|
|
&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 listWeekPlanDinnerIDs = `-- name: ListWeekPlanDinnerIDs :many
|
|
SELECT plan_days.dinner_id
|
|
FROM plan_days
|
|
JOIN week_plans ON week_plans.id = plan_days.plan_id
|
|
WHERE week_plans.week_start = ? AND plan_days.dinner_id IS NOT NULL
|
|
ORDER BY plan_days.date
|
|
`
|
|
|
|
// The ids of the dinners planned in the week starting week_start, Monday
|
|
// first.
|
|
func (q *Queries) ListWeekPlanDinnerIDs(ctx context.Context, weekStart string) ([]sql.NullString, error) {
|
|
rows, err := q.db.QueryContext(ctx, listWeekPlanDinnerIDs, weekStart)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
var items []sql.NullString
|
|
for rows.Next() {
|
|
var dinner_id sql.NullString
|
|
if err := rows.Scan(&dinner_id); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, dinner_id)
|
|
}
|
|
if err := rows.Close(); err != nil {
|
|
return nil, err
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
const markCalendarPlansEdited = `-- name: MarkCalendarPlansEdited :exec
|
|
UPDATE week_plans
|
|
SET status = CASE WHEN status = 'synced' THEN 'changed' ELSE status END,
|
|
revision = revision + 1,
|
|
updated_at = ?
|
|
WHERE EXISTS (
|
|
SELECT 1 FROM plan_days
|
|
WHERE plan_days.plan_id = week_plans.id AND plan_days.calendar_event_id IS NOT NULL
|
|
)
|
|
`
|
|
|
|
// Records that Calendar no longer shows the days with a calendar event id
|
|
// as planned, since another calendar or none is chosen: every plan with
|
|
// such a day is edited as MarkWeekPlanEdited does, so a synced plan becomes
|
|
// changed and a push that read it before is refused.
|
|
func (q *Queries) MarkCalendarPlansEdited(ctx context.Context, updatedAt string) error {
|
|
_, err := q.db.ExecContext(ctx, markCalendarPlansEdited, updatedAt)
|
|
return err
|
|
}
|
|
|
|
const markWeekPlanEdited = `-- name: MarkWeekPlanEdited :exec
|
|
UPDATE week_plans
|
|
SET status = CASE WHEN status = 'synced' THEN 'changed' ELSE status END,
|
|
revision = revision + 1,
|
|
updated_at = ?
|
|
WHERE id = ?
|
|
`
|
|
|
|
type MarkWeekPlanEditedParams struct {
|
|
UpdatedAt string
|
|
ID string
|
|
}
|
|
|
|
// Records an edit of plan id, raising its revision: a synced plan becomes
|
|
// changed, any other keeps its status.
|
|
func (q *Queries) MarkWeekPlanEdited(ctx context.Context, arg MarkWeekPlanEditedParams) error {
|
|
_, err := q.db.ExecContext(ctx, markWeekPlanEdited, arg.UpdatedAt, arg.ID)
|
|
return err
|
|
}
|
|
|
|
const markWeekPlanSynced = `-- name: MarkWeekPlanSynced :execrows
|
|
UPDATE week_plans
|
|
SET status = 'synced', calendar_synced_at = ?, updated_at = ?
|
|
WHERE id = ? AND revision = ?
|
|
`
|
|
|
|
type MarkWeekPlanSyncedParams struct {
|
|
CalendarSyncedAt sql.NullString
|
|
UpdatedAt string
|
|
ID string
|
|
Revision int64
|
|
}
|
|
|
|
// Records a Push to Calendar of plan id as it was at revision: the plan
|
|
// becomes synced. A plan edited since (another revision) is left as it is
|
|
// (0 rows).
|
|
func (q *Queries) MarkWeekPlanSynced(ctx context.Context, arg MarkWeekPlanSyncedParams) (int64, error) {
|
|
result, err := q.db.ExecContext(ctx, markWeekPlanSynced,
|
|
arg.CalendarSyncedAt,
|
|
arg.UpdatedAt,
|
|
arg.ID,
|
|
arg.Revision,
|
|
)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
return result.RowsAffected()
|
|
}
|
|
|
|
const putPlanDay = `-- name: PutPlanDay :exec
|
|
INSERT INTO plan_days (id, plan_id, date, dinner_id, is_new, created_at, updated_at)
|
|
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
ON CONFLICT (plan_id, date) DO UPDATE
|
|
SET dinner_id = excluded.dinner_id, is_new = excluded.is_new,
|
|
calendar_event_id = CASE WHEN plan_days.dinner_id IS excluded.dinner_id THEN plan_days.calendar_event_id END,
|
|
updated_at = excluded.updated_at
|
|
`
|
|
|
|
type PutPlanDayParams struct {
|
|
ID string
|
|
PlanID string
|
|
Date string
|
|
DinnerID sql.NullString
|
|
IsNew int64
|
|
CreatedAt string
|
|
UpdatedAt string
|
|
}
|
|
|
|
// Plans dinner_id on date of plan_id. An existing day keeps its calendar
|
|
// event id while its dinner stays the same; a new dinner clears it, since
|
|
// Calendar no longer shows the day as planned.
|
|
func (q *Queries) PutPlanDay(ctx context.Context, arg PutPlanDayParams) error {
|
|
_, err := q.db.ExecContext(ctx, putPlanDay,
|
|
arg.ID,
|
|
arg.PlanID,
|
|
arg.Date,
|
|
arg.DinnerID,
|
|
arg.IsNew,
|
|
arg.CreatedAt,
|
|
arg.UpdatedAt,
|
|
)
|
|
return err
|
|
}
|
|
|
|
const redraftWeekPlan = `-- name: RedraftWeekPlan :exec
|
|
UPDATE week_plans
|
|
SET status = ?, ai_note = ?, revision = revision + 1, updated_at = ?
|
|
WHERE id = ?
|
|
`
|
|
|
|
type RedraftWeekPlanParams struct {
|
|
Status string
|
|
AiNote sql.NullString
|
|
UpdatedAt string
|
|
ID string
|
|
}
|
|
|
|
// Records a new draft over plan id: its status and note, raising its
|
|
// revision.
|
|
func (q *Queries) RedraftWeekPlan(ctx context.Context, arg RedraftWeekPlanParams) error {
|
|
_, err := q.db.ExecContext(ctx, redraftWeekPlan,
|
|
arg.Status,
|
|
arg.AiNote,
|
|
arg.UpdatedAt,
|
|
arg.ID,
|
|
)
|
|
return err
|
|
}
|
|
|
|
const setPlanDayCalendarEvent = `-- name: SetPlanDayCalendarEvent :execrows
|
|
UPDATE plan_days
|
|
SET calendar_event_id = ?, updated_at = ?
|
|
WHERE plan_id = ? AND date = ?
|
|
`
|
|
|
|
type SetPlanDayCalendarEventParams struct {
|
|
CalendarEventID sql.NullString
|
|
UpdatedAt string
|
|
PlanID string
|
|
Date string
|
|
}
|
|
|
|
// Records that the event calendar_event_id on Calendar shows date of
|
|
// plan_id as planned.
|
|
func (q *Queries) SetPlanDayCalendarEvent(ctx context.Context, arg SetPlanDayCalendarEventParams) (int64, error) {
|
|
result, err := q.db.ExecContext(ctx, setPlanDayCalendarEvent,
|
|
arg.CalendarEventID,
|
|
arg.UpdatedAt,
|
|
arg.PlanID,
|
|
arg.Date,
|
|
)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
return result.RowsAffected()
|
|
}
|
|
|
|
const swapPlanDay = `-- name: SwapPlanDay :execrows
|
|
UPDATE plan_days
|
|
SET dinner_id = ?, is_new = ?, calendar_event_id = NULL, updated_at = ?
|
|
WHERE plan_id = ? AND date = ?
|
|
`
|
|
|
|
type SwapPlanDayParams struct {
|
|
DinnerID sql.NullString
|
|
IsNew int64
|
|
UpdatedAt string
|
|
PlanID string
|
|
Date string
|
|
}
|
|
|
|
// Plans dinner_id on an existing day of plan_id, clearing its calendar
|
|
// event id since Calendar no longer shows the day as planned; a date the
|
|
// plan does not have is left as it is (0 rows).
|
|
func (q *Queries) SwapPlanDay(ctx context.Context, arg SwapPlanDayParams) (int64, error) {
|
|
result, err := q.db.ExecContext(ctx, swapPlanDay,
|
|
arg.DinnerID,
|
|
arg.IsNew,
|
|
arg.UpdatedAt,
|
|
arg.PlanID,
|
|
arg.Date,
|
|
)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
return result.RowsAffected()
|
|
}
|