137 lines
4.3 KiB
TypeScript
137 lines
4.3 KiB
TypeScript
import { NextResponse } from "next/server";
|
|
import { prisma } from "@/lib/db";
|
|
import { getActiveAccountId } from "@/lib/account-context";
|
|
import { apiRouteErrorResponse } from "@/lib/auth/api";
|
|
import { isAccountInUserScope, requireUserAccountScope } from "@/lib/auth/user-scope";
|
|
import {
|
|
migrateOrphanProductContexts,
|
|
syncProfileContextToTopics,
|
|
validateProfileContext,
|
|
} from "@/lib/services/product-profile";
|
|
|
|
|
|
export async function GET() {
|
|
try {
|
|
const accountId = await getActiveAccountId();
|
|
const { where, accountIds } = await requireUserAccountScope(accountId);
|
|
try {
|
|
await migrateOrphanProductContexts(accountIds);
|
|
} catch (migrateErr) {
|
|
console.error("[product-profiles] migrate failed:", migrateErr);
|
|
}
|
|
const profiles = await prisma.productProfile.findMany({
|
|
where,
|
|
orderBy: { updatedAt: "desc" },
|
|
});
|
|
return NextResponse.json({ profiles });
|
|
} catch (error) {
|
|
return apiRouteErrorResponse(error, "product-profiles");
|
|
}
|
|
}
|
|
|
|
export async function POST(request: Request) {
|
|
try {
|
|
const { label, context } = (await request.json()) as {
|
|
label?: string;
|
|
context?: string;
|
|
};
|
|
|
|
if (!label?.trim()) {
|
|
return NextResponse.json({ error: "請填寫名稱" }, { status: 400 });
|
|
}
|
|
|
|
const serialized = context?.trim() || "";
|
|
|
|
if (!validateProfileContext(serialized)) {
|
|
return NextResponse.json({ error: "請填寫品牌、產品或特色" }, { status: 400 });
|
|
}
|
|
|
|
const accountId = await getActiveAccountId();
|
|
if (!accountId) {
|
|
return NextResponse.json({ error: "請先建立並選定經營帳號" }, { status: 400 });
|
|
}
|
|
|
|
const profile = await prisma.productProfile.create({
|
|
data: {
|
|
accountId,
|
|
label: label.trim(),
|
|
context: serialized,
|
|
},
|
|
});
|
|
|
|
return NextResponse.json({ profile });
|
|
} catch (error) {
|
|
return apiRouteErrorResponse(error, "product-profiles/create");
|
|
}
|
|
}
|
|
|
|
export async function PATCH(request: Request) {
|
|
try {
|
|
const { id, label, context } = (await request.json()) as {
|
|
id?: string;
|
|
label?: string;
|
|
context?: string;
|
|
};
|
|
|
|
if (!id) {
|
|
return NextResponse.json({ error: "缺少 id" }, { status: 400 });
|
|
}
|
|
|
|
const { accountIds } = await requireUserAccountScope(await getActiveAccountId());
|
|
const existing = await prisma.productProfile.findUnique({ where: { id } });
|
|
if (!existing || !isAccountInUserScope(accountIds, existing.accountId)) {
|
|
return NextResponse.json({ error: "找不到品牌與產品" }, { status: 404 });
|
|
}
|
|
|
|
const nextContext = context !== undefined ? context.trim() : existing.context;
|
|
if (context !== undefined && !validateProfileContext(nextContext)) {
|
|
return NextResponse.json({ error: "請填寫品牌、產品或特色" }, { status: 400 });
|
|
}
|
|
|
|
const profile = await prisma.productProfile.update({
|
|
where: { id },
|
|
data: {
|
|
...(label !== undefined && { label: label.trim() }),
|
|
...(context !== undefined && { context: nextContext }),
|
|
},
|
|
});
|
|
|
|
if (context !== undefined) {
|
|
await syncProfileContextToTopics(id, nextContext);
|
|
}
|
|
|
|
return NextResponse.json({ profile });
|
|
} catch (error) {
|
|
return apiRouteErrorResponse(error, "product-profiles/update");
|
|
}
|
|
}
|
|
|
|
export async function DELETE(request: Request) {
|
|
try {
|
|
const { searchParams } = new URL(request.url);
|
|
const id = searchParams.get("id");
|
|
|
|
if (!id) {
|
|
return NextResponse.json({ error: "缺少 id" }, { status: 400 });
|
|
}
|
|
|
|
const { accountIds } = await requireUserAccountScope(await getActiveAccountId());
|
|
const existing = await prisma.productProfile.findUnique({ where: { id } });
|
|
if (!existing || !isAccountInUserScope(accountIds, existing.accountId)) {
|
|
return NextResponse.json({ error: "找不到品牌與產品" }, { status: 404 });
|
|
}
|
|
|
|
const linked = await prisma.topic.count({ where: { productProfileId: id } });
|
|
if (linked > 0) {
|
|
return NextResponse.json(
|
|
{ error: `仍有 ${linked} 個主題使用此品牌,請先更換或刪除主題` },
|
|
{ status: 400 }
|
|
);
|
|
}
|
|
|
|
await prisma.productProfile.delete({ where: { id } });
|
|
return NextResponse.json({ success: true });
|
|
} catch (error) {
|
|
return apiRouteErrorResponse(error, "product-profiles/delete");
|
|
}
|
|
} |