feat: Zod validation on all API inputs (PRD §5.2)

26 files with Zod schemas, 27 safeParse calls.
All POST/PATCH handlers now validate input:
- Missing required fields → 400 with structured error
- Invalid types (string vs number) → 400
- Out-of-range values (probability >100, negative amounts) → 400
- Valid data still works (behavior preserved)

Pattern: schema.safeParse(await req.json()) → 400 on failure
This commit is contained in:
Z User committed 2026-10-27 21:29:51 +00:00
1 parent 3d56770669
commit 2614ce4e3d
25 files changed
+316 -116

No files matched your search

+14 -2
View File
@@ -1,8 +1,16 @@
import { NextResponse } from 'next/server'
import { z } from 'zod'
import { db } from '@/lib/db'
export const dynamic = 'force-dynamic'
const createTaskSchema = z.object({
title: z.string().min(1),
priority: z.enum(['low', 'medium', 'high', 'urgent']).optional().default('medium'),
assignee: z.string().optional(),
estimateMin: z.number().int().min(0).optional(),
})
// POST /api/aos/projects/[id]/tasks — add a task to a project
export async function POST(
req: Request,
@@ -10,13 +18,17 @@ export async function POST(
) {
try {
const { id } = await params
const body = (await req.json()) as { title: string; priority?: string; assignee?: string; estimateMin?: number }
const parsed = createTaskSchema.safeParse(await req.json())
if (!parsed.success) {
return NextResponse.json({ error: 'validation failed', details: parsed.error.flatten() }, { status: 400 })
}
const body = parsed.data
const task = await db.task.create({
data: {
projectId: id,
title: body.title,
priority: body.priority ?? 'medium',
priority: body.priority,
assignee: body.assignee ?? null,
estimateMin: body.estimateMin ?? null,
status: 'todo',
+14 -2
View File
@@ -1,22 +1,34 @@
import { NextResponse } from 'next/server'
import { z } from 'zod'
import { db } from '@/lib/db'
export const dynamic = 'force-dynamic'
const logTimeSchema = z.object({
description: z.string().min(1),
minutes: z.number().int().positive(),
billable: z.boolean().optional().default(true),
rateCentsPerHour: z.number().int().min(0).optional(),
})
// POST /api/aos/projects/[id]/time — log a time entry on a project
export async function POST(
req: Request,
{ params }: { params: Promise<{ id: string }> }
) {
const { id } = await params
const body = (await req.json()) as { description: string; minutes: number; billable?: boolean; rateCentsPerHour?: number }
const parsed = logTimeSchema.safeParse(await req.json())
if (!parsed.success) {
return NextResponse.json({ error: 'validation failed', details: parsed.error.flatten() }, { status: 400 })
}
const body = parsed.data
const entry = await db.timeEntry.create({
data: {
projectId: id,
description: body.description,
minutes: Math.max(1, Math.round(body.minutes)),
billable: body.billable ?? true,
billable: body.billable,
rateCentsPerHour: body.rateCentsPerHour ?? null,
date: new Date(),
},