Data model
The schema lives in a single file, prisma/schema.prisma, against Supabase PostgreSQL. The generated
client is emitted to prisma/generated and imported as @/prisma/generated/client.
generator client {
provider = "prisma-client-js"
output = "generated"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL") // pooled connection, used at runtime
directUrl = env("DIRECT_URL") // direct connection, used by migrations
}
Conventions
| Convention | Detail |
|---|---|
Primary keys |
|
Timestamps |
|
Scheduling columns |
|
Table naming |
Newer models use |
Field naming |
|
Soft deletes |
Boolean flags ( |
Flexible data |
|
Tenancy |
|
|
Table naming is inconsistent. |
Model inventory
| Model | Table | Page |
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Enums are catalogued on Enums.
High-level relationships
erDiagram
Subscription ||--o{ SubscriptionUser : members
Profile ||--o{ SubscriptionUser : memberships
Subscription ||--o{ Campaign : owns
Subscription ||--o{ Contact : owns
Campaign ||--o{ CampaignTarget : "call targets"
Campaign ||--o{ EmailCampaignTarget : "email recipients"
Campaign ||--o{ EmailVariation : variations
CampaignTarget ||--o{ Call : attempts
Call ||--o{ CallAiAgentAnalysisResult : analyses
Call ||--o{ WebhookEvent : events
PhoneNumbers ||--o{ Call : "dialed from"
AiAgent ||--o{ AiAgentVersion : versions
AiAgentVersion ||--o{ Call : "spoke on"
AiAgentVersion ||--o{ CallAiAgentAnalysisResult : "graded by"
Seller ||--o{ Contact : employs
Contact ||--o{ TargetPhoneNumbers : "phone endpoints"
Contact ||--o{ TargetEmails : "email endpoints"
Contact ||--o{ CampaignTarget : "dialer enrollment"
Contact ||--o{ EmailCampaignTarget : "email enrollment"
Contact ||--o{ ContactSequence : enrollments
Contact ||--o{ Activity : interactions
Contact ||--o{ FollowUpTask : tasks
Domain ||--o{ EmailSendingDomain : accounts
EmailSendingDomain ||--o{ Emails : "sent/received"
EmailCampaignTarget ||--o{ Emails : messages
SequenceTemplate ||--o{ SequenceTemplateStep : steps
SequenceTemplate ||--o{ ContactSequence : enrollments
ContactSequence ||--o{ ContactSequenceStep : "step log"
SalesRep ||--o{ FollowUpTask : assigned
Subscription ||--o{ Segment : owns
Segment ||--o{ SegmentMembership : members
Segment ||--o{ SegmentStrategy : methodologies
Segment ||--o{ SegmentRun : runs
SegmentStrategy ||--o{ SegmentRun : "applied by"
SegmentStrategy ||--o{ ReengagementDailyListEntry : "picked under"
SegmentRun ||--o{ ReengagementDailyListEntry : produced
Segment ||--o{ ReengagementDailyListEntry : sources
Contact ||--o{ SegmentMembership : "segment membership"
Contact ||--o{ ReengagementDailyListEntry : "daily picks"
SalesRep ||--o{ ReengagementDailyListEntry : "assigned picks"
Migrations
pnpm prisma migrate dev --name descriptive_name # create + apply locally
pnpm prisma migrate deploy # apply in an environment
pnpm prisma generate # regenerate the client
pnpm prisma format # format the schema
pnpm prisma studio # browse data
The repository also defines composite scripts: pnpm prisma:migrate runs
format && db push && generate (schema-push, no migration file — development only), and
pnpm prisma:migrate:dev runs format && migrate dev && generate.
Migrations live in prisma/migrations/, named <timestamp>_<slug>, with migration_lock.toml
pinning the provider. Do not edit an applied migration; add a new one.
Checklist for adding a field
-
Edit
prisma/schema.prisma. -
pnpm prisma migrate dev --name descriptive_name. -
Update the Zod schema in
lib/schema/<feature>.ts. -
Export the type from
lib/types.tsviaz.infer<>. -
Update the API serialiser (camelCase → snake_case).
-
Invalidate the affected React Query caches.
-
Update this module and the relevant domain page.
One-off SQL
prisma/sql/ holds scripts that were run manually and are not part of the migration chain:
| Script | Purpose |
|---|---|
|
Supabase auth hook enforcing the Payability email domain at signup |
|
Populate |
|
Build and clean the email registry |
|
Build the phone registry |
|
Reconstruct |
|
Partial unique index and |
Reporting views (vm_main_campaign_reports, main_required_summaries, main_sankey_chart_view,
vm_main_daily_agent_performance_report, phone_number_pickup_rate,
v_email_campaign_daily_engagement) and the get_and_lease_due_targets RPC are managed in Supabase,
not in this repository.
vm_main_campaign_reports and vm_main_daily_agent_performance_report are no longer materialised.
Each is now a plain view over an incrementally maintained rollup table — call_daily_stats and
agent_daily_performance_stats respectively — defined in lib/supabase/sql/. See
Daily report rollups. The remaining vm_-prefixed views are
still materialised, so check their refresh schedule before treating stale numbers as a bug.
Database triggers
handle_justcall_call_initiated(), wired to trg_webhookevent_call_initiated, watches for JustCall
call.initiated rows in WebhookEvent, extracts the JustCall user id, and marks the matching
profile unavailable while incrementing its active-call count — availability tracking that predates
the application-level dialer.