355 lines
9.6 KiB
TypeScript
355 lines
9.6 KiB
TypeScript
import { PrismaClient } from '@prisma/client';
|
||
|
||
const prisma = new PrismaClient();
|
||
|
||
async function main() {
|
||
await prisma.cartItem.deleteMany();
|
||
await prisma.cart.deleteMany();
|
||
await prisma.orderItem.deleteMany();
|
||
await prisma.order.deleteMany();
|
||
await prisma.inventory.deleteMany();
|
||
await prisma.productVariant.deleteMany();
|
||
await prisma.product.deleteMany();
|
||
await prisma.liveMessage.deleteMany();
|
||
await prisma.liveRoomChecklistItem.deleteMany();
|
||
await prisma.liveRoomScript.deleteMany();
|
||
await prisma.liveRoomRoleAssignment.deleteMany();
|
||
await prisma.liveRoom.deleteMany();
|
||
await prisma.liveTeamMember.deleteMany();
|
||
await prisma.user.deleteMany();
|
||
|
||
const user = await prisma.user.create({
|
||
data: {
|
||
email: 'streamer-fan@vtuber.local',
|
||
name: 'Demo User',
|
||
},
|
||
});
|
||
|
||
const room = await prisma.liveRoom.create({
|
||
data: {
|
||
title: 'VTuber Demo Live',
|
||
hostName: 'Ari',
|
||
description: '歡迎來到 VTuber Demo 直播間!',
|
||
isActive: true,
|
||
status: 'PLANNING',
|
||
streamUrl: 'https://videos.pexels.com/video-files/7297923/7297923-uhd_3840_2160_30fps.mp4',
|
||
liveGoal: '示範真人+主播互動+主推商品成交閉環',
|
||
plannedStartAt: new Date(),
|
||
plannedEndAt: new Date(Date.now() + 90 * 60 * 1000),
|
||
},
|
||
});
|
||
|
||
const teamMembers = await Promise.all([
|
||
prisma.liveTeamMember.create({
|
||
data: {
|
||
displayName: '主持人-Ari',
|
||
role: 'HOST',
|
||
phone: '0911000001',
|
||
email: 'host@vtuber.local',
|
||
},
|
||
}),
|
||
prisma.liveTeamMember.create({
|
||
data: {
|
||
displayName: '編導-Jess',
|
||
role: 'DIRECTOR',
|
||
phone: '0911000002',
|
||
email: 'director@vtuber.local',
|
||
},
|
||
}),
|
||
prisma.liveTeamMember.create({
|
||
data: {
|
||
displayName: '導播-Kim',
|
||
role: 'PRODUCER',
|
||
phone: '0911000003',
|
||
email: 'producer@vtuber.local',
|
||
},
|
||
}),
|
||
prisma.liveTeamMember.create({
|
||
data: {
|
||
displayName: '小編-Lina',
|
||
role: 'EDITOR',
|
||
phone: '0911000004',
|
||
email: 'editor@vtuber.local',
|
||
},
|
||
}),
|
||
prisma.liveTeamMember.create({
|
||
data: {
|
||
displayName: '客服-Ann',
|
||
role: 'SUPPORT',
|
||
phone: '0911000005',
|
||
email: 'support@vtuber.local',
|
||
},
|
||
}),
|
||
prisma.liveTeamMember.create({
|
||
data: {
|
||
displayName: '物流-Wei',
|
||
role: 'WAREHOUSE',
|
||
phone: '0911000006',
|
||
email: 'warehouse@vtuber.local',
|
||
},
|
||
}),
|
||
prisma.liveTeamMember.create({
|
||
data: {
|
||
displayName: '行銷-May',
|
||
role: 'MARKETING',
|
||
phone: '0911000007',
|
||
email: 'marketing@vtuber.local',
|
||
},
|
||
}),
|
||
]);
|
||
|
||
const [hostMember, directorMember, producerMember, editorMember, supportMember, warehouseMember, marketingMember] = teamMembers;
|
||
|
||
await prisma.liveRoomRoleAssignment.create({
|
||
data: {
|
||
liveRoomId: room.id,
|
||
teamMemberId: hostMember.id,
|
||
role: 'HOST',
|
||
isPrimary: true,
|
||
note: '直播前3分鐘啟播確認',
|
||
shiftStartAt: new Date(Date.now() - 30 * 60 * 1000),
|
||
shiftEndAt: new Date(Date.now() + 120 * 60 * 1000),
|
||
},
|
||
});
|
||
|
||
await prisma.liveRoomRoleAssignment.create({
|
||
data: {
|
||
liveRoomId: room.id,
|
||
teamMemberId: directorMember.id,
|
||
role: 'DIRECTOR',
|
||
note: '節奏控場,提醒商品切換順序',
|
||
shiftStartAt: new Date(Date.now() - 20 * 60 * 1000),
|
||
shiftEndAt: new Date(Date.now() + 120 * 60 * 1000),
|
||
},
|
||
});
|
||
|
||
await prisma.liveRoomRoleAssignment.create({
|
||
data: {
|
||
liveRoomId: room.id,
|
||
teamMemberId: producerMember.id,
|
||
role: 'PRODUCER',
|
||
note: '音畫輸出、畫面切換',
|
||
shiftStartAt: new Date(Date.now() - 20 * 60 * 1000),
|
||
shiftEndAt: new Date(Date.now() + 120 * 60 * 1000),
|
||
},
|
||
});
|
||
|
||
await prisma.liveRoomRoleAssignment.create({
|
||
data: {
|
||
liveRoomId: room.id,
|
||
teamMemberId: editorMember.id,
|
||
role: 'EDITOR',
|
||
note: '補充彈幕腳本文案',
|
||
shiftStartAt: new Date(Date.now() - 10 * 60 * 1000),
|
||
shiftEndAt: new Date(Date.now() + 120 * 60 * 1000),
|
||
},
|
||
});
|
||
|
||
await prisma.liveRoomRoleAssignment.create({
|
||
data: {
|
||
liveRoomId: room.id,
|
||
teamMemberId: supportMember.id,
|
||
role: 'SUPPORT',
|
||
note: '回覆 DM 與加購問題',
|
||
shiftStartAt: new Date(Date.now() - 10 * 60 * 1000),
|
||
shiftEndAt: new Date(Date.now() + 120 * 60 * 1000),
|
||
},
|
||
});
|
||
|
||
await prisma.liveRoomRoleAssignment.create({
|
||
data: {
|
||
liveRoomId: room.id,
|
||
teamMemberId: warehouseMember.id,
|
||
role: 'WAREHOUSE',
|
||
note: '監控出貨、保留庫存',
|
||
shiftStartAt: new Date(Date.now() - 10 * 60 * 1000),
|
||
shiftEndAt: new Date(Date.now() + 120 * 60 * 1000),
|
||
},
|
||
});
|
||
|
||
await prisma.liveRoomRoleAssignment.create({
|
||
data: {
|
||
liveRoomId: room.id,
|
||
teamMemberId: marketingMember.id,
|
||
role: 'MARKETING',
|
||
note: '活動主打標語與互動口號',
|
||
shiftStartAt: new Date(Date.now() - 10 * 60 * 1000),
|
||
shiftEndAt: new Date(Date.now() + 120 * 60 * 1000),
|
||
},
|
||
});
|
||
|
||
const productsCreated: {
|
||
id: string;
|
||
variants: Array<{ id: string }>;
|
||
}[] = [];
|
||
|
||
for (let i = 1; i <= 10; i += 1) {
|
||
const basePrice = 250 + i * 80;
|
||
const product = await prisma.product.create({
|
||
data: {
|
||
name: `Demo Product ${i}`,
|
||
slug: `demo-product-${i}`,
|
||
description: `這是第 ${i} 個直播間示範商品`,
|
||
imageUrl: `https://picsum.photos/seed/vtuber-${i}/640/640`,
|
||
basePrice,
|
||
liveRoomId: room.id,
|
||
},
|
||
});
|
||
|
||
const variant = await prisma.productVariant.create({
|
||
data: {
|
||
productId: product.id,
|
||
sku: `VT-P-${i.toString().padStart(3, '0')}`,
|
||
name: '標準款',
|
||
size: 'M',
|
||
price: basePrice,
|
||
},
|
||
});
|
||
|
||
await prisma.inventory.create({
|
||
data: {
|
||
productVariantId: variant.id,
|
||
quantity: 50 + i * 10,
|
||
reserved: 0,
|
||
},
|
||
});
|
||
|
||
productsCreated.push({
|
||
id: product.id,
|
||
variants: [{ id: variant.id }],
|
||
});
|
||
}
|
||
|
||
await prisma.liveRoomScript.create({
|
||
data: {
|
||
liveRoomId: room.id,
|
||
sequence: 1,
|
||
cue: '00:00',
|
||
title: '開場破冰與今晚主推',
|
||
content:
|
||
'先報告福利機制、引導觀眾先看 1~3 號主推商品,最後加入購物車可贈運費券。',
|
||
ownerRole: 'HOST',
|
||
isDone: false,
|
||
targetProductId: productsCreated[0]?.id,
|
||
},
|
||
});
|
||
|
||
await prisma.liveRoomScript.create({
|
||
data: {
|
||
liveRoomId: room.id,
|
||
sequence: 2,
|
||
cue: '00:20',
|
||
title: '第一輪上鏡',
|
||
content:
|
||
'切到主推款 Demo Product 1,補充材質、顏色、穿搭組合,並引導觀眾留言問價。',
|
||
ownerRole: 'HOST',
|
||
isDone: false,
|
||
targetProductId: productsCreated[1]?.id,
|
||
},
|
||
});
|
||
|
||
await prisma.liveRoomScript.create({
|
||
data: {
|
||
liveRoomId: room.id,
|
||
sequence: 3,
|
||
cue: '01:20',
|
||
title: '加碼互動與秒殺',
|
||
content: '導播轉場到購物掛件,客服同步回覆尺寸、庫存問題,提醒直播限時加價購。',
|
||
ownerRole: 'PRODUCER',
|
||
isDone: false,
|
||
targetProductId: productsCreated[2]?.id,
|
||
},
|
||
});
|
||
|
||
await prisma.liveRoomScript.create({
|
||
data: {
|
||
liveRoomId: room.id,
|
||
sequence: 4,
|
||
cue: '02:10',
|
||
title: '結尾總結',
|
||
content: '複盤今晚三款重點,提示未付款者可直接在結帳頁完成,下播後將開啟補貨預購名單。',
|
||
ownerRole: 'HOST',
|
||
isDone: false,
|
||
targetProductId: productsCreated[3]?.id,
|
||
},
|
||
});
|
||
|
||
await prisma.liveRoomChecklistItem.create({
|
||
data: {
|
||
liveRoomId: room.id,
|
||
title: '直播前 15 分鐘檢查音訊與畫面',
|
||
ownerRole: 'PRODUCER',
|
||
description: '確認鏡頭、麥克風、延遲與貨幣換算腳本無誤。',
|
||
isRequired: true,
|
||
isDone: true,
|
||
completedAt: new Date(Date.now() - 10 * 60 * 1000),
|
||
},
|
||
});
|
||
|
||
await prisma.liveRoomChecklistItem.create({
|
||
data: {
|
||
liveRoomId: room.id,
|
||
title: '補貨規則與庫存同步',
|
||
ownerRole: 'WAREHOUSE',
|
||
description: '確認各款庫存不低於 10 件,避免前 10 分鐘售罄。',
|
||
isRequired: true,
|
||
isDone: false,
|
||
},
|
||
});
|
||
|
||
await prisma.liveRoomChecklistItem.create({
|
||
data: {
|
||
liveRoomId: room.id,
|
||
title: '客服值守回覆',
|
||
ownerRole: 'SUPPORT',
|
||
description: '監看留言問題回覆:價格、尺寸、運送、下單步驟。',
|
||
isRequired: true,
|
||
isDone: false,
|
||
},
|
||
});
|
||
|
||
const sampleMessages = [
|
||
'主播你好,這款材質是什麼?',
|
||
'有沒有打折?',
|
||
'今晚能配這個紅色款嗎?',
|
||
'有現貨嗎?',
|
||
'這件能搭配什麼配件?',
|
||
'有貨到付款嗎?',
|
||
'什麼時候會補貨?',
|
||
'寄送多久可以到?',
|
||
'有試穿影片嗎?',
|
||
'價格會再便宜一點嗎?',
|
||
'尺寸表可以再清楚一點嗎?',
|
||
'包裝是不是有品牌紙袋?',
|
||
'可以直接加到購物車嗎?',
|
||
'有其他顏色嗎?',
|
||
'這張是第一個回應!',
|
||
'直播間氣氛很棒',
|
||
'主播的穿搭很有氛圍',
|
||
'我先看一下其他款',
|
||
'結帳流程有點複雜',
|
||
'今晚還會有其他新品嗎?',
|
||
];
|
||
|
||
for (const msg of sampleMessages) {
|
||
await prisma.liveMessage.create({
|
||
data: {
|
||
liveRoomId: room.id,
|
||
userName: `觀眾-${Math.floor(Math.random() * 99) + 1}`,
|
||
message: msg,
|
||
userId: user.id,
|
||
},
|
||
});
|
||
}
|
||
}
|
||
|
||
main()
|
||
.then(async () => {
|
||
await prisma.$disconnect();
|
||
})
|
||
.catch(async (error) => {
|
||
console.error(error);
|
||
await prisma.$disconnect();
|
||
process.exit(1);
|
||
});
|