fix: add parseInt(value, 10) with isNaN() guards to all integer query param parsing

- Add radix 10 to all parseInt() calls parsing integer query/path parameters
- Add isNaN() guards returning HTTP 400 with descriptive error messages
- Updated routes: annotations, candles, export, export/spans, annotation-types/[id], span-annotations, span-annotations/[id], span-label-types/[id]
- Ensures strict integer parsing and prevents invalid parameter values from reaching database queries
This commit is contained in:
Marko Djordjevic 2026-02-18 11:19:26 +01:00
parent 1678da2d9d
commit 15adf09b73
9 changed files with 124 additions and 17 deletions

View file

@ -14,6 +14,15 @@ export async function PATCH(
) {
try {
const { id } = await context.params;
const idNum = parseInt(id, 10);
if (isNaN(idNum)) {
return NextResponse.json(
{ error: 'Invalid parameter: id must be an integer' },
{ status: 400 }
);
}
const body = await request.json();
const { name, display_name, color, category, icon, is_active } = body;
@ -36,7 +45,7 @@ export async function PATCH(
const result = await db
.update(annotationTypes)
.set(updateData)
.where(eq(annotationTypes.id, parseInt(id)))
.where(eq(annotationTypes.id, idNum))
.returning();
if (result.length === 0) {

View file

@ -9,7 +9,7 @@ export async function PATCH(
) {
try {
const { id: idParam } = await params;
const id = parseInt(idParam);
const id = parseInt(idParam, 10);
if (isNaN(id)) {
return NextResponse.json(
@ -57,7 +57,7 @@ export async function DELETE(
) {
try {
const { id: idParam } = await params;
const id = parseInt(idParam);
const id = parseInt(idParam, 10);
if (isNaN(id)) {
return NextResponse.json(

View file

@ -18,10 +18,18 @@ export async function GET(request: NextRequest) {
chartId = String(latest[0].id);
}
const chartIdNum = parseInt(chartId, 10);
if (isNaN(chartIdNum)) {
return NextResponse.json(
{ error: 'Invalid parameter: chartId must be an integer' },
{ status: 400 }
);
}
const allAnnotations = await db
.select()
.from(annotations)
.where(eq(annotations.chart_id, parseInt(chartId, 10)));
.where(eq(annotations.chart_id, chartIdNum));
const normalized = allAnnotations.map((a) => ({
...a,
@ -108,13 +116,27 @@ export async function DELETE(request: NextRequest) {
{ status: 400 }
);
}
result = await db.delete(annotations).where(eq(annotations.chart_id, parseInt(chartId, 10))).returning();
const chartIdNum = parseInt(chartId, 10);
if (isNaN(chartIdNum)) {
return NextResponse.json(
{ error: 'Invalid parameter: chartId must be an integer' },
{ status: 400 }
);
}
result = await db.delete(annotations).where(eq(annotations.chart_id, chartIdNum)).returning();
} else if (type) {
const types = type.split(',').map((t) => t.trim());
if (chartId) {
const chartIdNum = parseInt(chartId, 10);
if (isNaN(chartIdNum)) {
return NextResponse.json(
{ error: 'Invalid parameter: chartId must be an integer' },
{ status: 400 }
);
}
result = await db
.delete(annotations)
.where(and(inArray(annotations.label_type, types), eq(annotations.chart_id, parseInt(chartId, 10))))
.where(and(inArray(annotations.label_type, types), eq(annotations.chart_id, chartIdNum)))
.returning();
} else {
result = await db

View file

@ -17,6 +17,14 @@ export async function GET(request: NextRequest) {
chartId = String(latest[0].id);
}
const chartIdNum = parseInt(chartId, 10);
if (isNaN(chartIdNum)) {
return NextResponse.json(
{ error: 'Invalid parameter: chartId must be an integer' },
{ status: 400 }
);
}
const allCandles = await db
.select({
time: candles.time,
@ -26,7 +34,7 @@ export async function GET(request: NextRequest) {
close: candles.close,
})
.from(candles)
.where(eq(candles.chart_id, parseInt(chartId, 10)))
.where(eq(candles.chart_id, chartIdNum))
.orderBy(asc(candles.time));
const normalized = allCandles.map((c) => ({

View file

@ -23,6 +23,13 @@ export async function GET(request: NextRequest) {
}
const chartIdNum = parseInt(chartId, 10);
if (isNaN(chartIdNum)) {
return NextResponse.json(
{ error: 'Invalid parameter: chartId must be an integer' },
{ status: 400 }
);
}
const allAnnotations = await db
.select()
.from(annotations)

View file

@ -19,6 +19,19 @@ export async function GET(request: NextRequest) {
}
const chartIdNum = parseInt(chartId, 10);
if (isNaN(chartIdNum)) {
return NextResponse.json(
{ error: 'Invalid parameter: chartId must be an integer' },
{ status: 400 }
);
}
if (isNaN(contextPadding)) {
return NextResponse.json(
{ error: 'Invalid parameter: context_padding must be an integer' },
{ status: 400 }
);
}
// Fetch span annotations for the chart
const spans = await db

View file

@ -10,6 +10,15 @@ export async function PATCH(
) {
try {
const { id } = await params;
const idNum = parseInt(id, 10);
if (isNaN(idNum)) {
return NextResponse.json(
{ error: 'Invalid parameter: id must be an integer' },
{ status: 400 }
);
}
const body = await request.json();
const { label, confidence, outcome, notes, sub_spans } = body;
@ -18,7 +27,7 @@ export async function PATCH(
const existing = await db
.select()
.from(spanAnnotations)
.where(eq(spanAnnotations.id, parseInt(id)))
.where(eq(spanAnnotations.id, idNum))
.limit(1);
if (existing.length === 0) {
@ -39,7 +48,7 @@ export async function PATCH(
const result = await db
.update(spanAnnotations)
.set(updates)
.where(eq(spanAnnotations.id, parseInt(id)))
.where(eq(spanAnnotations.id, idNum))
.returning();
const s = result[0];
@ -65,12 +74,20 @@ export async function DELETE(
) {
try {
const { id } = await params;
const idNum = parseInt(id, 10);
if (isNaN(idNum)) {
return NextResponse.json(
{ error: 'Invalid parameter: id must be an integer' },
{ status: 400 }
);
}
// Check if the span exists
const existing = await db
.select()
.from(spanAnnotations)
.where(eq(spanAnnotations.id, parseInt(id)))
.where(eq(spanAnnotations.id, idNum))
.limit(1);
if (existing.length === 0) {
@ -80,7 +97,7 @@ export async function DELETE(
);
}
await db.delete(spanAnnotations).where(eq(spanAnnotations.id, parseInt(id)));
await db.delete(spanAnnotations).where(eq(spanAnnotations.id, idNum));
return NextResponse.json({ message: 'Span annotation deleted successfully' });
} catch (error) {

View file

@ -16,10 +16,18 @@ export async function GET(request: NextRequest) {
);
}
const chartIdNum = parseInt(chartId, 10);
if (isNaN(chartIdNum)) {
return NextResponse.json(
{ error: 'Invalid parameter: chartId must be an integer' },
{ status: 400 }
);
}
const spans = await db
.select()
.from(spanAnnotations)
.where(eq(spanAnnotations.chart_id, parseInt(chartId)))
.where(eq(spanAnnotations.chart_id, chartIdNum))
.orderBy(desc(spanAnnotations.start_time));
const normalized = spans.map((s) => ({
@ -116,7 +124,13 @@ export async function DELETE(request: NextRequest) {
);
}
const chartIdInt = parseInt(chartId);
const chartIdInt = parseInt(chartId, 10);
if (isNaN(chartIdInt)) {
return NextResponse.json(
{ error: 'Invalid parameter: chartId must be an integer' },
{ status: 400 }
);
}
// Build filter conditions
const conditions = [eq(spanAnnotations.chart_id, chartIdInt)];

View file

@ -10,6 +10,15 @@ export async function PATCH(
) {
try {
const { id } = await params;
const idNum = parseInt(id, 10);
if (isNaN(idNum)) {
return NextResponse.json(
{ error: 'Invalid parameter: id must be an integer' },
{ status: 400 }
);
}
const body = await request.json();
const { name, display_name, color, hotkey, is_active, sort_order } = body;
@ -18,7 +27,7 @@ export async function PATCH(
const existing = await db
.select()
.from(spanLabelTypes)
.where(eq(spanLabelTypes.id, parseInt(id)))
.where(eq(spanLabelTypes.id, idNum))
.limit(1);
if (existing.length === 0) {
@ -40,7 +49,7 @@ export async function PATCH(
const result = await db
.update(spanLabelTypes)
.set(updates)
.where(eq(spanLabelTypes.id, parseInt(id)))
.where(eq(spanLabelTypes.id, idNum))
.returning();
return NextResponse.json(result[0]);
@ -68,12 +77,20 @@ export async function DELETE(
) {
try {
const { id } = await params;
const idNum = parseInt(id, 10);
if (isNaN(idNum)) {
return NextResponse.json(
{ error: 'Invalid parameter: id must be an integer' },
{ status: 400 }
);
}
// Check if the type exists
const type = await db
.select()
.from(spanLabelTypes)
.where(eq(spanLabelTypes.id, parseInt(id)))
.where(eq(spanLabelTypes.id, idNum))
.limit(1);
if (type.length === 0) {
@ -97,7 +114,7 @@ export async function DELETE(
);
}
await db.delete(spanLabelTypes).where(eq(spanLabelTypes.id, parseInt(id)));
await db.delete(spanLabelTypes).where(eq(spanLabelTypes.id, idNum));
return NextResponse.json({ message: 'Span label type deleted successfully' });
} catch (error) {