import bcrypt from "bcryptjs"; import { randomUUID } from "node:crypto"; import { db } from "./db.js"; const DEMO_EMAIL_ALICE = "alice@demo.msn"; const DEMO_EMAIL_BOB = "bob@demo.msn"; const DEMO_PASSWORD = "demo1234"; /** Stable UUIDs for docs and debugging. */ export const DemoIds = { alice: "a1111111-1111-4111-8111-111111111111", bob: "b2222222-2222-4222-8222-222222222222", ctxAlicePersonal: "c1111111-1111-4111-8111-111111111111", ctxBobPersonal: "c2222222-2222-4222-8222-222222222222", ctxWork: "d3333333-3333-4333-8333-333333333333", roomWorkDm: "e4444444-4444-4444-8444-444444444444", /** Alice–Bob DM in Alice's personal context */ roomPersonalDm: "f1111111-1111-4111-8111-111111111111", /** Group room in work context */ roomWorkGroup: "g1111111-1111-4111-8111-111111111111", } as const; /** Stable portrait URLs for Flutter NetworkImage (HTTPS, JPG). */ const DEMO_AVATAR_ALICE_PERSONAL = "https://i.pravatar.cc/400?img=47"; const DEMO_AVATAR_BOB_PERSONAL = "https://i.pravatar.cc/400?img=33"; const DEMO_AVATAR_ALICE_WORK = "https://i.pravatar.cc/400?img=12"; const DEMO_AVATAR_BOB_WORK = "https://i.pravatar.cc/400?img=59"; /** * Demo users, personal + work contexts, profiles, DM + sample messages. * Skips if alice@demo.msn already exists (idempotent). */ export function seedDemoIfNeeded(): void { const exists = db.prepare(`SELECT id FROM users WHERE email = ?`).get(DEMO_EMAIL_ALICE) as | { id: string } | undefined; if (exists) { return; } const hash = bcrypt.hashSync(DEMO_PASSWORD, 10); const { alice, bob, ctxAlicePersonal, ctxBobPersonal, ctxWork, roomWorkDm } = DemoIds; db.prepare(`INSERT INTO users (id, email, password_hash) VALUES (?, ?, ?)`).run( alice, DEMO_EMAIL_ALICE, hash ); db.prepare(`INSERT INTO users (id, email, password_hash) VALUES (?, ?, ?)`).run( bob, DEMO_EMAIL_BOB, hash ); db.prepare(`INSERT INTO contexts (id, name, kind) VALUES (?, ?, 'personal')`).run( ctxAlicePersonal, "일상" ); db.prepare(`INSERT INTO contexts (id, name, kind) VALUES (?, ?, 'personal')`).run( ctxBobPersonal, "일상" ); db.prepare( `INSERT INTO contexts (id, name, kind, retention_days, screenshot_blocked) VALUES (?, ?, 'work', 365, 0)` ).run(ctxWork, "데모 회사"); db.prepare( `INSERT INTO context_members (context_id, user_id, role) VALUES (?, ?, 'owner')` ).run(ctxAlicePersonal, alice); db.prepare( `INSERT INTO context_members (context_id, user_id, role) VALUES (?, ?, 'owner')` ).run(ctxBobPersonal, bob); db.prepare( `INSERT INTO context_members (context_id, user_id, role) VALUES (?, ?, 'owner')` ).run(ctxWork, alice); db.prepare( `INSERT INTO context_members (context_id, user_id, role) VALUES (?, ?, 'member')` ).run(ctxWork, bob); db.prepare( `INSERT INTO profiles (user_id, context_id, display_name, avatar_url, status_message) VALUES (?, ?, ?, ?, ?)` ).run( alice, ctxAlicePersonal, "앨리스", DEMO_AVATAR_ALICE_PERSONAL, "일상 프로필 · 친구들과 수다" ); db.prepare( `INSERT INTO profiles (user_id, context_id, display_name, avatar_url, status_message) VALUES (?, ?, ?, ?, ?)` ).run(bob, ctxBobPersonal, "밥", DEMO_AVATAR_BOB_PERSONAL, "일상 상태 메시지"); db.prepare( `INSERT INTO profiles (user_id, context_id, display_name, avatar_url, status_message) VALUES (?, ?, ?, ?, ?)` ).run(alice, ctxWork, "김앨리스 (기획)", DEMO_AVATAR_ALICE_WORK, "회사 맥락 전용 표시명"); db.prepare( `INSERT INTO profiles (user_id, context_id, display_name, avatar_url, status_message) VALUES (?, ?, ?, ?, ?)` ).run(bob, ctxWork, "박밥 (백엔드)", DEMO_AVATAR_BOB_WORK, "동료에게만 보이는 이름"); db.prepare( `INSERT INTO rooms (id, context_id, is_group, name) VALUES (?, ?, 0, NULL)` ).run(roomWorkDm, ctxWork); db.prepare(`INSERT INTO room_members (room_id, user_id) VALUES (?, ?)`).run(roomWorkDm, alice); db.prepare(`INSERT INTO room_members (room_id, user_id) VALUES (?, ?)`).run(roomWorkDm, bob); const m1 = randomUUID(); const m2 = randomUUID(); const m3 = randomUUID(); db.prepare( `INSERT INTO messages (id, room_id, sender_id, body, kind) VALUES (?, ?, ?, ?, 'text')` ).run(m1, roomWorkDm, bob, "안녕하세요, 회의 자료 공유드립니다."); db.prepare( `INSERT INTO messages (id, room_id, sender_id, body, kind) VALUES (?, ?, ?, ?, 'text')` ).run(m2, roomWorkDm, alice, "확인했습니다. 오후에 뵐게요."); db.prepare( `INSERT INTO messages (id, room_id, sender_id, body, kind) VALUES (?, ?, ?, ?, 'text')` ).run(m3, roomWorkDm, bob, "네, 감사합니다."); console.log(""); console.log("========== DEMO DATA SEEDED =========="); console.log(` ${DEMO_EMAIL_ALICE} / ${DEMO_PASSWORD} (Alice — switch to \"데모 회사\")`); console.log(` ${DEMO_EMAIL_BOB} / ${DEMO_PASSWORD} (Bob — second account)`); console.log("======================================"); console.log(""); } /** * Fills demo avatar_url for existing DBs seeded before avatars were added. * Safe to run on every startup (only updates NULL/empty for known demo rows). */ export function backfillDemoAvatarsIfNeeded(): void { const { alice, bob, ctxAlicePersonal, ctxBobPersonal, ctxWork } = DemoIds; const rows: Array<[string, string, string]> = [ [alice, ctxAlicePersonal, DEMO_AVATAR_ALICE_PERSONAL], [bob, ctxBobPersonal, DEMO_AVATAR_BOB_PERSONAL], [alice, ctxWork, DEMO_AVATAR_ALICE_WORK], [bob, ctxWork, DEMO_AVATAR_BOB_WORK], ]; const stmt = db.prepare( `UPDATE profiles SET avatar_url = ? WHERE user_id = ? AND context_id = ? AND (avatar_url IS NULL OR avatar_url = '')` ); for (const [uid, cid, url] of rows) { stmt.run(url, uid, cid); } } /** * Extra demo: Bob in Alice's personal context, personal DM, work group room + messages. * Idempotent — safe on every startup (uses INSERT OR IGNORE / message count checks). */ export function extendDemoDataIfNeeded(): void { const exists = db.prepare(`SELECT id FROM users WHERE email = ?`).get(DEMO_EMAIL_ALICE) as | { id: string } | undefined; if (!exists) { return; } const { alice, bob, ctxAlicePersonal, ctxWork, roomPersonalDm, roomWorkGroup } = DemoIds; db.prepare( `INSERT OR IGNORE INTO context_members (context_id, user_id, role) VALUES (?, ?, 'member')` ).run(ctxAlicePersonal, bob); db.prepare( `INSERT OR IGNORE INTO profiles (user_id, context_id, display_name, avatar_url, status_message) VALUES (?, ?, ?, ?, ?)` ).run( bob, ctxAlicePersonal, "밥", DEMO_AVATAR_BOB_PERSONAL, "앨리스 일상에서 보이는 상태" ); db.prepare( `INSERT OR IGNORE INTO rooms (id, context_id, is_group, name) VALUES (?, ?, 0, NULL)` ).run(roomPersonalDm, ctxAlicePersonal); db.prepare(`INSERT OR IGNORE INTO room_members (room_id, user_id) VALUES (?, ?)`).run( roomPersonalDm, alice ); db.prepare(`INSERT OR IGNORE INTO room_members (room_id, user_id) VALUES (?, ?)`).run( roomPersonalDm, bob ); const personalMsgN = db .prepare(`SELECT COUNT(*) as n FROM messages WHERE room_id = ?`) .get(roomPersonalDm) as { n: number }; if (personalMsgN.n === 0) { const a = randomUUID(); const b = randomUUID(); const c = randomUUID(); db.prepare( `INSERT INTO messages (id, room_id, sender_id, body, kind) VALUES (?, ?, ?, ?, 'text')` ).run(a, roomPersonalDm, alice, "주말에 카페 갈래?"); db.prepare( `INSERT INTO messages (id, room_id, sender_id, body, kind) VALUES (?, ?, ?, ?, 'text')` ).run(b, roomPersonalDm, bob, "좋아, 몇 시에 볼까?"); db.prepare( `INSERT INTO messages (id, room_id, sender_id, body, kind) VALUES (?, ?, ?, ?, 'text')` ).run(c, roomPersonalDm, alice, "2시 어때?"); } db.prepare( `INSERT OR IGNORE INTO rooms (id, context_id, is_group, name) VALUES (?, ?, 1, ?)` ).run(roomWorkGroup, ctxWork, "프로젝트 A"); db.prepare(`INSERT OR IGNORE INTO room_members (room_id, user_id) VALUES (?, ?)`).run( roomWorkGroup, alice ); db.prepare(`INSERT OR IGNORE INTO room_members (room_id, user_id) VALUES (?, ?)`).run( roomWorkGroup, bob ); const groupMsgN = db .prepare(`SELECT COUNT(*) as n FROM messages WHERE room_id = ?`) .get(roomWorkGroup) as { n: number }; if (groupMsgN.n === 0) { const m1 = randomUUID(); const m2 = randomUUID(); db.prepare( `INSERT INTO messages (id, room_id, sender_id, body, kind) VALUES (?, ?, ?, ?, 'text')` ).run(m1, roomWorkGroup, alice, "[프로젝트 A] 킥오프 슬라이드 올려뒀어요."); db.prepare( `INSERT INTO messages (id, room_id, sender_id, body, kind) VALUES (?, ?, ?, ?, 'text')` ).run(m2, roomWorkGroup, bob, "확인했습니다. 내일 정리해서 공유할게요."); } }