590 lines
18 KiB
JavaScript
590 lines
18 KiB
JavaScript
const { PrismaClient, RoleCode, TenantStatus, UserStatus, ChannelStatus, ConversationStatus, ConversationPriority, MessageDirection, MessageType, DeliveryStatus, OptInStatus, TemplateApprovalStatus, CampaignStatus, CampaignAudienceType, CampaignType, PaymentStatus } = require("@prisma/client");
|
|
const { pbkdf2Sync, randomBytes } = require("crypto");
|
|
|
|
const prisma = new PrismaClient();
|
|
|
|
function hashPassword(password) {
|
|
const iterations = 120000;
|
|
const salt = randomBytes(16);
|
|
const digest = pbkdf2Sync(password, salt, iterations, 32, "sha256");
|
|
|
|
return `pbkdf2$${iterations}$${salt.toString("base64url")}$${digest.toString("base64url")}`;
|
|
}
|
|
|
|
async function main() {
|
|
await prisma.campaignRecipient.deleteMany();
|
|
await prisma.broadcastCampaign.deleteMany();
|
|
await prisma.messageTemplate.deleteMany();
|
|
await prisma.segmentMember.deleteMany();
|
|
await prisma.contactSegment.deleteMany();
|
|
await prisma.conversationActivity.deleteMany();
|
|
await prisma.conversationTag.deleteMany();
|
|
await prisma.conversationNote.deleteMany();
|
|
await prisma.message.deleteMany();
|
|
await prisma.conversation.deleteMany();
|
|
await prisma.contactTag.deleteMany();
|
|
await prisma.tag.deleteMany();
|
|
await prisma.contact.deleteMany();
|
|
await prisma.webhookEvent.deleteMany();
|
|
await prisma.auditLog.deleteMany();
|
|
await prisma.usageMetric.deleteMany();
|
|
await prisma.billingInvoice.deleteMany();
|
|
await prisma.authToken.deleteMany();
|
|
await prisma.channel.deleteMany();
|
|
await prisma.user.deleteMany();
|
|
await prisma.role.deleteMany();
|
|
await prisma.tenant.deleteMany();
|
|
await prisma.subscriptionPlan.deleteMany();
|
|
|
|
await prisma.subscriptionPlan.createMany({
|
|
data: [
|
|
{
|
|
id: "plan-basic",
|
|
name: "Basic",
|
|
code: "basic",
|
|
priceMonthly: 1500000,
|
|
messageQuota: 10000,
|
|
seatQuota: 5,
|
|
broadcastQuota: 2000,
|
|
featuresJson: JSON.stringify({ inbox: true, contacts: true, analytics: "basic" })
|
|
},
|
|
{
|
|
id: "plan-pro",
|
|
name: "Pro",
|
|
code: "pro",
|
|
priceMonthly: 2500000,
|
|
messageQuota: 50000,
|
|
seatQuota: 15,
|
|
broadcastQuota: 10000,
|
|
featuresJson: JSON.stringify({ inbox: true, contacts: true, analytics: "extended", broadcast: true })
|
|
}
|
|
]
|
|
});
|
|
|
|
await prisma.tenant.createMany({
|
|
data: [
|
|
{
|
|
id: "tenant-acme",
|
|
name: "Acme Co",
|
|
slug: "acme-co",
|
|
companyName: "Acme Co",
|
|
timezone: "Asia/Jakarta",
|
|
status: TenantStatus.ACTIVE,
|
|
planId: "plan-pro"
|
|
},
|
|
{
|
|
id: "tenant-sinar",
|
|
name: "Sinar Abadi",
|
|
slug: "sinar-abadi",
|
|
companyName: "PT Sinar Abadi",
|
|
timezone: "Asia/Jakarta",
|
|
status: TenantStatus.TRIAL,
|
|
planId: "plan-basic"
|
|
}
|
|
]
|
|
});
|
|
|
|
await prisma.role.createMany({
|
|
data: [
|
|
{ id: "role-super-admin", name: "Super Admin", code: RoleCode.SUPER_ADMIN, permissionsJson: JSON.stringify({ global: true }) },
|
|
{ id: "role-admin-acme", tenantId: "tenant-acme", name: "Admin Client", code: RoleCode.ADMIN_CLIENT, permissionsJson: JSON.stringify({ tenant: true }) },
|
|
{ id: "role-agent-acme", tenantId: "tenant-acme", name: "Agent", code: RoleCode.AGENT, permissionsJson: JSON.stringify({ inbox: true }) },
|
|
{ id: "role-admin-sinar", tenantId: "tenant-sinar", name: "Admin Client", code: RoleCode.ADMIN_CLIENT, permissionsJson: JSON.stringify({ tenant: true }) }
|
|
]
|
|
});
|
|
|
|
await prisma.user.createMany({
|
|
data: [
|
|
{
|
|
id: "user-super-admin",
|
|
tenantId: "tenant-acme",
|
|
fullName: "Platform Owner",
|
|
email: "owner@inboxsuite.test",
|
|
passwordHash: hashPassword("demo123"),
|
|
roleId: "role-super-admin",
|
|
status: UserStatus.ACTIVE,
|
|
lastLoginAt: new Date("2026-04-20T08:00:00Z")
|
|
},
|
|
{
|
|
id: "user-admin-001",
|
|
tenantId: "tenant-acme",
|
|
fullName: "Admin Operations",
|
|
email: "admin@acme.test",
|
|
passwordHash: hashPassword("admin123"),
|
|
roleId: "role-admin-acme",
|
|
status: UserStatus.ACTIVE,
|
|
lastLoginAt: new Date("2026-04-20T09:00:00Z")
|
|
},
|
|
{
|
|
id: "user-agent-001",
|
|
tenantId: "tenant-acme",
|
|
fullName: "Farhan",
|
|
email: "farhan@acme.test",
|
|
passwordHash: hashPassword("agent123"),
|
|
roleId: "role-agent-acme",
|
|
status: UserStatus.ACTIVE,
|
|
lastLoginAt: new Date("2026-04-20T09:12:00Z")
|
|
},
|
|
{
|
|
id: "user-agent-002",
|
|
tenantId: "tenant-acme",
|
|
fullName: "Tiara",
|
|
email: "tiara@acme.test",
|
|
passwordHash: hashPassword("agent123"),
|
|
roleId: "role-agent-acme",
|
|
status: UserStatus.ACTIVE,
|
|
lastLoginAt: new Date("2026-04-20T08:54:00Z")
|
|
},
|
|
{
|
|
id: "user-admin-sinar",
|
|
tenantId: "tenant-sinar",
|
|
fullName: "Sinar Admin",
|
|
email: "admin@sinar.test",
|
|
passwordHash: hashPassword("sinar123"),
|
|
roleId: "role-admin-sinar",
|
|
status: UserStatus.INVITED
|
|
}
|
|
]
|
|
});
|
|
|
|
await prisma.channel.createMany({
|
|
data: [
|
|
{
|
|
id: "channel-main-acme",
|
|
tenantId: "tenant-acme",
|
|
channelName: "Main WA",
|
|
provider: "Meta BSP",
|
|
wabaId: "waba-acme-main",
|
|
phoneNumberId: "phone-acme-main",
|
|
displayPhoneNumber: "+628111111111",
|
|
status: ChannelStatus.CONNECTED,
|
|
webhookStatus: "healthy",
|
|
lastSyncAt: new Date("2026-04-20T09:10:00Z")
|
|
},
|
|
{
|
|
id: "channel-ops-acme",
|
|
tenantId: "tenant-acme",
|
|
channelName: "Ops WA",
|
|
provider: "Meta BSP",
|
|
wabaId: "waba-acme-ops",
|
|
phoneNumberId: "phone-acme-ops",
|
|
displayPhoneNumber: "+628222222222",
|
|
status: ChannelStatus.CONNECTED,
|
|
webhookStatus: "healthy",
|
|
lastSyncAt: new Date("2026-04-20T08:45:00Z")
|
|
},
|
|
{
|
|
id: "channel-sinar",
|
|
tenantId: "tenant-sinar",
|
|
channelName: "Sales WA",
|
|
provider: "Meta BSP",
|
|
wabaId: "waba-sinar-main",
|
|
phoneNumberId: "phone-sinar-main",
|
|
displayPhoneNumber: "+628333333333",
|
|
status: ChannelStatus.PENDING,
|
|
webhookStatus: "pending"
|
|
}
|
|
]
|
|
});
|
|
|
|
await prisma.tag.createMany({
|
|
data: [
|
|
{ id: "tag-enterprise", tenantId: "tenant-acme", name: "Enterprise", color: "#155eef" },
|
|
{ id: "tag-hot-lead", tenantId: "tenant-acme", name: "Hot Lead", color: "#ff7a00" },
|
|
{ id: "tag-payment", tenantId: "tenant-acme", name: "Payment Follow-up", color: "#b54708" },
|
|
{ id: "tag-b2b", tenantId: "tenant-acme", name: "B2B", color: "#067647" }
|
|
]
|
|
});
|
|
|
|
await prisma.contact.createMany({
|
|
data: [
|
|
{
|
|
id: "contact-001",
|
|
tenantId: "tenant-acme",
|
|
channelId: "channel-main-acme",
|
|
fullName: "Nadia Pratama",
|
|
phoneNumber: "+628123456789",
|
|
email: "nadia@example.com",
|
|
countryCode: "ID",
|
|
optInStatus: OptInStatus.OPTED_IN,
|
|
lastInteractionAt: new Date("2026-04-20T09:12:00Z")
|
|
},
|
|
{
|
|
id: "contact-002",
|
|
tenantId: "tenant-acme",
|
|
channelId: "channel-ops-acme",
|
|
fullName: "PT Sinar Abadi",
|
|
phoneNumber: "+62219876543",
|
|
email: "ops@sinarabadi.co.id",
|
|
countryCode: "ID",
|
|
optInStatus: OptInStatus.UNKNOWN,
|
|
lastInteractionAt: new Date("2026-04-19T11:00:00Z")
|
|
},
|
|
{
|
|
id: "contact-003",
|
|
tenantId: "tenant-acme",
|
|
channelId: "channel-main-acme",
|
|
fullName: "Rizky Saputra",
|
|
phoneNumber: "+628777777777",
|
|
email: "rizky@example.com",
|
|
countryCode: "ID",
|
|
optInStatus: OptInStatus.OPTED_IN,
|
|
lastInteractionAt: new Date("2026-04-18T12:00:00Z")
|
|
}
|
|
]
|
|
});
|
|
|
|
await prisma.contactTag.createMany({
|
|
data: [
|
|
{ id: "ct-001", tenantId: "tenant-acme", contactId: "contact-001", tagId: "tag-enterprise" },
|
|
{ id: "ct-002", tenantId: "tenant-acme", contactId: "contact-001", tagId: "tag-hot-lead" },
|
|
{ id: "ct-003", tenantId: "tenant-acme", contactId: "contact-002", tagId: "tag-b2b" },
|
|
{ id: "ct-004", tenantId: "tenant-acme", contactId: "contact-003", tagId: "tag-payment" }
|
|
]
|
|
});
|
|
|
|
await prisma.contactSegment.create({
|
|
data: {
|
|
id: "segment-hot-leads",
|
|
tenantId: "tenant-acme",
|
|
name: "Hot Leads",
|
|
description: "Enterprise prospects",
|
|
rulesJson: JSON.stringify({ tags: ["Enterprise", "Hot Lead"] })
|
|
}
|
|
});
|
|
|
|
await prisma.segmentMember.create({
|
|
data: {
|
|
id: "segment-member-001",
|
|
tenantId: "tenant-acme",
|
|
segmentId: "segment-hot-leads",
|
|
contactId: "contact-001"
|
|
}
|
|
});
|
|
|
|
await prisma.conversation.createMany({
|
|
data: [
|
|
{
|
|
id: "conv-001",
|
|
tenantId: "tenant-acme",
|
|
channelId: "channel-main-acme",
|
|
contactId: "contact-001",
|
|
subject: "Enterprise pricing",
|
|
status: ConversationStatus.OPEN,
|
|
priority: ConversationPriority.HIGH,
|
|
assignedUserId: "user-agent-001",
|
|
firstMessageAt: new Date("2026-04-20T09:00:00Z"),
|
|
lastMessageAt: new Date("2026-04-20T09:12:00Z"),
|
|
lastInboundAt: new Date("2026-04-20T09:12:00Z")
|
|
},
|
|
{
|
|
id: "conv-002",
|
|
tenantId: "tenant-acme",
|
|
channelId: "channel-main-acme",
|
|
contactId: "contact-003",
|
|
subject: "Payment follow-up",
|
|
status: ConversationStatus.PENDING,
|
|
priority: ConversationPriority.NORMAL,
|
|
firstMessageAt: new Date("2026-04-20T08:30:00Z"),
|
|
lastMessageAt: new Date("2026-04-20T08:47:00Z"),
|
|
lastInboundAt: new Date("2026-04-20T08:47:00Z")
|
|
},
|
|
{
|
|
id: "conv-003",
|
|
tenantId: "tenant-acme",
|
|
channelId: "channel-ops-acme",
|
|
contactId: "contact-002",
|
|
subject: "Template request",
|
|
status: ConversationStatus.RESOLVED,
|
|
priority: ConversationPriority.NORMAL,
|
|
assignedUserId: "user-agent-002",
|
|
firstMessageAt: new Date("2026-04-19T09:00:00Z"),
|
|
lastMessageAt: new Date("2026-04-19T11:00:00Z"),
|
|
lastInboundAt: new Date("2026-04-19T10:20:00Z"),
|
|
lastOutboundAt: new Date("2026-04-19T11:00:00Z"),
|
|
resolvedAt: new Date("2026-04-19T11:10:00Z")
|
|
}
|
|
]
|
|
});
|
|
|
|
await prisma.conversationTag.createMany({
|
|
data: [
|
|
{ id: "cvt-001", tenantId: "tenant-acme", conversationId: "conv-001", tagId: "tag-enterprise" },
|
|
{ id: "cvt-002", tenantId: "tenant-acme", conversationId: "conv-001", tagId: "tag-hot-lead" },
|
|
{ id: "cvt-003", tenantId: "tenant-acme", conversationId: "conv-002", tagId: "tag-payment" },
|
|
{ id: "cvt-004", tenantId: "tenant-acme", conversationId: "conv-003", tagId: "tag-b2b" }
|
|
]
|
|
});
|
|
|
|
await prisma.message.createMany({
|
|
data: [
|
|
{
|
|
id: "msg-001",
|
|
tenantId: "tenant-acme",
|
|
conversationId: "conv-001",
|
|
channelId: "channel-main-acme",
|
|
contactId: "contact-001",
|
|
direction: MessageDirection.INBOUND,
|
|
type: MessageType.TEXT,
|
|
contentText: "Halo, saya mau tanya soal paket enterprise untuk tim saya.",
|
|
deliveryStatus: DeliveryStatus.READ,
|
|
createdAt: new Date("2026-04-20T09:00:00Z"),
|
|
readAt: new Date("2026-04-20T09:00:00Z")
|
|
},
|
|
{
|
|
id: "msg-002",
|
|
tenantId: "tenant-acme",
|
|
conversationId: "conv-001",
|
|
channelId: "channel-main-acme",
|
|
direction: MessageDirection.OUTBOUND,
|
|
type: MessageType.TEXT,
|
|
contentText: "Tentu. Boleh saya tahu estimasi jumlah agent yang akan memakai sistem?",
|
|
deliveryStatus: DeliveryStatus.DELIVERED,
|
|
sentByUserId: "user-agent-001",
|
|
createdAt: new Date("2026-04-20T09:05:00Z"),
|
|
sentAt: new Date("2026-04-20T09:05:00Z"),
|
|
deliveredAt: new Date("2026-04-20T09:05:05Z")
|
|
},
|
|
{
|
|
id: "msg-003",
|
|
tenantId: "tenant-acme",
|
|
conversationId: "conv-002",
|
|
channelId: "channel-main-acme",
|
|
contactId: "contact-003",
|
|
direction: MessageDirection.INBOUND,
|
|
type: MessageType.TEXT,
|
|
contentText: "Sudah saya transfer, mohon dicek ya.",
|
|
deliveryStatus: DeliveryStatus.READ,
|
|
createdAt: new Date("2026-04-20T08:47:00Z"),
|
|
readAt: new Date("2026-04-20T08:47:00Z")
|
|
}
|
|
]
|
|
});
|
|
|
|
await prisma.conversationNote.create({
|
|
data: {
|
|
id: "note-001",
|
|
tenantId: "tenant-acme",
|
|
conversationId: "conv-001",
|
|
userId: "user-agent-001",
|
|
content: "Prospek enterprise, follow-up sore ini."
|
|
}
|
|
});
|
|
|
|
await prisma.conversationActivity.createMany({
|
|
data: [
|
|
{
|
|
id: "act-001",
|
|
tenantId: "tenant-acme",
|
|
conversationId: "conv-001",
|
|
actorUserId: "user-agent-001",
|
|
activityType: "assigned",
|
|
metadataJson: JSON.stringify({ assignee: "Farhan" })
|
|
},
|
|
{
|
|
id: "act-002",
|
|
tenantId: "tenant-acme",
|
|
conversationId: "conv-001",
|
|
actorUserId: "user-agent-001",
|
|
activityType: "message_sent",
|
|
metadataJson: JSON.stringify({ messageId: "msg-002" })
|
|
}
|
|
]
|
|
});
|
|
|
|
await prisma.messageTemplate.createMany({
|
|
data: [
|
|
{
|
|
id: "tpl-001",
|
|
tenantId: "tenant-acme",
|
|
channelId: "channel-main-acme",
|
|
name: "promo_april_2026",
|
|
category: "Marketing",
|
|
languageCode: "id",
|
|
templateType: "text",
|
|
bodyText: "Halo {{1}}, ada promo spesial untuk bisnis Anda minggu ini.",
|
|
approvalStatus: TemplateApprovalStatus.APPROVED
|
|
},
|
|
{
|
|
id: "tpl-002",
|
|
tenantId: "tenant-acme",
|
|
channelId: "channel-ops-acme",
|
|
name: "billing_reminder",
|
|
category: "Utility",
|
|
languageCode: "id",
|
|
templateType: "text",
|
|
bodyText: "Halo {{1}}, berikut pengingat pembayaran Anda.",
|
|
approvalStatus: TemplateApprovalStatus.PENDING
|
|
}
|
|
]
|
|
});
|
|
|
|
await prisma.broadcastCampaign.createMany({
|
|
data: [
|
|
{
|
|
id: "campaign-001",
|
|
tenantId: "tenant-acme",
|
|
channelId: "channel-main-acme",
|
|
templateId: "tpl-001",
|
|
createdByUserId: "user-admin-001",
|
|
name: "Promo April",
|
|
campaignType: CampaignType.BROADCAST,
|
|
audienceType: CampaignAudienceType.SEGMENT,
|
|
segmentId: "segment-hot-leads",
|
|
scheduledAt: new Date("2026-04-20T10:00:00Z"),
|
|
startedAt: new Date("2026-04-20T10:00:00Z"),
|
|
finishedAt: new Date("2026-04-20T10:30:00Z"),
|
|
status: CampaignStatus.COMPLETED,
|
|
totalRecipients: 1203,
|
|
totalSent: 1203,
|
|
totalDelivered: 1180,
|
|
totalRead: 873,
|
|
totalFailed: 23
|
|
},
|
|
{
|
|
id: "campaign-002",
|
|
tenantId: "tenant-acme",
|
|
channelId: "channel-ops-acme",
|
|
templateId: "tpl-002",
|
|
createdByUserId: "user-admin-001",
|
|
name: "Billing Reminder",
|
|
campaignType: CampaignType.BULK_FOLLOWUP,
|
|
audienceType: CampaignAudienceType.MANUAL,
|
|
status: CampaignStatus.DRAFT,
|
|
totalRecipients: 0
|
|
}
|
|
]
|
|
});
|
|
|
|
await prisma.campaignRecipient.createMany({
|
|
data: [
|
|
{
|
|
id: "recipient-001",
|
|
tenantId: "tenant-acme",
|
|
campaignId: "campaign-001",
|
|
contactId: "contact-001",
|
|
phoneNumber: "+628123456789",
|
|
sendStatus: DeliveryStatus.DELIVERED,
|
|
sentAt: new Date("2026-04-20T10:01:00Z"),
|
|
deliveredAt: new Date("2026-04-20T10:01:05Z"),
|
|
readAt: new Date("2026-04-20T11:00:00Z")
|
|
},
|
|
{
|
|
id: "recipient-002",
|
|
tenantId: "tenant-acme",
|
|
campaignId: "campaign-001",
|
|
contactId: "contact-003",
|
|
phoneNumber: "+628777777777",
|
|
sendStatus: DeliveryStatus.FAILED,
|
|
failureReason: "Template mismatch"
|
|
}
|
|
]
|
|
});
|
|
|
|
await prisma.usageMetric.createMany({
|
|
data: [
|
|
{
|
|
id: "usage-001",
|
|
tenantId: "tenant-acme",
|
|
metricDate: new Date("2026-04-20T00:00:00Z"),
|
|
inboundMessages: 128,
|
|
outboundMessages: 96,
|
|
activeContacts: 3,
|
|
activeAgents: 2,
|
|
broadcastSent: 1203,
|
|
storageUsedMb: 512
|
|
}
|
|
]
|
|
});
|
|
|
|
await prisma.billingInvoice.createMany({
|
|
data: [
|
|
{
|
|
id: "invoice-001",
|
|
tenantId: "tenant-acme",
|
|
planId: "plan-pro",
|
|
invoiceNumber: "INV-2026-001",
|
|
periodStart: new Date("2026-04-01T00:00:00Z"),
|
|
periodEnd: new Date("2026-04-30T23:59:59Z"),
|
|
subtotal: 2500000,
|
|
taxAmount: 275000,
|
|
totalAmount: 2775000,
|
|
paymentStatus: PaymentStatus.PAID,
|
|
dueDate: new Date("2026-04-30T00:00:00Z"),
|
|
paidAt: new Date("2026-04-15T00:00:00Z")
|
|
},
|
|
{
|
|
id: "invoice-002",
|
|
tenantId: "tenant-sinar",
|
|
planId: "plan-basic",
|
|
invoiceNumber: "INV-2026-002",
|
|
periodStart: new Date("2026-04-01T00:00:00Z"),
|
|
periodEnd: new Date("2026-04-30T23:59:59Z"),
|
|
subtotal: 1500000,
|
|
taxAmount: 165000,
|
|
totalAmount: 1665000,
|
|
paymentStatus: PaymentStatus.UNPAID,
|
|
dueDate: new Date("2026-05-01T00:00:00Z")
|
|
}
|
|
]
|
|
});
|
|
|
|
await prisma.auditLog.createMany({
|
|
data: [
|
|
{
|
|
id: "audit-001",
|
|
tenantId: "tenant-acme",
|
|
actorUserId: "user-agent-001",
|
|
entityType: "conversation",
|
|
entityId: "conv-001",
|
|
action: "message_sent",
|
|
metadataJson: JSON.stringify({ messageId: "msg-002" })
|
|
},
|
|
{
|
|
id: "audit-002",
|
|
tenantId: "tenant-acme",
|
|
actorUserId: "user-admin-001",
|
|
entityType: "campaign",
|
|
entityId: "campaign-001",
|
|
action: "campaign_created",
|
|
metadataJson: JSON.stringify({ recipients: 1203 })
|
|
}
|
|
]
|
|
});
|
|
|
|
await prisma.webhookEvent.createMany({
|
|
data: [
|
|
{
|
|
id: "webhook-001",
|
|
tenantId: "tenant-acme",
|
|
channelId: "channel-main-acme",
|
|
eventType: "message.inbound",
|
|
providerEventId: "evt_001",
|
|
payloadJson: JSON.stringify({ source: "meta", conversationId: "conv-001" }),
|
|
processStatus: "processed",
|
|
processedAt: new Date("2026-04-20T09:00:02Z")
|
|
},
|
|
{
|
|
id: "webhook-002",
|
|
tenantId: "tenant-acme",
|
|
channelId: "channel-main-acme",
|
|
eventType: "message.status",
|
|
providerEventId: "evt_002",
|
|
payloadJson: JSON.stringify({ source: "meta", campaignId: "campaign-001" }),
|
|
processStatus: "failed",
|
|
failedReason: "Temporary provider mismatch"
|
|
}
|
|
]
|
|
});
|
|
}
|
|
|
|
main()
|
|
.then(async () => {
|
|
await prisma.$disconnect();
|
|
})
|
|
.catch(async (error) => {
|
|
console.error(error);
|
|
await prisma.$disconnect();
|
|
process.exit(1);
|
|
});
|