Files
biblical-guide.com/prisma/schema.prisma
2025-11-12 07:18:57 +00:00

604 lines
18 KiB
Plaintext

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[]
userHighlights UserHighlight[]
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 UserHighlight {
id String @id @default(cuid())
userId String
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
verseId String
color String @default("yellow") // yellow, orange, pink, blue
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@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])
}