493 lines
16 KiB
Plaintext
493 lines
16 KiB
Plaintext
generator client {
|
|
provider = "prisma-client-js"
|
|
binaryTargets = ["rhel-openssl-3.0.x"]
|
|
}
|
|
|
|
datasource db {
|
|
provider = "postgresql"
|
|
url = env("DATABASE_URL")
|
|
}
|
|
|
|
model User {
|
|
id String @id @default(cuid())
|
|
email String @unique
|
|
name String
|
|
passwordHash String?
|
|
provider String @default("email")
|
|
providerId String?
|
|
avatar String?
|
|
flhBalance Int @default(5000)
|
|
isPro Boolean @default(false)
|
|
isPremium Boolean @default(false)
|
|
experienceLevel String?
|
|
madhab String?
|
|
coachPersona String?
|
|
preferredName String?
|
|
coachingGoals String?
|
|
lastCoachedAt DateTime?
|
|
dailyMsgCount Int @default(0)
|
|
dailyMsgResetAt DateTime?
|
|
stripeCustomerId String?
|
|
stripeSubscriptionId String?
|
|
trialEndsAt DateTime?
|
|
createdAt DateTime @default(now())
|
|
|
|
@@index([provider, providerId])
|
|
|
|
listings Listing[]
|
|
purchases Purchase[] @relation("BuyerPurchases")
|
|
sales Purchase[] @relation("SellerPurchases")
|
|
cashouts CashoutRequest[]
|
|
halalBookmarks HalalBookmark[]
|
|
halalUsage HalalUsage?
|
|
userPlaces UserPlace[]
|
|
ownedGroups Group[] @relation("GroupOwner")
|
|
groupMemberships GroupMember[]
|
|
forumThreads ForumThread[]
|
|
forumPosts ForumPost[]
|
|
chatHistory ChatHistory[]
|
|
notifications Notification[]
|
|
referredReferrals Referral[] @relation("Referrer")
|
|
referrerReferral Referral? @relation("Referred")
|
|
dailyStreak DailyStreak?
|
|
xpTransactions XpTransaction[]
|
|
achievements Achievement[]
|
|
userLevel UserLevel?
|
|
dhikrSessions DhikrSession[]
|
|
dhikrDays DhikrDay[]
|
|
reviewListings Review[] @relation("ListingReviews")
|
|
}
|
|
|
|
model DailyStreak {
|
|
userId String @id
|
|
user User @relation(fields: [userId], references: [id])
|
|
currentStreak Int @default(0)
|
|
longestStreak Int @default(0)
|
|
lastClaimDate DateTime?
|
|
streakFreeze Int @default(0)
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
}
|
|
|
|
model XpTransaction {
|
|
id String @id @default(cuid())
|
|
userId String
|
|
user User @relation(fields: [userId], references: [id])
|
|
amount Int
|
|
reason String // 'daily_login' | 'chat_message' | 'forum_post' | 'purchase' | 'referral' | 'achievement'
|
|
createdAt DateTime @default(now())
|
|
|
|
@@index([userId])
|
|
}
|
|
|
|
model Achievement {
|
|
id String @id @default(cuid())
|
|
userId String
|
|
user User @relation(fields: [userId], references: [id])
|
|
type String // 'first_chat' | 'streak_7' | 'streak_30' | 'level_5' | 'first_purchase' | 'first_post'
|
|
title String
|
|
description String?
|
|
icon String?
|
|
unlockedAt DateTime @default(now())
|
|
|
|
@@index([userId])
|
|
@@unique([userId, type])
|
|
}
|
|
|
|
model UserLevel {
|
|
userId String @id
|
|
user User @relation(fields: [userId], references: [id])
|
|
level Int @default(1)
|
|
xp Int @default(0)
|
|
xpToNext Int @default(100)
|
|
updatedAt DateTime @updatedAt
|
|
}
|
|
|
|
model Listing {
|
|
id String @id @default(cuid())
|
|
title String
|
|
description String
|
|
category String
|
|
subcategory String?
|
|
priceFlh Int
|
|
packages String? // JSON: [{name, description, price, deliveryDays, revisions}]
|
|
tags String? // JSON: ["tag1", "tag2"]
|
|
images String? // JSON: ["url1"]
|
|
deliveryDays Int?
|
|
rating Float @default(0)
|
|
reviewCount Int @default(0)
|
|
salesCount Int @default(0)
|
|
sellerId String
|
|
seller User @relation(fields: [sellerId], references: [id])
|
|
status String @default("active")
|
|
featured Boolean @default(false)
|
|
featuredUntil DateTime?
|
|
fileType String?
|
|
createdAt DateTime @default(now())
|
|
|
|
purchases Purchase[]
|
|
reviews Review[]
|
|
}
|
|
|
|
model Purchase {
|
|
id String @id @default(cuid())
|
|
listingId String
|
|
listing Listing @relation(fields: [listingId], references: [id])
|
|
buyerId String
|
|
buyer User @relation("BuyerPurchases", fields: [buyerId], references: [id])
|
|
sellerId String
|
|
seller User @relation("SellerPurchases", fields: [sellerId], references: [id])
|
|
packageName String?
|
|
amountFlh Int
|
|
platformFee Int
|
|
sellerPayout Int
|
|
status String @default("pending") // pending, paid, in_progress, delivered, completed, disputed, cancelled
|
|
messages String? // JSON: [{from, text, timestamp}]
|
|
deliveryFiles String? // JSON: [{name, size, url}]
|
|
autoConfirmAt DateTime?
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
reviews Review[]
|
|
}
|
|
|
|
model Review {
|
|
id String @id @default(cuid())
|
|
listingId String
|
|
listing Listing @relation(fields: [listingId], references: [id])
|
|
purchaseId String
|
|
purchase Purchase @relation(fields: [purchaseId], references: [id])
|
|
reviewerId String
|
|
reviewer User @relation("ListingReviews", fields: [reviewerId], references: [id])
|
|
sellerId String
|
|
rating Int
|
|
title String?
|
|
text String
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@index([listingId])
|
|
@@index([sellerId])
|
|
}
|
|
|
|
model CashoutRequest {
|
|
id String @id @default(cuid())
|
|
userId String
|
|
user User @relation(fields: [userId], references: [id])
|
|
amountFlh Int
|
|
fiatAmount Float
|
|
status String @default("pending")
|
|
createdAt DateTime @default(now())
|
|
}
|
|
|
|
model ForumCategory {
|
|
id String @id @default(cuid())
|
|
name String @unique
|
|
description String?
|
|
icon String?
|
|
order Int?
|
|
createdAt DateTime @default(now())
|
|
|
|
threads ForumThread[]
|
|
}
|
|
|
|
model ForumThread {
|
|
id String @id @default(cuid())
|
|
title String
|
|
content String
|
|
categoryId String
|
|
category ForumCategory @relation(fields: [categoryId], references: [id])
|
|
authorId String
|
|
author User @relation(fields: [authorId], references: [id])
|
|
pinned Boolean @default(false)
|
|
shariahStatus String @default("pending")
|
|
shariahFlags String?
|
|
createdAt DateTime @default(now())
|
|
|
|
posts ForumPost[]
|
|
group Group? @relation(fields: [groupId], references: [id])
|
|
groupId String?
|
|
}
|
|
|
|
model Group {
|
|
id String @id @default(cuid())
|
|
name String
|
|
description String?
|
|
ownerId String
|
|
owner User @relation("GroupOwner", fields: [ownerId], references: [id])
|
|
inviteCode String @unique
|
|
createdAt DateTime @default(now())
|
|
|
|
members GroupMember[]
|
|
threads ForumThread[]
|
|
}
|
|
|
|
model GroupMember {
|
|
id String @id @default(cuid())
|
|
groupId String
|
|
group Group @relation(fields: [groupId], references: [id])
|
|
userId String
|
|
user User @relation(fields: [userId], references: [id])
|
|
role String @default("member")
|
|
joinedAt DateTime @default(now())
|
|
|
|
@@unique([groupId, userId])
|
|
}
|
|
|
|
model ForumPost {
|
|
id String @id @default(cuid())
|
|
content String
|
|
threadId String
|
|
thread ForumThread @relation(fields: [threadId], references: [id])
|
|
authorId String
|
|
author User @relation(fields: [authorId], references: [id])
|
|
shariahStatus String @default("pending")
|
|
shariahFlags String?
|
|
createdAt DateTime @default(now())
|
|
}
|
|
|
|
model ChatHistory {
|
|
id String @id @default(cuid())
|
|
userId String
|
|
user User @relation(fields: [userId], references: [id])
|
|
role String
|
|
content String
|
|
metadata String?
|
|
createdAt DateTime @default(now())
|
|
|
|
@@index([userId])
|
|
}
|
|
|
|
model HalalBookmark {
|
|
id String @id @default(cuid())
|
|
userId String
|
|
user User @relation(fields: [userId], references: [id])
|
|
itemId String
|
|
itemType String
|
|
label String?
|
|
createdAt DateTime @default(now())
|
|
|
|
@@index([userId])
|
|
}
|
|
|
|
model HalalUsage {
|
|
userId String @id
|
|
user User @relation(fields: [userId], references: [id])
|
|
queriesUsed Int @default(0)
|
|
queriesLimit Int @default(100)
|
|
periodEnd DateTime?
|
|
}
|
|
|
|
model UserPlace {
|
|
id String @id @default(cuid())
|
|
userId String
|
|
user User @relation(fields: [userId], references: [id])
|
|
name String
|
|
address String
|
|
lat Float
|
|
lng Float
|
|
type String // "mosque" | "restaurant" | "cafe"
|
|
notes String?
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@index([userId])
|
|
@@index([lat, lng])
|
|
}
|
|
|
|
model Notification {
|
|
id String @id @default(cuid())
|
|
userId String
|
|
user User @relation(fields: [userId], references: [id])
|
|
type String // 'streak_reminder' | 'daily_verse' | 'premium_expiring' | 'new_listing' | 'forum_reply' | 'achievement' | 'referral_bonus'
|
|
title String
|
|
body String?
|
|
data String? // JSON string with extra payload
|
|
read Boolean @default(false)
|
|
createdAt DateTime @default(now())
|
|
|
|
@@index([userId, read])
|
|
}
|
|
|
|
model Referral {
|
|
id String @id @default(cuid())
|
|
referrerId String
|
|
referrer User @relation("Referrer", fields: [referrerId], references: [id])
|
|
referredId String @unique
|
|
referred User @relation("Referred", fields: [referredId], references: [id])
|
|
status String @default("pending") // pending | joined | upgraded
|
|
rewardFlh Int @default(0)
|
|
createdAt DateTime @default(now())
|
|
upgradedAt DateTime?
|
|
|
|
@@index([referrerId])
|
|
}
|
|
|
|
model PremiumBenefit {
|
|
id String @id @default(cuid())
|
|
tier String // 'premium' | 'pro'
|
|
name String
|
|
description String?
|
|
icon String?
|
|
active Boolean @default(true)
|
|
createdAt DateTime @default(now())
|
|
}
|
|
|
|
model HalalPlaceCache {
|
|
id String @id @default(cuid())
|
|
cacheKey String @unique
|
|
data String
|
|
expiresAt DateTime
|
|
}
|
|
|
|
model DhikrSession {
|
|
id String @id @default(cuid())
|
|
userId String
|
|
user User @relation(fields: [userId], references: [id])
|
|
type String // 'subhanallah' | 'alhamdulillah' | 'allahuakbar' | 'custom'
|
|
count Int @default(0)
|
|
target Int @default(33)
|
|
completed Boolean @default(true)
|
|
createdAt DateTime @default(now())
|
|
|
|
@@index([userId, createdAt])
|
|
}
|
|
|
|
model DhikrDay {
|
|
userId String
|
|
user User @relation(fields: [userId], references: [id])
|
|
date String // YYYY-MM-DD
|
|
subhanallah Int @default(0)
|
|
alhamdulillah Int @default(0)
|
|
allahuakbar Int @default(0)
|
|
custom Int @default(0)
|
|
totalCount Int @default(0)
|
|
sessionCount Int @default(0)
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@id([userId, date])
|
|
@@index([userId])
|
|
}
|
|
|
|
// ──────────────────────────────────────
|
|
// LEARN / MICRO LEARNING MODULE
|
|
// ──────────────────────────────────────
|
|
|
|
model LearnCourse {
|
|
id String @id @default(cuid())
|
|
slug String @unique
|
|
title String
|
|
author String
|
|
description String
|
|
imageUrl String?
|
|
moduleCount Int @default(0)
|
|
totalMinutes Int @default(0)
|
|
difficulty String @default("beginner")
|
|
giteaPath String // path in Gitea courses repo
|
|
priceFlh Int? // FLH one-time price
|
|
priceUsd Float? // USD one-time price via Polar
|
|
subscriptionOnly Boolean @default(false)
|
|
published Boolean @default(false)
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
modules LearnModule[]
|
|
enrollments LearnEnrollment[]
|
|
certificates LearnCertificate[]
|
|
}
|
|
|
|
model LearnModule {
|
|
id String @id @default(cuid())
|
|
courseId String
|
|
course LearnCourse @relation(fields: [courseId], references: [id], onDelete: Cascade)
|
|
order Int
|
|
title String
|
|
slug String
|
|
keyTakeaway String?
|
|
duration Int @default(5) // minutes
|
|
content String? // cached markdown body
|
|
audioPath String? // cached TTS audio file path
|
|
quizData String? // JSON: [{question, options[], correctIndex}]
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
progress LearnModuleProgress[]
|
|
|
|
@@unique([courseId, slug])
|
|
}
|
|
|
|
model LearnEnrollment {
|
|
id String @id @default(cuid())
|
|
userId String
|
|
courseId String
|
|
course LearnCourse @relation(fields: [courseId], references: [id])
|
|
purchaseType String @default("one_time") // "one_time" | "subscription" | "free"
|
|
completed Boolean @default(false)
|
|
progress Float @default(0) // 0.0 to 1.0
|
|
startedAt DateTime @default(now())
|
|
completedAt DateTime?
|
|
updatedAt DateTime @updatedAt
|
|
|
|
modules LearnModuleProgress[]
|
|
|
|
@@unique([userId, courseId])
|
|
@@index([userId])
|
|
}
|
|
|
|
model LearnModuleProgress {
|
|
id String @id @default(cuid())
|
|
enrollmentId String
|
|
enrollment LearnEnrollment @relation(fields: [enrollmentId], references: [id], onDelete: Cascade)
|
|
moduleId String
|
|
module LearnModule @relation(fields: [moduleId], references: [id], onDelete: Cascade)
|
|
completed Boolean @default(false)
|
|
score Float? // quiz score 0-100
|
|
listened Boolean @default(false)
|
|
completedAt DateTime?
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@unique([enrollmentId, moduleId])
|
|
}
|
|
|
|
model LearnSubscription {
|
|
id String @id @default(cuid())
|
|
userId String
|
|
status String @default("active") // active | cancelled | expired
|
|
polarSubId String? @unique
|
|
currentPeriodStart DateTime
|
|
currentPeriodEnd DateTime
|
|
cancelledAt DateTime?
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@index([userId])
|
|
@@index([status])
|
|
}
|
|
|
|
model LearnCertificate {
|
|
id String @id @default(cuid())
|
|
serialNumber String @unique
|
|
userId String
|
|
userName String // cached at issue time
|
|
courseId String
|
|
course LearnCourse @relation(fields: [courseId], references: [id])
|
|
moduleCount Int @default(0)
|
|
score Float? // aggregate quiz score
|
|
pdfPath String? // /data/certificates/{serial}.pdf
|
|
issuedAt DateTime @default(now())
|
|
viewedAt DateTime?
|
|
revoked Boolean @default(false)
|
|
revokedAt DateTime?
|
|
hashOnChain String? // SHA256 for future blockchain anchoring
|
|
metadata String? // JSON extra
|
|
|
|
@@index([serialNumber])
|
|
@@index([userId])
|
|
@@index([courseId, userId])
|
|
}
|
|
|
|
// Add relations to User model
|
|
/// NOTE: The Notification and Referral relations are declared on the models above.
|
|
/// User now implicitly has: notifications Notification[], referredReferrals Referral[] (via "Referrer"), referrerReferral Referral? (via "Referred")
|
|
|