Files
msgapp/src/app/api/posts/route.ts

112 lines
3.3 KiB
TypeScript
Raw Normal View History

2025-10-09 11:23:27 +09:00
import { NextResponse } from "next/server";
import prisma from "@/lib/prisma";
import { z } from "zod";
const createPostSchema = z.object({
boardId: z.string().min(1),
authorId: z.string().optional(),
title: z.string().min(1),
content: z.string().min(1),
isAnonymous: z.boolean().optional(),
});
export async function POST(req: Request) {
const body = await req.json();
const parsed = createPostSchema.safeParse(body);
if (!parsed.success) {
return NextResponse.json({ error: parsed.error.flatten() }, { status: 400 });
}
const { boardId, authorId, title, content, isAnonymous } = parsed.data;
2025-10-09 14:18:55 +09:00
const board = await prisma.board.findUnique({ where: { id: boardId } });
const requiresApproval = board?.requiresApproval ?? false;
2025-10-09 11:23:27 +09:00
const post = await prisma.post.create({
2025-10-09 14:18:55 +09:00
data: {
boardId,
authorId: authorId ?? null,
title,
content,
isAnonymous: !!isAnonymous,
status: requiresApproval ? "hidden" : "published",
},
2025-10-09 11:23:27 +09:00
});
return NextResponse.json({ post }, { status: 201 });
}
2025-10-09 14:18:55 +09:00
const listQuerySchema = z.object({
page: z.coerce.number().min(1).default(1),
pageSize: z.coerce.number().min(1).max(100).default(10),
boardId: z.string().optional(),
q: z.string().optional(),
sort: z.enum(["recent", "popular"]).default("recent").optional(),
tag: z.string().optional(), // Tag.slug
author: z.string().optional(), // User.nickname contains
start: z.coerce.date().optional(), // createdAt >= start
end: z.coerce.date().optional(), // createdAt <= end
2025-10-09 14:18:55 +09:00
});
export async function GET(req: Request) {
const { searchParams } = new URL(req.url);
const parsed = listQuerySchema.safeParse(Object.fromEntries(searchParams));
if (!parsed.success) {
return NextResponse.json({ error: parsed.error.flatten() }, { status: 400 });
}
const { page, pageSize, boardId, q, sort = "recent", tag, author, start, end } = parsed.data;
2025-10-09 14:18:55 +09:00
const where = {
NOT: { status: "deleted" as const },
2025-10-09 14:18:55 +09:00
...(boardId ? { boardId } : {}),
...(q
? {
OR: [
{ title: { contains: q } },
{ content: { contains: q } },
],
}
: {}),
...(tag
? {
postTags: { some: { tag: { slug: tag } } },
}
: {}),
...(author
? {
author: { nickname: { contains: author } },
}
: {}),
...(start || end
? {
createdAt: {
...(start ? { gte: start } : {}),
...(end ? { lte: end } : {}),
},
}
: {}),
2025-10-09 14:18:55 +09:00
} as const;
const [total, items] = await Promise.all([
prisma.post.count({ where }),
prisma.post.findMany({
where,
orderBy:
sort === "popular"
? [{ isPinned: "desc" }, { stat: { recommendCount: "desc" } }, { createdAt: "desc" }]
: [{ isPinned: "desc" }, { createdAt: "desc" }],
2025-10-09 14:18:55 +09:00
skip: (page - 1) * pageSize,
take: pageSize,
select: {
id: true,
title: true,
createdAt: true,
boardId: true,
isPinned: true,
status: true,
stat: { select: { recommendCount: true, views: true, commentsCount: true } },
postTags: { select: { tag: { select: { name: true, slug: true } } } },
2025-10-09 14:18:55 +09:00
},
}),
]);
return NextResponse.json({ total, page, pageSize, items, sort });
2025-10-09 14:18:55 +09:00
}
2025-10-09 11:23:27 +09:00