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? // Subscription fields subscriptionTier String @default("free") // "free", "premium" subscriptionStatus String @default("active") // "active", "cancelled", "expired", "past_due" conversationLimit Int @default(10) conversationCount Int @default(0) // Reset monthly limitResetDate DateTime? // When to reset conversation count stripeCustomerId String? @unique // For subscriptions (separate from donations) stripeSubscriptionId String? @unique sessions Session[] bookmarks Bookmark[] chapterBookmarks ChapterBookmark[] highlights Highlight[] notes Note[] chatMessages ChatMessage[] chatConversations ChatConversation[] prayerRequests PrayerRequest[] userPrayers UserPrayer[] readingHistory ReadingHistory[] preferences UserPreference[] userReadingPlans UserReadingPlan[] readingProgress UserReadingProgress[] createdPages Page[] @relation("PageCreator") updatedPages Page[] @relation("PageUpdater") uploadedFiles MediaFile[] createdSocialMedia SocialMediaLink[] @relation("SocialMediaCreator") updatedSocialMedia SocialMediaLink[] @relation("SocialMediaUpdater") updatedMailgunSettings MailgunSettings[] @relation("MailgunSettingsUpdater") donations Donation[] subscriptions Subscription[] @@index([role]) @@index([subscriptionTier]) @@index([stripeCustomerId]) } 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[] highlights Highlight[] @@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 Highlight { id String @id @default(uuid()) userId String verseId String color String // yellow, green, blue, purple, orange, pink, red note String? @db.Text tags String[] @default([]) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt user User @relation(fields: [userId], references: [id], onDelete: Cascade) verse BibleVerse @relation(fields: [verseId], references: [id]) @@unique([userId, verseId]) @@index([userId]) @@index([verseId]) } 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]) } model Donation { id String @id @default(uuid()) userId String? // Optional - can be anonymous stripeSessionId String @unique stripePaymentId String? // Payment intent ID email String name String? amount Int // Amount in cents currency String @default("usd") status DonationStatus @default(PENDING) message String? @db.Text // Optional message from donor isAnonymous Boolean @default(false) isRecurring Boolean @default(false) recurringInterval String? // monthly, yearly metadata Json? // Store additional Stripe metadata createdAt DateTime @default(now()) updatedAt DateTime @updatedAt user User? @relation(fields: [userId], references: [id], onDelete: SetNull) @@index([userId]) @@index([status]) @@index([createdAt]) @@index([email]) } enum DonationStatus { PENDING COMPLETED FAILED REFUNDED CANCELLED } model Subscription { id String @id @default(uuid()) userId String stripeSubscriptionId String @unique stripePriceId String // Stripe price ID for the plan stripeCustomerId String status SubscriptionStatus currentPeriodStart DateTime currentPeriodEnd DateTime cancelAtPeriodEnd Boolean @default(false) tier String // "premium" interval String // "month" or "year" metadata Json? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt user User @relation(fields: [userId], references: [id], onDelete: Cascade) @@index([userId]) @@index([status]) @@index([stripeSubscriptionId]) } enum SubscriptionStatus { ACTIVE CANCELLED PAST_DUE TRIALING INCOMPLETE INCOMPLETE_EXPIRED UNPAID } // Reading Plans model ReadingPlan { id String @id @default(uuid()) name String // "Bible in One Year", "New Testament in 30 Days" description String? @db.Text type ReadingPlanType @default(PREDEFINED) duration Int // Number of days schedule Json // Daily reading schedule: {day: 1, readings: [{book, chapter, verses}]} difficulty String @default("beginner") // beginner, intermediate, advanced language String @default("en") isActive Boolean @default(true) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt userPlans UserReadingPlan[] @@index([type]) @@index([language]) @@index([isActive]) } enum ReadingPlanType { PREDEFINED CUSTOM } model UserReadingPlan { id String @id @default(uuid()) userId String planId String? // Null for custom plans name String // Plan name (especially for custom plans) startDate DateTime @default(now()) targetEndDate DateTime // Expected completion date actualEndDate DateTime? // When actually completed status ReadingPlanStatus @default(ACTIVE) currentDay Int @default(1) // Current day in plan completedDays Int @default(0) // Total days completed streak Int @default(0) // Current consecutive days longestStreak Int @default(0) // Best streak achieved customSchedule Json? // For custom plans: same format as ReadingPlan.schedule reminderEnabled Boolean @default(true) reminderTime String? // "08:00" - time of day for reminder createdAt DateTime @default(now()) updatedAt DateTime @updatedAt user User @relation(fields: [userId], references: [id], onDelete: Cascade) plan ReadingPlan? @relation(fields: [planId], references: [id], onDelete: SetNull) progress UserReadingProgress[] @@index([userId]) @@index([status]) @@index([userId, status]) } enum ReadingPlanStatus { ACTIVE COMPLETED PAUSED CANCELLED } model UserReadingProgress { id String @id @default(uuid()) userId String userPlanId String planDay Int // Day number in the reading plan date DateTime @default(now()) // Date of reading bookId String // Bible book read chapterNum Int versesRead String? // "1-10" or "all" or null for whole chapter completed Boolean @default(true) notes String? @db.Text createdAt DateTime @default(now()) updatedAt DateTime @updatedAt user User @relation(fields: [userId], references: [id], onDelete: Cascade) userPlan UserReadingPlan @relation(fields: [userPlanId], references: [id], onDelete: Cascade) @@unique([userPlanId, planDay, bookId, chapterNum]) // One entry per chapter per day per plan @@index([userId]) @@index([userPlanId]) @@index([userId, date]) }