46 KiB
Complete Schema Reference
This page provides a comprehensive listing of all 33 models across both Prisma and Drizzle ORMs.
Models Summary
| Group | Model | Table Name | Description | ORM |
|---|---|---|---|---|
| Auth & Users | User | users |
User accounts with role-based access control | Prisma |
| RefreshToken | refresh_tokens |
JWT refresh token storage | Prisma | |
| Influence | Campaign | campaigns |
Advocacy campaigns with feature flags | Prisma |
| Representative | representatives |
Cached representative data from Represent API | Prisma | |
| CampaignEmail | campaign_emails |
Email tracking and delivery logs | Prisma | |
| RepresentativeResponse | representative_responses |
Response wall submissions with moderation | Prisma | |
| ResponseUpvote | response_upvotes |
Upvote tracking with deduplication | Prisma | |
| CustomRecipient | custom_recipients |
Custom email targets for campaigns | Prisma | |
| PostalCodeCache | postal_code_cache |
Postal code geocoding cache | Prisma | |
| EmailLog | email_logs |
Global email audit trail | Prisma | |
| EmailVerification | email_verifications |
Email verification tokens | Prisma | |
| Call | calls |
Phone call tracking | Prisma | |
| Map — Locations | Location | locations |
Building-level address data with geocoding | Prisma |
| Address | addresses |
Unit-level data with support levels | Prisma | |
| LocationHistory | location_history |
Audit trail for location changes | Prisma | |
| Map — Shifts & Cuts | Shift | shifts |
Volunteer shifts with scheduling | Prisma |
| ShiftSignup | shift_signups |
Shift signup tracking | Prisma | |
| Cut | cuts |
GeoJSON polygon overlays for map filtering | Prisma | |
| MapSettings | map_settings |
Singleton for map configuration | Prisma | |
| Canvassing | CanvassSession | canvass_sessions |
Canvassing session lifecycle | Prisma |
| CanvassVisit | canvass_visits |
Visit recording with outcomes | Prisma | |
| TrackingSession | tracking_sessions |
GPS tracking sessions | Prisma | |
| TrackPoint | track_points |
GPS breadcrumb trail | Prisma | |
| Email Templates | EmailTemplate | email_templates |
Email template master records | Prisma |
| EmailTemplateVariable | email_template_variables |
Template variable definitions | Prisma | |
| EmailTemplateVersion | email_template_versions |
Template version history | Prisma | |
| EmailTemplateTestLog | email_template_test_logs |
Test email audit logs | Prisma | |
| Landing Pages | LandingPage | landing_pages |
GrapesJS editor output with MkDocs export | Prisma |
| PageBlock | page_blocks |
Reusable block library | Prisma | |
| Site Settings | SiteSettings | site_settings |
Global site configuration singleton | Prisma |
| Media | videos | videos |
Video library with metadata | Drizzle |
| compilations | compilations |
Video compilation tracking | Drizzle | |
| jobs | jobs |
Job queue with resource management | Drizzle |
Total: 33 models (30 Prisma + 3 Drizzle)
Auth & Users
User
Table: users
Description: User accounts with role-based access control, temporary user support, and audit tracking.
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| id | String | ✓ | cuid() |
Primary key |
| String | ✓ | — | Unique email address | |
| password | String | ✓ | — | bcrypt hashed password (12+ chars policy) |
| name | String | ✗ | null |
User display name |
| phone | String | ✗ | null |
Phone number |
| role | UserRole | ✓ | USER |
Role: SUPER_ADMIN, INFLUENCE_ADMIN, MAP_ADMIN, USER, TEMP |
| status | UserStatus | ✓ | ACTIVE |
Status: ACTIVE, INACTIVE, SUSPENDED, EXPIRED |
| permissions | Json | ✗ | null |
Granular per-app permissions object |
| createdVia | UserCreatedVia | ✓ | STANDARD |
Creation source: ADMIN, PUBLIC_SHIFT_SIGNUP, STANDARD |
| expiresAt | DateTime | ✗ | null |
Expiration date for TEMP users |
| expireDays | Int | ✗ | null |
Days until expiration for TEMP users |
| lastLoginAt | DateTime | ✗ | null |
Last login timestamp |
| emailVerified | Boolean | ✓ | false |
Email verification status |
| createdAt | DateTime | ✓ | now() |
Creation timestamp |
| updatedAt | DateTime | ✓ | Auto | Last update timestamp |
Indexes:
- Unique:
email
Relations (33 total):
- refreshTokens → RefreshToken[]
- campaignsCreated → Campaign[]
- campaignEmails → CampaignEmail[]
- responses → RepresentativeResponse[]
- responseUpvotes → ResponseUpvote[]
- shiftSignups → ShiftSignup[]
- locationsCreated → Location[]
- locationsUpdated → Location[]
- addressesCreated → Address[]
- addressesUpdated → Address[]
- locationEdits → LocationHistory[]
- cutsCreated → Cut[]
- canvassVisits → CanvassVisit[]
- canvassSessions → CanvassSession[]
- trackingSessions → TrackingSession[]
- templatesCreated → EmailTemplate[]
- templatesUpdated → EmailTemplate[]
- templateVersionsCreated → EmailTemplateVersion[]
- templateTestsSent → EmailTemplateTestLog[]
RefreshToken
Table: refresh_tokens
Description: JWT refresh token storage with expiration tracking.
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| id | String | ✓ | cuid() |
Primary key |
| token | String | ✓ | — | JWT refresh token (unique) |
| userId | String | ✓ | — | Foreign key to User |
| expiresAt | DateTime | ✓ | — | Token expiration timestamp |
| createdAt | DateTime | ✓ | now() |
Creation timestamp |
Indexes:
- Unique:
token - Foreign key:
userId
Relations:
- user → User (onDelete: Cascade)
Influence
Campaign
Table: campaigns
Description: Advocacy campaigns with 12 feature flags and government-level targeting.
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| id | String | ✓ | cuid() |
Primary key |
| slug | String | ✓ | — | URL-friendly slug (unique) |
| title | String | ✓ | — | Campaign title |
| description | String | ✗ | null |
Campaign description (long text) |
| emailSubject | String | ✓ | — | Default email subject line |
| emailBody | String | ✓ | — | Default email body (long text) |
| callToAction | String | ✗ | null |
Call-to-action text (long text) |
| coverPhoto | String | ✗ | null |
Cover photo URL |
| status | CampaignStatus | ✓ | DRAFT |
Status: DRAFT, ACTIVE, PAUSED, ARCHIVED |
| allowSmtpEmail | Boolean | ✓ | true |
Allow SMTP email sending |
| allowMailtoLink | Boolean | ✓ | true |
Allow mailto: links |
| collectUserInfo | Boolean | ✓ | true |
Collect user information |
| showEmailCount | Boolean | ✓ | true |
Show email sent count |
| showCallCount | Boolean | ✓ | true |
Show call made count |
| allowEmailEditing | Boolean | ✓ | false |
Allow users to edit email content |
| allowCustomRecipients | Boolean | ✓ | false |
Allow custom email recipients |
| showResponseWall | Boolean | ✓ | false |
Show public response wall |
| highlightCampaign | Boolean | ✓ | false |
Highlight on campaign list page |
| targetGovernmentLevels | GovernmentLevel[] | ✓ | [] |
Target levels: FEDERAL, PROVINCIAL, MUNICIPAL, SCHOOL_BOARD |
| createdByUserId | String | ✗ | null |
Foreign key to User (creator) |
| createdByUserEmail | String | ✗ | null |
Creator email (denormalized) |
| createdByUserName | String | ✗ | null |
Creator name (denormalized) |
| createdAt | DateTime | ✓ | now() |
Creation timestamp |
| updatedAt | DateTime | ✓ | Auto | Last update timestamp |
Indexes:
- Unique:
slug
Relations:
- createdByUser → User (onDelete: SetNull)
- emails → CampaignEmail[]
- responses → RepresentativeResponse[]
- customRecipients → CustomRecipient[]
- calls → Call[]
Representative
Table: representatives
Description: Cached representative data from Represent API.
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| id | String | ✓ | cuid() |
Primary key |
| postalCode | String | ✓ | — | Canadian postal code (indexed) |
| name | String | ✗ | null |
Representative name |
| String | ✗ | null |
Representative email | |
| districtName | String | ✗ | null |
Electoral district name |
| electedOffice | String | ✗ | null |
Office title |
| partyName | String | ✗ | null |
Political party |
| representativeSetName | String | ✗ | null |
Representative set from Represent API |
| url | String | ✗ | null |
Official website URL |
| photoUrl | String | ✗ | null |
Photo URL |
| offices | Json | ✗ | null |
Array of office contact info objects |
| cachedAt | DateTime | ✓ | now() |
Cache timestamp |
Indexes:
- Non-unique:
postalCode
Relations: None (standalone cache)
CampaignEmail
Table: campaign_emails
Description: Email tracking and delivery logs for campaign emails.
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| id | String | ✓ | cuid() |
Primary key |
| campaignId | String | ✓ | — | Foreign key to Campaign |
| campaignSlug | String | ✓ | — | Denormalized campaign slug |
| userId | String | ✗ | null |
Foreign key to User (sender) |
| userEmail | String | ✗ | null |
Sender email |
| userName | String | ✗ | null |
Sender name |
| userPostalCode | String | ✗ | null |
Sender postal code |
| recipientEmail | String | ✓ | — | Recipient email address |
| recipientName | String | ✗ | null |
Recipient name |
| recipientTitle | String | ✗ | null |
Recipient title |
| recipientLevel | GovernmentLevel | ✗ | null |
Government level |
| emailMethod | EmailMethod | ✓ | — | Method: SMTP, MAILTO |
| subject | String | ✓ | — | Email subject line |
| message | String | ✓ | — | Email message body (long text) |
| status | CampaignEmailStatus | ✓ | SENT |
Status: QUEUED, SENT, FAILED, CLICKED, USER_INFO_CAPTURED |
| senderIp | String | ✗ | null |
Sender IP address |
| sentAt | DateTime | ✓ | now() |
Send timestamp |
Indexes:
- Foreign key:
campaignId - Non-unique:
campaignSlug
Relations:
- campaign → Campaign (onDelete: Cascade)
- user → User (onDelete: SetNull)
RepresentativeResponse
Table: representative_responses
Description: Response wall submissions with moderation workflow.
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| id | String | ✓ | cuid() |
Primary key |
| campaignId | String | ✓ | — | Foreign key to Campaign |
| campaignSlug | String | ✓ | — | Denormalized campaign slug |
| representativeName | String | ✓ | — | Representative name |
| representativeTitle | String | ✗ | null |
Representative title |
| representativeLevel | GovernmentLevel | ✓ | — | Government level |
| representativeEmail | String | ✗ | null |
Representative email |
| responseType | ResponseType | ✓ | — | Type: EMAIL, LETTER, PHONE_CALL, MEETING, SOCIAL_MEDIA, OTHER |
| responseText | String | ✓ | — | Response text (long text) |
| userComment | String | ✗ | null |
User comment (long text) |
| screenshotUrl | String | ✗ | null |
Screenshot URL |
| submittedByUserId | String | ✗ | null |
Foreign key to User |
| submittedByName | String | ✗ | null |
Submitter name |
| submittedByEmail | String | ✗ | null |
Submitter email |
| isAnonymous | Boolean | ✓ | false |
Anonymous submission flag |
| status | ResponseStatus | ✓ | PENDING |
Status: PENDING, APPROVED, REJECTED |
| isVerified | Boolean | ✓ | false |
Email verification status |
| verificationToken | String | ✗ | null |
Verification token |
| verificationSentAt | DateTime | ✗ | null |
Verification email timestamp |
| verifiedAt | DateTime | ✗ | null |
Verification timestamp |
| verifiedBy | String | ✗ | null |
Email address that verified |
| upvoteCount | Int | ✓ | 0 |
Upvote count (denormalized) |
| submittedIp | String | ✗ | null |
Submitter IP address |
| createdAt | DateTime | ✓ | now() |
Creation timestamp |
| updatedAt | DateTime | ✓ | Auto | Last update timestamp |
Indexes:
- Foreign key:
campaignId - Non-unique:
campaignSlug
Relations:
- campaign → Campaign (onDelete: Cascade)
- submittedByUser → User (onDelete: SetNull)
- upvotes → ResponseUpvote[]
ResponseUpvote
Table: response_upvotes
Description: Upvote tracking with deduplication by user ID and IP address.
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| id | String | ✓ | cuid() |
Primary key |
| responseId | String | ✓ | — | Foreign key to RepresentativeResponse |
| userId | String | ✗ | null |
Foreign key to User |
| userEmail | String | ✗ | null |
User email (for guest upvotes) |
| upvotedIp | String | ✗ | null |
Upvoter IP address |
Indexes:
- Unique:
[responseId, userId](prevent duplicate upvotes from logged-in users) - Unique:
[responseId, upvotedIp](prevent duplicate upvotes from same IP)
Relations:
- response → RepresentativeResponse (onDelete: Cascade)
- user → User (onDelete: SetNull)
CustomRecipient
Table: custom_recipients
Description: Custom email targets for campaigns (when allowCustomRecipients enabled).
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| id | String | ✓ | cuid() |
Primary key |
| campaignId | String | ✓ | — | Foreign key to Campaign |
| campaignSlug | String | ✓ | — | Denormalized campaign slug |
| recipientName | String | ✓ | — | Recipient name |
| recipientEmail | String | ✓ | — | Recipient email address |
| recipientTitle | String | ✗ | null |
Recipient title |
| recipientOrganization | String | ✗ | null |
Recipient organization |
| notes | String | ✗ | null |
Admin notes (long text) |
| isActive | Boolean | ✓ | true |
Active status |
| createdAt | DateTime | ✓ | now() |
Creation timestamp |
| updatedAt | DateTime | ✓ | Auto | Last update timestamp |
Indexes:
- Foreign key:
campaignId
Relations:
- campaign → Campaign (onDelete: Cascade)
PostalCodeCache
Table: postal_code_cache
Description: Postal code geocoding cache for centroid lookups.
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| id | String | ✓ | cuid() |
Primary key |
| postalCode | String | ✓ | — | Canadian postal code (unique) |
| city | String | ✗ | null |
City name |
| province | String | ✗ | null |
Province code (e.g., "AB") |
| centroidLat | Decimal(10,8) | ✗ | null |
Centroid latitude |
| centroidLng | Decimal(11,8) | ✗ | null |
Centroid longitude |
| lastUpdated | DateTime | ✓ | now() |
Last cache update |
Indexes:
- Unique:
postalCode
Relations: None (standalone cache)
EmailLog
Table: email_logs
Description: Global email audit trail (all email types).
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| id | String | ✓ | cuid() |
Primary key |
| recipientEmail | String | ✓ | — | Recipient email address |
| senderName | String | ✓ | — | Sender name |
| senderEmail | String | ✓ | — | Sender email address |
| subject | String | ✗ | null |
Email subject line |
| message | String | ✗ | null |
Email message body (long text) |
| postalCode | String | ✗ | null |
Sender postal code |
| status | String | ✓ | "sent" |
Status: sent, failed, previewed |
| senderIp | String | ✗ | null |
Sender IP address |
| sentAt | DateTime | ✓ | now() |
Send timestamp |
Indexes: None
Relations: None (audit log only)
EmailVerification
Table: email_verifications
Description: Email verification tokens for response wall submissions.
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| id | String | ✓ | cuid() |
Primary key |
| token | String | ✓ | — | Verification token (unique) |
| String | ✓ | — | Email address to verify | |
| tempCampaignData | String | ✗ | null |
Temporary campaign data JSON (long text) |
| createdAt | DateTime | ✓ | now() |
Creation timestamp |
| expiresAt | DateTime | ✓ | — | Token expiration timestamp |
| used | Boolean | ✓ | false |
Token used flag |
Indexes:
- Unique:
token
Relations: None (standalone)
Call
Table: calls
Description: Phone call tracking for advocacy campaigns.
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| id | String | ✓ | cuid() |
Primary key |
| representativeName | String | ✓ | — | Representative name |
| representativeTitle | String | ✗ | null |
Representative title |
| phoneNumber | String | ✓ | — | Phone number called |
| officeType | String | ✗ | null |
Office type (constituency, legislative, etc.) |
| callerName | String | ✗ | null |
Caller name |
| callerEmail | String | ✗ | null |
Caller email |
| postalCode | String | ✗ | null |
Caller postal code |
| campaignId | String | ✗ | null |
Foreign key to Campaign |
| campaignSlug | String | ✗ | null |
Denormalized campaign slug |
| callerIp | String | ✗ | null |
Caller IP address |
| calledAt | DateTime | ✓ | now() |
Call timestamp |
Indexes:
- Foreign key:
campaignId
Relations:
- campaign → Campaign (onDelete: SetNull)
Map — Locations
Location
Table: locations
Description: Building-level address data with geocoding and NAR integration.
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| id | String | ✓ | cuid() |
Primary key |
| latitude | Decimal(10,8) | ✓ | — | Latitude coordinate (required) |
| longitude | Decimal(11,8) | ✓ | — | Longitude coordinate (required) |
| address | String | ✓ | — | Base street address (no unit number) |
| postalCode | String | ✗ | null |
Canadian postal code |
| province | String | ✗ | null |
Province code (e.g., "AB") |
| federalDistrict | String | ✗ | null |
Federal electoral district name |
| buildingUse | Int | ✗ | null |
NAR BU_USE: 1=Residential, 2=Partial, 3=Non-Residential, 4=Unknown |
| locGuid | String | ✗ | null |
NAR LOC_GUID (unique) |
| buildingType | BuildingType | ✓ | SINGLE_FAMILY |
Type: SINGLE_FAMILY, MULTI_UNIT, MIXED_USE, COMMERCIAL |
| totalUnits | Int | ✓ | 1 |
Total units in building |
| buildingNotes | String | ✗ | null |
Access codes, manager contact (long text) |
| geocodeConfidence | Int | ✗ | null |
Geocoding confidence (0-100) |
| geocodeProvider | GeocodeProvider | ✗ | null |
Provider: GOOGLE, MAPBOX, NOMINATIM, PHOTON, LOCATIONIQ, ARCGIS, UNKNOWN |
| createdByUserId | String | ✗ | null |
Foreign key to User (creator) |
| updatedByUserId | String | ✗ | null |
Foreign key to User (last updater) |
| createdAt | DateTime | ✓ | now() |
Creation timestamp |
| updatedAt | DateTime | ✓ | Auto | Last update timestamp |
Indexes:
- Unique:
locGuid - Composite:
[latitude, longitude](spatial queries) - Non-unique:
postalCode
Relations:
- createdByUser → User (onDelete: SetNull)
- updatedByUser → User (onDelete: SetNull)
- addresses → Address[]
- history → LocationHistory[]
Address
Table: addresses
Description: Unit-level data with support levels and canvassing information.
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| id | String | ✓ | cuid() |
Primary key |
| locationId | String | ✓ | — | Foreign key to Location |
| unitNumber | String | ✗ | null |
Unit/apartment number |
| addrGuid | String | ✗ | null |
NAR ADDR_GUID (unique) |
| firstName | String | ✗ | null |
Occupant first name |
| lastName | String | ✗ | null |
Occupant last name |
| String | ✗ | null |
Occupant email | |
| phone | String | ✗ | null |
Occupant phone |
| supportLevel | SupportLevel | ✗ | null |
Support level: 1, 2, 3, 4 |
| sign | Boolean | ✓ | false |
Sign requested flag |
| signSize | String | ✗ | null |
Sign size |
| notes | String | ✗ | null |
Canvassing notes (long text) |
| createdByUserId | String | ✗ | null |
Foreign key to User (creator) |
| updatedByUserId | String | ✗ | null |
Foreign key to User (last updater) |
| createdAt | DateTime | ✓ | now() |
Creation timestamp |
| updatedAt | DateTime | ✓ | Auto | Last update timestamp |
Indexes:
- Unique:
addrGuid - Foreign key:
locationId - Composite:
[locationId, unitNumber](unit lookups)
Relations:
- location → Location (onDelete: Cascade)
- createdByUser → User (onDelete: SetNull)
- updatedByUser → User (onDelete: SetNull)
- canvassVisits → CanvassVisit[]
LocationHistory
Table: location_history
Description: Audit trail for location changes with action types.
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| id | String | ✓ | cuid() |
Primary key |
| locationId | String | ✓ | — | Foreign key to Location |
| userId | String | ✗ | null |
Foreign key to User |
| action | LocationHistoryAction | ✓ | — | Action: CREATED, UPDATED, GEOCODED, BULK_GEOCODED, MOVED_ON_MAP, IMPORTED_CSV, IMPORTED_NAR |
| field | String | ✗ | null |
Field name that changed |
| oldValue | String | ✗ | null |
Old value (long text) |
| newValue | String | ✗ | null |
New value (long text) |
| metadata | Json | ✗ | null |
Provider, confidence, etc. |
| createdAt | DateTime | ✓ | now() |
Timestamp |
Indexes:
- Foreign key:
locationId - Foreign key:
userId - Non-unique:
createdAt(temporal queries)
Relations:
- location → Location (onDelete: Cascade)
- user → User (onDelete: SetNull)
Map — Shifts & Cuts
Shift
Table: shifts
Description: Volunteer shifts with scheduling and capacity tracking.
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| id | String | ✓ | cuid() |
Primary key |
| title | String | ✓ | — | Shift title |
| description | String | ✗ | null |
Shift description (long text) |
| date | DateTime | ✓ | — | Shift date (date only, no time) |
| startTime | String | ✓ | — | Start time (HH:MM format) |
| endTime | String | ✓ | — | End time (HH:MM format) |
| location | String | ✗ | null |
Shift location description |
| maxVolunteers | Int | ✓ | — | Maximum volunteer capacity |
| currentVolunteers | Int | ✓ | 0 |
Current signup count |
| status | ShiftStatus | ✓ | OPEN |
Status: OPEN, FULL, CANCELLED |
| isPublic | Boolean | ✓ | false |
Public signup allowed |
| cutId | String | ✗ | null |
Foreign key to Cut |
| createdBy | String | ✗ | null |
Creator user ID (string, not FK) |
| createdAt | DateTime | ✓ | now() |
Creation timestamp |
| updatedAt | DateTime | ✓ | Auto | Last update timestamp |
Indexes:
- Foreign key:
cutId
Relations:
- cut → Cut (onDelete: SetNull)
- signups → ShiftSignup[]
- canvassVisits → CanvassVisit[]
- canvassSessions → CanvassSession[]
ShiftSignup
Table: shift_signups
Description: Shift signup tracking with source attribution.
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| id | String | ✓ | cuid() |
Primary key |
| shiftId | String | ✓ | — | Foreign key to Shift |
| shiftTitle | String | ✗ | null |
Denormalized shift title |
| userId | String | ✗ | null |
Foreign key to User |
| userEmail | String | ✓ | — | User email (for guest signups) |
| userName | String | ✗ | null |
User name |
| userPhone | String | ✗ | null |
User phone |
| signupDate | DateTime | ✓ | now() |
Signup timestamp |
| status | SignupStatus | ✓ | CONFIRMED |
Status: CONFIRMED, CANCELLED |
| signupSource | SignupSource | ✓ | AUTHENTICATED |
Source: AUTHENTICATED, PUBLIC, ADMIN |
Indexes:
- Unique:
[shiftId, userEmail](prevent duplicate signups) - Foreign key:
shiftId
Relations:
- shift → Shift (onDelete: Cascade)
- user → User (onDelete: SetNull)
Cut
Table: cuts
Description: GeoJSON polygon overlays for map filtering and canvassing.
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| id | String | ✓ | cuid() |
Primary key |
| name | String | ✓ | — | Cut name |
| description | String | ✗ | null |
Cut description (long text) |
| color | String | ✓ | "#3388ff" |
Polygon fill color (hex) |
| opacity | Decimal(3,2) | ✓ | 0.3 |
Polygon opacity (0.00-1.00) |
| category | CutCategory | ✗ | null |
Category: CUSTOM, WARD, NEIGHBORHOOD, DISTRICT |
| isPublic | Boolean | ✓ | false |
Public visibility flag |
| isOfficial | Boolean | ✓ | false |
Official boundary flag |
| geojson | String | ✓ | — | GeoJSON polygon data (long text) |
| bounds | String | ✗ | null |
Bounding box JSON (long text) |
| showLocations | Boolean | ✓ | true |
Show locations on map |
| exportEnabled | Boolean | ✓ | true |
Export enabled flag |
| assignedTo | String | ✗ | null |
Assigned user ID (string, not FK) |
| filterSettings | Json | ✗ | null |
Filter configuration object |
| lastCanvassed | DateTime | ✗ | null |
Last canvass timestamp |
| completionPercentage | Int | ✓ | 0 |
Canvass completion percentage |
| createdByUserId | String | ✗ | null |
Foreign key to User (creator) |
| createdAt | DateTime | ✓ | now() |
Creation timestamp |
| updatedAt | DateTime | ✓ | Auto | Last update timestamp |
Indexes: None
Relations:
- createdByUser → User (onDelete: SetNull)
- shifts → Shift[]
- canvassSessions → CanvassSession[]
MapSettings
Table: map_settings
Description: Singleton for map center/zoom and walk sheet configuration.
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| id | String | ✓ | cuid() |
Primary key (always "default") |
| latitude | Decimal(10,8) | ✗ | null |
Map center latitude |
| longitude | Decimal(11,8) | ✗ | null |
Map center longitude |
| zoom | Int | ✗ | null |
Default map zoom level |
| walkSheetTitle | String | ✗ | null |
Walk sheet header title |
| walkSheetSubtitle | String | ✗ | null |
Walk sheet header subtitle |
| walkSheetFooter | String | ✗ | null |
Walk sheet footer text (long text) |
| qrCode1Url | String | ✗ | null |
QR code 1 URL |
| qrCode1Label | String | ✗ | null |
QR code 1 label |
| qrCode2Url | String | ✗ | null |
QR code 2 URL |
| qrCode2Label | String | ✗ | null |
QR code 2 label |
| qrCode3Url | String | ✗ | null |
QR code 3 URL |
| qrCode3Label | String | ✗ | null |
QR code 3 label |
| createdBy | String | ✗ | null |
Creator user ID (string, not FK) |
| createdAt | DateTime | ✓ | now() |
Creation timestamp |
| updatedAt | DateTime | ✓ | Auto | Last update timestamp |
Indexes: None
Relations: None (singleton)
Canvassing
CanvassSession
Table: canvass_sessions
Description: Canvassing session lifecycle with status tracking.
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| id | String | ✓ | cuid() |
Primary key |
| userId | String | ✓ | — | Foreign key to User |
| cutId | String | ✓ | — | Foreign key to Cut |
| shiftId | String | ✗ | null |
Foreign key to Shift |
| status | CanvassSessionStatus | ✓ | ACTIVE |
Status: ACTIVE, COMPLETED, ABANDONED |
| startedAt | DateTime | ✓ | now() |
Session start timestamp |
| endedAt | DateTime | ✗ | null |
Session end timestamp |
| startLatitude | Decimal(10,8) | ✗ | null |
Starting latitude |
| startLongitude | Decimal(11,8) | ✗ | null |
Starting longitude |
Indexes:
- Foreign key:
userId - Foreign key:
cutId - Foreign key:
shiftId
Relations:
- user → User (onDelete: Cascade)
- cut → Cut (onDelete: Cascade)
- shift → Shift (onDelete: SetNull)
- visits → CanvassVisit[]
- trackingSession → TrackingSession (one-to-one)
CanvassVisit
Table: canvass_visits
Description: Visit recording with outcome tracking.
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| id | String | ✓ | cuid() |
Primary key |
| addressId | String | ✓ | — | Foreign key to Address |
| userId | String | ✓ | — | Foreign key to User |
| shiftId | String | ✗ | null |
Foreign key to Shift |
| sessionId | String | ✗ | null |
Foreign key to CanvassSession |
| outcome | VisitOutcome | ✓ | — | Outcome: NOT_HOME, REFUSED, MOVED, ALREADY_VOTED, SPOKE_WITH, LEFT_LITERATURE, COME_BACK_LATER |
| supportLevel | SupportLevel | ✗ | null |
Support level: 1, 2, 3, 4 |
| signRequested | Boolean | ✓ | false |
Sign requested flag |
| signSize | String | ✗ | null |
Sign size |
| notes | String | ✗ | null |
Visit notes (long text) |
| durationSeconds | Int | ✗ | null |
Visit duration in seconds |
| visitedAt | DateTime | ✓ | now() |
Visit timestamp |
Indexes:
- Foreign key:
addressId - Foreign key:
userId - Foreign key:
shiftId - Foreign key:
sessionId - Non-unique:
visitedAt(temporal queries)
Relations:
- address → Address (onDelete: Cascade)
- user → User (onDelete: Cascade)
- shift → Shift (onDelete: SetNull)
- session → CanvassSession (onDelete: SetNull)
TrackingSession
Table: tracking_sessions
Description: GPS tracking sessions with distance calculation.
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| id | String | ✓ | cuid() |
Primary key |
| userId | String | ✓ | — | Foreign key to User |
| canvassSessionId | String | ✗ | null |
Foreign key to CanvassSession (unique, one-to-one) |
| startedAt | DateTime | ✓ | now() |
Tracking start timestamp |
| endedAt | DateTime | ✗ | null |
Tracking end timestamp |
| isActive | Boolean | ✓ | true |
Active tracking flag |
| totalPoints | Int | ✓ | 0 |
Total GPS points recorded |
| totalDistanceM | Float | ✓ | 0 |
Total distance in meters |
| lastLatitude | Decimal(10,8) | ✗ | null |
Last recorded latitude |
| lastLongitude | Decimal(11,8) | ✗ | null |
Last recorded longitude |
| lastRecordedAt | DateTime | ✗ | null |
Last GPS point timestamp |
Indexes:
- Unique:
canvassSessionId - Foreign key:
userId - Non-unique:
isActive - Composite:
[isActive, lastRecordedAt](cleanup queries)
Relations:
- user → User (onDelete: Cascade)
- canvassSession → CanvassSession (onDelete: SetNull)
- trackPoints → TrackPoint[]
TrackPoint
Table: track_points
Description: GPS breadcrumb trail with event types.
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| id | String | ✓ | cuid() |
Primary key |
| trackingSessionId | String | ✓ | — | Foreign key to TrackingSession |
| latitude | Decimal(10,8) | ✓ | — | GPS latitude |
| longitude | Decimal(11,8) | ✓ | — | GPS longitude |
| accuracy | Float | ✗ | null |
GPS accuracy in meters |
| recordedAt | DateTime | ✓ | now() |
GPS point timestamp |
| eventType | TrackPointEvent | ✗ | null |
Event: LOCATION_ADDED, VISIT_RECORDED, SESSION_STARTED, SESSION_ENDED |
Indexes:
- Composite:
[trackingSessionId, recordedAt](temporal queries) - Non-unique:
recordedAt
Relations:
- trackingSession → TrackingSession (onDelete: Cascade)
Email Templates
EmailTemplate
Table: email_templates
Description: Email template master records with category organization.
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| id | String | ✓ | cuid() |
Primary key |
| key | String | ✓ | — | Template key (unique, e.g., "campaign-email") |
| name | String | ✓ | — | Display name |
| description | String | ✗ | null |
Template description (long text) |
| category | EmailTemplateCategory | ✓ | — | Category: INFLUENCE, MAP, SYSTEM |
| subjectLine | String | ✓ | — | Subject line with {{VAR}} support |
| htmlContent | String | ✓ | — | HTML template (long text) |
| textContent | String | ✓ | — | Plain text template (long text) |
| isSystem | Boolean | ✓ | false |
System template (prevent deletion) |
| isActive | Boolean | ✓ | true |
Active status |
| createdByUserId | String | ✓ | — | Foreign key to User (creator) |
| updatedByUserId | String | ✗ | null |
Foreign key to User (last updater) |
| createdAt | DateTime | ✓ | now() |
Creation timestamp |
| updatedAt | DateTime | ✓ | Auto | Last update timestamp |
Indexes:
- Unique:
key - Non-unique:
category - Non-unique:
isActive
Relations:
- createdBy → User
- updatedBy → User
- variables → EmailTemplateVariable[]
- versions → EmailTemplateVersion[]
- testLogs → EmailTemplateTestLog[]
EmailTemplateVariable
Table: email_template_variables
Description: Template variable definitions with validation.
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| id | String | ✓ | cuid() |
Primary key |
| templateId | String | ✓ | — | Foreign key to EmailTemplate |
| key | String | ✓ | — | Variable key (e.g., "USER_NAME") |
| label | String | ✓ | — | Variable label (e.g., "User Name") |
| description | String | ✗ | null |
Variable description (long text) |
| isRequired | Boolean | ✓ | true |
Required flag |
| isConditional | Boolean | ✓ | false |
Conditional variable (used in {{#if}}) |
| sampleValue | String | ✗ | null |
Sample value for testing (long text) |
| sortOrder | Int | ✓ | 0 |
Display order |
Indexes:
- Unique:
[templateId, key](unique variable keys per template) - Foreign key:
templateId
Relations:
- template → EmailTemplate (onDelete: Cascade)
EmailTemplateVersion
Table: email_template_versions
Description: Template version history with auto-increment version numbers.
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| id | String | ✓ | cuid() |
Primary key |
| templateId | String | ✓ | — | Foreign key to EmailTemplate |
| versionNumber | Int | ✓ | — | Auto-increment version number per template |
| subjectLine | String | ✓ | — | Subject line snapshot |
| htmlContent | String | ✓ | — | HTML content snapshot (long text) |
| textContent | String | ✓ | — | Plain text snapshot (long text) |
| changeNotes | String | ✗ | null |
Version notes (long text) |
| createdByUserId | String | ✓ | — | Foreign key to User |
| createdAt | DateTime | ✓ | now() |
Version timestamp |
Indexes:
- Unique:
[templateId, versionNumber](sequential version numbers) - Composite:
[templateId, createdAt(sort: Desc)](recent versions)
Relations:
- template → EmailTemplate (onDelete: Cascade)
- createdBy → User
EmailTemplateTestLog
Table: email_template_test_logs
Description: Test email audit logs.
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| id | String | ✓ | cuid() |
Primary key |
| templateId | String | ✓ | — | Foreign key to EmailTemplate |
| recipientEmail | String | ✓ | — | Test recipient email |
| testData | Json | ✓ | — | Sample variable values JSON |
| success | Boolean | ✓ | — | Test success flag |
| errorMessage | String | ✗ | null |
Error message (long text) |
| messageId | String | ✗ | null |
Nodemailer message ID |
| sentByUserId | String | ✓ | — | Foreign key to User |
| sentAt | DateTime | ✓ | now() |
Send timestamp |
Indexes:
- Composite:
[templateId, sentAt(sort: Desc)](recent tests)
Relations:
- template → EmailTemplate (onDelete: Cascade)
- sentBy → User
Landing Pages
LandingPage
Table: landing_pages
Description: GrapesJS editor output with MkDocs export support.
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| id | String | ✓ | cuid() |
Primary key |
| slug | String | ✓ | — | URL slug (unique) |
| title | String | ✓ | — | Page title |
| description | String | ✗ | null |
Page description (long text) |
| blocks | Json | ✓ | — | GrapesJS editor JSON |
| htmlOutput | String | ✗ | null |
Rendered HTML (long text) |
| cssOutput | String | ✗ | null |
Rendered CSS (long text) |
| editorMode | EditorMode | ✓ | VISUAL |
Editor mode: VISUAL, CODE |
| mkdocsPath | String | ✗ | null |
Path in mkdocs/overrides/ |
| mkdocsStubPath | String | ✗ | null |
Path to .md stub in mkdocs/docs/ |
| mkdocsExportMode | MkdocsExportMode | ✓ | THEMED |
Export mode: THEMED, STANDALONE |
| mkdocsHideNav | Boolean | ✓ | true |
Hide navigation in MkDocs |
| mkdocsHideToc | Boolean | ✓ | true |
Hide table of contents in MkDocs |
| mkdocsSkipExport | Boolean | ✓ | false |
Skip MkDocs export flag |
| published | Boolean | ✓ | false |
Published status |
| seoTitle | String | ✗ | null |
SEO title override |
| seoDescription | String | ✗ | null |
SEO description (long text) |
| seoImage | String | ✗ | null |
SEO image URL |
| createdAt | DateTime | ✓ | now() |
Creation timestamp |
| updatedAt | DateTime | ✓ | Auto | Last update timestamp |
Indexes:
- Unique:
slug
Relations: None
PageBlock
Table: page_blocks
Description: Reusable block library for GrapesJS editor.
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| id | String | ✓ | cuid() |
Primary key |
| type | String | ✓ | — | Block type (hero, text, image, cta, features, testimonials, form) |
| label | String | ✓ | — | Block label |
| schema | Json | ✓ | — | Block configuration schema JSON |
| defaults | Json | ✓ | — | Default values JSON |
| thumbnail | String | ✗ | null |
Thumbnail URL |
| category | String | ✗ | null |
Block category |
| sortOrder | Int | ✓ | 0 |
Display order |
| createdAt | DateTime | ✓ | now() |
Creation timestamp |
| updatedAt | DateTime | ✓ | Auto | Last update timestamp |
Indexes: None
Relations: None
Site Settings
SiteSettings
Table: site_settings
Description: Global site configuration singleton for branding, theme, SMTP, and feature toggles.
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| id | String | ✓ | cuid() |
Primary key (always "default") |
| organizationName | String | ✓ | "Changemaker Lite" |
Organization name |
| organizationShortName | String | ✓ | "CML" |
Short name/acronym |
| organizationLogoUrl | String | ✗ | null |
Logo URL |
| organizationFaviconUrl | String | ✗ | null |
Favicon URL |
| adminColorPrimary | String | ✓ | "#9d4edd" |
Admin primary color (hex) |
| adminColorBgBase | String | ✓ | "#1a1025" |
Admin background color (hex) |
| publicColorPrimary | String | ✓ | "#3498db" |
Public primary color (hex) |
| publicColorBgBase | String | ✓ | "#0d1b2a" |
Public background color (hex) |
| publicColorBgContainer | String | ✓ | "#1b2838" |
Public container color (hex) |
| publicHeaderGradient | String | ✓ | "linear-gradient(135deg, #005a9c 0%, #007acc 100%)" |
Public header gradient (CSS) |
| footerText | String | ✓ | "Powered by Changemaker Lite" |
Footer text |
| loginSubtitle | String | ✓ | "Admin" |
Login page subtitle |
| emailFromName | String | ✓ | "Changemaker Lite" |
Email from name |
| smtpHost | String | ✓ | "" |
SMTP host (empty = use env) |
| smtpPort | Int | ✓ | 0 |
SMTP port (0 = use env) |
| smtpUser | String | ✓ | "" |
SMTP username (empty = use env) |
| smtpPass | String | ✓ | "" |
SMTP password (empty = use env) |
| smtpFromAddress | String | ✓ | "" |
SMTP from address (empty = use env) |
| smtpActiveProvider | String | ✓ | "mailhog" |
Active provider: "mailhog", "production" |
| emailTestMode | Boolean | ✓ | true |
Email test mode flag |
| testEmailRecipient | String | ✓ | "" |
Test email recipient |
| enableInfluence | Boolean | ✓ | true |
Enable Influence module |
| enableMap | Boolean | ✓ | true |
Enable Map module |
| enableNewsletter | Boolean | ✓ | true |
Enable Newsletter module |
| enableLandingPages | Boolean | ✓ | true |
Enable Landing Pages module |
| createdAt | DateTime | ✓ | now() |
Creation timestamp |
| updatedAt | DateTime | ✓ | Auto | Last update timestamp |
Indexes: None
Relations: None (singleton)
Media (Drizzle ORM)
videos
Table: videos
Description: Video library with metadata extraction and engagement tracking.
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| id | serial | ✓ | Auto | Primary key (auto-increment) |
| path | text | ✓ | — | File path (unique) |
| filename | text | ✓ | — | File name |
| producer | text | ✗ | null |
Producer name |
| creator | text | ✗ | null |
Creator name |
| title | text | ✗ | null |
Video title |
| durationSeconds | integer | ✗ | null |
Duration in seconds (FFprobe) |
| quality | text | ✗ | null |
Quality string (e.g., "1080p") |
| orientation | text | ✗ | null |
Orientation: portrait, landscape, square |
| hasAudio | boolean | ✓ | true |
Audio track present flag |
| fileSize | bigint | ✗ | null |
File size in bytes |
| fileHash | text | ✗ | null |
MD5 hash |
| width | integer | ✗ | null |
Video width (FFprobe) |
| height | integer | ✗ | null |
Video height (FFprobe) |
| lastValidated | timestamp | ✗ | null |
Last validation timestamp |
| isValid | boolean | ✓ | true |
Valid file flag |
| thumbnailPath | text | ✗ | null |
Thumbnail file path |
| createdAt | timestamp | ✓ | now() |
Creation timestamp |
| tags | jsonb | ✗ | null |
Array of tag strings |
| directoryType | text | ✗ | null |
Directory type: studios, gifs, private, inbox, curated, playback, compilations, videos, highlights |
| publicViewCount | integer | ✗ | null |
Public view count (historical) |
| publicUpvoteCount | integer | ✗ | null |
Public upvote count (historical) |
| publicCommentCount | integer | ✗ | null |
Public comment count (historical) |
| publicCompletionCount | integer | ✗ | null |
Public completion count (historical) |
| publicTotalWatchTime | integer | ✗ | null |
Public total watch time (historical) |
| movedFromPublicAt | timestamp | ✗ | null |
Timestamp when moved from public media |
| originalFilename | text | ✗ | null |
Original filename before standardization |
| originalPath | text | ✗ | null |
Original path before standardization |
| standardizedAt | timestamp | ✗ | null |
Standardization timestamp |
Indexes:
- Unique:
path - Non-unique:
orientation - Non-unique:
producer - Non-unique:
isValid - Non-unique:
directoryType - Composite:
[durationSeconds, fileSize, width, height](fingerprint) - Composite:
[directoryType, isValid, orientation](common filtering)
Relations: None (standalone)
compilations
Table: compilations
Description: Video compilation tracking.
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| id | serial | ✓ | Auto | Primary key (auto-increment) |
| filename | text | ✓ | — | Compilation filename |
| path | text | ✗ | null |
Compilation file path |
| durationSeconds | integer | ✗ | null |
Total duration in seconds |
| videoIds | jsonb | ✗ | null |
Array of video IDs included |
| settings | jsonb | ✗ | null |
Compilation settings object |
| createdAt | timestamp | ✓ | now() |
Creation timestamp |
Indexes: None
Relations: None (video IDs stored as JSON array)
jobs
Table: jobs
Description: Job queue with resource category management.
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| id | serial | ✓ | Auto | Primary key (auto-increment) |
| type | text | ✓ | — | Job type (compilation, scan, organize, etc.) |
| status | text | ✓ | "pending" |
Status: pending, queued, running, completed, failed, cancelled |
| progress | integer | ✓ | 0 |
Progress percentage (0-100) |
| log | text | ✗ | null |
Job log output |
| params | jsonb | ✗ | null |
Job parameters object |
| startedAt | timestamp | ✗ | null |
Job start timestamp |
| completedAt | timestamp | ✗ | null |
Job completion timestamp |
| createdAt | timestamp | ✓ | now() |
Creation timestamp |
| resourceCategory | text | ✓ | "cpu" |
Resource category: gpu_ai, gpu_encode, cpu |
| vramRequired | integer | ✓ | 0 |
VRAM required in MB |
| queuePosition | integer | ✗ | null |
Queue position |
| waitingReason | text | ✗ | null |
Reason for waiting |
| priority | integer | ✓ | 5 |
Job priority (lower = higher priority) |
| pipelineId | integer | ✗ | null |
Pipeline ID (for pipeline jobs) |
| pipelineStepId | integer | ✗ | null |
Pipeline step ID |
Indexes:
- Composite:
[status, priority, createdAt](queue processing) - Composite:
[resourceCategory, status](resource filtering) - Non-unique:
pipelineId
Relations: None (pipeline relations are external)
Related Documentation
- Database Overview — Complete ER diagram and architecture
- Migration Workflow — Prisma and Drizzle migration processes
- Seeding — Default data and seed script
- Indexes — Index strategy and performance
- Auth Models — User and authentication models
- Influence Models — Campaign and advocacy models
- Map Models — Location, shift, and cut models
- Canvassing Models — Session and visit tracking
- Email Template Models — Template system models
- Landing Page Models — Page builder models
- Settings Models — Site and map settings
- Media Models — Video library models (Drizzle)