Skip to main content

Database & Migrations

The schema lives in supabase/migrations/ as plain SQL. apps/api only maps existing tables (app/models/db.py) and views (app/services/discovery_repo.py); nothing in the app creates or alters the schema.

Tables

Users themselves live in Supabase Auth (auth.users). User-owned rows reference auth.users.id with on delete cascade, so deleting the auth user removes their data. Account deletion goes through the Supabase Auth Admin API (DELETE /auth/v1/admin/users/{id}) using the service role key. That removes the auth.users row and cascades to app.* rows via the foreign key, and it keeps the API’s Postgres role from needing write access to auth.users.

Discovery layer

The read endpoints never query the normalized graph directly. The discovery schema exposes one denormalized view, discovery.companies, that folds the graph joins, merged_into_id filtering, and 180-day signal aggregation into a single table. The API exposes it as typed, read-only resources: These are plain SELECTs against the view — typed Pydantic models all the way through — not a raw-SQL query endpoint. The list endpoint paginates with a keyset cursor (next_cursor), not LIMIT/OFFSET, so deep pages stay index-friendly as the graph grows.

Growth velocity timeseries

Metric observations are appended to app.signals_timeseries in Postgres. The API computes rolling growth velocity directly from this table: Supported metric_type values: github_stars, github_forks, commit_count, headcount, open_jobs_count, website_traffic_rank, twitter_followers, devpost_likes. The query aggregates the earliest and latest values per organization in the window to compute absolute growth and growth percentage.

Access model

  • app is backend-only: anon/authenticated are revoked, RLS is enabled with no policies (default deny), and only service_role has grants.
  • apps/api connects as the postgres role, which bypasses RLS. User-owned queries (app.profiles) filter by the verified user id; discovery queries read from the discovery.companies view.

Applying migrations

Or paste the file into the Supabase SQL editor. It is idempotent and safe to re-run.

Connecting

Point POSTGRES_* at the Supabase session pooler (port 5432), not the transaction pooler (6543), which breaks prepared statements. SUPABASE_URL must be set for token verification. See Configuration.