Files
cmroubao/backend-api/migrations/00002_tasks_and_assets.sql

145 lines
4.4 KiB
SQL

-- +goose Up
CREATE TABLE assets (
id TEXT PRIMARY KEY NOT NULL
CHECK (length(id) = 36),
creator_subject TEXT NOT NULL
CHECK (length(trim(creator_subject)) > 0),
purpose TEXT NOT NULL
CHECK (purpose IN ('TASK_REFERENCE')),
media_type TEXT NOT NULL
CHECK (media_type = 'image/jpeg'),
size_bytes INTEGER NOT NULL
CHECK (size_bytes > 0),
sha256 TEXT NOT NULL
CHECK (
length(sha256) = 64
AND sha256 NOT GLOB '*[^0-9a-f]*'
),
storage_key TEXT NOT NULL UNIQUE
CHECK (
length(storage_key) > 0
AND substr(storage_key, 1, 1) <> '/'
AND instr(storage_key, '\') = 0
AND instr(storage_key, '..') = 0
),
created_at TEXT NOT NULL
);
CREATE INDEX assets_creator_created_idx
ON assets (creator_subject, created_at DESC, id DESC);
CREATE TABLE purchase_tasks (
id TEXT PRIMARY KEY NOT NULL
CHECK (length(id) = 36),
creator_subject TEXT NOT NULL
CHECK (length(trim(creator_subject)) > 0),
source_ref TEXT
CHECK (
source_ref IS NULL
OR (
length(trim(source_ref)) > 0
AND length(CAST(source_ref AS BLOB)) <= 256
)
),
title TEXT NOT NULL
CHECK (
length(trim(title)) > 0
AND length(title) <= 120
AND length(CAST(title AS BLOB)) <= 2048
),
description TEXT NOT NULL
CHECK (length(CAST(description AS BLOB)) <= 8192),
sku TEXT NOT NULL
CHECK (
length(trim(sku)) > 0
AND length(CAST(sku AS BLOB)) <= 512
),
image_asset_id TEXT NOT NULL UNIQUE
REFERENCES assets(id) ON UPDATE RESTRICT ON DELETE RESTRICT,
quantity INTEGER NOT NULL
CHECK (quantity > 0),
max_budget_cents INTEGER
CHECK (max_budget_cents IS NULL OR max_budget_cents > 0),
currency TEXT NOT NULL DEFAULT 'CNY'
CHECK (currency = 'CNY'),
status TEXT NOT NULL
CHECK (
status IN (
'PENDING',
'CLAIMED',
'RUNNING',
'WAITING_CONFIRMATION',
'SUCCEEDED',
'FAILED',
'CANCELED'
)
),
version INTEGER NOT NULL
CHECK (version > 0),
cancel_reason TEXT
CHECK (
cancel_reason IS NULL
OR length(CAST(cancel_reason AS BLOB)) <= 500
),
canceled_at TEXT,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL
);
CREATE UNIQUE INDEX purchase_tasks_creator_source_ref_idx
ON purchase_tasks (creator_subject, source_ref)
WHERE source_ref IS NOT NULL;
CREATE INDEX purchase_tasks_creator_created_idx
ON purchase_tasks (creator_subject, created_at DESC, id DESC);
CREATE INDEX purchase_tasks_creator_status_created_idx
ON purchase_tasks (
creator_subject,
status,
created_at DESC,
id DESC
);
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
);
CREATE INDEX task_events_task_occurred_idx
ON task_events (task_id, occurred_at ASC, id ASC);
CREATE TABLE idempotency_records (
creator_subject TEXT NOT NULL,
operation TEXT NOT NULL,
idempotency_key TEXT NOT NULL,
request_sha256 TEXT NOT NULL
CHECK (
length(request_sha256) = 64
AND request_sha256 NOT GLOB '*[^0-9a-f]*'
),
resource_type TEXT NOT NULL
CHECK (resource_type IN ('ASSET', 'PURCHASE_TASK')),
resource_id TEXT NOT NULL
CHECK (length(resource_id) = 36),
created_at TEXT NOT NULL,
PRIMARY KEY (creator_subject, operation, idempotency_key)
);
-- +goose Down
DROP TABLE IF EXISTS idempotency_records;
DROP INDEX IF EXISTS task_events_task_occurred_idx;
DROP TABLE IF EXISTS task_events;
DROP INDEX IF EXISTS purchase_tasks_creator_status_created_idx;
DROP INDEX IF EXISTS purchase_tasks_creator_created_idx;
DROP INDEX IF EXISTS purchase_tasks_creator_source_ref_idx;
DROP TABLE IF EXISTS purchase_tasks;
DROP INDEX IF EXISTS assets_creator_created_idx;
DROP TABLE IF EXISTS assets;