CREATE TABLE users ( id TEXT PRIMARY KEY, email TEXT UNIQUE, name TEXT, image_url TEXT, role TEXT NOT NULL DEFAULT 'user' CHECK (role IN ('user', 'admin')), created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')), updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')) ); CREATE TABLE oauth_accounts ( id TEXT PRIMARY KEY, user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE, provider TEXT NOT NULL CHECK (provider IN ('google', 'github', 'apple')), provider_account_id TEXT NOT NULL, created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')), UNIQUE(provider, provider_account_id) ); CREATE TABLE sessions ( id TEXT PRIMARY KEY, user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE, session_token_hash TEXT NOT NULL UNIQUE, expires_at TEXT NOT NULL, created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')) ); CREATE TABLE links ( id TEXT PRIMARY KEY, scope TEXT NOT NULL CHECK (scope IN ('public', 'private')), owner_user_id TEXT REFERENCES users(id) ON DELETE CASCADE, alias TEXT NOT NULL, link_type TEXT NOT NULL CHECK (link_type IN ('redirect', 'custom')), target_url TEXT, content_markdown TEXT, description TEXT, status TEXT NOT NULL DEFAULT 'active' CHECK (status IN ('active', 'archived', 'deleted')), click_count INTEGER NOT NULL DEFAULT 0, created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')), updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')), CHECK ( (scope = 'public' AND owner_user_id IS NULL) OR (scope = 'private' AND owner_user_id IS NOT NULL) ), CHECK ( (link_type = 'redirect' AND target_url IS NOT NULL) OR (link_type = 'custom' AND content_markdown IS NOT NULL) ) ); CREATE UNIQUE INDEX links_public_alias_unique ON links(alias) WHERE scope = 'public' AND status != 'deleted'; CREATE UNIQUE INDEX links_private_owner_alias_unique ON links(owner_user_id, alias) WHERE scope = 'private' AND status != 'deleted'; CREATE INDEX links_owner_idx ON links(owner_user_id, updated_at DESC); CREATE INDEX links_scope_updated_idx ON links(scope, updated_at DESC); CREATE TABLE tags ( id TEXT PRIMARY KEY, slug TEXT NOT NULL UNIQUE, name TEXT NOT NULL, created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')) ); CREATE TABLE link_tags ( link_id TEXT NOT NULL REFERENCES links(id) ON DELETE CASCADE, tag_id TEXT NOT NULL REFERENCES tags(id) ON DELETE CASCADE, PRIMARY KEY (link_id, tag_id) ); CREATE TABLE promotion_submissions ( id TEXT PRIMARY KEY, private_link_id TEXT NOT NULL REFERENCES links(id) ON DELETE CASCADE, submitted_by_user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE, proposed_alias TEXT NOT NULL, note TEXT, status TEXT NOT NULL DEFAULT 'pending' CHECK (status IN ('pending', 'approved', 'rejected', 'needs_changes')), reviewed_by_user_id TEXT REFERENCES users(id), rejection_reason TEXT, public_link_id TEXT REFERENCES links(id), created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')), reviewed_at TEXT ); CREATE INDEX promotion_status_idx ON promotion_submissions(status, created_at DESC); CREATE TABLE click_daily ( link_id TEXT NOT NULL REFERENCES links(id) ON DELETE CASCADE, day TEXT NOT NULL, count INTEGER NOT NULL DEFAULT 0, PRIMARY KEY (link_id, day) );