From c5bc8f5b49e0804349e0a50676efd0f4721ab713 Mon Sep 17 00:00:00 2001 From: koreacomp5 Date: Mon, 10 Nov 2025 21:58:36 +0900 Subject: [PATCH] check --- src/app/api/auth/session/route.ts | 16 ++++++++++++++-- src/app/api/boards/route.ts | 2 +- src/app/boards/[id]/page.tsx | 6 +++++- src/app/components/BoardPanelClient.tsx | 19 ++++++++++++++++++- src/app/page.tsx | 18 +++++++++++++++--- src/middleware.ts | 9 +++++++-- 6 files changed, 60 insertions(+), 10 deletions(-) diff --git a/src/app/api/auth/session/route.ts b/src/app/api/auth/session/route.ts index 534fbee..ac07ed9 100644 --- a/src/app/api/auth/session/route.ts +++ b/src/app/api/auth/session/route.ts @@ -61,15 +61,27 @@ export async function POST(req: Request) { }); isAdmin = !!hasAdmin; } + // 추가 안전장치: 사용자 레코드의 authLevel이 ADMIN이면 관리자 취급 + if (!isAdmin && user.authLevel === "ADMIN") { + isAdmin = true; + } const res = NextResponse.json({ ok: true, user: { userId: user.userId, nickname: user.nickname } }); + // HTTPS 요청에서만 Secure 속성 부여 (HTTP 환경에서는 생략하여 로컬 start에서도 동작) + let secureAttr = ""; + try { + const isHttps = new URL(req.url).protocol === "https:"; + secureAttr = isHttps ? "; Secure" : ""; + } catch { + secureAttr = ""; + } res.headers.append( "Set-Cookie", - `uid=${encodeURIComponent(user.userId)}; Path=/; HttpOnly; SameSite=Lax` + `uid=${encodeURIComponent(user.userId)}; Path=/; HttpOnly; SameSite=Lax${secureAttr}` ); res.headers.append( "Set-Cookie", - `isAdmin=${isAdmin ? "1" : "0"}; Path=/; HttpOnly; SameSite=Lax` + `isAdmin=${isAdmin ? "1" : "0"}; Path=/; HttpOnly; SameSite=Lax${secureAttr}` ); return res; } diff --git a/src/app/api/boards/route.ts b/src/app/api/boards/route.ts index 64bfb48..783292b 100644 --- a/src/app/api/boards/route.ts +++ b/src/app/api/boards/route.ts @@ -4,7 +4,7 @@ import prisma from "@/lib/prisma"; export async function GET(req: Request) { const { searchParams } = new URL(req.url); const category = searchParams.get("category"); // slug or id - const where: any = {}; + const where: any = { status: "active" }; if (category) { if (category.length === 25 || category.length === 24) { where.categoryId = category; diff --git a/src/app/boards/[id]/page.tsx b/src/app/boards/[id]/page.tsx index d044ed7..5c1933d 100644 --- a/src/app/boards/[id]/page.tsx +++ b/src/app/boards/[id]/page.tsx @@ -4,6 +4,7 @@ import Link from "next/link"; import { BoardToolbar } from "@/app/components/BoardToolbar"; import { headers } from "next/headers"; import prisma from "@/lib/prisma"; +import { notFound } from "next/navigation"; import { UserAvatar } from "@/app/components/UserAvatar"; import { RankIcon1st } from "@/app/components/RankIcon1st"; import { RankIcon2nd } from "@/app/components/RankIcon2nd"; @@ -33,7 +34,10 @@ export default async function BoardDetail({ params, searchParams }: { params: an 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.slug === idOrSlug || b.id === idOrSlug); - const id = board?.id as string; + if (!board) { + return notFound(); + } + 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 ?? ""; // 메인배너 표시 설정 diff --git a/src/app/components/BoardPanelClient.tsx b/src/app/components/BoardPanelClient.tsx index 022e18c..9a30340 100644 --- a/src/app/components/BoardPanelClient.tsx +++ b/src/app/components/BoardPanelClient.tsx @@ -1,6 +1,6 @@ "use client"; -import { useState } from "react"; +import { useEffect, useState } from "react"; import Link from "next/link"; import { RankIcon1st } from "./RankIcon1st"; import { RankIcon2nd } from "./RankIcon2nd"; @@ -52,6 +52,23 @@ export function BoardPanelClient({ }) { const [selectedBoardId, setSelectedBoardId] = useState(initialBoardId); + // 데이터가 비어있을 때 안전 처리 + if (!boardsData || boardsData.length === 0) { + return ( +
+ 선택된 게시판이 없습니다. +
+ ); + } + + // 선택된 보드가 목록에 없으면 첫 번째 보드로 동기화 + useEffect(() => { + const exists = boardsData.some((bd) => bd.board.id === selectedBoardId); + if (!exists) { + setSelectedBoardId(boardsData[0].board.id); + } + }, [boardsData, selectedBoardId]); + // 선택된 게시판 데이터 찾기 const selectedBoardData = boardsData.find(bd => bd.board.id === selectedBoardId) || boardsData[0]; const { board, categoryName, siblingBoards } = selectedBoardData; diff --git a/src/app/page.tsx b/src/app/page.tsx index f6a1f13..cb12fef 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -77,7 +77,7 @@ export default async function Home({ searchParams }: { searchParams: Promise<{ s // 보드 메타데이터 (메인뷰 타입 포함) const boardsMeta = visibleBoardIds.length ? await prisma.board.findMany({ - where: { id: { in: visibleBoardIds } }, + where: { id: { in: visibleBoardIds }, status: "active" }, select: { id: true, name: true, @@ -116,14 +116,26 @@ export default async function Home({ searchParams }: { searchParams: Promise<{ s // 게시판 패널 데이터 수집 함수 (모든 sibling boards 포함) const prepareBoardPanelData = async (board: { id: string; name: string; slug: string; categoryId: string | null; categoryName: string; mainTypeKey?: string }) => { // 같은 카테고리의 소분류(게시판) 탭 목록 - const siblingBoards = board.categoryId + let siblingBoards: any[] = board.categoryId ? await prisma.board.findMany({ - where: { categoryId: board.categoryId }, + where: { categoryId: board.categoryId, status: "active" }, select: { id: true, name: true, slug: true, mainPageViewType: { select: { key: true } } }, orderBy: [{ sortOrder: "asc" }, { name: "asc" }], }) : []; + // 카테고리가 없거나 동료 게시판이 없을 때, 선택된 보드 단독 구성으로 대체 + if (siblingBoards.length === 0) { + siblingBoards = [ + { + id: board.id, + name: board.name, + slug: board.slug, + mainPageViewType: { key: board.mainTypeKey }, + }, + ]; + } + const boardsData = []; for (const sb of siblingBoards) { diff --git a/src/middleware.ts b/src/middleware.ts index 8a9dd54..08ebe9e 100644 --- a/src/middleware.ts +++ b/src/middleware.ts @@ -17,7 +17,8 @@ export async function middleware(req: NextRequest) { } // uid 쿠키가 있어도 실제 유저가 존재하지 않으면 비로그인으로 간주하고 쿠키 정리 - let authenticated = false; + // 기본값은 쿠키 존재 여부 기준(네트워크 오류 시 과도한 로그아웃 방지) + let authenticated = !!uid; if (uid) { try { const verifyUrl = new URL("/api/auth/session", req.url); @@ -30,9 +31,13 @@ export async function middleware(req: NextRequest) { if (verifyRes.ok) { const data = await verifyRes.json().catch(() => ({ ok: false })); authenticated = !!data?.ok; + } else { + // 검증 API가 200이 아니면 기존 판단 유지(일시적 오류 대비) + authenticated = !!uid; } } catch { - authenticated = false; + // 네트워크/런타임 오류 시 기존 판단 유지(쿠키 있으면 로그인 간주) + authenticated = !!uid; } // 유효하지 않은 uid 쿠키는 즉시 제거 if (!authenticated) {