diff --git a/openspec/changes/code-review-fix/tasks.md b/openspec/changes/code-review-fix/tasks.md index 47a35ca..731397f 100644 --- a/openspec/changes/code-review-fix/tasks.md +++ b/openspec/changes/code-review-fix/tasks.md @@ -33,7 +33,7 @@ - [x] 4.5 `[sonnet]` Add Zod schema validation to `src/app/api/patterns/detect/route.ts` (validate candles, patterns array) - [x] 4.6 `[sonnet]` Replace `error.message` with generic `"Internal server error"` in all catch blocks across 7+ route files: `health/route.ts`, `candles/route.ts`, `annotations/route.ts`, `annotations/[id]/route.ts`, `upload/route.ts`, `export/route.ts`, `span-annotations/export/route.ts` - [x] 4.7 `[sonnet]` Require `chartId` for bulk delete in `src/app/api/annotations/route.ts` — reject `?all=true` without chartId with HTTP 400 -- [ ] 4.8 `[sonnet]` Wrap chart cascade delete in `db.transaction()` and add `spanAnnotations` deletion in `src/app/api/charts/[id]/route.ts` +- [x] 4.8 `[sonnet]` Wrap chart cascade delete in `db.transaction()` and add `spanAnnotations` deletion in `src/app/api/charts/[id]/route.ts` - [ ] 4.9 `[haiku]` Add `parseInt(value, 10)` with `isNaN()` guard to all routes parsing integer query params - [ ] 4.10 `[sonnet]` Add CSV injection protection (prefix `=+@-` cells with `'`) to all export routes - [ ] 4.11 `[sonnet]` Add `response.ok` checks before `.json()` in `src/app/page.tsx` (lines 214, 230, 245, 257) diff --git a/src/app/api/charts/[id]/route.ts b/src/app/api/charts/[id]/route.ts index a14fd49..6be9952 100644 --- a/src/app/api/charts/[id]/route.ts +++ b/src/app/api/charts/[id]/route.ts @@ -1,6 +1,6 @@ import { NextRequest, NextResponse } from 'next/server'; import { db } from '@/lib/db'; -import { charts, candles, annotations } from '@/lib/db/schema'; +import { charts, candles, annotations, spanAnnotations } from '@/lib/db/schema'; import { eq } from 'drizzle-orm'; export async function GET( @@ -39,10 +39,13 @@ export async function DELETE( return NextResponse.json({ error: 'Chart not found' }, { status: 404 }); } - // Delete in order: annotations, candles, then chart (application-level cascade) - await db.delete(annotations).where(eq(annotations.chart_id, chartId)); - await db.delete(candles).where(eq(candles.chart_id, chartId)); - await db.delete(charts).where(eq(charts.id, chartId)); + // Delete atomically in FK-safe order: spanAnnotations, annotations, candles, then chart + await db.transaction(async (tx) => { + await tx.delete(spanAnnotations).where(eq(spanAnnotations.chart_id, chartId)); + await tx.delete(annotations).where(eq(annotations.chart_id, chartId)); + await tx.delete(candles).where(eq(candles.chart_id, chartId)); + await tx.delete(charts).where(eq(charts.id, chartId)); + }); return NextResponse.json({ success: true }); }