93499867d5
- IdeaCategory 扩展 7→9,新增 experience(体验)和 nature(自然) - 替换 outdoor boolean 为 costLevel/intensity/needsBooking 三个高价值字段 - AI 标注 prompt 同步更新,行程规划新增强度交替、费用平衡、预约提醒原则 - selectIdeasForSlots 重写为四优先级:timeSlot+category > category > timeSlot > 任意 - 前端想法卡片展示费用/强度/预约标签
142 lines
4.1 KiB
TypeScript
142 lines
4.1 KiB
TypeScript
import { NextResponse } from "next/server";
|
|
import { prisma } from "@/lib/prisma";
|
|
import { requireMembership } from "@/lib/blindbox";
|
|
import { apiHandler, ApiError, requireUserId } from "@/lib/api";
|
|
import { validateIdeaContent, requireString } from "@/lib/validation";
|
|
import { tagIdea } from "@/lib/ai";
|
|
|
|
const TAG_TIMEOUT_MS = 3000;
|
|
|
|
export const POST = apiHandler(async (req) => {
|
|
const { roomId, userId, content } = await req.json();
|
|
|
|
requireUserId(userId);
|
|
requireString(roomId, "roomId");
|
|
const trimmedContent = validateIdeaContent(content);
|
|
|
|
await requireMembership(roomId, userId);
|
|
|
|
const idea = await prisma.blindBoxIdea.create({
|
|
data: { roomId, userId, content: trimmedContent },
|
|
});
|
|
|
|
const tags = await Promise.race([
|
|
tagIdea(trimmedContent),
|
|
new Promise<null>((resolve) => setTimeout(() => resolve(null), TAG_TIMEOUT_MS)),
|
|
]);
|
|
|
|
if (tags) {
|
|
await prisma.blindBoxIdea.update({
|
|
where: { id: idea.id },
|
|
data: {
|
|
category: tags.category,
|
|
timeSlot: tags.timeSlot,
|
|
estimatedMinutes: tags.estimatedMinutes,
|
|
costLevel: tags.costLevel,
|
|
intensity: tags.intensity,
|
|
needsBooking: tags.needsBooking,
|
|
searchQuery: tags.searchQuery,
|
|
searchType: tags.searchType,
|
|
},
|
|
});
|
|
}
|
|
|
|
return NextResponse.json(
|
|
{ id: idea.id, ...tags && { tags } },
|
|
{ status: 201 },
|
|
);
|
|
});
|
|
|
|
export const GET = apiHandler(async (req) => {
|
|
const userId = requireUserId(req.nextUrl.searchParams.get("userId"));
|
|
const roomId = requireString(req.nextUrl.searchParams.get("roomId"), "roomId");
|
|
|
|
await requireMembership(roomId, userId);
|
|
|
|
const [poolCount, myIdeas, drawn] = await Promise.all([
|
|
prisma.blindBoxIdea.count({
|
|
where: { roomId, status: "in_pool" },
|
|
}),
|
|
prisma.blindBoxIdea.findMany({
|
|
where: { roomId, userId, status: "in_pool" },
|
|
orderBy: { createdAt: "desc" },
|
|
select: {
|
|
id: true,
|
|
content: true,
|
|
createdAt: true,
|
|
category: true,
|
|
timeSlot: true,
|
|
estimatedMinutes: true,
|
|
costLevel: true,
|
|
intensity: true,
|
|
needsBooking: true,
|
|
searchQuery: true,
|
|
searchType: true,
|
|
},
|
|
}),
|
|
prisma.blindBoxIdea.findMany({
|
|
where: { roomId, status: "drawn" },
|
|
orderBy: { createdAt: "desc" },
|
|
include: {
|
|
user: { select: { id: true, username: true, avatar: true } },
|
|
drawnBy: { select: { id: true, username: true, avatar: true } },
|
|
},
|
|
}),
|
|
]);
|
|
|
|
return NextResponse.json({ poolCount, myIdeas, drawn });
|
|
});
|
|
|
|
export const PUT = apiHandler(async (req) => {
|
|
const { ideaId, userId, content } = await req.json();
|
|
|
|
requireUserId(userId);
|
|
requireString(ideaId, "ideaId");
|
|
const trimmedContent = validateIdeaContent(content);
|
|
|
|
const { count } = await prisma.blindBoxIdea.updateMany({
|
|
where: { id: ideaId, userId, status: "in_pool" },
|
|
data: { content: trimmedContent },
|
|
});
|
|
|
|
if (count === 0) throw new ApiError("想法不存在、已被抽中或无权编辑", 404);
|
|
|
|
const tags = await Promise.race([
|
|
tagIdea(trimmedContent),
|
|
new Promise<null>((resolve) => setTimeout(() => resolve(null), TAG_TIMEOUT_MS)),
|
|
]);
|
|
|
|
if (tags) {
|
|
await prisma.blindBoxIdea.updateMany({
|
|
where: { id: ideaId, userId, status: "in_pool" },
|
|
data: {
|
|
category: tags.category,
|
|
timeSlot: tags.timeSlot,
|
|
estimatedMinutes: tags.estimatedMinutes,
|
|
costLevel: tags.costLevel,
|
|
intensity: tags.intensity,
|
|
needsBooking: tags.needsBooking,
|
|
searchQuery: tags.searchQuery,
|
|
searchType: tags.searchType,
|
|
},
|
|
});
|
|
}
|
|
|
|
return NextResponse.json({ id: ideaId, content: trimmedContent, ...tags && { tags } });
|
|
});
|
|
|
|
export const DELETE = apiHandler(async (req) => {
|
|
const { ideaId, userId } = await req.json();
|
|
|
|
requireUserId(userId);
|
|
requireString(ideaId, "ideaId");
|
|
|
|
const { count } = await prisma.blindBoxIdea.deleteMany({
|
|
where: { id: ideaId, userId, status: "in_pool" },
|
|
});
|
|
|
|
if (count === 0) throw new ApiError("想法不存在、已被抽中或无权删除", 404);
|
|
|
|
return NextResponse.json({ deleted: true });
|
|
});
|