Guard plan drafts against swaps and keep confirm on single-flight
Return the draft job id as jobId, as the plan API contract specifies. Add a week_plans revision (migration 0013) that every swap and stored draft raises. A draft_week job reads the revision before draft_week runs and stores its week only while the plan still has it; a plan edited while the AI ran is left as it is and the job fails plan_changed, so a draft never erases the household's swap. A confirmed draft now joins only a confirmed queued or running job, so a job asked for without confirm can no longer fail it with confirm_required after the plan was synced. Test simultaneous drafts, a swap during a draft, a confirmed draft while an unconfirmed one runs, impossible calendar dates and start_by across the end of daylight saving time. Maestro task ea48e650-d510-49cc-8485-0f07174ee731. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -316,4 +316,5 @@ type WeekPlan struct {
|
||||
CalendarSyncedAt sql.NullString
|
||||
CreatedAt string
|
||||
UpdatedAt string
|
||||
Revision int64
|
||||
}
|
||||
|
||||
@@ -39,6 +39,7 @@ SELECT id FROM jobs
|
||||
WHERE kind = ?1
|
||||
AND status IN ('queued', 'running')
|
||||
AND json_extract(payload, '$.week_start') = ?2
|
||||
AND (CAST(?3 AS INTEGER) = 0 OR json_extract(payload, '$.confirm') = 1)
|
||||
ORDER BY id
|
||||
LIMIT 1
|
||||
`
|
||||
@@ -46,19 +47,21 @@ LIMIT 1
|
||||
type GetActiveDraftJobIDParams struct {
|
||||
Kind string
|
||||
WeekStart string
|
||||
Confirmed int64
|
||||
}
|
||||
|
||||
// The id of the job of kind queued or running for the week starting
|
||||
// week_start. Returns sql.ErrNoRows when there is none.
|
||||
// week_start, one whose payload has confirm when confirmed is 1. Returns
|
||||
// sql.ErrNoRows when there is none.
|
||||
func (q *Queries) GetActiveDraftJobID(ctx context.Context, arg GetActiveDraftJobIDParams) (string, error) {
|
||||
row := q.db.QueryRowContext(ctx, getActiveDraftJobID, arg.Kind, arg.WeekStart)
|
||||
row := q.db.QueryRowContext(ctx, getActiveDraftJobID, arg.Kind, arg.WeekStart, arg.Confirmed)
|
||||
var id string
|
||||
err := row.Scan(&id)
|
||||
return id, err
|
||||
}
|
||||
|
||||
const getWeekPlan = `-- name: GetWeekPlan :one
|
||||
SELECT id, week_start, status, ai_note, ai_run_id, calendar_synced_at, created_at, updated_at FROM week_plans
|
||||
SELECT id, week_start, status, ai_note, ai_run_id, calendar_synced_at, created_at, updated_at, revision FROM week_plans
|
||||
WHERE week_start = ?
|
||||
`
|
||||
|
||||
@@ -76,6 +79,7 @@ func (q *Queries) GetWeekPlan(ctx context.Context, weekStart string) (WeekPlan,
|
||||
&i.CalendarSyncedAt,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
&i.Revision,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
@@ -241,6 +245,7 @@ func (q *Queries) ListWeekPlanDinnerIDs(ctx context.Context, weekStart string) (
|
||||
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 = ?
|
||||
`
|
||||
@@ -250,8 +255,8 @@ type MarkWeekPlanEditedParams struct {
|
||||
ID string
|
||||
}
|
||||
|
||||
// Records an edit of plan id: a synced plan becomes changed, any other
|
||||
// keeps its status.
|
||||
// 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
|
||||
@@ -291,7 +296,7 @@ func (q *Queries) PutPlanDay(ctx context.Context, arg PutPlanDayParams) error {
|
||||
|
||||
const redraftWeekPlan = `-- name: RedraftWeekPlan :exec
|
||||
UPDATE week_plans
|
||||
SET status = ?, ai_note = ?, updated_at = ?
|
||||
SET status = ?, ai_note = ?, revision = revision + 1, updated_at = ?
|
||||
WHERE id = ?
|
||||
`
|
||||
|
||||
@@ -302,7 +307,8 @@ type RedraftWeekPlanParams struct {
|
||||
ID string
|
||||
}
|
||||
|
||||
// Records a new draft over plan id: its status and note.
|
||||
// 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,
|
||||
|
||||
@@ -69,7 +69,8 @@ type Querier interface {
|
||||
// Returns sql.ErrNoRows until the row exists.
|
||||
GetAISettings(ctx context.Context) (AiSetting, error)
|
||||
// The id of the job of kind queued or running for the week starting
|
||||
// week_start. Returns sql.ErrNoRows when there is none.
|
||||
// week_start, one whose payload has confirm when confirmed is 1. Returns
|
||||
// sql.ErrNoRows when there is none.
|
||||
GetActiveDraftJobID(ctx context.Context, arg GetActiveDraftJobIDParams) (string, error)
|
||||
// The batch still pending or running, oldest first. Returns sql.ErrNoRows
|
||||
// when there is none.
|
||||
@@ -148,8 +149,8 @@ type Querier interface {
|
||||
// The ids of the dinners planned in the week starting week_start, Monday
|
||||
// first.
|
||||
ListWeekPlanDinnerIDs(ctx context.Context, weekStart string) ([]sql.NullString, error)
|
||||
// Records an edit of plan id: a synced plan becomes changed, any other
|
||||
// keeps its status.
|
||||
// Records an edit of plan id, raising its revision: a synced plan becomes
|
||||
// changed, any other keeps its status.
|
||||
MarkWeekPlanEdited(ctx context.Context, arg MarkWeekPlanEditedParams) error
|
||||
// Moves a dinner still suggested to status (quest_log or archived),
|
||||
// recording updated_by; a dinner no longer suggested is left as it is
|
||||
@@ -160,7 +161,8 @@ type Querier interface {
|
||||
// Plans dinner_id on date of plan_id, keeping the day's calendar event id
|
||||
// when the day exists.
|
||||
PutPlanDay(ctx context.Context, arg PutPlanDayParams) error
|
||||
// Records a new draft over plan id: its status and note.
|
||||
// Records a new draft over plan id: its status and note, raising its
|
||||
// revision.
|
||||
RedraftWeekPlan(ctx context.Context, arg RedraftWeekPlanParams) error
|
||||
// An unknown id returns sql.ErrNoRows.
|
||||
SetDinnerFavorite(ctx context.Context, arg SetDinnerFavoriteParams) (Dinner, error)
|
||||
|
||||
Reference in New Issue
Block a user