edtech / packages /database /prisma /schema.prisma
CognxSafeTrack
feat(db): remove unique constraint on Organization.wabaId
348fb8a
Raw History Blame Contribute Delete
17.5 kB
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
model Organization {
id String @id @default(uuid())
name String
wabaId String?
metaBusinessId String?
systemUserToken String?
systemUserTokenIssuedAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
slug String? @unique // For subdomains like polytech.xamle.studio
brandingData Json?
customPrompt String?
personalityConfig Json?
flowConfig Json?
knowledgeBaseUrl String?
mode OrganizationMode @default(EDTECH)
useCase OrganizationUseCase @default(EDUCATION)
isCrmActive Boolean @default(false)
isEdTechActive Boolean @default(false)
webhookSecret String?
webhookUrl String?
openAiApiKey String?
googleAiApiKey String?
subscriptionStatus String? @default("ACTIVE")
subscriptionPlan SubscriptionPlan @default(STARTER)
stripeCustomerId String? @unique
stripeSubscriptionId String? @unique
aiCreditsLimit Int @default(500)
aiCreditsUsed Int @default(0)
whatsappMessagesSent Int @default(0)
billingPeriodStart DateTime @default(now())
walletBalance Int @default(0)
isHardStopped Boolean @default(false)
businessProfiles BusinessProfile[]
usageEvents UsageEvent[]
walletTransactions WalletTransaction[]
enrollments Enrollment[]
kbEntries KnowledgeBaseEntry[]
messages Message[]
payments Payment[]
responses Response[]
teamMembers TeamMember[]
tracks Track[]
trackDays TrackDay[]
users User[]
userBadges UserBadge[]
progress UserProgress[]
phoneNumbers WhatsAppPhoneNumber[]
analytics AnalyticsLog[]
campaigns CampaignHistory[]
contacts Contact[]
broadcastLists BroadcastList[]
}
model Contact {
id String @id @default(uuid())
phoneNumber String
name String?
attributes Json? // For dynamic Excel columns
tags String[] @default([])
organizationId String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
organization Organization @relation(fields: [organizationId], references: [id])
campaigns CampaignHistory[]
broadcastLists BroadcastList[] @relation("ContactBroadcastLists")
messages Message[]
language Language @default(FR)
@@unique([phoneNumber, organizationId])
@@index([organizationId])
}
model BroadcastList {
id String @id @default(uuid())
name String
organizationId String
organization Organization @relation(fields: [organizationId], references: [id])
contacts Contact[] @relation("ContactBroadcastLists")
createdAt DateTime @default(now())
@@index([organizationId])
}
// ... existing models ...
model CampaignHistory {
id String @id @default(uuid())
organizationId String
contactId String
whatsappMessageId String? @unique // ID returned by Meta
content String
status CampaignStatus @default(SENT)
error String?
sentAt DateTime @default(now())
updatedAt DateTime @updatedAt
organization Organization @relation(fields: [organizationId], references: [id])
contact Contact @relation(fields: [contactId], references: [id], onDelete: Cascade)
analytics AnalyticsLog[]
@@index([organizationId])
@@index([contactId])
@@index([whatsappMessageId])
}
model AnalyticsLog {
id String @id @default(uuid())
organizationId String
campaignHistoryId String
eventType AnalyticsEventType
metadata Json?
occurredAt DateTime @default(now())
organization Organization @relation(fields: [organizationId], references: [id])
campaign CampaignHistory @relation(fields: [campaignHistoryId], references: [id], onDelete: Cascade)
@@index([organizationId])
@@index([campaignHistoryId])
@@index([eventType])
}
// ... existing enums ...
model KnowledgeBaseEntry {
id String @id @default(uuid())
organizationId String
content String
embedding Unsupported("vector")?
metadata Json?
createdAt DateTime @default(now())
organization Organization @relation(fields: [organizationId], references: [id])
@@index([organizationId])
}
model WhatsAppPhoneNumber {
id String @id
displayPhone String
organizationId String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
organization Organization @relation(fields: [organizationId], references: [id])
}
model User {
id String @id @default(uuid())
phone String? // Made optional for Email-based admins
name String?
role Role @default(STUDENT)
language Language @default(FR)
city String?
activity String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
email String?
passwordHash String?
currentStreak Int @default(0)
longestStreak Int @default(0)
lastActivityAt DateTime?
deletedAt DateTime?
organizationId String
businessProfile BusinessProfile?
enrollments Enrollment[]
messages Message[]
payments Payment[]
responses Response[]
organization Organization @relation(fields: [organizationId], references: [id])
progress UserProgress[]
pushSubscriptions PushSubscription[]
@@unique([phone, organizationId])
@@unique([email, organizationId])
@@index([organizationId])
@@index([phone])
@@index([email])
}
model PushSubscription {
id String @id @default(uuid())
userId String
organizationId String
endpoint String @unique
p256dh String
auth String
createdAt DateTime @default(now())
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@index([organizationId])
@@index([userId])
}
model BusinessProfile {
id String @id @default(uuid())
userId String @unique
activityLabel String?
activityPhrase String?
activityType String?
locationCity String?
mainCustomer String?
mainProblem String?
offerSimple String?
promise String?
marketData Json?
competitorList Json?
financialProjections Json?
fundingAsk String?
lastUpdatedFromDay Int @default(0)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
organizationId String
organization Organization @relation(fields: [organizationId], references: [id])
user User @relation(fields: [userId], references: [id])
teamMembersList TeamMember[]
@@index([organizationId])
}
model TeamMember {
id String @id @default(uuid())
businessProfileId String
name String?
role String?
bio String?
photoUrl String?
organizationId String
businessProfile BusinessProfile @relation(fields: [businessProfileId], references: [id], onDelete: Cascade)
organization Organization @relation(fields: [organizationId], references: [id])
@@index([organizationId])
}
model Track {
id String @id @default(uuid())
title String
description String?
duration Int
language Language @default(FR)
isPremium Boolean @default(false)
priceAmount Int?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
organizationId String
enrollments Enrollment[]
payments Payment[]
organization Organization @relation(fields: [organizationId], references: [id])
days TrackDay[]
progress UserProgress[]
@@index([organizationId])
}
model TrackDay {
id String @id @default(uuid())
trackId String
dayNumber Int
title String?
audioUrl String?
imageUrl String?
videoUrl String?
videoCaption String?
lessonText String?
exerciseType ExerciseType @default(TEXT)
exercisePrompt String?
validationKeyword String?
buttonsJson Json?
exerciseCriteria Json?
badges Json?
unlockCondition String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
organizationId String
organization Organization @relation(fields: [organizationId], references: [id])
track Track @relation(fields: [trackId], references: [id])
@@index([organizationId])
@@index([trackId, organizationId])
}
model UserProgress {
id String @id @default(uuid())
userId String
trackId String
score Int @default(0)
lastInteraction DateTime @default(now())
exerciseStatus ExerciseStatus @default(PENDING)
badges Json? @map("badges")
behavioralScoring Json?
confidenceScore Float?
adminTranscription String?
overrideAudioUrl String?
reviewedBy String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
iterationCount Int @default(0)
aiSource String?
organizationId String
userBadges UserBadge[]
organization Organization @relation(fields: [organizationId], references: [id])
track Track @relation(fields: [trackId], references: [id])
user User @relation(fields: [userId], references: [id])
@@unique([userId, trackId])
@@index([organizationId])
@@index([organizationId, exerciseStatus])
}
model Enrollment {
id String @id @default(uuid())
userId String
trackId String
status EnrollmentStatus @default(ACTIVE)
currentDay Int @default(1)
startedAt DateTime @default(now())
completedAt DateTime?
lastActivityAt DateTime @default(now())
deletedAt DateTime?
organizationId String
organization Organization @relation(fields: [organizationId], references: [id])
track Track @relation(fields: [trackId], references: [id])
user User @relation(fields: [userId], references: [id])
responses Response[]
@@unique([userId, trackId])
@@index([organizationId])
@@index([userId])
}
model Response {
id String @id @default(uuid())
enrollmentId String
userId String
dayNumber Int
content String?
mediaUrl String?
createdAt DateTime @default(now())
aiSource String?
organizationId String
enrollment Enrollment @relation(fields: [enrollmentId], references: [id], onDelete: Cascade)
organization Organization @relation(fields: [organizationId], references: [id])
user User @relation(fields: [userId], references: [id])
@@index([organizationId])
@@index([userId])
@@index([enrollmentId])
}
model Message {
id String @id @default(uuid())
userId String?
contactId String?
direction Direction
channel String @default("WHATSAPP")
content String?
mediaUrl String?
mediaType MediaType?
mediaId String?
payload Json?
createdAt DateTime @default(now())
status MessageStatus @default(SENT)
organizationId String
organization Organization @relation(fields: [organizationId], references: [id])
user User? @relation(fields: [userId], references: [id])
contact Contact? @relation(fields: [contactId], references: [id])
@@index([userId, createdAt])
@@index([contactId, createdAt])
@@index([organizationId, createdAt])
@@index([organizationId])
}
model Payment {
id String @id @default(uuid())
userId String
trackId String
amount Int
currency String @default("XOF")
status PaymentStatus @default(PENDING)
paymentSessionId String? @unique
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
organizationId String
organization Organization @relation(fields: [organizationId], references: [id])
track Track @relation(fields: [trackId], references: [id])
user User @relation(fields: [userId], references: [id])
@@index([organizationId])
}
model TrainingData {
id String @id @default(uuid())
audioUrl String
transcription String
manualCorrection String?
rawWER Float?
normalizedWER Float?
status TrainingStatus @default(PENDING)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model NormalizationRule {
id String @id @default(uuid())
original String @unique
replacement String
language String @default("WOLOF")
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([original])
}
model UserBadge {
id String @id @default(uuid())
userProgressId String
name String
earnedAt DateTime @default(now())
organizationId String
organization Organization @relation(fields: [organizationId], references: [id])
userProgress UserProgress @relation(fields: [userProgressId], references: [id], onDelete: Cascade)
@@index([organizationId])
}
enum Role {
STUDENT
ADMIN
ORG_MEMBER
ORG_ADMIN
SUPER_ADMIN
}
enum OrganizationMode {
EDTECH
WEBHOOK
AI_AGENT
CRM_MARKETING
PEDAGOGY
CUSTOMER_SERVICE
}
enum OrganizationUseCase {
EDUCATION
CRM_WHATSAPP
}
enum EnrollmentStatus {
ACTIVE
COMPLETED
DROPPED
}
enum Language {
FR
EN
ES
PT
WOLOF
}
enum ContentType {
TEXT
AUDIO
IMAGE
VIDEO
}
enum MessageStatus {
SENT
DELIVERED
READ
RECEIVED
}
enum Direction {
INBOUND
OUTBOUND
}
enum PaymentStatus {
PENDING
COMPLETED
FAILED
REFUNDED
}
enum ResponseType {
TEXT
AUDIO
VIDEO
}
enum MediaType {
AUDIO
IMAGE
VIDEO
DOCUMENT
}
enum ExerciseType {
TEXT
AUDIO
BUTTON
}
enum ExerciseStatus {
PENDING
PENDING_REMEDIATION
PENDING_REVIEW
COMPLETED
PENDING_DEEPDIVE
}
enum TrainingStatus {
PENDING
REVIEWED
IGNORED
}
enum SubscriptionPlan {
STARTER
GROWTH
SCALE
ENTERPRISE
}
enum UsageEventType {
AI_TEXT
AI_AUDIO
AI_IMAGE
WHATSAPP_SENT
}
enum UsageProvider {
GEMINI
OPENAI
META
}
enum UsageFeature {
LESSON
FEEDBACK
DEEPDIVE
TRANSCRIPTION
IMAGE_ANALYSIS
SPEECH
CAMPAIGN
KB_QUERY
ONBOARDING
OTHER
}
model UsageEvent {
id String @id @default(uuid())
organizationId String
userId String?
type UsageEventType
provider UsageProvider
feature UsageFeature
tokensIn Int @default(0)
tokensOut Int @default(0)
durationMs Int?
costUsd Float @default(0)
createdAt DateTime @default(now())
organization Organization @relation(fields: [organizationId], references: [id])
@@index([organizationId, createdAt])
@@index([organizationId, type])
}
model WalletTransaction {
id String @id @default(uuid())
organizationId String
amount Int
balanceAfter Int
type WalletTransactionType
description String?
actorId String?
byok Boolean @default(false)
metadata Json?
createdAt DateTime @default(now())
organization Organization @relation(fields: [organizationId], references: [id])
@@index([organizationId, createdAt])
@@index([type])
@@index([organizationId, type])
}
enum WalletTransactionType {
TOP_UP_MANUAL
TOP_UP_PAYMENT
DEBIT_AI
DEBIT_WHATSAPP
DEBIT_BROADCAST
ADJUSTMENT
}
model AuditLog {
id String @id @default(uuid())
action String
actorId String?
resourceId String?
details Json?
createdAt DateTime @default(now())
@@index([action])
@@index([actorId])
@@index([resourceId])
@@index([createdAt])
}
enum CampaignStatus {
SENT
DELIVERED
READ
FAILED
}
enum AnalyticsEventType {
CLICK
READ
RESPONSE
}