diff --git a/prisma/migrations/20251012204802_add_category/migration.sql b/prisma/migrations/20251012204802_add_category/migration.sql
new file mode 100644
index 0000000..42c728d
--- /dev/null
+++ b/prisma/migrations/20251012204802_add_category/migration.sql
@@ -0,0 +1,62 @@
+/*
+ Warnings:
+
+ - A unique constraint covering the columns `[name]` on the table `partners` will be added. If there are existing duplicate values, this will fail.
+
+*/
+-- CreateTable
+CREATE TABLE "board_categories" (
+ "id" TEXT NOT NULL PRIMARY KEY,
+ "name" TEXT NOT NULL,
+ "slug" TEXT NOT NULL,
+ "sortOrder" INTEGER NOT NULL DEFAULT 0,
+ "status" TEXT NOT NULL DEFAULT 'active',
+ "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ "updatedAt" DATETIME NOT NULL
+);
+
+-- RedefineTables
+PRAGMA defer_foreign_keys=ON;
+PRAGMA foreign_keys=OFF;
+CREATE TABLE "new_boards" (
+ "id" TEXT NOT NULL PRIMARY KEY,
+ "name" TEXT NOT NULL,
+ "slug" TEXT NOT NULL,
+ "description" TEXT,
+ "sortOrder" INTEGER NOT NULL DEFAULT 0,
+ "status" TEXT NOT NULL DEFAULT 'active',
+ "type" TEXT NOT NULL DEFAULT 'general',
+ "requiresApproval" BOOLEAN NOT NULL DEFAULT false,
+ "allowAnonymousPost" BOOLEAN NOT NULL DEFAULT false,
+ "allowSecretComment" BOOLEAN NOT NULL DEFAULT false,
+ "isAdultOnly" BOOLEAN NOT NULL DEFAULT false,
+ "requiredTags" JSONB,
+ "requiredFields" JSONB,
+ "readLevel" TEXT NOT NULL DEFAULT 'public',
+ "writeLevel" TEXT NOT NULL DEFAULT 'member',
+ "categoryId" TEXT,
+ "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ "updatedAt" DATETIME NOT NULL,
+ CONSTRAINT "boards_categoryId_fkey" FOREIGN KEY ("categoryId") REFERENCES "board_categories" ("id") ON DELETE SET NULL ON UPDATE CASCADE
+);
+INSERT INTO "new_boards" ("allowAnonymousPost", "allowSecretComment", "createdAt", "description", "id", "isAdultOnly", "name", "readLevel", "requiredFields", "requiredTags", "requiresApproval", "slug", "sortOrder", "status", "type", "updatedAt", "writeLevel") SELECT "allowAnonymousPost", "allowSecretComment", "createdAt", "description", "id", "isAdultOnly", "name", "readLevel", "requiredFields", "requiredTags", "requiresApproval", "slug", "sortOrder", "status", "type", "updatedAt", "writeLevel" FROM "boards";
+DROP TABLE "boards";
+ALTER TABLE "new_boards" RENAME TO "boards";
+CREATE UNIQUE INDEX "boards_slug_key" ON "boards"("slug");
+CREATE INDEX "boards_status_sortOrder_idx" ON "boards"("status", "sortOrder");
+CREATE INDEX "boards_type_requiresApproval_idx" ON "boards"("type", "requiresApproval");
+CREATE INDEX "boards_categoryId_idx" ON "boards"("categoryId");
+PRAGMA foreign_keys=ON;
+PRAGMA defer_foreign_keys=OFF;
+
+-- CreateIndex
+CREATE UNIQUE INDEX "board_categories_name_key" ON "board_categories"("name");
+
+-- CreateIndex
+CREATE UNIQUE INDEX "board_categories_slug_key" ON "board_categories"("slug");
+
+-- CreateIndex
+CREATE INDEX "board_categories_status_sortOrder_idx" ON "board_categories"("status", "sortOrder");
+
+-- CreateIndex
+CREATE UNIQUE INDEX "partners_name_key" ON "partners"("name");
diff --git a/prisma/schema.prisma b/prisma/schema.prisma
index 57976d0..15358be 100644
--- a/prisma/schema.prisma
+++ b/prisma/schema.prisma
@@ -101,6 +101,23 @@ enum Action {
// ==== Models ====
+// 게시판 대분류
+model BoardCategory {
+ id String @id @default(cuid())
+ name String @unique
+ slug String @unique
+ sortOrder Int @default(0)
+ status String @default("active")
+
+ boards Board[]
+
+ createdAt DateTime @default(now())
+ updatedAt DateTime @updatedAt
+
+ @@index([status, sortOrder])
+ @@map("board_categories")
+}
+
// 게시판 정의(관리자 생성/정렬)
model Board {
id String @id @default(cuid())
@@ -120,6 +137,10 @@ model Board {
readLevel AccessLevel @default(public) // 읽기 권한
writeLevel AccessLevel @default(member) // 글쓰기 권한
+ // 대분류
+ categoryId String?
+ category BoardCategory? @relation(fields: [categoryId], references: [id], onDelete: SetNull)
+
posts Post[]
moderators BoardModerator[]
@@ -128,6 +149,7 @@ model Board {
@@index([status, sortOrder])
@@index([type, requiresApproval])
+ @@index([categoryId])
@@map("boards")
}
diff --git a/prisma/seed.js b/prisma/seed.js
index cee5d47..27defae 100644
--- a/prisma/seed.js
+++ b/prisma/seed.js
@@ -2,6 +2,25 @@ const { PrismaClient } = require("@prisma/client");
const prisma = new PrismaClient();
+async function upsertCategories() {
+ const categories = [
+ { name: "운영", slug: "ops", sortOrder: 1, status: "active" },
+ { name: "커뮤니티", slug: "community", sortOrder: 2, status: "active" },
+ { name: "특수", slug: "special", sortOrder: 3, status: "active" },
+ { name: "제휴", slug: "partners", sortOrder: 4, status: "active" },
+ ];
+ 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 }
+}
+
async function upsertRoles() {
const roles = [
{ name: "admin", description: "관리자" },
@@ -85,7 +104,7 @@ async function upsertAdmin() {
return admin;
}
-async function upsertBoards(admin) {
+async function upsertBoards(admin, categoryMap) {
const boards = [
// 일반
{ name: "공지사항", slug: "notice", description: "공지", type: "general", sortOrder: 1, writeLevel: "moderator" },
@@ -111,6 +130,13 @@ async function upsertBoards(admin) {
const created = [];
for (const b of boards) {
+ // 카테고리 매핑 규칙
+ let categorySlug = "community";
+ if (["notice", "bug-report"].includes(b.slug)) categorySlug = "ops";
+ if (["attendance", "nearby-partners", "ranking", "free-coupons", "monthly-stats"].includes(b.slug)) categorySlug = "special";
+ if (["partners-photos"].includes(b.slug)) categorySlug = "partners";
+
+ const category = categoryMap[categorySlug];
const board = await prisma.board.upsert({
where: { slug: b.slug },
update: {
@@ -120,6 +146,7 @@ async function upsertBoards(admin) {
requiresApproval: !!b.requiresApproval,
allowAnonymousPost: !!b.allowAnonymousPost,
readLevel: b.readLevel || undefined,
+ categoryId: category ? category.id : undefined,
},
create: {
name: b.name,
@@ -130,6 +157,7 @@ async function upsertBoards(admin) {
requiresApproval: !!b.requiresApproval,
allowAnonymousPost: !!b.allowAnonymousPost,
readLevel: b.readLevel || undefined,
+ categoryId: category ? category.id : undefined,
},
});
created.push(board);
@@ -181,7 +209,8 @@ async function seedPolicies() {
async function main() {
await upsertRoles();
const admin = await upsertAdmin();
- const boards = await upsertBoards(admin);
+ const categoryMap = await upsertCategories();
+ const boards = await upsertBoards(admin, categoryMap);
// 샘플 글 하나
const free = boards.find((b) => b.slug === "free") || boards[0];
diff --git a/public/erd.svg b/public/erd.svg
index ff55d8f..93d7cbd 100644
--- a/public/erd.svg
+++ b/public/erd.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file