- Add jobTitle, company, bio columns to User model (previously localStorage only) - Replace legacy Authentik GET /api/auth/me with session-based profile endpoint - Update PUT /api/auth/me to persist all profile fields to DB - Add PUT /api/auth/change-password endpoint with current password verification - Add Security card to profile page with change password form - Update user-profile provider to fetch from API instead of localStorage Made-with: Cursor
487 lines
15 KiB
Text
487 lines
15 KiB
Text
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])
|
|
}
|