CREATE TABLE dumps ( id TEXT PRIMARY KEY, kind TEXT NOT NULL, title TEXT NOT NULL, comment TEXT, user_id TEXT NOT NULL, created_at TEXT NOT NULL, updated_at TEXT, url TEXT, slug TEXT, rich_content TEXT, file_name TEXT, file_mime TEXT, file_size INTEGER, vote_count INTEGER NOT NULL DEFAULT 0, is_private INTEGER NOT NULL DEFAULT 0, FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE ); CREATE TABLE users ( id TEXT PRIMARY KEY, username TEXT NOT NULL UNIQUE, password_hash TEXT NOT NULL, is_admin INTEGER NOT NULL DEFAULT 0, created_at TEXT NOT NULL, updated_at TEXT, avatar_mime TEXT, description TEXT, invited_by TEXT REFERENCES users(id), email TEXT NOT NULL ); CREATE TABLE votes ( dump_id TEXT NOT NULL, user_id TEXT NOT NULL, created_at TEXT NOT NULL, PRIMARY KEY (dump_id, user_id), FOREIGN KEY (dump_id) REFERENCES dumps(id) ON DELETE CASCADE, FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE ); CREATE TABLE playlists ( id TEXT PRIMARY KEY, user_id TEXT NOT NULL, title TEXT NOT NULL, slug TEXT, description TEXT, is_public INTEGER NOT NULL DEFAULT 1, created_at TEXT NOT NULL, updated_at TEXT, image_mime TEXT, FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE ); CREATE TABLE playlist_dumps ( playlist_id TEXT NOT NULL, dump_id TEXT NOT NULL, position INTEGER NOT NULL, added_at TEXT NOT NULL, PRIMARY KEY (playlist_id, dump_id), FOREIGN KEY (playlist_id) REFERENCES playlists(id) ON DELETE CASCADE, FOREIGN KEY (dump_id) REFERENCES dumps(id) ON DELETE CASCADE ); CREATE TABLE comments ( id TEXT PRIMARY KEY, dump_id TEXT NOT NULL, user_id TEXT NOT NULL, parent_id TEXT, body TEXT NOT NULL, created_at TEXT NOT NULL, updated_at TEXT, deleted INTEGER NOT NULL DEFAULT 0, FOREIGN KEY (dump_id) REFERENCES dumps(id) ON DELETE CASCADE, FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE, FOREIGN KEY (parent_id) REFERENCES comments(id) ON DELETE CASCADE ); CREATE INDEX idx_dumps_user ON dumps(user_id); CREATE INDEX idx_votes_user ON votes(user_id); CREATE INDEX idx_playlists_user ON playlists(user_id); CREATE INDEX idx_playlist_dumps_order ON playlist_dumps(playlist_id, position); CREATE INDEX idx_playlist_dumps_dump ON playlist_dumps(dump_id); CREATE INDEX idx_comments_dump ON comments(dump_id, created_at); CREATE TABLE follows ( id TEXT PRIMARY KEY, follower_id TEXT NOT NULL, followed_user_id TEXT, followed_playlist_id TEXT, created_at TEXT NOT NULL, FOREIGN KEY (follower_id) REFERENCES users(id) ON DELETE CASCADE, FOREIGN KEY (followed_user_id) REFERENCES users(id) ON DELETE CASCADE, FOREIGN KEY (followed_playlist_id) REFERENCES playlists(id) ON DELETE CASCADE, CHECK ( (followed_user_id IS NOT NULL AND followed_playlist_id IS NULL) OR (followed_user_id IS NULL AND followed_playlist_id IS NOT NULL) ) ); CREATE UNIQUE INDEX idx_follows_user ON follows(follower_id, followed_user_id) WHERE followed_user_id IS NOT NULL; CREATE UNIQUE INDEX idx_follows_playlist ON follows(follower_id, followed_playlist_id) WHERE followed_playlist_id IS NOT NULL; CREATE INDEX idx_follows_follower ON follows(follower_id); CREATE TABLE invites ( token TEXT PRIMARY KEY, inviter_id TEXT NOT NULL, used_at TEXT, created_at TEXT NOT NULL, FOREIGN KEY (inviter_id) REFERENCES users(id) ON DELETE CASCADE ); CREATE TABLE attachments ( id TEXT PRIMARY KEY, resource_id TEXT, mime TEXT NOT NULL, created_at TEXT NOT NULL ); CREATE INDEX idx_attachments_resource ON attachments(resource_id); CREATE TABLE notifications ( id TEXT PRIMARY KEY, user_id TEXT NOT NULL, type TEXT NOT NULL, data TEXT NOT NULL, read INTEGER NOT NULL DEFAULT 0, created_at TEXT NOT NULL, source_key TEXT, FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE ); CREATE INDEX idx_notifications_user ON notifications(user_id, created_at); CREATE UNIQUE INDEX idx_notifications_dedup ON notifications(user_id, source_key) WHERE source_key IS NOT NULL;