Voegt IdeaProduct schema toe (dependency van story-qtkvz6ly), breidt IdeaWithProduct type en IdeaDto interface uit met secondary_products array, en laadt de relatie mee in findMany/findFirst in page.tsx en REST GET. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
97 lines
3.3 KiB
TypeScript
97 lines
3.3 KiB
TypeScript
// REST endpoints voor de Idee-entity (M12).
|
|
// - Strikt user_id-only — geen productAccessFilter.
|
|
// - Auth via session OF API-token (zelfde patroon als /api/todos).
|
|
// - Demo blokkeert POST/PATCH/DELETE (proxy.ts laag + 403 hier als second-line).
|
|
|
|
import { authenticateApiRequest } from '@/lib/api-auth'
|
|
import { prisma } from '@/lib/prisma'
|
|
import { ideaCreateSchema } from '@/lib/schemas/idea'
|
|
import { ideaStatusFromApi, ideaStatusToApi } from '@/lib/idea-status'
|
|
import { nextIdeaCode } from '@/lib/idea-code-server'
|
|
import { ideaToDto } from '@/lib/idea-dto'
|
|
|
|
export async function GET(request: Request) {
|
|
const auth = await authenticateApiRequest(request)
|
|
if ('error' in auth) {
|
|
return Response.json({ error: auth.error }, { status: auth.status })
|
|
}
|
|
|
|
const url = new URL(request.url)
|
|
const archivedParam = url.searchParams.get('archived')
|
|
const productIdParam = url.searchParams.get('product_id')
|
|
const statusParam = url.searchParams.get('status')
|
|
|
|
const archived =
|
|
archivedParam === 'true' ? true : archivedParam === 'false' ? false : undefined
|
|
const status = statusParam ? ideaStatusFromApi(statusParam) ?? undefined : undefined
|
|
|
|
const ideas = await prisma.idea.findMany({
|
|
where: {
|
|
user_id: auth.userId,
|
|
...(archived !== undefined ? { archived } : {}),
|
|
...(productIdParam ? { product_id: productIdParam } : {}),
|
|
...(status ? { status } : {}),
|
|
},
|
|
include: {
|
|
product: { select: { id: true, name: true, repo_url: true } },
|
|
secondary_products: { include: { product: { select: { id: true, name: true } } } },
|
|
},
|
|
orderBy: { created_at: 'desc' },
|
|
take: 200,
|
|
})
|
|
|
|
return Response.json({ ideas: ideas.map(ideaToDto) })
|
|
}
|
|
|
|
export async function POST(request: Request) {
|
|
const auth = await authenticateApiRequest(request)
|
|
if ('error' in auth) {
|
|
return Response.json({ error: auth.error }, { status: auth.status })
|
|
}
|
|
if (auth.isDemo) {
|
|
return Response.json({ error: 'Niet beschikbaar in demo-modus' }, { status: 403 })
|
|
}
|
|
|
|
let body: unknown
|
|
try {
|
|
body = await request.json()
|
|
} catch {
|
|
return Response.json({ error: 'Malformed JSON' }, { status: 400 })
|
|
}
|
|
const parsed = ideaCreateSchema.safeParse(body)
|
|
if (!parsed.success) {
|
|
return Response.json({ error: parsed.error.flatten() }, { status: 422 })
|
|
}
|
|
|
|
// Optionele product-binding: alleen toelaten als gebruiker eigenaar/member is.
|
|
if (parsed.data.product_id) {
|
|
const product = await prisma.product.findFirst({
|
|
where: { id: parsed.data.product_id, user_id: auth.userId, archived: false },
|
|
select: { id: true },
|
|
})
|
|
if (!product) {
|
|
return Response.json({ error: 'Product niet gevonden' }, { status: 404 })
|
|
}
|
|
}
|
|
|
|
const userId = auth.userId
|
|
const idea = await prisma.$transaction(async (tx) => {
|
|
const code = await nextIdeaCode(userId, tx)
|
|
return tx.idea.create({
|
|
data: {
|
|
user_id: userId,
|
|
product_id: parsed.data.product_id ?? null,
|
|
code,
|
|
title: parsed.data.title,
|
|
description: parsed.data.description ?? null,
|
|
status: 'DRAFT',
|
|
},
|
|
include: { product: { select: { id: true, name: true, repo_url: true } } },
|
|
})
|
|
})
|
|
|
|
return Response.json(
|
|
{ idea: { ...ideaToDto(idea), status: ideaStatusToApi(idea.status) } },
|
|
{ status: 201 },
|
|
)
|
|
}
|