generator client { provider = "prisma-client" output = "../src/generated/prisma" } datasource db { provider = "postgresql" } // ─── Auth.js tables ────────────────────────────────────────── model User { id String @id @default(cuid()) email String @unique emailVerified DateTime? hashedPassword String? username String? displayName String? avatarUrl String @default("") jobTitle String @default("") company String @default("") bio String @default("") role String @default("viewer") activeOrgId String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt accounts Account[] sessions Session[] memberships OrgMember[] assignedCards ResponseCard[] @relation("AssignedCards") assignorCards ResponseCard[] @relation("AssignorCards") reviewedCards ResponseCard[] @relation("ReviewedCards") activityLogs ActivityLog[] notifications Notification[] authentikUid String? @unique } model Account { id String @id @default(cuid()) userId String type String provider String providerAccountId String refresh_token String? access_token String? expires_at Int? token_type String? scope String? id_token String? session_state String? user User @relation(fields: [userId], references: [id], onDelete: Cascade) @@unique([provider, providerAccountId]) } model Session { id String @id @default(cuid()) sessionToken String @unique userId String expires DateTime user User @relation(fields: [userId], references: [id], onDelete: Cascade) } model VerificationToken { identifier String token String @unique expires DateTime @@unique([identifier, token]) } // ─── Organization / Multi-tenancy ──────────────────────────── model Organization { id String @id @default(cuid()) name String slug String @unique type String @default("church") timezone String @default("America/Chicago") settings Json? allowedDomains String[] @default([]) onboardingComplete Boolean @default(false) onboardingStep Int @default(0) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt locations Location[] members OrgMember[] invitations Invitation[] apiKeys ApiKey[] cards ResponseCard[] jobs ProcessingJob[] integrations Integration[] formTemplates FormTemplate[] persons Person[] } model OrgMember { id String @id @default(cuid()) userId String organizationId String role String @default("viewer") dismissedHints Json? user User @relation(fields: [userId], references: [id], onDelete: Cascade) organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade) createdAt DateTime @default(now()) @@unique([userId, organizationId]) } model Location { id String @id @default(cuid()) organizationId String name String address String? timezone String? organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade) collectionDays CollectionDay[] cards ResponseCard[] createdAt DateTime @default(now()) updatedAt DateTime @updatedAt } model CollectionDay { id String @id @default(cuid()) locationId String name String description String? rrule String? dayOfWeek Int? timeStart String? timeEnd String? isRecurring Boolean @default(true) date DateTime? isActive Boolean @default(true) location Location @relation(fields: [locationId], references: [id], onDelete: Cascade) cards ResponseCard[] createdAt DateTime @default(now()) updatedAt DateTime @updatedAt } // ─── Form Templates (Dynamic Fields) ──────────────────────── model FormTemplate { id String @id @default(cuid()) organizationId String name String slug String description String? isDefault Boolean @default(false) isActive Boolean @default(true) version Int @default(1) branding Json? settings Json? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade) fields FormField[] cards ResponseCard[] @@unique([organizationId, slug]) @@index([organizationId]) } model FormField { id String @id @default(cuid()) formTemplateId String key String label String type String section String @default("personal") required Boolean @default(false) removable Boolean @default(true) sortOrder Int @default(0) options Json? placeholder String? helpText String? validation Json? visibleOnCard Boolean @default(true) visibleOnSurvey Boolean @default(true) isCore Boolean @default(false) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt formTemplate FormTemplate @relation(fields: [formTemplateId], references: [id], onDelete: Cascade) @@unique([formTemplateId, key]) @@index([formTemplateId, sortOrder]) } // ─── People (Contact Directory) ───────────────────────────── model Person { id String @id @default(cuid()) organizationId String firstName String lastName String email String? cellPhone String? fieldData Json? mergedIntoId String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade) cards ResponseCard[] mergedInto Person? @relation("PersonMerge", fields: [mergedIntoId], references: [id]) mergedFrom Person[] @relation("PersonMerge") @@index([organizationId]) @@index([email]) @@index([organizationId, firstName, lastName]) @@index([mergedIntoId]) } // ─── Integrations ──────────────────────────────────────────── model Integration { id String @id @default(cuid()) organizationId String provider String name String enabled Boolean @default(false) config Json fieldMapping Json? syncDirection String @default("push") triggerEvents Json? lastSyncAt DateTime? lastSyncStatus String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade) @@unique([organizationId, provider, name]) @@index([organizationId, provider]) } // ─── Invitations ───────────────────────────────────────────── model Invitation { id String @id @default(cuid()) email String role String @default("viewer") organizationId String invitedById String token String @unique @default(cuid()) expiresAt DateTime acceptedAt DateTime? createdAt DateTime @default(now()) organization Organization @relation(fields: [organizationId], references: [id]) } // ─── Password Reset ───────────────────────────────────────── model PasswordResetToken { id String @id @default(cuid()) email String token String @unique expiresAt DateTime createdAt DateTime @default(now()) @@index([email]) } // ─── System config ─────────────────────────────────────────── model SystemConfig { id String @id @default("singleton") isSetupComplete Boolean @default(false) setupStep Int @default(0) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt } // ─── Core application models ───────────────────────────────── model ResponseCard { id String @id @default(cuid()) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt firstName String? lastName String? name String? gender String? dateOfBirth String? maritalStatus String? maritalStatusOther String? visitType String? cellPhone String? homePhone String? email String? address String? aptNumber String? city String? state String? zip String? prayerRequests String? prayerForTeam Boolean @default(false) prayerConfidential Boolean @default(false) messageTopics Json? messageTopicsOther String? nextStep Json? attendanceDuration String? campusPreference Json? campusPreferenceOther String? howHeard Json? howHeardOther String? serviceAttended String? followUp String? notes String? serviceTime String? planningCenter String? iSaidYesBookSent Boolean @default(false) ftGuestLetterSent Boolean @default(false) firstTimeGuestDate DateTime? salvationDate DateTime? assignedToId String? assignedById String? assignedAt DateTime? reviewedById String? reviewedAt DateTime? reviewNotes String? assignedTo User? @relation("AssignedCards", fields: [assignedToId], references: [id]) assignedBy User? @relation("AssignorCards", fields: [assignedById], references: [id]) reviewedBy User? @relation("ReviewedCards", fields: [reviewedById], references: [id]) sourceFile String? frontImagePath String? backImagePath String? ocrStatus String @default("pending") reviewStatus String @default("unreviewed") ocrConfidence Float? ocrError String? rawOcrResponse Json? mondayItemId String? fieldData Json? formTemplateId String? personId String? submissionSource String? organizationId String? locationId String? collectionDayId String? collectionDate DateTime? organization Organization? @relation(fields: [organizationId], references: [id]) location Location? @relation(fields: [locationId], references: [id]) collectionDay CollectionDay? @relation(fields: [collectionDayId], references: [id]) formTemplate FormTemplate? @relation(fields: [formTemplateId], references: [id]) person Person? @relation(fields: [personId], references: [id]) @@index([ocrStatus]) @@index([reviewStatus]) @@index([name]) @@index([firstName, lastName]) @@index([createdAt]) @@index([mondayItemId]) @@index([assignedToId]) @@index([reviewedById]) @@index([organizationId]) @@index([locationId]) @@index([collectionDayId]) @@index([formTemplateId]) @@index([personId]) } model ProcessingJob { id String @id @default(cuid()) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt fileName String filePath String status String @default("queued") totalPages Int @default(0) processed Int @default(0) error String? cardIds Json? organizationId String? organization Organization? @relation(fields: [organizationId], references: [id]) @@index([status]) } model AppSettings { id String @id @default("singleton") ollamaUrl String @default("http://192.168.68.108:11434") model String @default("llava:7b") watchDir String @default("") watching Boolean @default(false) sourceRetentionDays Int @default(30) imageRetentionDays Int @default(180) aiProvider String @default("gateway") aiModel String @default("") mondayApiToken String @default("") mondayBoardId String @default("") mondayEnabled Boolean @default(false) mondayColumnMap Json? mondayWebhookId String @default("") mondayWebhookUrl String @default("") webhookUrl String @default("") webhookSecret String @default("") webhookEnabled Boolean @default(false) webhookEvents Json? emailImapHost String @default("imap.dreamhost.com") emailImapPort Int @default(993) emailImapUser String @default("echo-ocr@stillwell.cloud") emailImapPass String @default("") emailImapTls Boolean @default(true) emailFolder String @default("INBOX") emailWatching Boolean @default(false) emailProcessed String @default("mark_read") emailProcessedFolder String @default("Processed") ftpEnabled Boolean @default(false) ftpHost String @default("") ftpPort Int @default(22) ftpUser String @default("") ftpPass String @default("") ftpProtocol String @default("sftp") // "sftp" | "ftp" | "ftps" ftpIncomingDir String @default("/incoming") ftpProcessedDir String @default("/processed") } model ActivityLog { id String @id @default(cuid()) createdAt DateTime @default(now()) cardId String? action String source String summary String changes Json? userId String? user User? @relation(fields: [userId], references: [id]) @@index([cardId, createdAt]) } model Notification { id String @id @default(cuid()) createdAt DateTime @default(now()) read Boolean @default(false) dismissed Boolean @default(false) type String title String message String cardId String? actionUrl String? meta Json? userId String? user User? @relation(fields: [userId], references: [id]) @@index([read, dismissed, createdAt]) @@index([cardId]) @@index([userId]) } model ApiKey { id String @id @default(cuid()) name String hashedKey String @unique prefix String organizationId String permissions Json lastUsedAt DateTime? expiresAt DateTime? createdAt DateTime @default(now()) organization Organization @relation(fields: [organizationId], references: [id]) }