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