Files
biblical-guide.com/prisma/schema.prisma
Andrei a01377b21a feat: implement AI chat with vector search and random loading messages
Major Features:
-  AI chat with Azure OpenAI GPT-4o integration
-  Vector search across Bible versions (ASV English, RVA 1909 Spanish)
-  Multi-language support with automatic English fallback
-  Bible version citations in responses [ASV] [RVA 1909]
-  Random Bible-themed loading messages (5 variants)
-  Safe build script with memory guardrails
-  8GB swap memory for build safety
-  Stripe donation integration (multiple payment methods)

AI Chat Improvements:
- Implement vector search with 1536-dim embeddings (Azure text-embedding-ada-002)
- Search all Bible versions in user's language, fallback to English
- Cite Bible versions properly in AI responses
- Add 5 random loading messages: "Searching the Scriptures...", etc.
- Fix Ollama conflict (disabled to use Azure OpenAI exclusively)
- Optimize hybrid search queries for actual table schema

Build & Infrastructure:
- Create safe-build.sh script with memory monitoring (prevents server crashes)
- Add 8GB swap memory for emergency relief
- Document build process in BUILD_GUIDE.md
- Set Node.js memory limits (4GB max during builds)

Database:
- Clean up 115 old vector tables with wrong dimensions
- Keep only 2 tables with correct 1536-dim embeddings
- Add Stripe schema for donations and subscriptions

Documentation:
- AI_CHAT_FINAL_STATUS.md - Complete implementation status
- AI_CHAT_IMPLEMENTATION_COMPLETE.md - Technical details
- BUILD_GUIDE.md - Safe building guide with guardrails
- CHAT_LOADING_MESSAGES.md - Loading messages implementation
- STRIPE_IMPLEMENTATION_COMPLETE.md - Stripe integration docs
- STRIPE_SETUP_GUIDE.md - Stripe configuration guide

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-12 19:37:24 +00:00

458 lines
13 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?
sessions Session[]
bookmarks Bookmark[]
chapterBookmarks ChapterBookmark[]
highlights Highlight[]
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")
donations Donation[]
@@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[]
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
}