feat: harden analytics dashboard
This commit is contained in:
62
lib/api-params.ts
Normal file
62
lib/api-params.ts
Normal file
@@ -0,0 +1,62 @@
|
||||
import { NextResponse } from "next/server";
|
||||
|
||||
export type ParseResult<T> =
|
||||
| { ok: true; value: T }
|
||||
| { ok: false; field: string };
|
||||
|
||||
interface PositiveIntOptions {
|
||||
field: string;
|
||||
defaultValue: number;
|
||||
min: number;
|
||||
max?: number;
|
||||
}
|
||||
|
||||
function parseInteger(raw: string | null, field: string): ParseResult<number | undefined> {
|
||||
if (raw === null || raw === "") return { ok: true, value: undefined };
|
||||
if (!/^-?\d+$/.test(raw)) return { ok: false, field };
|
||||
|
||||
const value = Number(raw);
|
||||
if (!Number.isSafeInteger(value)) return { ok: false, field };
|
||||
return { ok: true, value };
|
||||
}
|
||||
|
||||
export function parseOptionalInt(raw: string | null, field: string): ParseResult<number | undefined> {
|
||||
const parsed = parseInteger(raw, field);
|
||||
if (!parsed.ok || parsed.value === undefined) return parsed;
|
||||
if (parsed.value < 0) return { ok: false, field };
|
||||
return parsed;
|
||||
}
|
||||
|
||||
export function parsePositiveInt(raw: string | null, options: PositiveIntOptions): ParseResult<number> {
|
||||
const parsed = parseInteger(raw, options.field);
|
||||
if (!parsed.ok) return parsed;
|
||||
|
||||
let value = parsed.value ?? options.defaultValue;
|
||||
if (value < 0) return { ok: false, field: options.field };
|
||||
if (value < options.min) value = options.min;
|
||||
if (options.max !== undefined && value > options.max) value = options.max;
|
||||
return { ok: true, value };
|
||||
}
|
||||
|
||||
export function parseTimestampRange(
|
||||
searchParams: URLSearchParams
|
||||
): ParseResult<{ startTs?: number; endTs?: number }> {
|
||||
const start = parseOptionalInt(searchParams.get("start"), "start");
|
||||
if (!start.ok) return start;
|
||||
|
||||
const end = parseOptionalInt(searchParams.get("end"), "end");
|
||||
if (!end.ok) return end;
|
||||
|
||||
if (start.value !== undefined && end.value !== undefined && start.value > end.value) {
|
||||
return { ok: false, field: "range" };
|
||||
}
|
||||
|
||||
return { ok: true, value: { startTs: start.value, endTs: end.value } };
|
||||
}
|
||||
|
||||
export function jsonError(field?: string, status = 400) {
|
||||
const body = field
|
||||
? { error: "Invalid query parameter", field }
|
||||
: { error: "Internal server error" };
|
||||
return NextResponse.json(body, { status });
|
||||
}
|
||||
Reference in New Issue
Block a user