← Back to portfolio
Full-Stack Web Development · Case Study

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:


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


5. API layer


6. Data model — ERD (core entities)

PartnerOS core data model: 70 tables and 92 foreign keys grouped into Identity & Tenancy, Rights-holder Supply, Marketplace Deal Spine, Brand Demand, Deal Lifecycle, Intelligence/IQI, and Platform & Compliance clusters — with inbound_interest as the deal spine.
Core entities, generated from the real foreign-key graph (70 tables · 92 FKs). Full text version below.

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

  1. 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.
  2. Migration discipline — 61 sequential, numbered migrations with reusable triggers (set_updated_at), integrity constraints, and audit triggers. The schema has a legible, reviewable history.
  3. Security in the database — RLS + audit triggers + constraints put authorization and integrity where they can't be bypassed by an app bug.
  4. Domain-service lib/ layerintelligence, inventory, inngest, email, pdf, rate-limit, ops keep route handlers thin and logic testable.
  5. 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.
  6. 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

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

  1. Publish a rendered ERD diagram (Mermaid/dbdiagram) generated from the migration set.
  2. A short auth-and-RLS deep-dive (the tenancy + policy model) as a security exemplar.
  3. A deploy-pipeline + observability write-up (ties into the Cloud/DevOps brief).
  4. 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)
Email 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.