diff --git a/prisma/seed.js b/prisma/seed.js index e1fb93d..cfe427c 100644 --- a/prisma/seed.js +++ b/prisma/seed.js @@ -105,6 +105,8 @@ async function upsertBoards(admin) { { 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 }, + // 제휴업소 일반(사진) + { name: "제휴업소 일반(사진)", slug: "partners-photos", description: "사진 전용 게시판", type: "general", sortOrder: 17, requiredFields: { imageOnly: true, minImages: 1, maxImages: 10 } }, ]; const created = []; diff --git a/src/app/api/posts/route.ts b/src/app/api/posts/route.ts index 715351d..0303448 100644 --- a/src/app/api/posts/route.ts +++ b/src/app/api/posts/route.ts @@ -19,6 +19,15 @@ 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; + if (isImageOnly || minImages > 0) { + const imageLinks = (content.match(/!\[[^\]]*\]\([^\)]+\)/g) ?? []).length; + if (imageLinks < (minImages || 1)) { + return NextResponse.json({ error: { message: `이미지 최소 ${minImages || 1}개 필요` } }, { status: 400 }); + } + } const post = await prisma.post.create({ data: { boardId, diff --git a/src/app/components/UploadButton.tsx b/src/app/components/UploadButton.tsx index a726e83..e3b63e4 100644 --- a/src/app/components/UploadButton.tsx +++ b/src/app/components/UploadButton.tsx @@ -2,20 +2,22 @@ import { useState } from "react"; import { useToast } from "@/app/components/ui/ToastProvider"; -export function UploadButton({ onUploaded }: { onUploaded: (url: string) => void }) { +export function UploadButton({ onUploaded, multiple = false }: { onUploaded: (url: string) => void; multiple?: boolean }) { const { show } = useToast(); const [loading, setLoading] = useState(false); async function onChange(e: React.ChangeEvent) { - const f = e.target.files?.[0]; - if (!f) return; - const fd = new FormData(); - fd.append("file", f); + const files = Array.from(e.target.files ?? []); + if (files.length === 0) return; try { setLoading(true); - const r = await fetch("/api/uploads", { method: "POST", body: fd }); - const data = await r.json(); - if (!r.ok) throw new Error(JSON.stringify(data)); - onUploaded(data.url); + for (const f of files) { + const fd = new FormData(); + fd.append("file", f); + const r = await fetch("/api/uploads", { method: "POST", body: fd }); + const data = await r.json(); + if (!r.ok) throw new Error(JSON.stringify(data)); + onUploaded(data.url); + } show("업로드 완료"); } catch (e) { show("업로드 실패"); @@ -26,7 +28,7 @@ export function UploadButton({ onUploaded }: { onUploaded: (url: string) => void } return ; } diff --git a/src/app/posts/new/page.tsx b/src/app/posts/new/page.tsx index 2e2ce87..fe58ae5 100644 --- a/src/app/posts/new/page.tsx +++ b/src/app/posts/new/page.tsx @@ -33,7 +33,7 @@ export default function NewPostPage({ searchParams }: { searchParams?: { boardId setForm({ ...form, boardId: e.target.value })} /> setForm({ ...form, title: e.target.value })} />