-- +goose Up CREATE TABLE users ( id TEXT PRIMARY KEY NOT NULL CHECK (length(id) = 36), username TEXT NOT NULL UNIQUE CHECK ( length(trim(username)) > 0 AND username = lower(trim(username)) AND length(CAST(username AS BLOB)) <= 128 ), password_hash TEXT NOT NULL CHECK ( length(password_hash) > 0 AND length(CAST(password_hash AS BLOB)) <= 255 ), role TEXT NOT NULL CHECK (role IN ('ADMIN', 'BUYER')), is_active INTEGER NOT NULL CHECK (is_active IN (0, 1)), created_at TEXT NOT NULL, updated_at TEXT NOT NULL ); CREATE TABLE devices ( id TEXT PRIMARY KEY NOT NULL CHECK (length(id) = 36), name TEXT NOT NULL CHECK ( length(trim(name)) > 0 AND length(CAST(name AS BLOB)) <= 128 ), token_hash TEXT NOT NULL UNIQUE CHECK ( length(token_hash) = 64 AND token_hash NOT GLOB '*[^0-9a-f]*' ), bound_user_id TEXT REFERENCES users(id) ON UPDATE RESTRICT ON DELETE RESTRICT, app_version TEXT CHECK ( app_version IS NULL OR ( length(trim(app_version)) > 0 AND length(CAST(app_version AS BLOB)) <= 128 ) ), android_version TEXT CHECK ( android_version IS NULL OR ( length(trim(android_version)) > 0 AND length(CAST(android_version AS BLOB)) <= 128 ) ), pdd_version TEXT CHECK ( pdd_version IS NULL OR ( length(trim(pdd_version)) > 0 AND length(CAST(pdd_version AS BLOB)) <= 128 ) ), last_seen_at TEXT, is_enabled INTEGER NOT NULL CHECK (is_enabled IN (0, 1)), created_at TEXT NOT NULL, updated_at TEXT NOT NULL ); CREATE INDEX devices_bound_user_idx ON devices (bound_user_id); CREATE TABLE admin_sessions ( id TEXT PRIMARY KEY NOT NULL CHECK (length(id) = 36), token_hash TEXT NOT NULL UNIQUE CHECK ( length(token_hash) = 64 AND token_hash NOT GLOB '*[^0-9a-f]*' ), user_id TEXT NOT NULL REFERENCES users(id) ON UPDATE RESTRICT ON DELETE CASCADE, expires_at TEXT NOT NULL, revoked_at TEXT, created_at TEXT NOT NULL ); CREATE INDEX admin_sessions_user_expires_idx ON admin_sessions (user_id, expires_at); CREATE TABLE access_tokens ( id TEXT PRIMARY KEY NOT NULL CHECK (length(id) = 36), token_hash TEXT NOT NULL UNIQUE CHECK ( length(token_hash) = 64 AND token_hash NOT GLOB '*[^0-9a-f]*' ), user_id TEXT NOT NULL REFERENCES users(id) ON UPDATE RESTRICT ON DELETE CASCADE, device_id TEXT NOT NULL REFERENCES devices(id) ON UPDATE RESTRICT ON DELETE CASCADE, expires_at TEXT NOT NULL, revoked_at TEXT, created_at TEXT NOT NULL ); CREATE INDEX access_tokens_user_device_expires_idx ON access_tokens (user_id, device_id, expires_at); ALTER TABLE purchase_tasks ADD COLUMN created_by_user_id TEXT REFERENCES users(id) ON UPDATE RESTRICT ON DELETE RESTRICT; ALTER TABLE task_events ADD COLUMN actor_user_id TEXT REFERENCES users(id) ON UPDATE RESTRICT ON DELETE RESTRICT; CREATE INDEX purchase_tasks_created_by_user_idx ON purchase_tasks (created_by_user_id, created_at DESC, id DESC); CREATE INDEX task_events_actor_user_idx ON task_events (actor_user_id, occurred_at DESC, id DESC); -- +goose Down DROP INDEX IF EXISTS task_events_actor_user_idx; DROP INDEX IF EXISTS purchase_tasks_created_by_user_idx; ALTER TABLE task_events DROP COLUMN actor_user_id; ALTER TABLE purchase_tasks DROP COLUMN created_by_user_id; DROP INDEX IF EXISTS access_tokens_user_device_expires_idx; DROP TABLE IF EXISTS access_tokens; DROP INDEX IF EXISTS admin_sessions_user_expires_idx; DROP TABLE IF EXISTS admin_sessions; DROP INDEX IF EXISTS devices_bound_user_idx; DROP TABLE IF EXISTS devices; DROP TABLE IF EXISTS users;