오대리ㅣㅣㅣㅣ

This commit is contained in:
송원형
2026-04-07 16:17:03 +09:00
commit 5bb54fdefe
63 changed files with 7897 additions and 0 deletions

230
server/src/seed.ts Normal file
View File

@@ -0,0 +1,230 @@
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, "확인했습니다. 내일 정리해서 공유할게요.");
}
}