231 lines
8.7 KiB
TypeScript
231 lines
8.7 KiB
TypeScript
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, "확인했습니다. 내일 정리해서 공유할게요.");
|
||
}
|
||
}
|