29 KiB
Database Documentation
Overview
Changemaker Lite V2 uses a dual ORM architecture with PostgreSQL 16 as the backing database:
- Prisma ORM (Express API, port 4000) — 30 models for auth, influence, map, canvassing, email templates, landing pages, and tracking
- Drizzle ORM (Fastify Media API, port 4100) — 3 models for video library, compilations, and job queue
Both ORMs share the same PostgreSQL database but maintain separate schemas and migration workflows.
Database Architecture
Database: PostgreSQL 16
Connection: DATABASE_URL environment variable
Total Models: 33 models organized into 9 groups
Migration Tools: Prisma Migrate (main API), Drizzle Kit (media API)
Key Design Patterns
-
Audit Fields — Most models include:
createdAt/updatedAttimestampscreatedByUserId/updatedByUserIduser references- Automatic tracking via Prisma middleware
-
Soft Deletes — Some models use status fields instead of hard deletes:
- User:
status(ACTIVE/INACTIVE/SUSPENDED/EXPIRED) - Campaign:
status(DRAFT/ACTIVE/PAUSED/ARCHIVED) - Shift:
status(OPEN/FULL/CANCELLED)
- User:
-
JSON Fields — Used for flexible schema:
permissions(User) — granular per-app permissionsoffices(Representative) — array of office contact infotags(videos) — array of tag stringsgeojson(Cut) — GeoJSON polygon coordinatesblocks(LandingPage) — GrapesJS editor output
-
Enums — 18 enums for type safety:
- UserRole, UserStatus, CampaignStatus, GovernmentLevel, EmailMethod, ResponseType, ResponseStatus, SupportLevel, GeocodeProvider, BuildingType, LocationHistoryAction, ShiftStatus, SignupStatus, SignupSource, CutCategory, VisitOutcome, CanvassSessionStatus, TrackPointEvent, EmailTemplateCategory, EditorMode, MkdocsExportMode
-
Cascade Deletes — Foreign keys with
onDelete: Cascade:- Deleting a Campaign deletes all CampaignEmail, RepresentativeResponse, CustomRecipient, Call records
- Deleting a Location deletes all Address and LocationHistory records
- Deleting a Shift deletes all ShiftSignup records
- Deleting a CanvassSession deletes all CanvassVisit records
-
Indexes — Strategic indexing for performance:
- All foreign keys indexed (userId, campaignId, locationId, etc.)
- Composite indexes for common queries (latitude+longitude, locationId+unitNumber, etc.)
- Unique constraints (email, slug, postalCode, token, etc.)
Complete Entity Relationship Diagram
erDiagram
%% ============================================================================
%% AUTH & USERS
%% ============================================================================
User ||--o{ RefreshToken : has
User ||--o{ Campaign : creates
User ||--o{ CampaignEmail : sends
User ||--o{ RepresentativeResponse : submits
User ||--o{ ResponseUpvote : upvotes
User ||--o{ ShiftSignup : "signs up for"
User ||--o{ Location : creates
User ||--o{ Location : updates
User ||--o{ Address : "creates (addresses)"
User ||--o{ Address : "updates (addresses)"
User ||--o{ LocationHistory : edits
User ||--o{ Cut : "creates (cuts)"
User ||--o{ CanvassVisit : visits
User ||--o{ CanvassSession : "has (sessions)"
User ||--o{ TrackingSession : "tracks (gps)"
User ||--o{ EmailTemplate : "creates (templates)"
User ||--o{ EmailTemplate : "updates (templates)"
User ||--o{ EmailTemplateVersion : "versions (templates)"
User ||--o{ EmailTemplateTestLog : "tests (templates)"
User {
String id PK
String email UK "bcrypt hashed"
String password "bcrypt"
String name
String phone
UserRole role "SUPER_ADMIN | INFLUENCE_ADMIN | MAP_ADMIN | USER | TEMP"
UserStatus status "ACTIVE | INACTIVE | SUSPENDED | EXPIRED"
Json permissions "granular per-app"
UserCreatedVia createdVia "ADMIN | PUBLIC_SHIFT_SIGNUP | STANDARD"
DateTime expiresAt "for TEMP users"
Int expireDays
DateTime lastLoginAt
Boolean emailVerified
DateTime createdAt
DateTime updatedAt
}
RefreshToken {
String id PK
String token UK "JWT refresh token"
String userId FK
DateTime expiresAt
DateTime createdAt
}
%% ============================================================================
%% INFLUENCE — CAMPAIGNS
%% ============================================================================
Campaign ||--o{ CampaignEmail : sends
Campaign ||--o{ RepresentativeResponse : receives
Campaign ||--o{ CustomRecipient : targets
Campaign ||--o{ Call : tracks
Campaign {
String id PK
String slug UK
String title
String description
String emailSubject
String emailBody
String callToAction
String coverPhoto
CampaignStatus status "DRAFT | ACTIVE | PAUSED | ARCHIVED"
Boolean allowSmtpEmail "default: true"
Boolean allowMailtoLink "default: true"
Boolean collectUserInfo "default: true"
Boolean showEmailCount "default: true"
Boolean showCallCount "default: true"
Boolean allowEmailEditing "default: false"
Boolean allowCustomRecipients "default: false"
Boolean showResponseWall "default: false"
Boolean highlightCampaign "default: false"
GovernmentLevel[] targetGovernmentLevels
String createdByUserId FK
String createdByUserEmail
String createdByUserName
DateTime createdAt
DateTime updatedAt
}
CampaignEmail {
String id PK
String campaignId FK
String campaignSlug
String userId FK
String userEmail
String userName
String userPostalCode
String recipientEmail
String recipientName
String recipientTitle
GovernmentLevel recipientLevel
EmailMethod emailMethod "SMTP | MAILTO"
String subject
String message
CampaignEmailStatus status "QUEUED | SENT | FAILED | CLICKED | USER_INFO_CAPTURED"
String senderIp
DateTime sentAt
}
Representative {
String id PK
String postalCode IDX
String name
String email
String districtName
String electedOffice
String partyName
String representativeSetName
String url
String photoUrl
Json offices "array of office contact info"
DateTime cachedAt
}
CustomRecipient {
String id PK
String campaignId FK
String campaignSlug
String recipientName
String recipientEmail
String recipientTitle
String recipientOrganization
String notes
Boolean isActive
DateTime createdAt
DateTime updatedAt
}
PostalCodeCache {
String id PK
String postalCode UK
String city
String province
Decimal centroidLat
Decimal centroidLng
DateTime lastUpdated
}
Call {
String id PK
String representativeName
String representativeTitle
String phoneNumber
String officeType
String callerName
String callerEmail
String postalCode
String campaignId FK
String campaignSlug
String callerIp
DateTime calledAt
}
%% ============================================================================
%% INFLUENCE — RESPONSE WALL
%% ============================================================================
RepresentativeResponse ||--o{ ResponseUpvote : gets
RepresentativeResponse {
String id PK
String campaignId FK
String campaignSlug
String representativeName
String representativeTitle
GovernmentLevel representativeLevel
String representativeEmail
ResponseType responseType "EMAIL | LETTER | PHONE_CALL | MEETING | SOCIAL_MEDIA | OTHER"
String responseText
String userComment
String screenshotUrl
String submittedByUserId FK
String submittedByName
String submittedByEmail
Boolean isAnonymous
ResponseStatus status "PENDING | APPROVED | REJECTED"
Boolean isVerified
String verificationToken
DateTime verificationSentAt
DateTime verifiedAt
String verifiedBy
Int upvoteCount
String submittedIp
DateTime createdAt
DateTime updatedAt
}
ResponseUpvote {
String id PK
String responseId FK
String userId FK
String userEmail
String upvotedIp
}
EmailLog {
String id PK
String recipientEmail
String senderName
String senderEmail
String subject
String message
String postalCode
String status "sent | failed | previewed"
String senderIp
DateTime sentAt
}
EmailVerification {
String id PK
String token UK
String email
String tempCampaignData "JSON"
DateTime createdAt
DateTime expiresAt
Boolean used
}
%% ============================================================================
%% MAP — LOCATIONS
%% ============================================================================
Location ||--o{ Address : contains
Location ||--o{ LocationHistory : logs
Location {
String id PK
Decimal latitude "required, precision: 10,8"
Decimal longitude "required, precision: 11,8"
String address "base street address, no unit"
String postalCode
String province
String federalDistrict
Int buildingUse "NAR BU_USE: 1=Residential, 2=Partial, 3=Non-Residential, 4=Unknown"
String locGuid UK "NAR LOC_GUID"
BuildingType buildingType "SINGLE_FAMILY | MULTI_UNIT | MIXED_USE | COMMERCIAL"
Int totalUnits
String buildingNotes "access codes, manager contact"
Int geocodeConfidence "0-100"
GeocodeProvider geocodeProvider
String createdByUserId FK
String updatedByUserId FK
DateTime createdAt
DateTime updatedAt
}
Address {
String id PK
String locationId FK
String unitNumber
String addrGuid UK "NAR ADDR_GUID"
String firstName
String lastName
String email
String phone
SupportLevel supportLevel "1 | 2 | 3 | 4"
Boolean sign
String signSize
String notes
String createdByUserId FK
String updatedByUserId FK
DateTime createdAt
DateTime updatedAt
}
LocationHistory {
String id PK
String locationId FK
String userId FK
LocationHistoryAction action "CREATED | UPDATED | GEOCODED | BULK_GEOCODED | MOVED_ON_MAP | IMPORTED_CSV | IMPORTED_NAR"
String field "which field changed"
String oldValue
String newValue
Json metadata "provider, confidence, etc"
DateTime createdAt
}
%% ============================================================================
%% MAP — SHIFTS & CUTS
%% ============================================================================
Cut ||--o{ Shift : schedules
Shift ||--o{ ShiftSignup : has
Shift ||--o{ CanvassVisit : "visits (shift)"
Shift ||--o{ CanvassSession : "sessions (shift)"
Shift {
String id PK
String title
String description
DateTime date
String startTime "HH:MM"
String endTime "HH:MM"
String location
Int maxVolunteers
Int currentVolunteers
ShiftStatus status "OPEN | FULL | CANCELLED"
Boolean isPublic
String cutId FK
String createdBy
DateTime createdAt
DateTime updatedAt
}
ShiftSignup {
String id PK
String shiftId FK
String shiftTitle
String userId FK
String userEmail
String userName
String userPhone
DateTime signupDate
SignupStatus status "CONFIRMED | CANCELLED"
SignupSource signupSource "AUTHENTICATED | PUBLIC | ADMIN"
}
Cut {
String id PK
String name
String description
String color
Decimal opacity
CutCategory category "CUSTOM | WARD | NEIGHBORHOOD | DISTRICT"
Boolean isPublic
Boolean isOfficial
String geojson "GeoJSON polygon data"
String bounds "bounding box JSON"
Boolean showLocations
Boolean exportEnabled
String assignedTo
Json filterSettings
DateTime lastCanvassed
Int completionPercentage
String createdByUserId FK
DateTime createdAt
DateTime updatedAt
}
MapSettings {
String id PK
Decimal latitude
Decimal longitude
Int zoom
String walkSheetTitle
String walkSheetSubtitle
String walkSheetFooter
String qrCode1Url
String qrCode1Label
String qrCode2Url
String qrCode2Label
String qrCode3Url
String qrCode3Label
String createdBy
DateTime createdAt
DateTime updatedAt
}
%% ============================================================================
%% CANVASSING
%% ============================================================================
Cut ||--o{ CanvassSession : "sessions (cut)"
CanvassSession ||--o{ CanvassVisit : records
CanvassSession ||--|| TrackingSession : tracks
Address ||--o{ CanvassVisit : "visited (address)"
CanvassSession {
String id PK
String userId FK
String cutId FK
String shiftId FK
CanvassSessionStatus status "ACTIVE | COMPLETED | ABANDONED"
DateTime startedAt
DateTime endedAt
Decimal startLatitude
Decimal startLongitude
}
CanvassVisit {
String id PK
String addressId FK
String userId FK
String shiftId FK
String sessionId FK
VisitOutcome outcome "NOT_HOME | REFUSED | MOVED | ALREADY_VOTED | SPOKE_WITH | LEFT_LITERATURE | COME_BACK_LATER"
SupportLevel supportLevel
Boolean signRequested
String signSize
String notes
Int durationSeconds
DateTime visitedAt
}
TrackingSession {
String id PK
String userId FK
String canvassSessionId UK
DateTime startedAt
DateTime endedAt
Boolean isActive
Int totalPoints
Float totalDistanceM
Decimal lastLatitude
Decimal lastLongitude
DateTime lastRecordedAt
}
TrackingSession ||--o{ TrackPoint : logs
TrackPoint {
String id PK
String trackingSessionId FK
Decimal latitude
Decimal longitude
Float accuracy
DateTime recordedAt
TrackPointEvent eventType "LOCATION_ADDED | VISIT_RECORDED | SESSION_STARTED | SESSION_ENDED"
}
%% ============================================================================
%% EMAIL TEMPLATES
%% ============================================================================
EmailTemplate ||--o{ EmailTemplateVariable : defines
EmailTemplate ||--o{ EmailTemplateVersion : versions
EmailTemplate ||--o{ EmailTemplateTestLog : tests
EmailTemplate {
String id PK
String key UK "e.g., campaign-email"
String name "display name"
String description
EmailTemplateCategory category "INFLUENCE | MAP | SYSTEM"
String subjectLine "with {{VAR}} support"
String htmlContent
String textContent
Boolean isSystem "prevent deletion"
Boolean isActive
String createdByUserId FK
String updatedByUserId FK
DateTime createdAt
DateTime updatedAt
}
EmailTemplateVariable {
String id PK
String templateId FK
String key "e.g., USER_NAME"
String label "e.g., User Name"
String description
Boolean isRequired
Boolean isConditional "used in {{#if}} blocks"
String sampleValue
Int sortOrder
}
EmailTemplateVersion {
String id PK
String templateId FK
Int versionNumber "auto-increment per template"
String subjectLine
String htmlContent
String textContent
String changeNotes
String createdByUserId FK
DateTime createdAt
}
EmailTemplateTestLog {
String id PK
String templateId FK
String recipientEmail
Json testData "sample variable values"
Boolean success
String errorMessage
String messageId "nodemailer message ID"
String sentByUserId FK
DateTime sentAt
}
%% ============================================================================
%% LANDING PAGES
%% ============================================================================
LandingPage {
String id PK
String slug UK
String title
String description
Json blocks "GrapesJS editor JSON"
String htmlOutput
String cssOutput
EditorMode editorMode "VISUAL | CODE"
String mkdocsPath "path in mkdocs/overrides/"
String mkdocsStubPath "path to .md stub"
MkdocsExportMode mkdocsExportMode "THEMED | STANDALONE"
Boolean mkdocsHideNav
Boolean mkdocsHideToc
Boolean mkdocsSkipExport
Boolean published
String seoTitle
String seoDescription
String seoImage
DateTime createdAt
DateTime updatedAt
}
PageBlock {
String id PK
String type "hero | text | image | cta | features | testimonials | form"
String label
Json schema "block configuration schema"
Json defaults "default values"
String thumbnail
String category
Int sortOrder
DateTime createdAt
DateTime updatedAt
}
%% ============================================================================
%% SITE SETTINGS
%% ============================================================================
SiteSettings {
String id PK
String organizationName
String organizationShortName
String organizationLogoUrl
String organizationFaviconUrl
String adminColorPrimary
String adminColorBgBase
String publicColorPrimary
String publicColorBgBase
String publicColorBgContainer
String publicHeaderGradient
String footerText
String loginSubtitle
String emailFromName
String smtpHost
Int smtpPort
String smtpUser
String smtpPass
String smtpFromAddress
String smtpActiveProvider "mailhog | production"
Boolean emailTestMode
String testEmailRecipient
Boolean enableInfluence
Boolean enableMap
Boolean enableNewsletter
Boolean enableLandingPages
DateTime createdAt
DateTime updatedAt
}
Model Groups
The database is organized into 9 logical groups:
1. Auth & Users
- User — User accounts with roles (SUPER_ADMIN, INFLUENCE_ADMIN, MAP_ADMIN, USER, TEMP)
- RefreshToken — JWT refresh token storage with rotation
Key Features: bcrypt passwords (12+ chars policy), role-based access control, temp user expiration, email verification
2. Influence
- Campaign — Advocacy campaigns with 12 feature flags
- Representative — Cached representative data from Represent API
- CampaignEmail — Email tracking (SMTP vs MAILTO)
- RepresentativeResponse — Response wall with moderation
- ResponseUpvote — Upvote tracking with IP + user uniqueness
- CustomRecipient — Custom email targets
- PostalCodeCache — Postal code geocoding cache
- EmailLog — Email audit trail
- EmailVerification — Verification token storage
- Call — Phone call tracking
Key Features: Multi-government-level targeting, response moderation workflow (PENDING → APPROVED/REJECTED), BullMQ integration for email queue, upvote deduplication
3. Map — Locations
- Location — Building-level data with lat/lng, NAR integration
- Address — Unit-level data with support levels
- LocationHistory — Audit trail with 7 action types
- Shift — Volunteer shifts with cut relation
- ShiftSignup — Signup tracking
- Cut — GeoJSON polygon overlays
- MapSettings — Singleton for map center/zoom + walk sheet config
Key Features: Building vs unit architecture, multi-provider geocoding (6 providers), NAR 2025 import support, spatial indexing, GeoJSON storage
4. Canvassing
- CanvassSession — Session lifecycle (ACTIVE → COMPLETED/ABANDONED)
- CanvassVisit — Visit recording with 7 outcome types
- TrackingSession — GPS tracking integration
- TrackPoint — GPS breadcrumb trail
Key Features: Walking route algorithm, session abandonment logic (12h timeout), distance calculation, support level tracking
5. Email Templates
- EmailTemplate — Template master with categories
- EmailTemplateVariable — Variable definitions with validation
- EmailTemplateVersion — Version history
- EmailTemplateTestLog — Test email audit
Key Features: Handlebars-style variable interpolation ({{VAR}}), conditional variables, system template protection, version auto-increment
6. Landing Pages
- LandingPage — GrapesJS editor output with MkDocs export
- PageBlock — Reusable block library
Key Features: GrapesJS JSON storage, MkDocs export modes (THEMED vs STANDALONE), SEO metadata, slug-based routing
7. Settings
- SiteSettings — Org branding + theme + SMTP + feature toggles
- MapSettings — Map center/zoom + walk sheet config
Key Features: Singleton pattern, SMTP override hierarchy (SiteSettings → .env), feature flags
8. Media (Drizzle ORM)
- videos — Video library with metadata, directory types, engagement stats
- compilations — Video compilation tracking
- jobs — Job queue with resource categories
Key Features: Dual ORM architecture, FFprobe metadata extraction, directory type enum (9 types), job queue with GPU/CPU resource tracking
9. Shared/Standalone Models
- Representative — Shared across campaigns
- PostalCodeCache — Shared geocoding cache
- EmailLog — Audit trail (no relations)
- EmailVerification — Standalone verification tokens
Field Types Reference
| Prisma Type | PostgreSQL Type | Description | Example |
|---|---|---|---|
String |
text |
Variable-length text | "admin@cmlite.org" |
String @db.Text |
text |
Long-form text (no char limit) | Campaign descriptions |
Int |
integer |
32-bit integer | 42 |
BigInt |
bigint |
64-bit integer (Node: number mode) |
File sizes |
Boolean |
boolean |
True/false | true |
Decimal |
numeric |
Arbitrary precision decimal | Lat/lng coordinates |
Decimal @db.Decimal(10, 8) |
numeric(10, 8) |
10 digits, 8 after decimal | 53.54612345 |
DateTime |
timestamp with time zone |
Timestamp | 2025-02-11T10:30:00Z |
DateTime @db.Date |
date |
Date only (no time) | Shift dates |
Json |
jsonb |
JSON data (binary storage) | Arrays, objects |
Enum |
enum |
Enumerated type | UserRole.SUPER_ADMIN |
Enum Definitions
Auth & Users
- UserRole:
SUPER_ADMIN,INFLUENCE_ADMIN,MAP_ADMIN,USER,TEMP - UserStatus:
ACTIVE,INACTIVE,SUSPENDED,EXPIRED - UserCreatedVia:
ADMIN,PUBLIC_SHIFT_SIGNUP,STANDARD
Influence
- CampaignStatus:
DRAFT,ACTIVE,PAUSED,ARCHIVED - GovernmentLevel:
FEDERAL,PROVINCIAL,MUNICIPAL,SCHOOL_BOARD - EmailMethod:
SMTP,MAILTO - CampaignEmailStatus:
QUEUED,SENT,FAILED,CLICKED,USER_INFO_CAPTURED - ResponseType:
EMAIL,LETTER,PHONE_CALL,MEETING,SOCIAL_MEDIA,OTHER - ResponseStatus:
PENDING,APPROVED,REJECTED
Map
- SupportLevel:
LEVEL_1(mapped to"1"),LEVEL_2,LEVEL_3,LEVEL_4 - GeocodeProvider:
GOOGLE,MAPBOX,NOMINATIM,PHOTON,LOCATIONIQ,ARCGIS,UNKNOWN - BuildingType:
SINGLE_FAMILY,MULTI_UNIT,MIXED_USE,COMMERCIAL - LocationHistoryAction:
CREATED,UPDATED,GEOCODED,BULK_GEOCODED,MOVED_ON_MAP,IMPORTED_CSV,IMPORTED_NAR - ShiftStatus:
OPEN,FULL,CANCELLED - SignupStatus:
CONFIRMED,CANCELLED - SignupSource:
AUTHENTICATED,PUBLIC,ADMIN - CutCategory:
CUSTOM,WARD,NEIGHBORHOOD,DISTRICT
Canvassing
- VisitOutcome:
NOT_HOME,REFUSED,MOVED,ALREADY_VOTED,SPOKE_WITH,LEFT_LITERATURE,COME_BACK_LATER - CanvassSessionStatus:
ACTIVE,COMPLETED,ABANDONED - TrackPointEvent:
LOCATION_ADDED,VISIT_RECORDED,SESSION_STARTED,SESSION_ENDED
Email Templates
- EmailTemplateCategory:
INFLUENCE,MAP,SYSTEM
Landing Pages
- EditorMode:
VISUAL,CODE - MkdocsExportMode:
THEMED,STANDALONE
Media (Drizzle)
- DirectoryType (TypeScript literal):
'studios','gifs','private','inbox','curated','playback','compilations','videos','highlights' - ResourceCategory (TypeScript literal):
'gpu_ai','gpu_encode','cpu' - JobStatus (TypeScript literal):
'pending','queued','running','completed','failed','cancelled'
Index Strategy Overview
Foreign Key Indexes
All foreign key fields are indexed for join performance:
userId,campaignId,locationId,addressId,shiftId,cutId,sessionId,templateId,trackingSessionId
Composite Indexes
Strategic multi-column indexes for common query patterns:
[latitude, longitude](Location) — spatial queries[locationId, unitNumber](Address) — unit lookups[campaignId, status](RepresentativeResponse) — filtered response lists[isActive, lastRecordedAt](TrackingSession) — active session cleanup[templateId, createdAt(sort: Desc)](EmailTemplateVersion) — version history[directoryType, isValid, orientation](videos) — media library filtering
Unique Constraints
Enforce data integrity:
email(User)slug(Campaign, LandingPage)postalCode(PostalCodeCache)token(RefreshToken, EmailVerification)key(EmailTemplate)[responseId, userId](ResponseUpvote) — prevent duplicate upvotes from logged-in users[responseId, upvotedIp](ResponseUpvote) — prevent duplicate upvotes from same IP[shiftId, userEmail](ShiftSignup) — prevent duplicate shift signups[templateId, key](EmailTemplateVariable) — unique variable keys per template[templateId, versionNumber](EmailTemplateVersion) — sequential version numbers
Foreign Key Conventions
Cascade Deletes
onDelete: Cascade
Used when child records should be deleted with parent:
- RefreshToken → User
- CampaignEmail → Campaign
- RepresentativeResponse → Campaign
- CustomRecipient → Campaign
- Call → Campaign (SetNull)
- Address → Location
- LocationHistory → Location
- ShiftSignup → Shift
- CanvassVisit → Address, CanvassSession
- TrackPoint → TrackingSession
- EmailTemplateVariable → EmailTemplate
- EmailTemplateVersion → EmailTemplate
- EmailTemplateTestLog → EmailTemplate
Set Null
onDelete: SetNull
Used when child records should remain but orphan the reference:
- Campaign.createdByUserId → User
- CampaignEmail.userId → User
- RepresentativeResponse.submittedByUserId → User
- Location.createdByUserId/updatedByUserId → User
- Shift.cutId → Cut
- CanvassSession.shiftId → Shift
- TrackingSession.canvassSessionId → CanvassSession
Related Documentation
- Schema Reference — Complete table and field listing
- Migration Workflow — Prisma and Drizzle migration processes
- Seeding — Default data and seed script
- Indexes — Detailed index strategy and performance notes
- Auth Models — User and authentication tables
- Influence Models — Campaign and advocacy tables
- Map Models — Location, shift, and cut tables
- Canvassing Models — Session and visit tracking
- Email Template Models — Template system
- Landing Page Models — Page builder and blocks
- Settings Models — Site and map settings
- Media Models — Video library (Drizzle ORM)