Case Study — PartnerOS: a two-sided partnership marketplace (full-stack architecture)
What: a production-grade Next.js (App Router) + TypeScript + Supabase/Postgres application connecting rights-holders (sports/media properties) with brands, across the full deal lifecycle.
Role: sole architect & full-stack engineer · Where it lives: ~/PartnerOS/1
Prepared 2026-07-12
Honest status: the application is built to production standards and demoable end-to-end — real multi-tenant auth, a ~70-table Postgres schema with row-level security and audit triggers, 141 API routes, billing, background jobs, and observability. It is at the pre-customer / demo stage (see the IQI productionization notes); this case study describes the engineering and architecture, and makes no live-traffic or revenue claims.
1. Summary
PartnerOS is a two-sided marketplace with a full transactional spine. Rights-holders model their sellable inventory (sponsorship assets, packages) on properties; brands discover, match, and inquire; both sides move a deal through stages to signed contracts, payment, and post-deal performance — all multi-tenant, permissioned, and audited. On top sits an AI "intelligence" layer (scoring, enrichment, recommendations — documented separately in the Fabric case study).
The engineering story is breadth executed with discipline: a large surface (141 API routes, 70 tables) held together by consistent patterns — an RLS-first security model, a sequential migration history with triggers, a domain-service lib/ layer, and graceful env-gated degradation so every external dependency (Redis, AI, email) can be absent without breaking the app.
2. What it is — the domain
Two roles, one marketplace, a full lifecycle:
- Rights-holders define properties → inventory (assets) → packages, publish share links, and receive inbound interest.
- Brands build profiles/briefs/watchlists, get match results, and submit applications / inbound interest.
- A deal moves through deal-stages with proposals → contracts → signatures → payment → performance snapshots.
- Everything is scoped to an organization (multi-tenant), with members + roles (RBAC) and a plan tier (
free / pro / enterprise) carrying feature flags.
3. Architecture at a glance
┌───────────────────────────── Next.js (App Router, TypeScript) ─────────────────────────────┐
│ Front end: RSC + client components · Tailwind + class-variance-authority + Radix/shadcn │
│ ~20 route sections: marketplace, inventory, intelligence, org, onboarding, deals, share … │
├──────────────────────────────────────────────────────────────────────────────────────────────┤
│ API layer: 141 route handlers across ~30 domains + public /api/v1 (API keys) + webhooks │
├──────────────────────────────────────────────────────────────────────────────────────────────┤
│ lib/ domain services: intelligence · inventory · inngest · email · pdf · rate-limit · ops … │
├──────────────────────────────────────────────────────────────────────────────────────────────┤
│ Data: Supabase Postgres — 70 tables · Row-Level Security · audit triggers · 61 migrations │
└──────────────────────────────────────────────────────────────────────────────────────────────┘
Cross-cutting: Upstash Redis (rate limit) · Inngest (jobs) · Stripe (billing) · Resend (email)
Sentry (errors) · PostHog (analytics) · Vercel AI Gateway → Claude (intelligence)
Each layer degrades safely: if Upstash / AI / email env vars are absent, those helpers become no-ops rather than failures.
4. Front end
- Next.js App Router with a deliberate server/client component split — data-fetching and auth in server components, interactivity isolated to client islands.
- A disciplined design system: Tailwind through
class-variance-authority+tailwind-mergeover Radix/shadcn primitives (lucide-reacticons) — the pattern teams adopt specifically to keep a growing UI maintainable, not tutorial CSS. - ~20 route sections including
marketplace,inventory,intelligence,org,onboarding,deals/pipeline,brand,rights-holder,notifications, and publicsharepages backed by hardened share-link tokens. - Scale: 505
.ts+ 236.tsxfiles across the project.
5. API layer
- 141 route handlers across ~30 domains:
marketplace,inventory,intelligence,deal-stages,billing,applications,inbound,activations,onboarding,org,notifications,webhooks,cron,inngest, and more. - A public, versioned API (
/api/v1) authenticated byapi_keyswith per-tier rate limiting (lib/api/tier-rate-limit.ts) — the app is a platform, not just a UI. - Outbound webhooks (
webhooks+webhook_deliveries) and inbound webhooks (Stripe) are first-class.
6. Data model — ERD (core entities)
70 tables organize into seven clusters. organizations and profiles are the hubs (each referenced by ~28 foreign keys). Simplified core:
┌──────────────┐ ┌──────────────────────┐
auth.users ─1:1─▶ │ profiles │◀──────▶│ organizations │ plan_tier, feature_flags
└──────┬───────┘ member └───────────┬──────────┘
│ (organization_members: role) │
┌───────────────────────┼──────────────────────────────┼───────────────────────┐
▼ RIGHTS-HOLDER ▼ BRAND ▼ PLATFORM
┌────────────┐ ┌───────────────┐ ┌──────────────┐ ┌───────────┐
│ properties │ │ brand_profiles│ │ api_keys │ │ webhooks │
└─────┬──────┘ │ brand_briefs │ └──────────────┘ │ + deliveries
│ 1:N │ brand_watchlist ┌──────────────┐ └───────────┘
┌─────▼──────┐ └───────┬───────┘ │ notifications│
│ inventory │◀───┐ │ │ + preferences│
└─────┬──────┘ │ package_items └──────────────┘
│ 1:N │ │ MARKETPLACE / MATCHING
┌─────▼──────────┐│ ┌──────▼─────────┐ ┌──────────────────┐
│ inventory_ ││ │ match_results │ │ inbound_interest │◀─ (15 FKs: the demand hub)
│ packages ││ │ (matching_ │ │ partner_apps │
└────────────────┘│ │ weights) │ └────────┬─────────┘
┌────────────────┐│ └────────────────┘ │
│ inventory_ ││ ▼ DEAL LIFECYCLE
│ share_links ││ ┌───────┐ ┌────────────┐ ┌──────────────┐ ┌───────────────────┐
└────────────────┘│ │ deals │─▶│ deal_stages│ │deal_proposals│ │ deal_performance │
│ └───┬───┘ └────────────┘ │deal_contracts│ │ (+ snapshots) │
│ │ ┌────────────┐ │contract_sigs │ └───────────────────┘
│ └──────▶│deal_documents │ payment_events (Stripe) │
│ └────────────┘ └──────────────┘
│ INTELLIGENCE / IQI (see Fabric case study)
│ property_intelligence_profiles · iqi_snapshots · fact_ledger
└──▶ agent_runs · external_sources · review_queue · valuation_estimates
price_recommendations · calibration_metrics · provenance_events
Cluster → representative tables:
| Cluster | Tables (sample) |
|---|---|
| Identity & tenancy | profiles, organizations, organization_members, org_invites |
| Rights-holder supply | properties, inventory, inventory_packages, inventory_package_items, inventory_share_links, inventory_audit_log |
| Brand demand | brand_profiles, brand_briefs, brand_watchlist, brand_match_alerts |
| Marketplace & matching | match_results, matching_weights, partner_applications, inbound_interest, target_lists |
| Deal lifecycle | deals, deal_stages, deal_proposals, deal_contracts, contract_signatures, deal_documents, deal_performance(_snapshots), payment_events |
| Intelligence / IQI | property_intelligence_profiles, iqi_snapshots, fact_ledger, agent_runs, external_sources, review_queue, valuation_estimates, price_recommendations, calibration_metrics |
| Platform & compliance | api_keys, webhooks, webhook_deliveries, notifications, notification_preferences, consent_log, deletion_requests, cron_run_log, audit triggers |
7. Backend systems
7.1 Auth & multi-tenancy
Supabase Auth (@supabase/ssr) with profiles 1:1 on auth.users. Tenancy is organizations + organization_members with roles (RBAC) and a dual-role model (an org can be both rights-holder and brand). plan_tier (free/pro/enterprise) + feature_flags JSONB gate capabilities.
7.2 Security — RLS-first
Row-Level Security is the primary authorization boundary (migration 016_rls_owner_id), backed by database audit triggers (010, 015), integrity constraints (008), and a compliance layer (consent_log, deletion_requests, share_link_hardening). Authorization lives in the database, not just the app — the hard-to-bypass place.
7.3 Rate limiting (Upstash Redis)
@upstash/ratelimit sliding windows tuned per surface — share-token reads 60/min, inquiries 5/hr, inquiry emails 3/hr — plus per-tier limits on the public API. Limiters are lazily constructed and no-op when Redis env vars are absent (graceful degradation by design).
7.4 Background jobs (Inngest)
Event-driven durable functions — e.g. recompute-iqi-on-owned-event and refresh-iqi-on-demand-external — decouple expensive work (score recompute, external enrichment) from the request path.
7.5 Billing (Stripe)
Stripe integration with inbound webhooks recorded to payment_events, plus deal_contracts / contract_signatures for the agreement flow.
7.6 Email, notifications, documents
Resend for transactional email (email_notifications_log); a notifications + notification_preferences system; and a Node.js document-generation path (pptxgenjs, docx, @react-pdf/renderer) producing generated_deliverables (decks, contracts, reports).
7.7 Observability & ops
Sentry (@sentry/nextjs) for errors, PostHog (posthog-js) for product analytics, a cron_run_log for scheduled-job auditing, health endpoints, and an alerting.ts ops helper.
8. Cross-cutting engineering patterns
- Graceful, env-gated degradation — Redis, AI, and email helpers all no-op cleanly when unconfigured. Absent infra never crashes the app; it just turns off that capability.
- Migration discipline — 61 sequential, numbered migrations with reusable triggers (
set_updated_at), integrity constraints, and audit triggers. The schema has a legible, reviewable history. - Security in the database — RLS + audit triggers + constraints put authorization and integrity where they can't be bypassed by an app bug.
- Domain-service
lib/layer —intelligence,inventory,inngest,email,pdf,rate-limit,opskeep route handlers thin and logic testable. - Platform-shaped from day one — versioned public API, API keys, per-tier limits, and outbound webhooks mean it's extensible by third parties, not a closed UI.
- Docs-as-code — a
docs/solution-architecture/set records the design decisions (the IQI trust invariants, productionization plan) alongside the code.
9. Outcomes & what it demonstrates
- True full-stack ownership — from a ~70-table RLS-secured Postgres schema, through 141 API routes and a domain-service layer, to a Radix/Tailwind design system and public API — designed and built solo.
- Production instincts, not demo shortcuts — auth + tenancy + RBAC, rate limiting, background jobs, billing, observability, compliance, and audit are all present. These are exactly the parts most projects skip.
- Systems coherence at scale — a large surface stays maintainable through consistent patterns (RLS-first, migration discipline, graceful degradation, thin routes).
- Honest maturity — production-grade build at pre-customer stage, with the hardening/eval backlog written down rather than glossed over.
Net: evidence of an engineer who can own a complex product end-to-end — data model to deploy — and keep it coherent.
10. What's next
- Publish a rendered ERD diagram (Mermaid/dbdiagram) generated from the migration set.
- A short auth-and-RLS deep-dive (the tenancy + policy model) as a security exemplar.
- A deploy-pipeline + observability write-up (ties into the Cloud/DevOps brief).
- Extract a reusable component-library sample from the
cva/Radix system.
Appendix · Stack & code map
| Layer | Technology |
|---|---|
| Framework | Next.js (App Router), React, TypeScript |
| UI | Tailwind CSS, class-variance-authority, tailwind-merge, Radix/shadcn, lucide-react |
| Data | Supabase (Postgres, Auth, RLS), 61 SQL migrations |
| Caching / limits | Upstash Redis + @upstash/ratelimit |
| Jobs | Inngest (durable, event-driven) |
| Payments | Stripe (+ webhooks → payment_events) |
| Resend | |
| Docs | pptxgenjs, docx, @react-pdf/renderer |
| AI | Vercel AI Gateway → Anthropic Claude (see Fabric case study) |
| Observability | Sentry, PostHog, cron_run_log, health endpoints |
| Path | Responsibility |
|---|---|
app/** |
App Router pages + ~20 route sections |
app/api/** (141 routes) |
REST handlers incl. /api/v1 public API + webhooks + cron + inngest |
lib/rate-limit.ts, lib/api/tier-rate-limit.ts |
Upstash sliding-window + per-tier limits |
lib/inngest/* |
Durable background functions |
lib/intelligence/* |
AI/IQI domain (Fabric) |
lib/pdf/*, lib/email/* |
Document generation, transactional email |
lib/supabase*.ts |
Server/browser Supabase clients |
supabase/migrations/* (61) |
Schema, RLS, triggers, constraints |
docs/solution-architecture/* |
Architecture + productionization docs |
Grounded entirely in the code and schema on file in ~/PartnerOS/1 as read on 2026-07-12 (route/table/migration counts and rate-limit windows are from the repository). Status statements reflect the repo's own productionization notes; no live-traffic or revenue claims are made.