chore: initial project import
Some checks failed
CI - Production Readiness / Verify (push) Has been cancelled

This commit is contained in:
Wira Basalamah
2026-04-21 09:29:29 +07:00
commit adde003fba
222 changed files with 37657 additions and 0 deletions

570
prisma/schema.prisma Normal file
View File

@ -0,0 +1,570 @@
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "sqlite"
url = env("DATABASE_URL")
}
enum TenantStatus {
ACTIVE
SUSPENDED
TRIAL
INACTIVE
}
enum UserStatus {
ACTIVE
INVITED
DISABLED
}
enum RoleCode {
SUPER_ADMIN
ADMIN_CLIENT
AGENT
}
enum ChannelStatus {
CONNECTED
DISCONNECTED
PENDING
ERROR
}
enum ConversationStatus {
OPEN
PENDING
RESOLVED
ARCHIVED
SPAM
}
enum ConversationPriority {
LOW
NORMAL
HIGH
URGENT
}
enum MessageDirection {
INBOUND
OUTBOUND
}
enum AuthTokenType {
PASSWORD_RESET
INVITE_ACCEPTANCE
}
enum MessageType {
TEXT
IMAGE
VIDEO
AUDIO
DOCUMENT
TEMPLATE
INTERACTIVE
}
enum DeliveryStatus {
QUEUED
SENT
DELIVERED
READ
FAILED
}
enum OptInStatus {
UNKNOWN
OPTED_IN
OPTED_OUT
}
enum TemplateApprovalStatus {
DRAFT
PENDING
APPROVED
REJECTED
DISABLED
}
enum CampaignStatus {
DRAFT
SCHEDULED
PROCESSING
COMPLETED
PARTIAL_FAILED
FAILED
CANCELED
}
enum CampaignAudienceType {
SEGMENT
IMPORT
MANUAL
}
enum CampaignType {
BROADCAST
BULK_FOLLOWUP
}
enum PaymentStatus {
UNPAID
PAID
OVERDUE
VOID
}
model SubscriptionPlan {
id String @id @default(cuid())
name String
code String @unique
priceMonthly Int
messageQuota Int
seatQuota Int
broadcastQuota Int
featuresJson String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
tenants Tenant[]
invoices BillingInvoice[]
}
model Tenant {
id String @id @default(cuid())
name String
slug String @unique
companyName String
timezone String
status TenantStatus @default(TRIAL)
planId String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
plan SubscriptionPlan @relation(fields: [planId], references: [id])
users User[]
roles Role[]
channels Channel[]
contacts Contact[]
tags Tag[]
conversations Conversation[]
messages Message[]
notes ConversationNote[]
activities ConversationActivity[]
segments ContactSegment[]
templates MessageTemplate[]
campaigns BroadcastCampaign[]
auditLogs AuditLog[]
webhookEvents WebhookEvent[]
usageMetrics UsageMetric[]
billingInvoices BillingInvoice[]
}
model Role {
id String @id @default(cuid())
tenantId String?
name String
code RoleCode
permissionsJson String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
tenant Tenant? @relation(fields: [tenantId], references: [id])
users User[]
}
model User {
id String @id @default(cuid())
tenantId String
fullName String
email String @unique
passwordHash String
roleId String
status UserStatus @default(INVITED)
avatarUrl String?
lastLoginAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
tenant Tenant @relation(fields: [tenantId], references: [id])
role Role @relation(fields: [roleId], references: [id])
assignedConversations Conversation[]
notes ConversationNote[]
sentMessages Message[]
activities ConversationActivity[] @relation("ActorActivities")
campaigns BroadcastCampaign[]
auditLogs AuditLog[]
}
model AuthToken {
id String @id @default(cuid())
userId String
tenantId String
tokenType AuthTokenType
tokenHash String @unique
expiresAt DateTime
consumedAt DateTime?
createdByUser String?
createdAt DateTime @default(now())
metadataJson String?
@@index([userId, tokenType])
@@index([tenantId, tokenType, expiresAt])
}
model Channel {
id String @id @default(cuid())
tenantId String
channelName String
provider String
wabaId String?
phoneNumberId String?
displayPhoneNumber String?
status ChannelStatus @default(PENDING)
webhookStatus String?
lastSyncAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
tenant Tenant @relation(fields: [tenantId], references: [id])
contacts Contact[]
conversations Conversation[]
messages Message[]
templates MessageTemplate[]
campaigns BroadcastCampaign[]
webhookEvents WebhookEvent[]
}
model Contact {
id String @id @default(cuid())
tenantId String
channelId String?
externalRef String?
fullName String
phoneNumber String
email String?
avatarUrl String?
countryCode String?
optInStatus OptInStatus @default(UNKNOWN)
lastInteractionAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
tenant Tenant @relation(fields: [tenantId], references: [id])
channel Channel? @relation(fields: [channelId], references: [id])
conversations Conversation[]
messages Message[]
contactTags ContactTag[]
segmentMembers SegmentMember[]
campaignRecipients CampaignRecipient[]
@@unique([tenantId, phoneNumber])
}
model Tag {
id String @id @default(cuid())
tenantId String
name String
color String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
tenant Tenant @relation(fields: [tenantId], references: [id])
contactTags ContactTag[]
conversationTags ConversationTag[]
@@unique([tenantId, name])
}
model ContactTag {
id String @id @default(cuid())
tenantId String
contactId String
tagId String
createdAt DateTime @default(now())
contact Contact @relation(fields: [contactId], references: [id])
tag Tag @relation(fields: [tagId], references: [id])
@@unique([contactId, tagId])
}
model Conversation {
id String @id @default(cuid())
tenantId String
channelId String
contactId String
subject String?
status ConversationStatus @default(OPEN)
priority ConversationPriority @default(NORMAL)
assignedUserId String?
firstMessageAt DateTime?
lastMessageAt DateTime?
lastInboundAt DateTime?
lastOutboundAt DateTime?
resolvedAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
tenant Tenant @relation(fields: [tenantId], references: [id])
channel Channel @relation(fields: [channelId], references: [id])
contact Contact @relation(fields: [contactId], references: [id])
assignedUser User? @relation(fields: [assignedUserId], references: [id])
messages Message[]
notes ConversationNote[]
activities ConversationActivity[]
conversationTags ConversationTag[]
@@index([tenantId, status])
@@index([tenantId, assignedUserId])
@@index([tenantId, lastMessageAt])
}
model Message {
id String @id @default(cuid())
tenantId String
conversationId String
channelId String
contactId String?
direction MessageDirection
type MessageType @default(TEXT)
providerMessageId String? @unique
replyToMessageId String?
contentText String?
mediaUrl String?
mimeType String?
deliveryStatus DeliveryStatus @default(QUEUED)
failedReason String?
sentByUserId String?
sentAt DateTime?
deliveredAt DateTime?
readAt DateTime?
createdAt DateTime @default(now())
tenant Tenant @relation(fields: [tenantId], references: [id])
conversation Conversation @relation(fields: [conversationId], references: [id])
channel Channel @relation(fields: [channelId], references: [id])
contact Contact? @relation(fields: [contactId], references: [id])
sentByUser User? @relation(fields: [sentByUserId], references: [id])
}
model ConversationNote {
id String @id @default(cuid())
tenantId String
conversationId String
userId String
content String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
tenant Tenant @relation(fields: [tenantId], references: [id])
conversation Conversation @relation(fields: [conversationId], references: [id])
user User @relation(fields: [userId], references: [id])
}
model ConversationTag {
id String @id @default(cuid())
tenantId String
conversationId String
tagId String
createdAt DateTime @default(now())
conversation Conversation @relation(fields: [conversationId], references: [id])
tag Tag @relation(fields: [tagId], references: [id])
@@unique([conversationId, tagId])
}
model ConversationActivity {
id String @id @default(cuid())
tenantId String
conversationId String
actorUserId String?
activityType String
metadataJson String?
createdAt DateTime @default(now())
tenant Tenant @relation(fields: [tenantId], references: [id])
conversation Conversation @relation(fields: [conversationId], references: [id])
actorUser User? @relation("ActorActivities", fields: [actorUserId], references: [id])
}
model ContactSegment {
id String @id @default(cuid())
tenantId String
name String
description String?
rulesJson String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
tenant Tenant @relation(fields: [tenantId], references: [id])
members SegmentMember[]
campaigns BroadcastCampaign[]
}
model SegmentMember {
id String @id @default(cuid())
tenantId String
segmentId String
contactId String
createdAt DateTime @default(now())
segment ContactSegment @relation(fields: [segmentId], references: [id])
contact Contact @relation(fields: [contactId], references: [id])
@@unique([segmentId, contactId])
}
model MessageTemplate {
id String @id @default(cuid())
tenantId String
channelId String
name String
category String
languageCode String
templateType String?
bodyText String
headerType String?
footerText String?
buttonsJson String?
providerTemplateId String?
approvalStatus TemplateApprovalStatus @default(DRAFT)
rejectedReason String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
tenant Tenant @relation(fields: [tenantId], references: [id])
channel Channel @relation(fields: [channelId], references: [id])
campaigns BroadcastCampaign[]
}
model BroadcastCampaign {
id String @id @default(cuid())
tenantId String
channelId String
templateId String
createdByUserId String
name String
campaignType CampaignType @default(BROADCAST)
audienceType CampaignAudienceType
segmentId String?
scheduledAt DateTime?
startedAt DateTime?
finishedAt DateTime?
status CampaignStatus @default(DRAFT)
totalRecipients Int @default(0)
totalSent Int @default(0)
totalDelivered Int @default(0)
totalRead Int @default(0)
totalFailed Int @default(0)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
tenant Tenant @relation(fields: [tenantId], references: [id])
channel Channel @relation(fields: [channelId], references: [id])
template MessageTemplate @relation(fields: [templateId], references: [id])
createdByUser User @relation(fields: [createdByUserId], references: [id])
segment ContactSegment? @relation(fields: [segmentId], references: [id])
recipients CampaignRecipient[]
@@index([tenantId, status])
@@index([status, scheduledAt])
}
model CampaignRecipient {
id String @id @default(cuid())
tenantId String
campaignId String
contactId String
phoneNumber String
sendStatus DeliveryStatus @default(QUEUED)
sendAttempts Int @default(0)
maxSendAttempts Int @default(3)
lastAttemptAt DateTime?
nextRetryAt DateTime?
failureReason String?
providerMessageId String?
sentAt DateTime?
deliveredAt DateTime?
readAt DateTime?
createdAt DateTime @default(now())
campaign BroadcastCampaign @relation(fields: [campaignId], references: [id])
contact Contact @relation(fields: [contactId], references: [id])
@@index([campaignId, sendStatus, nextRetryAt])
@@index([campaignId, sendStatus])
}
model BackgroundJobState {
id String @id @default(cuid())
jobName String @unique
lockedBy String
lockedUntil DateTime?
runs Int @default(0)
consecutiveFailures Int @default(0)
lastRunStartedAt DateTime?
lastRunCompletedAt DateTime?
lastRunStatus String?
lastRunSummaryJson String?
lastError String?
lastFailureAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model AuditLog {
id String @id @default(cuid())
tenantId String
actorUserId String?
entityType String
entityId String
action String
metadataJson String?
ipAddress String?
userAgent String?
createdAt DateTime @default(now())
tenant Tenant @relation(fields: [tenantId], references: [id])
actorUser User? @relation(fields: [actorUserId], references: [id])
}
model WebhookEvent {
id String @id @default(cuid())
tenantId String
channelId String?
eventType String
providerEventId String?
eventHash String?
payloadJson String
processStatus String
failedReason String?
createdAt DateTime @default(now())
processedAt DateTime?
tenant Tenant @relation(fields: [tenantId], references: [id])
channel Channel? @relation(fields: [channelId], references: [id])
@@index([tenantId, channelId, eventHash])
}
model UsageMetric {
id String @id @default(cuid())
tenantId String
metricDate DateTime
inboundMessages Int @default(0)
outboundMessages Int @default(0)
activeContacts Int @default(0)
activeAgents Int @default(0)
broadcastSent Int @default(0)
storageUsedMb Int @default(0)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
tenant Tenant @relation(fields: [tenantId], references: [id])
@@unique([tenantId, metricDate])
}
model BillingInvoice {
id String @id @default(cuid())
tenantId String
planId String
invoiceNumber String @unique
periodStart DateTime
periodEnd DateTime
subtotal Int
taxAmount Int
totalAmount Int
paymentStatus PaymentStatus @default(UNPAID)
dueDate DateTime
paidAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
tenant Tenant @relation(fields: [tenantId], references: [id])
plan SubscriptionPlan @relation(fields: [planId], references: [id])
}