Files
iykyk_msn/server/src/seed.ts
2026-04-07 16:17:03 +09:00

231 lines
8.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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",
/** AliceBob 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, "확인했습니다. 내일 정리해서 공유할게요.");
}
}