Database schema
Six tables, all in public, all with Row Level Security enabled. Migrations live in supabase/migrations/.
videos
Every scraped video, scored. Unique on (platform, external_id).
| Column | Type | Notes |
|---|---|---|
| platform | text | tiktok | instagram | youtube |
| external_id | text | Platform-native ID |
| author_handle / caption / cover_url | text | Display data |
| views, likes, comments, shares, collects | bigint | Metrics |
| viral_score | int | 50–99, batch-normalized |
| engagement_rate | numeric | Weighted engagement |
| niche | text | Keyword that sourced it (nullable) |
| posted_at / fetched_at | timestamptz | Freshness + velocity |
| raw | jsonb | Full source response (nothing lost) |
RLS: any authenticated user can read.
scrape_runs
Audit log of every pipeline run: platform, endpoint, items, credits, status, error, ran_at.
saved_ideas
Per-user idea drafts from Generate. RLS: users select/insert/delete only their own rows (auth.uid() = user_id).
user_credits
One row per user: plan, monthly_allowance, used, cycle_start. Reads via RLS; writes only via the service role (server-side credit engine).
credit_transactions
Append-only ledger of every credit movement, per user.
folders, folder_videos, folder_shares
Named collections with email-based sharing:
folders— owner + name. Shared reads go throughis_folder_shared_with(), a SECURITY DEFINER function that checks shares without triggering RLS recursion.folder_videos— (folder_id, video_id) pairs, cascade on delete.folder_shares— folder_id + shared_with_email. Recipients get read-only access by matchingauth.jwt() ->> 'email'.