Scrum4Me/lib/idea-dto.ts
Madhura68 4b234dc300 api: REST endpoints for ideas (M12 T-500)
- app/api/ideas/route.ts: GET (list with archived/product_id/status filters,
  user_id-scope), POST (creates DRAFT with auto IDEA-NNN code, 201)
- app/api/ideas/[id]/route.ts: GET (idea + recent logs), PATCH
  (ideaUpdateSchema, isIdeaEditable guard)
- lib/idea-dto.ts: API projection — converts Prisma row → DTO with
  lowercase status + has_grill_md/has_plan_md flags (md content excluded
  from list payloads, fetch via dedicated download action)

Auth: session OR API-token via authenticateApiRequest. Strict user_id
scope (no productAccessFilter — Idee is privé per Q8). 404 (not 403) for
foreign-user reads to prevent enumeration.

Tests: 13 cases (auth-401, demo-403, validation-422, malformed-400,
not-found-404, status-mismatch-422, filter param round-trip, DTO shape).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 19:55:49 +02:00

49 lines
1.5 KiB
TypeScript

// API-projection voor Idea — converteert Prisma-row naar het externe contract.
// Belangrijk: status wordt naar lowercase API-string vertaald (zelfde patroon
// als TaskStatus / StoryStatus / PbiStatus elders in de codebase).
import { ideaStatusToApi } from '@/lib/idea-status'
import type { Idea, IdeaStatus, Product } from '@prisma/client'
type IdeaWithProduct = Idea & {
product: Pick<Product, 'id' | 'name' | 'repo_url'> | null
pbi?: { id: string; code: string; title: string } | null
}
export interface IdeaDto {
id: string
code: string
title: string
description: string | null
status: ReturnType<typeof ideaStatusToApi>
product_id: string | null
product: { id: string; name: string; repo_url: string | null } | null
pbi_id: string | null
pbi?: { id: string; code: string; title: string } | null
archived: boolean
has_grill_md: boolean
has_plan_md: boolean
created_at: string
updated_at: string
}
export function ideaToDto(idea: IdeaWithProduct & { status: IdeaStatus }): IdeaDto {
return {
id: idea.id,
code: idea.code,
title: idea.title,
description: idea.description,
status: ideaStatusToApi(idea.status),
product_id: idea.product_id,
product: idea.product,
pbi_id: idea.pbi_id,
pbi: idea.pbi ?? null,
archived: idea.archived,
// Geen md-content in lijst-payloads (kan groot zijn) — enkel een vlag.
has_grill_md: idea.grill_md !== null,
has_plan_md: idea.plan_md !== null,
created_at: idea.created_at.toISOString(),
updated_at: idea.updated_at.toISOString(),
}
}