1039 lines
46 KiB
Markdown
1039 lines
46 KiB
Markdown
# 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 |
|
|
| email | 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 |
|
|
| email | 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) |
|
|
| email | 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 |
|
|
| email | 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](./index.md) — Complete ER diagram and architecture
|
|
- [Migration Workflow](./migrations.md) — Prisma and Drizzle migration processes
|
|
- [Seeding](./seeding.md) — Default data and seed script
|
|
- [Indexes](./indexes.md) — Index strategy and performance
|
|
- [Auth Models](./models/auth.md) — User and authentication models
|
|
- [Influence Models](./models/influence.md) — Campaign and advocacy models
|
|
- [Map Models](./models/map.md) — Location, shift, and cut models
|
|
- [Canvassing Models](./models/canvass.md) — Session and visit tracking
|
|
- [Email Template Models](./models/email-templates.md) — Template system models
|
|
- [Landing Page Models](./models/pages.md) — Page builder models
|
|
- [Settings Models](./models/settings.md) — Site and map settings
|
|
- [Media Models](./models/media.md) — Video library models (Drizzle)
|