## Typography Enhancements - Add letter spacing control (0-2px, default 0.5px for WCAG compliance) - Add word spacing control (0-4px, default 0px) - Add paragraph spacing control (1.0-2.5x line height, default 1.8x) - Add max line length control (50-100ch, default 75ch for optimal readability) - Apply WCAG 2.1 SC 1.4.12 text spacing recommendations ## Multi-Color Highlighting System - Implement 7-color highlight palette (yellow, green, blue, purple, orange, pink, red) - Theme-aware highlight colors (light/dark/sepia modes) - Persistent visual highlights with database storage - Color picker UI with current highlight indicator - Support for highlight notes and tags (infrastructure ready) - Bulk highlight loading for performance - Add/update/remove highlight functionality ## Database Schema - Add Highlight model with verse relationship - Support for color, note, tags, and timestamps - Unique constraint per user-verse pair - Proper indexing for performance ## API Routes - POST /api/highlights - Create new highlight - GET /api/highlights - Get all user highlights - POST /api/highlights/bulk - Bulk fetch highlights for verses - PUT /api/highlights/[id] - Update highlight color/note/tags - DELETE /api/highlights/[id] - Remove highlight ## UI Improvements - Enhanced settings dialog with new typography controls - Highlight color picker menu - Verse menu updated with highlight option - Visual feedback for highlighted verses - Remove highlight button in color picker Note: Database migration pending - run `npx prisma db push` to apply schema 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
423 lines
12 KiB
Plaintext
423 lines
12 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")
|
|
|
|
@@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])
|
|
}
|