CREATE TABLE IF NOT EXISTS users ( id text PRIMARY KEY, email text NOT NULL UNIQUE, password_hash text NOT NULL, name text NOT NULL, avatar_url text, created_at timestamptz NOT NULL DEFAULT now() ); CREATE TABLE IF NOT EXISTS albums ( id text PRIMARY KEY, owner_id text NOT NULL REFERENCES users(id) ON DELETE CASCADE, title text NOT NULL, description text NOT NULL DEFAULT '', cover_photo_id text, created_at timestamptz NOT NULL DEFAULT now(), updated_at timestamptz NOT NULL DEFAULT now() ); CREATE TABLE IF NOT EXISTS album_members ( album_id text NOT NULL REFERENCES albums(id) ON DELETE CASCADE, user_id text NOT NULL REFERENCES users(id) ON DELETE CASCADE, role text NOT NULL, invited_by text REFERENCES users(id) ON DELETE SET NULL, joined_at timestamptz NOT NULL DEFAULT now(), PRIMARY KEY (album_id, user_id) ); CREATE TABLE IF NOT EXISTS invites ( id text PRIMARY KEY, album_id text NOT NULL REFERENCES albums(id) ON DELETE CASCADE, token text NOT NULL UNIQUE, role text NOT NULL, created_by text NOT NULL REFERENCES users(id) ON DELETE CASCADE, expires_at timestamptz NOT NULL, max_uses integer NOT NULL DEFAULT 1, uses integer NOT NULL DEFAULT 0, created_at timestamptz NOT NULL DEFAULT now() ); CREATE TABLE IF NOT EXISTS photos ( id text PRIMARY KEY, album_id text NOT NULL REFERENCES albums(id) ON DELETE CASCADE, uploader_id text NOT NULL REFERENCES users(id) ON DELETE CASCADE, storage_key text NOT NULL, thumb_key text, preview_key text, original_filename text NOT NULL DEFAULT '', size bigint NOT NULL DEFAULT 0, width integer NOT NULL DEFAULT 0, height integer NOT NULL DEFAULT 0, mime_type text NOT NULL DEFAULT '', taken_at timestamptz, latitude double precision, longitude double precision, caption text NOT NULL DEFAULT '', created_at timestamptz NOT NULL DEFAULT now() ); CREATE INDEX IF NOT EXISTS photos_album_created_idx ON photos (album_id, created_at DESC, id DESC); CREATE TABLE IF NOT EXISTS comments ( id text PRIMARY KEY, photo_id text NOT NULL REFERENCES photos(id) ON DELETE CASCADE, user_id text NOT NULL REFERENCES users(id) ON DELETE CASCADE, text text NOT NULL, created_at timestamptz NOT NULL DEFAULT now(), updated_at timestamptz NOT NULL DEFAULT now() ); CREATE INDEX IF NOT EXISTS comments_photo_idx ON comments (photo_id, created_at); CREATE TABLE IF NOT EXISTS likes ( photo_id text NOT NULL REFERENCES photos(id) ON DELETE CASCADE, user_id text NOT NULL REFERENCES users(id) ON DELETE CASCADE, created_at timestamptz NOT NULL DEFAULT now(), PRIMARY KEY (photo_id, user_id) ); CREATE TABLE IF NOT EXISTS notifications ( id text PRIMARY KEY, user_id text NOT NULL REFERENCES users(id) ON DELETE CASCADE, type text NOT NULL, payload_json jsonb NOT NULL DEFAULT '{}'::jsonb, read_at timestamptz, created_at timestamptz NOT NULL DEFAULT now() ); CREATE INDEX IF NOT EXISTS notifications_user_idx ON notifications (user_id, created_at DESC); CREATE TABLE IF NOT EXISTS share_links ( id text PRIMARY KEY, album_id text NOT NULL REFERENCES albums(id) ON DELETE CASCADE, token text NOT NULL UNIQUE, created_by text NOT NULL REFERENCES users(id) ON DELETE CASCADE, created_at timestamptz NOT NULL DEFAULT now(), expires_at timestamptz NOT NULL, revoked_at timestamptz ); CREATE TABLE IF NOT EXISTS refresh_tokens ( id text PRIMARY KEY, user_id text NOT NULL REFERENCES users(id) ON DELETE CASCADE, token_hash text NOT NULL UNIQUE, expires_at timestamptz NOT NULL, revoked_at timestamptz, created_at timestamptz NOT NULL DEFAULT now() ); CREATE INDEX IF NOT EXISTS refresh_tokens_user_idx ON refresh_tokens (user_id);