Files
cmroubao/backend-api/migrations/00004_claims_and_lifecycle.sql
T

290 lines
8.4 KiB
SQL
Raw Normal View History

-- +goose Up
ALTER TABLE devices
ADD COLUMN accessibility_enabled INTEGER NOT NULL DEFAULT 0
CHECK (accessibility_enabled IN (0, 1));
ALTER TABLE devices
ADD COLUMN pdd_installed INTEGER NOT NULL DEFAULT 0
CHECK (pdd_installed IN (0, 1));
ALTER TABLE devices
ADD COLUMN readiness_reported_at TEXT;
ALTER TABLE purchase_tasks
ADD COLUMN claimed_by_user_id TEXT
REFERENCES users(id) ON UPDATE RESTRICT ON DELETE RESTRICT;
ALTER TABLE purchase_tasks
ADD COLUMN claimed_by_device_id TEXT
REFERENCES devices(id) ON UPDATE RESTRICT ON DELETE RESTRICT;
ALTER TABLE purchase_tasks
ADD COLUMN claim_generation INTEGER NOT NULL DEFAULT 0
CHECK (claim_generation >= 0);
ALTER TABLE purchase_tasks
ADD COLUMN claim_token_hash TEXT
CHECK (
claim_token_hash IS NULL
OR (
length(claim_token_hash) = 64
AND claim_token_hash NOT GLOB '*[^0-9a-f]*'
)
);
ALTER TABLE purchase_tasks
ADD COLUMN claim_issued_at TEXT;
ALTER TABLE purchase_tasks
ADD COLUMN claim_expires_at TEXT;
ALTER TABLE purchase_tasks
ADD COLUMN cancel_requested_at TEXT;
ALTER TABLE purchase_tasks
ADD COLUMN cancel_requested_by_user_id TEXT
REFERENCES users(id) ON UPDATE RESTRICT ON DELETE RESTRICT;
CREATE UNIQUE INDEX purchase_tasks_active_device_idx
ON purchase_tasks (claimed_by_device_id)
WHERE claimed_by_device_id IS NOT NULL
AND status IN ('CLAIMED', 'RUNNING', 'WAITING_CONFIRMATION');
CREATE TABLE task_executions (
id TEXT PRIMARY KEY NOT NULL
CHECK (length(id) = 36),
task_id TEXT NOT NULL
REFERENCES purchase_tasks(id) ON UPDATE RESTRICT ON DELETE CASCADE,
attempt_no INTEGER NOT NULL
CHECK (attempt_no > 0),
claim_generation INTEGER NOT NULL
CHECK (claim_generation > 0),
user_id TEXT NOT NULL
REFERENCES users(id) ON UPDATE RESTRICT ON DELETE RESTRICT,
device_id TEXT NOT NULL
REFERENCES devices(id) ON UPDATE RESTRICT ON DELETE RESTRICT,
current_step TEXT NOT NULL
CHECK (
length(trim(current_step)) > 0
AND length(CAST(current_step AS BLOB)) <= 64
),
last_heartbeat_at TEXT NOT NULL,
order_submitted INTEGER NOT NULL DEFAULT 0
CHECK (order_submitted = 0),
started_at TEXT NOT NULL,
finished_at TEXT,
UNIQUE (task_id, attempt_no),
UNIQUE (task_id, claim_generation)
);
CREATE UNIQUE INDEX task_executions_active_task_idx
ON task_executions (task_id)
WHERE finished_at IS NULL;
CREATE INDEX task_executions_device_heartbeat_idx
ON task_executions (device_id, last_heartbeat_at DESC);
CREATE TABLE lifecycle_requests (
user_id TEXT NOT NULL
REFERENCES users(id) ON UPDATE RESTRICT ON DELETE RESTRICT,
device_id TEXT NOT NULL
REFERENCES devices(id) ON UPDATE RESTRICT ON DELETE RESTRICT,
operation TEXT NOT NULL
CHECK (
operation IN ('CLAIM_NEXT', 'START', 'RELEASE', 'CANCEL_ACK')
),
idempotency_key TEXT NOT NULL
CHECK (
length(trim(idempotency_key)) > 0
AND length(CAST(idempotency_key AS BLOB)) <= 128
),
request_sha256 TEXT NOT NULL
CHECK (
length(request_sha256) = 64
AND request_sha256 NOT GLOB '*[^0-9a-f]*'
),
result_kind TEXT NOT NULL
CHECK (result_kind IN ('TASK', 'NO_TASK', 'EXECUTION')),
task_id TEXT
REFERENCES purchase_tasks(id) ON UPDATE RESTRICT ON DELETE RESTRICT,
claim_generation INTEGER
CHECK (claim_generation IS NULL OR claim_generation > 0),
execution_id TEXT
REFERENCES task_executions(id) ON UPDATE RESTRICT ON DELETE RESTRICT,
created_at TEXT NOT NULL,
PRIMARY KEY (user_id, device_id, operation, idempotency_key),
CHECK (
(
operation = 'CLAIM_NEXT'
AND result_kind = 'NO_TASK'
AND task_id IS NULL
AND claim_generation IS NULL
AND execution_id IS NULL
)
OR (
operation = 'CLAIM_NEXT'
AND result_kind = 'TASK'
AND task_id IS NOT NULL
AND claim_generation IS NOT NULL
AND execution_id IS NULL
)
OR (
operation = 'START'
AND result_kind = 'EXECUTION'
AND task_id IS NOT NULL
AND claim_generation IS NOT NULL
AND execution_id IS NOT NULL
)
OR (
operation = 'RELEASE'
AND result_kind = 'TASK'
AND task_id IS NOT NULL
AND claim_generation IS NOT NULL
AND execution_id IS NULL
)
OR (
operation = 'CANCEL_ACK'
AND result_kind = 'TASK'
AND task_id IS NOT NULL
AND claim_generation IS NOT NULL
AND execution_id IS NOT NULL
)
)
);
ALTER TABLE task_events RENAME TO task_events_v3;
CREATE TABLE task_events (
id TEXT PRIMARY KEY NOT NULL
CHECK (length(id) = 36),
task_id TEXT NOT NULL
REFERENCES purchase_tasks(id) ON UPDATE RESTRICT ON DELETE CASCADE,
event_type TEXT NOT NULL
CHECK (
event_type IN (
'TASK_CREATED',
'TASK_CLAIMED',
'TASK_RECLAIMED',
'TASK_RELEASED',
'TASK_STARTED',
'TASK_CANCEL_REQUESTED',
'TASK_CANCELED'
)
),
message TEXT NOT NULL,
occurred_at TEXT NOT NULL,
actor_user_id TEXT
REFERENCES users(id) ON UPDATE RESTRICT ON DELETE RESTRICT,
actor_device_id TEXT
REFERENCES devices(id) ON UPDATE RESTRICT ON DELETE RESTRICT
);
INSERT INTO task_events (
id,
task_id,
event_type,
message,
occurred_at,
actor_user_id,
actor_device_id
)
SELECT
id,
task_id,
event_type,
message,
occurred_at,
actor_user_id,
NULL
FROM task_events_v3;
DROP TABLE task_events_v3;
CREATE INDEX task_events_task_occurred_idx
ON task_events (task_id, occurred_at ASC, id ASC);
CREATE INDEX task_events_actor_user_idx
ON task_events (actor_user_id, occurred_at DESC, id DESC);
CREATE INDEX task_events_actor_device_idx
ON task_events (actor_device_id, occurred_at DESC, id DESC);
-- +goose Down
CREATE TEMP TABLE task_events_v4_down_guard (
allowed INTEGER NOT NULL
CHECK (allowed = 1)
);
INSERT INTO task_events_v4_down_guard (allowed)
SELECT CASE
WHEN EXISTS (
SELECT 1
FROM task_events
WHERE event_type NOT IN ('TASK_CREATED', 'TASK_CANCELED')
OR actor_device_id IS NOT NULL
)
THEN 0
ELSE 1
END;
DROP TABLE task_events_v4_down_guard;
ALTER TABLE task_events RENAME TO task_events_v4;
CREATE TABLE task_events (
id TEXT PRIMARY KEY NOT NULL
CHECK (length(id) = 36),
task_id TEXT NOT NULL
REFERENCES purchase_tasks(id) ON UPDATE RESTRICT ON DELETE CASCADE,
event_type TEXT NOT NULL
CHECK (event_type IN ('TASK_CREATED', 'TASK_CANCELED')),
message TEXT NOT NULL,
occurred_at TEXT NOT NULL,
actor_user_id TEXT
REFERENCES users(id) ON UPDATE RESTRICT ON DELETE RESTRICT
);
INSERT INTO task_events (
id,
task_id,
event_type,
message,
occurred_at,
actor_user_id
)
SELECT
id,
task_id,
event_type,
message,
occurred_at,
actor_user_id
FROM task_events_v4;
DROP TABLE task_events_v4;
CREATE INDEX task_events_task_occurred_idx
ON task_events (task_id, occurred_at ASC, id ASC);
CREATE INDEX task_events_actor_user_idx
ON task_events (actor_user_id, occurred_at DESC, id DESC);
DROP TABLE lifecycle_requests;
DROP INDEX task_executions_device_heartbeat_idx;
DROP INDEX task_executions_active_task_idx;
DROP TABLE task_executions;
DROP INDEX purchase_tasks_active_device_idx;
ALTER TABLE purchase_tasks DROP COLUMN cancel_requested_by_user_id;
ALTER TABLE purchase_tasks DROP COLUMN cancel_requested_at;
ALTER TABLE purchase_tasks DROP COLUMN claim_expires_at;
ALTER TABLE purchase_tasks DROP COLUMN claim_issued_at;
ALTER TABLE purchase_tasks DROP COLUMN claim_token_hash;
ALTER TABLE purchase_tasks DROP COLUMN claim_generation;
ALTER TABLE purchase_tasks DROP COLUMN claimed_by_device_id;
ALTER TABLE purchase_tasks DROP COLUMN claimed_by_user_id;
ALTER TABLE devices DROP COLUMN readiness_reported_at;
ALTER TABLE devices DROP COLUMN pdd_installed;
ALTER TABLE devices DROP COLUMN accessibility_enabled;