diff --git a/prisma/schema.prisma b/prisma/schema.prisma index d511eb0..7f71d45 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -124,7 +124,6 @@ model Board { description String? sortOrder Int @default(0) status BoardStatus @default(active) - requiresApproval Boolean @default(false) // 게시물 승인 필요 여부 allowAnonymousPost Boolean @default(false) // 익명 글 허용 allowSecretComment Boolean @default(false) // 비밀댓글 허용 isAdultOnly Boolean @default(false) // 성인 인증 필요 여부 diff --git a/prisma/seed.js b/prisma/seed.js index 9e3f464..a2dd809 100644 --- a/prisma/seed.js +++ b/prisma/seed.js @@ -200,6 +200,10 @@ async function upsertBoards(admin, categoryMap) { ]; const created = []; + // 특수 랭킹 뷰 타입 ID 조회 (사전에 upsertViewTypes로 생성됨) + const mainSpecial = await prisma.boardViewType.findUnique({ where: { key: "main_special_rank" } }); + const listSpecial = await prisma.boardViewType.findUnique({ where: { key: "list_special_rank" } }); + for (const b of boards) { // 카테고리 매핑 규칙 (트리 기준 상위 카테고리) const mapBySlug = { @@ -227,20 +231,22 @@ async function upsertBoards(admin, categoryMap) { update: { description: b.description, sortOrder: b.sortOrder, - requiresApproval: !!b.requiresApproval, allowAnonymousPost: !!b.allowAnonymousPost, readLevel: b.readLevel || undefined, categoryId: category ? category.id : undefined, + ...(b.slug === "ranking" && mainSpecial ? { mainPageViewTypeId: mainSpecial.id } : {}), + ...(b.slug === "ranking" && listSpecial ? { listViewTypeId: listSpecial.id } : {}), }, create: { name: b.name, slug: b.slug, description: b.description, sortOrder: b.sortOrder, - requiresApproval: !!b.requiresApproval, allowAnonymousPost: !!b.allowAnonymousPost, readLevel: b.readLevel || undefined, categoryId: category ? category.id : undefined, + ...(b.slug === "ranking" && mainSpecial ? { mainPageViewTypeId: mainSpecial.id } : {}), + ...(b.slug === "ranking" && listSpecial ? { listViewTypeId: listSpecial.id } : {}), }, }); created.push(board); diff --git a/public/uploads/1762025382776-83vifeqk7rk.webp b/public/uploads/1762025382776-83vifeqk7rk.webp new file mode 100644 index 0000000..8373104 Binary files /dev/null and b/public/uploads/1762025382776-83vifeqk7rk.webp differ diff --git a/src/app/admin/boards/page.tsx b/src/app/admin/boards/page.tsx index e0f2a8f..f404a38 100644 --- a/src/app/admin/boards/page.tsx +++ b/src/app/admin/boards/page.tsx @@ -368,7 +368,6 @@ export default function AdminBoardsPage() { 쓰기 익명 비밀댓 - 승인 성인 대분류 이동 활성 @@ -501,7 +500,6 @@ function BoardRowCells({ b, onDirty, onDelete, allowMove, categories, onMove, ma { const v = { ...edit, allowAnonymousPost: e.target.checked }; setEdit(v); onDirty(b.id, v); }} /> { const v = { ...edit, allowSecretComment: e.target.checked }; setEdit(v); onDirty(b.id, v); }} /> - { const v = { ...edit, requiresApproval: e.target.checked }; setEdit(v); onDirty(b.id, v); }} /> { const v = { ...edit, isAdultOnly: e.target.checked }; setEdit(v); onDirty(b.id, v); }} /> {allowMove && categories && onMove ? ( diff --git a/src/app/api/admin/boards/[id]/route.ts b/src/app/api/admin/boards/[id]/route.ts index 309dd72..deb1dcf 100644 --- a/src/app/api/admin/boards/[id]/route.ts +++ b/src/app/api/admin/boards/[id]/route.ts @@ -5,7 +5,7 @@ export async function PATCH(req: Request, context: { params: Promise<{ id: strin const { id } = await context.params; const body = await req.json().catch(() => ({})); const data: any = {}; - for (const k of ["name", "slug", "description", "sortOrder", "readLevel", "writeLevel", "allowAnonymousPost", "allowSecretComment", "requiresApproval", "status", "isAdultOnly", "categoryId", "mainPageViewTypeId", "listViewTypeId"]) { + for (const k of ["name", "slug", "description", "sortOrder", "readLevel", "writeLevel", "allowAnonymousPost", "allowSecretComment", "status", "isAdultOnly", "categoryId", "mainPageViewTypeId", "listViewTypeId"]) { if (k in body) data[k] = body[k]; } if ("requiredTags" in body) { diff --git a/src/app/api/admin/boards/route.ts b/src/app/api/admin/boards/route.ts index d5c24bb..0cf87fa 100644 --- a/src/app/api/admin/boards/route.ts +++ b/src/app/api/admin/boards/route.ts @@ -16,7 +16,6 @@ export async function GET() { writeLevel: true, allowAnonymousPost: true, allowSecretComment: true, - requiresApproval: true, status: true, categoryId: true, mainPageViewTypeId: true, @@ -36,7 +35,6 @@ const createSchema = z.object({ writeLevel: z.string().optional(), allowAnonymousPost: z.boolean().optional(), allowSecretComment: z.boolean().optional(), - requiresApproval: z.boolean().optional(), status: z.string().optional(), isAdultOnly: z.boolean().optional(), categoryId: z.string().nullable().optional(), diff --git a/src/app/api/boards/route.ts b/src/app/api/boards/route.ts index d3e871c..64bfb48 100644 --- a/src/app/api/boards/route.ts +++ b/src/app/api/boards/route.ts @@ -20,7 +20,6 @@ export async function GET(req: Request) { name: true, slug: true, description: true, - requiresApproval: true, allowAnonymousPost: true, isAdultOnly: true, category: { select: { id: true, name: true, slug: true } }, diff --git a/src/app/api/categories/route.ts b/src/app/api/categories/route.ts index ae379f0..36e4721 100644 --- a/src/app/api/categories/route.ts +++ b/src/app/api/categories/route.ts @@ -10,7 +10,7 @@ export async function GET() { boards: { where: { status: "active" }, orderBy: [{ sortOrder: "asc" }, { createdAt: "asc" }], - select: { id: true, name: true, slug: true, requiresApproval: true }, + select: { id: true, name: true, slug: true }, }, }, }); diff --git a/src/app/api/posts/route.ts b/src/app/api/posts/route.ts index 97230ef..139d97a 100644 --- a/src/app/api/posts/route.ts +++ b/src/app/api/posts/route.ts @@ -18,7 +18,6 @@ export async function POST(req: Request) { } const { boardId, authorId, title, content, isAnonymous } = parsed.data; const board = await prisma.board.findUnique({ where: { id: boardId } }); - const requiresApproval = board?.requiresApproval ?? false; // 사진형 보드 필수 이미지 검증: content 내 이미지 링크 최소 1개 const isImageOnly = (board?.requiredFields as any)?.imageOnly; const minImages = (board?.requiredFields as any)?.minImages ?? 0; @@ -35,7 +34,7 @@ export async function POST(req: Request) { title, content, isAnonymous: !!isAnonymous, - status: requiresApproval ? "hidden" : "published", + status: "published", }, }); return NextResponse.json({ post }, { status: 201 }); diff --git a/src/app/boards/[id]/page.tsx b/src/app/boards/[id]/page.tsx index 8f43d0a..b02a84b 100644 --- a/src/app/boards/[id]/page.tsx +++ b/src/app/boards/[id]/page.tsx @@ -7,7 +7,7 @@ import { headers } from "next/headers"; export default async function BoardDetail({ params, searchParams }: { params: any; searchParams: any }) { const p = params?.then ? await params : params; const sp = searchParams?.then ? await searchParams : searchParams; - const id = p.id as string; + const idOrSlug = p.id as string; const sort = (sp?.sort as "recent" | "popular" | undefined) ?? "recent"; // 보드 slug 조회 (새 글 페이지 프리셋 전달) const h = await headers(); @@ -16,7 +16,8 @@ export default async function BoardDetail({ params, searchParams }: { params: an const base = process.env.NEXT_PUBLIC_BASE_URL || `${proto}://${host}`; const res = await fetch(new URL("/api/boards", base).toString(), { cache: "no-store" }); const { boards } = await res.json(); - const board = (boards || []).find((b: any) => b.id === id); + const board = (boards || []).find((b: any) => b.slug === idOrSlug || b.id === idOrSlug); + const id = board?.id as string; const siblingBoards = (boards || []).filter((b: any) => b.category?.id && b.category.id === board?.category?.id); const categoryName = board?.category?.name ?? ""; return ( @@ -24,14 +25,14 @@ export default async function BoardDetail({ params, searchParams }: { params: an {/* 상단 배너 (서브카테고리 표시) */}
({ id: b.id, name: b.name, href: `/boards/${b.id}` }))} + subItems={siblingBoards.map((b: any) => ({ id: b.id, name: b.name, href: `/boards/${b.slug}` }))} activeSubId={id} />
{/* 검색/필터 툴바 + 리스트 */}
- +
게시판
    {boards?.map((b: any) => ( -
  • {b.name}
  • +
  • {b.name}
  • ))}
diff --git a/src/app/components/AppHeader.tsx b/src/app/components/AppHeader.tsx index 65944f4..845af95 100644 --- a/src/app/components/AppHeader.tsx +++ b/src/app/components/AppHeader.tsx @@ -35,7 +35,7 @@ export function AppHeader() { }, [pathname]); const activeCategorySlug = React.useMemo(() => { if (activeBoardId) { - const found = categories.find((c) => c.boards.some((b) => b.id === activeBoardId)); + const found = categories.find((c) => c.boards.some((b) => b.slug === activeBoardId)); return found?.slug ?? null; } if (pathname === "/boards") { @@ -337,7 +337,7 @@ export function AppHeader() { style={idx === categories.length - 1 ? { minWidth: 120 } : undefined} > ( {b.name} @@ -416,7 +416,7 @@ export function AppHeader() {
{cat.name}
{cat.boards.map((b) => ( - setMobileOpen(false)} className="rounded px-2 py-1 text-neutral-700 hover:bg-neutral-100 hover:text-neutral-900"> + setMobileOpen(false)} className="rounded px-2 py-1 text-neutral-700 hover:bg-neutral-100 hover:text-neutral-900"> {b.name} ))} diff --git a/src/app/components/CategoryBoardBrowser.tsx b/src/app/components/CategoryBoardBrowser.tsx index a3d959f..28c85b2 100644 --- a/src/app/components/CategoryBoardBrowser.tsx +++ b/src/app/components/CategoryBoardBrowser.tsx @@ -7,7 +7,7 @@ type ApiCategory = { id: string; name: string; slug: string; - boards: { id: string; name: string; slug: string; requiresApproval: boolean }[]; + boards: { id: string; name: string; slug: string }[]; }; type PostItem = { @@ -101,7 +101,7 @@ export default function CategoryBoardBrowser({ categoryName, categorySlug }: Pro className="shrink-0 text-lg md:text-xl font-bold text-neutral-800 truncate" onClick={() => { const first = selectedCategory?.boards?.[0]; - if (first?.id) router.push(`/boards/${first.id}`); + if (first?.slug) router.push(`/boards/${first.slug}`); }} title={(selectedCategory?.name ?? categoryName ?? "").toString()} > @@ -113,7 +113,7 @@ export default function CategoryBoardBrowser({ categoryName, categorySlug }: Pro className="shrink-0 w-6 h-6 rounded-full border border-neutral-300 text-neutral-500 hover:bg-neutral-50 flex items-center justify-center" onClick={() => { const first = selectedCategory?.boards?.[0]; - if (first?.id) router.push(`/boards/${first.id}`); + if (first?.slug) router.push(`/boards/${first.slug}`); }} >