2025-10-08 23:04:12 +09:00
|
|
|
const { PrismaClient } = require("@prisma/client");
|
|
|
|
|
|
|
|
|
|
const prisma = new PrismaClient();
|
|
|
|
|
|
2025-10-13 06:29:33 +09:00
|
|
|
async function upsertCategories() {
|
2025-10-13 07:23:08 +09:00
|
|
|
// 카테고리 트리 (projectmemo 기준 상위 그룹)
|
2025-10-13 06:29:33 +09:00
|
|
|
const categories = [
|
2025-10-13 14:41:51 +09:00
|
|
|
{ name: "암실소문", slug: "main", sortOrder: 1, status: "active" },
|
2025-10-13 07:23:08 +09:00
|
|
|
{ name: "명예의 전당", slug: "hall-of-fame", sortOrder: 2, status: "active" },
|
|
|
|
|
{ name: "주변 제휴업체", slug: "nearby-partners", sortOrder: 3, status: "active" },
|
|
|
|
|
{ name: "제휴업소 정보", slug: "partner-info", sortOrder: 4, status: "active" },
|
|
|
|
|
{ name: "방문후기", slug: "reviews", sortOrder: 5, status: "active" },
|
|
|
|
|
{ name: "소통방", slug: "community", sortOrder: 6, status: "active" },
|
2025-10-13 11:34:00 +09:00
|
|
|
{ name: "광고/제휴", slug: "ads-affiliates", sortOrder: 7, status: "active" },
|
2025-10-13 06:29:33 +09:00
|
|
|
];
|
|
|
|
|
const map = {};
|
|
|
|
|
for (const c of categories) {
|
|
|
|
|
const created = await prisma.boardCategory.upsert({
|
|
|
|
|
where: { slug: c.slug },
|
|
|
|
|
update: { name: c.name, sortOrder: c.sortOrder, status: c.status },
|
|
|
|
|
create: c,
|
|
|
|
|
});
|
|
|
|
|
map[c.slug] = created;
|
|
|
|
|
}
|
|
|
|
|
return map; // { slug: category }
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-09 11:23:27 +09:00
|
|
|
async function upsertRoles() {
|
|
|
|
|
const roles = [
|
|
|
|
|
{ name: "admin", description: "관리자" },
|
|
|
|
|
{ name: "editor", description: "운영진" },
|
|
|
|
|
{ name: "user", description: "일반 사용자" }
|
|
|
|
|
];
|
|
|
|
|
for (const r of roles) {
|
|
|
|
|
await prisma.role.upsert({
|
|
|
|
|
where: { name: r.name },
|
|
|
|
|
update: { description: r.description },
|
|
|
|
|
create: r,
|
|
|
|
|
});
|
|
|
|
|
}
|
2025-10-09 14:37:42 +09:00
|
|
|
// 기본 권한 매핑
|
|
|
|
|
const roleMap = {
|
|
|
|
|
admin: [
|
|
|
|
|
["ADMIN", "ADMINISTER"],
|
|
|
|
|
["BOARD", "MODERATE"],
|
|
|
|
|
["POST", "CREATE"],
|
|
|
|
|
["POST", "UPDATE"],
|
|
|
|
|
["POST", "DELETE"],
|
|
|
|
|
["COMMENT", "DELETE"],
|
|
|
|
|
["USER", "UPDATE"],
|
|
|
|
|
],
|
|
|
|
|
editor: [
|
|
|
|
|
["BOARD", "MODERATE"],
|
|
|
|
|
["POST", "UPDATE"],
|
|
|
|
|
["POST", "DELETE"],
|
|
|
|
|
["COMMENT", "DELETE"],
|
|
|
|
|
],
|
|
|
|
|
user: [
|
|
|
|
|
["POST", "CREATE"],
|
|
|
|
|
["COMMENT", "CREATE"],
|
|
|
|
|
["POST", "READ"],
|
|
|
|
|
["COMMENT", "READ"],
|
|
|
|
|
],
|
|
|
|
|
};
|
|
|
|
|
for (const [roleName, perms] of Object.entries(roleMap)) {
|
|
|
|
|
const role = await prisma.role.findUnique({ where: { name: roleName } });
|
|
|
|
|
if (!role) continue;
|
|
|
|
|
for (const [resource, action] of perms) {
|
|
|
|
|
await prisma.rolePermission.upsert({
|
|
|
|
|
where: {
|
|
|
|
|
roleId_resource_action: {
|
|
|
|
|
roleId: role.roleId,
|
|
|
|
|
resource,
|
|
|
|
|
action,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
update: { allowed: true },
|
|
|
|
|
create: { roleId: role.roleId, resource, action, allowed: true },
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-10-09 11:23:27 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function upsertAdmin() {
|
|
|
|
|
const admin = await prisma.user.upsert({
|
|
|
|
|
where: { nickname: "admin" },
|
2025-10-08 23:04:12 +09:00
|
|
|
update: {},
|
|
|
|
|
create: {
|
2025-10-09 11:23:27 +09:00
|
|
|
nickname: "admin",
|
|
|
|
|
name: "Administrator",
|
2025-10-08 23:44:21 +09:00
|
|
|
birth: new Date("1990-01-01"),
|
2025-10-09 11:23:27 +09:00
|
|
|
phone: "010-0000-0001",
|
|
|
|
|
agreementTermsAt: new Date(),
|
|
|
|
|
authLevel: "ADMIN",
|
|
|
|
|
},
|
2025-10-08 23:04:12 +09:00
|
|
|
});
|
2025-10-08 23:44:21 +09:00
|
|
|
|
2025-10-09 11:23:27 +09:00
|
|
|
const adminRole = await prisma.role.findUnique({ where: { name: "admin" } });
|
|
|
|
|
if (adminRole) {
|
|
|
|
|
await prisma.userRole.upsert({
|
|
|
|
|
where: {
|
|
|
|
|
userId_roleId: { userId: admin.userId, roleId: adminRole.roleId },
|
|
|
|
|
},
|
|
|
|
|
update: {},
|
|
|
|
|
create: { userId: admin.userId, roleId: adminRole.roleId },
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
return admin;
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-13 06:29:33 +09:00
|
|
|
async function upsertBoards(admin, categoryMap) {
|
2025-10-09 11:23:27 +09:00
|
|
|
const boards = [
|
|
|
|
|
// 일반
|
2025-10-09 17:13:54 +09:00
|
|
|
{ name: "공지사항", slug: "notice", description: "공지", type: "general", sortOrder: 1, writeLevel: "moderator" },
|
2025-10-09 11:23:27 +09:00
|
|
|
{ name: "가입인사", slug: "greetings", description: "가입인사", type: "general", sortOrder: 2 },
|
|
|
|
|
{ name: "버그건의", slug: "bug-report", description: "버그/건의", type: "general", sortOrder: 3 },
|
2025-10-09 17:13:54 +09:00
|
|
|
{ name: "이벤트", slug: "event", description: "이벤트", type: "general", sortOrder: 4, requiredTags: { required: ["이벤트"] } },
|
2025-10-09 11:23:27 +09:00
|
|
|
{ name: "자유게시판", slug: "free", description: "자유", type: "general", sortOrder: 5 },
|
|
|
|
|
{ name: "무엇이든", slug: "qna", description: "무엇이든 물어보세요", type: "general", sortOrder: 6 },
|
|
|
|
|
{ name: "마사지꿀팁", slug: "tips", description: "팁", type: "general", sortOrder: 7 },
|
2025-10-09 17:13:54 +09:00
|
|
|
{ name: "익명게시판", slug: "anonymous", description: "익명", type: "general", sortOrder: 8, allowAnonymousPost: true, allowSecretComment: true },
|
2025-10-09 11:23:27 +09:00
|
|
|
{ name: "관리사찾아요", slug: "find-therapist", description: "구인/구직", type: "general", sortOrder: 9 },
|
|
|
|
|
{ name: "청와대", slug: "blue-house", description: "레벨 제한", type: "general", sortOrder: 10, readLevel: "member" },
|
2025-10-09 17:13:54 +09:00
|
|
|
{ name: "방문후기", slug: "reviews", description: "운영자 승인 후 공개", type: "general", sortOrder: 11, requiresApproval: true, requiredTags: { anyOf: ["업체명", "지역"] } },
|
2025-10-09 11:23:27 +09:00
|
|
|
// 특수
|
|
|
|
|
{ name: "출석부", slug: "attendance", description: "데일리 체크인", type: "special", sortOrder: 12 },
|
|
|
|
|
{ name: "주변 제휴업체", slug: "nearby-partners", description: "위치 기반", type: "special", sortOrder: 13 },
|
|
|
|
|
{ name: "회원랭킹", slug: "ranking", description: "랭킹", type: "special", sortOrder: 14 },
|
|
|
|
|
{ name: "무료쿠폰", slug: "free-coupons", description: "쿠폰", type: "special", sortOrder: 15 },
|
|
|
|
|
{ name: "월간집계", slug: "monthly-stats", description: "월간 통계", type: "special", sortOrder: 16 },
|
2025-10-13 14:41:51 +09:00
|
|
|
// 제휴업소 일반
|
|
|
|
|
{ name: "제휴업소", slug: "partners-photos", description: "사진 전용 게시판", type: "general", sortOrder: 17, requiredFields: { imageOnly: true, minImages: 1, maxImages: 10 } },
|
|
|
|
|
// 광고/제휴
|
|
|
|
|
{ name: "제휴문의", slug: "partner-contact", description: "제휴문의", type: "general", sortOrder: 18, requiredFields: { imageOnly: true, minImages: 1, maxImages: 10 } },
|
|
|
|
|
{ name: "제휴업소 요청", slug: "partner-req", description: "제휴업소 요청", type: "general", sortOrder: 19, requiredFields: { imageOnly: true, minImages: 1, maxImages: 10 } },
|
2025-10-09 11:23:27 +09:00
|
|
|
];
|
|
|
|
|
|
|
|
|
|
const created = [];
|
|
|
|
|
for (const b of boards) {
|
2025-10-13 07:23:08 +09:00
|
|
|
// 카테고리 매핑 규칙 (트리 기준 상위 카테고리)
|
|
|
|
|
const mapBySlug = {
|
|
|
|
|
// 암실소문 (메인)
|
|
|
|
|
notice: "main",
|
|
|
|
|
greetings: "main",
|
|
|
|
|
"bug-report": "main",
|
|
|
|
|
event: "main",
|
|
|
|
|
attendance: "main",
|
|
|
|
|
// 명예의 전당
|
|
|
|
|
ranking: "hall-of-fame",
|
|
|
|
|
"free-coupons": "hall-of-fame",
|
|
|
|
|
"monthly-stats": "hall-of-fame",
|
|
|
|
|
// 주변 제휴업체
|
|
|
|
|
"nearby-partners": "nearby-partners",
|
|
|
|
|
// 제휴업소 정보
|
|
|
|
|
"partners-photos": "partner-info",
|
|
|
|
|
// 방문후기
|
|
|
|
|
reviews: "reviews",
|
|
|
|
|
// 소통방(기본값 community로 처리)
|
|
|
|
|
free: "community",
|
|
|
|
|
qna: "community",
|
|
|
|
|
tips: "community",
|
|
|
|
|
anonymous: "community",
|
|
|
|
|
"find-therapist": "community",
|
|
|
|
|
"blue-house": "community",
|
2025-10-13 14:41:51 +09:00
|
|
|
// 광고/제휴
|
|
|
|
|
"partner-contact": "ads-affiliates",
|
|
|
|
|
"partner-req": "ads-affiliates",
|
2025-10-13 07:23:08 +09:00
|
|
|
};
|
|
|
|
|
const categorySlug = mapBySlug[b.slug] || "community";
|
2025-10-13 06:29:33 +09:00
|
|
|
const category = categoryMap[categorySlug];
|
2025-10-09 11:23:27 +09:00
|
|
|
const board = await prisma.board.upsert({
|
|
|
|
|
where: { slug: b.slug },
|
|
|
|
|
update: {
|
|
|
|
|
description: b.description,
|
|
|
|
|
sortOrder: b.sortOrder,
|
|
|
|
|
type: b.type,
|
|
|
|
|
requiresApproval: !!b.requiresApproval,
|
|
|
|
|
allowAnonymousPost: !!b.allowAnonymousPost,
|
|
|
|
|
readLevel: b.readLevel || undefined,
|
2025-10-13 06:29:33 +09:00
|
|
|
categoryId: category ? category.id : undefined,
|
2025-10-09 11:23:27 +09:00
|
|
|
},
|
|
|
|
|
create: {
|
|
|
|
|
name: b.name,
|
|
|
|
|
slug: b.slug,
|
|
|
|
|
description: b.description,
|
|
|
|
|
sortOrder: b.sortOrder,
|
|
|
|
|
type: b.type,
|
|
|
|
|
requiresApproval: !!b.requiresApproval,
|
|
|
|
|
allowAnonymousPost: !!b.allowAnonymousPost,
|
|
|
|
|
readLevel: b.readLevel || undefined,
|
2025-10-13 06:29:33 +09:00
|
|
|
categoryId: category ? category.id : undefined,
|
2025-10-09 11:23:27 +09:00
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
created.push(board);
|
|
|
|
|
|
|
|
|
|
// 공지/운영 보드는 관리자 모더레이터 지정
|
|
|
|
|
if (["notice", "bug-report"].includes(board.slug)) {
|
|
|
|
|
await prisma.boardModerator.upsert({
|
|
|
|
|
where: {
|
|
|
|
|
boardId_userId: { boardId: board.id, userId: admin.userId },
|
|
|
|
|
},
|
|
|
|
|
update: {},
|
|
|
|
|
create: { boardId: board.id, userId: admin.userId, role: "MANAGER" },
|
|
|
|
|
});
|
2025-10-08 23:44:21 +09:00
|
|
|
}
|
2025-10-09 11:23:27 +09:00
|
|
|
}
|
|
|
|
|
return created;
|
|
|
|
|
}
|
2025-10-08 23:44:21 +09:00
|
|
|
|
2025-10-31 16:27:04 +09:00
|
|
|
async function upsertViewTypes() {
|
|
|
|
|
const viewTypes = [
|
|
|
|
|
// main scope
|
|
|
|
|
{ key: "main_default", name: "기본", scope: "main" },
|
|
|
|
|
{ key: "main_text", name: "텍스트", scope: "main" },
|
|
|
|
|
{ key: "main_preview", name: "미리보기", scope: "main" },
|
|
|
|
|
{ key: "main_special_rank", name: "특수랭킹", scope: "main" },
|
|
|
|
|
// list scope
|
|
|
|
|
{ key: "list_default", name: "기본", scope: "list" },
|
|
|
|
|
{ key: "list_text", name: "텍스트", scope: "list" },
|
|
|
|
|
{ key: "list_preview", name: "미리보기", scope: "list" },
|
|
|
|
|
{ key: "list_special_rank", name: "특수랭킹", scope: "list" },
|
|
|
|
|
];
|
|
|
|
|
for (const vt of viewTypes) {
|
|
|
|
|
await prisma.boardViewType.upsert({
|
|
|
|
|
where: { key: vt.key },
|
|
|
|
|
update: { name: vt.name, scope: vt.scope },
|
|
|
|
|
create: vt,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-09 11:23:27 +09:00
|
|
|
async function seedPolicies() {
|
|
|
|
|
// 금칙어 예시
|
|
|
|
|
const banned = [
|
|
|
|
|
{ pattern: "광고", appliesTo: "POST", severity: 1 },
|
|
|
|
|
{ pattern: "욕설", appliesTo: "COMMENT", severity: 2 },
|
|
|
|
|
{ pattern: "스팸", appliesTo: "POST", severity: 2 },
|
|
|
|
|
];
|
|
|
|
|
for (const k of banned) {
|
|
|
|
|
await prisma.bannedKeyword.upsert({
|
|
|
|
|
where: { pattern: k.pattern },
|
|
|
|
|
update: { appliesTo: k.appliesTo, severity: k.severity, active: true },
|
|
|
|
|
create: k,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 레벨 임계 예시
|
|
|
|
|
const levels = [
|
|
|
|
|
{ level: 1, minPoints: 0 },
|
|
|
|
|
{ level: 2, minPoints: 100 },
|
|
|
|
|
{ level: 3, minPoints: 300 },
|
|
|
|
|
];
|
|
|
|
|
for (const l of levels) {
|
|
|
|
|
await prisma.levelThreshold.upsert({
|
|
|
|
|
where: { level: l.level },
|
|
|
|
|
update: { minPoints: l.minPoints },
|
|
|
|
|
create: l,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function main() {
|
|
|
|
|
await upsertRoles();
|
|
|
|
|
const admin = await upsertAdmin();
|
2025-10-13 06:29:33 +09:00
|
|
|
const categoryMap = await upsertCategories();
|
2025-10-31 16:27:04 +09:00
|
|
|
await upsertViewTypes();
|
2025-10-13 06:29:33 +09:00
|
|
|
const boards = await upsertBoards(admin, categoryMap);
|
2025-10-09 11:23:27 +09:00
|
|
|
|
|
|
|
|
// 샘플 글 하나
|
|
|
|
|
const free = boards.find((b) => b.slug === "free") || boards[0];
|
2025-10-08 23:44:21 +09:00
|
|
|
const post = await prisma.post.create({
|
|
|
|
|
data: {
|
2025-10-09 11:23:27 +09:00
|
|
|
boardId: free.id,
|
|
|
|
|
authorId: admin.userId,
|
2025-10-08 23:44:21 +09:00
|
|
|
title: "첫 글",
|
2025-10-09 11:23:27 +09:00
|
|
|
content: "메시지 앱 초기 설정 완료",
|
|
|
|
|
status: "published",
|
|
|
|
|
},
|
2025-10-08 23:44:21 +09:00
|
|
|
});
|
|
|
|
|
await prisma.comment.createMany({
|
|
|
|
|
data: [
|
2025-10-09 11:23:27 +09:00
|
|
|
{ postId: post.id, authorId: admin.userId, content: "환영합니다!" },
|
|
|
|
|
{ postId: post.id, authorId: admin.userId, content: "댓글 테스트" },
|
|
|
|
|
],
|
2025-10-08 23:44:21 +09:00
|
|
|
});
|
2025-10-09 11:23:27 +09:00
|
|
|
|
|
|
|
|
await seedPolicies();
|
2025-10-09 17:38:46 +09:00
|
|
|
|
|
|
|
|
// 제휴업체 예시 데이터
|
|
|
|
|
const partners = [
|
|
|
|
|
{ name: "힐링마사지", category: "spa", latitude: 37.5665, longitude: 126.9780, address: "서울 중구" },
|
|
|
|
|
{ name: "웰빙테라피", category: "spa", latitude: 37.5700, longitude: 126.9769, address: "서울 종로구" },
|
|
|
|
|
{ name: "굿짐", category: "gym", latitude: 37.5600, longitude: 126.9820, address: "서울 중구" },
|
|
|
|
|
];
|
|
|
|
|
for (const p of partners) {
|
|
|
|
|
await prisma.partner.upsert({ where: { name: p.name }, update: p, create: p });
|
|
|
|
|
}
|
2025-10-08 23:04:12 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
main()
|
|
|
|
|
.catch((e) => {
|
|
|
|
|
console.error(e);
|
|
|
|
|
process.exit(1);
|
|
|
|
|
})
|
|
|
|
|
.finally(async () => {
|
|
|
|
|
await prisma.$disconnect();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|