generator client { provider = "prisma-client-js" } datasource db { provider = "postgresql" url = env("DATABASE_URL") } model User { id String @id @default(uuid()) email String @unique passwordHash String name String? role String @default("user") // "user", "admin", "moderator" theme String @default("light") fontSize String @default("medium") favoriteBibleVersion String? // User's preferred Bible version ID createdAt DateTime @default(now()) updatedAt DateTime @updatedAt lastLoginAt DateTime? sessions Session[] bookmarks Bookmark[] chapterBookmarks ChapterBookmark[] notes Note[] chatMessages ChatMessage[] chatConversations ChatConversation[] prayerRequests PrayerRequest[] userPrayers UserPrayer[] readingHistory ReadingHistory[] preferences UserPreference[] createdPages Page[] @relation("PageCreator") updatedPages Page[] @relation("PageUpdater") uploadedFiles MediaFile[] createdSocialMedia SocialMediaLink[] @relation("SocialMediaCreator") updatedSocialMedia SocialMediaLink[] @relation("SocialMediaUpdater") updatedMailgunSettings MailgunSettings[] @relation("MailgunSettingsUpdater") @@index([role]) } model Session { id String @id @default(uuid()) userId String token String @unique expiresAt DateTime createdAt DateTime @default(now()) user User @relation(fields: [userId], references: [id], onDelete: Cascade) @@index([userId]) @@index([token]) } model BibleVersion { id String @id @default(uuid()) name String // e.g., "King James Version", "Cornilescu" abbreviation String // e.g., "KJV", "CORNILESCU", "NIV" language String // e.g., "en", "ro", "es" description String? country String? englishTitle String? flagImageUrl String? zipFileUrl String? isDefault Boolean @default(false) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt books BibleBook[] @@unique([abbreviation, language]) @@index([language]) @@index([isDefault]) @@index([language, isDefault]) // Composite index for filtered + sorted queries @@index([name]) // Index for search by name @@index([abbreviation]) // Index for search by abbreviation } model BibleBook { id String @id @default(uuid()) versionId String name String // Version-specific book name testament String orderNum Int bookKey String // For cross-version matching (e.g., "genesis", "exodus") chapters BibleChapter[] chapterBookmarks ChapterBookmark[] version BibleVersion @relation(fields: [versionId], references: [id]) @@unique([versionId, orderNum]) @@unique([versionId, bookKey]) @@index([versionId]) @@index([testament]) } model BibleChapter { id String @id @default(uuid()) bookId String chapterNum Int verses BibleVerse[] book BibleBook @relation(fields: [bookId], references: [id]) @@unique([bookId, chapterNum]) @@index([bookId]) } model BibleVerse { id String @id @default(uuid()) chapterId String verseNum Int text String @db.Text chapter BibleChapter @relation(fields: [chapterId], references: [id]) bookmarks Bookmark[] notes Note[] @@unique([chapterId, verseNum]) @@index([chapterId]) } model BiblePassage { id String @id @default(uuid()) testament String // 'OT' or 'NT' book String chapter Int verse Int ref String // Generated field: "book chapter:verse" lang String @default("ro") translation String @default("FIDELA") textRaw String @db.Text textNorm String @db.Text // Normalized text for embedding embedding String? // Will be changed to vector later when extension is available createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@unique([translation, lang, book, chapter, verse]) @@index([book, chapter]) @@index([testament]) } model ChatConversation { id String @id @default(uuid()) userId String? // Optional for anonymous users title String // Auto-generated from first message language String // 'ro' or 'en' isActive Boolean @default(true) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt lastMessageAt DateTime @default(now()) user User? @relation(fields: [userId], references: [id], onDelete: Cascade) messages ChatMessage[] @@index([userId, language, lastMessageAt]) @@index([isActive, lastMessageAt]) } model ChatMessage { id String @id @default(uuid()) conversationId String userId String? // Keep for backward compatibility role ChatMessageRole content String @db.Text metadata Json? // Store verse references, etc. timestamp DateTime @default(now()) conversation ChatConversation @relation(fields: [conversationId], references: [id], onDelete: Cascade) user User? @relation(fields: [userId], references: [id], onDelete: Cascade) @@index([conversationId, timestamp]) @@index([userId, timestamp]) } enum ChatMessageRole { USER ASSISTANT SYSTEM } model Bookmark { id String @id @default(uuid()) userId String verseId String note String? color String @default("#FFD700") createdAt DateTime @default(now()) user User @relation(fields: [userId], references: [id], onDelete: Cascade) verse BibleVerse @relation(fields: [verseId], references: [id]) @@unique([userId, verseId]) @@index([userId]) } model ChapterBookmark { id String @id @default(uuid()) userId String bookId String chapterNum Int note String? createdAt DateTime @default(now()) user User @relation(fields: [userId], references: [id], onDelete: Cascade) book BibleBook @relation(fields: [bookId], references: [id]) @@unique([userId, bookId, chapterNum]) @@index([userId]) } model Note { id String @id @default(uuid()) userId String verseId String content String @db.Text createdAt DateTime @default(now()) updatedAt DateTime @updatedAt user User @relation(fields: [userId], references: [id], onDelete: Cascade) verse BibleVerse @relation(fields: [verseId], references: [id]) @@index([userId]) @@index([verseId]) } model PrayerRequest { id String @id @default(uuid()) userId String? title String description String @db.Text category String // personal, family, health, work, ministry, world author String // Display name (can be "Anonymous" or user's name) isAnonymous Boolean @default(false) isPublic Boolean @default(true) language String @default("en") prayerCount Int @default(0) isActive Boolean @default(true) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt user User? @relation(fields: [userId], references: [id], onDelete: Cascade) prayers Prayer[] userPrayers UserPrayer[] @@index([createdAt]) @@index([category]) @@index([isActive]) } model Prayer { id String @id @default(uuid()) requestId String ipAddress String // For anonymous prayer counting createdAt DateTime @default(now()) request PrayerRequest @relation(fields: [requestId], references: [id], onDelete: Cascade) @@unique([requestId, ipAddress]) } model UserPrayer { id String @id @default(uuid()) userId String requestId String createdAt DateTime @default(now()) user User @relation(fields: [userId], references: [id], onDelete: Cascade) request PrayerRequest @relation(fields: [requestId], references: [id], onDelete: Cascade) @@unique([userId, requestId]) @@index([userId]) @@index([requestId]) } model ReadingHistory { id String @id @default(uuid()) userId String versionId String // Bible version ID bookId String chapterNum Int verseNum Int? viewedAt DateTime @default(now()) user User @relation(fields: [userId], references: [id], onDelete: Cascade) @@index([userId, viewedAt]) @@index([userId, versionId]) @@unique([userId, versionId]) // Only one reading position per user per version } model UserPreference { id String @id @default(uuid()) userId String key String value String user User @relation(fields: [userId], references: [id], onDelete: Cascade) @@unique([userId, key]) } model Page { id String @id @default(uuid()) title String slug String @unique content String @db.Text contentType PageContentType @default(RICH_TEXT) excerpt String? @db.Text featuredImage String? seoTitle String? seoDescription String? status PageStatus @default(DRAFT) showInNavigation Boolean @default(false) showInFooter Boolean @default(false) navigationOrder Int? footerOrder Int? createdBy String updatedBy String createdAt DateTime @default(now()) updatedAt DateTime @updatedAt publishedAt DateTime? creator User @relation("PageCreator", fields: [createdBy], references: [id]) updater User @relation("PageUpdater", fields: [updatedBy], references: [id]) @@index([slug]) @@index([status]) @@index([showInNavigation, navigationOrder]) @@index([showInFooter, footerOrder]) } model MediaFile { id String @id @default(uuid()) filename String originalName String mimeType String size Int path String url String alt String? uploadedBy String createdAt DateTime @default(now()) uploader User @relation(fields: [uploadedBy], references: [id]) @@index([uploadedBy]) @@index([mimeType]) } enum PageContentType { RICH_TEXT HTML MARKDOWN } enum PageStatus { DRAFT PUBLISHED ARCHIVED } model SocialMediaLink { id String @id @default(uuid()) platform String // facebook, twitter, instagram, youtube, linkedin, tiktok, etc. name String // Display name (e.g., "Facebook", "Instagram") url String // Full URL to the social media profile icon String // Icon identifier (material-ui icon name) isEnabled Boolean @default(true) order Int @default(0) // Display order in footer createdBy String updatedBy String createdAt DateTime @default(now()) updatedAt DateTime @updatedAt creator User @relation("SocialMediaCreator", fields: [createdBy], references: [id]) updater User @relation("SocialMediaUpdater", fields: [updatedBy], references: [id]) @@unique([platform]) @@index([isEnabled, order]) } model MailgunSettings { id String @id @default(uuid()) apiKey String // Encrypted Mailgun API key domain String // Mailgun domain (e.g., mg.yourdomain.com) region String @default("US") // US or EU fromEmail String // Default from email address fromName String // Default from name replyToEmail String? // Optional reply-to address isEnabled Boolean @default(false) testMode Boolean @default(true) webhookUrl String? // Mailgun webhook URL for tracking updatedBy String createdAt DateTime @default(now()) updatedAt DateTime @updatedAt updater User @relation("MailgunSettingsUpdater", fields: [updatedBy], references: [id]) @@index([isEnabled]) }