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).

ColumnTypeNotes
platformtexttiktok | instagram | youtube
external_idtextPlatform-native ID
author_handle / caption / cover_urltextDisplay data
views, likes, comments, shares, collectsbigintMetrics
viral_scoreint50–99, batch-normalized
engagement_ratenumericWeighted engagement
nichetextKeyword that sourced it (nullable)
posted_at / fetched_attimestamptzFreshness + velocity
rawjsonbFull 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 through is_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 matching auth.jwt() ->> 'email'.