Skip to content

Commit 32c12f5

Browse files
Merge pull request #60 from Juliusolsson05/chore/admin-endpoint-cleanup
fix: tighten remaining map admin route validation
2 parents 3bb387a + dddee7c commit 32c12f5

7 files changed

Lines changed: 35 additions & 104 deletions

File tree

src/app/api/v1/admin/[conflictId]/map/assets/route.ts

Lines changed: 5 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@ import { NextRequest, NextResponse } from 'next/server';
22

33
import { requireAdmin } from '@/server/lib/admin-auth';
44
import { validateOptionalEventId } from '@/server/lib/admin-relations';
5-
import { assertEnum, assertRequired, INSTALLATION_STATUSES,INSTALLATION_TYPES, MAP_ACTOR_KEYS, MAP_PRIORITIES, parseISODate, safeJson } from '@/server/lib/admin-validate';
5+
import { parseBodyWithSchema, toJsonValue } from '@/server/lib/admin-schema-utils';
6+
import { adminAssetCreateSchema } from '@/server/lib/admin-schemas';
7+
import { parseISODate } from '@/server/lib/admin-validate';
68
import { err,ok } from '@/server/lib/api-utils';
79
import { prisma } from '@/server/lib/db';
810
import { checkMapFeatureEnforcement } from '@/server/lib/enforcement';
@@ -17,26 +19,9 @@ export async function POST(
1719
if (denied) return denied;
1820

1921
const { conflictId } = await params;
20-
const body = await safeJson(req);
22+
const body = await parseBodyWithSchema(req, adminAssetCreateSchema);
2123
if (body instanceof NextResponse) return body;
2224

23-
const missing = assertRequired(body, ['id', 'actor', 'priority', 'category', 'type']);
24-
if (missing) return err('VALIDATION', missing);
25-
26-
const actorErr = assertEnum(body.actor, MAP_ACTOR_KEYS, 'actor');
27-
if (actorErr) return err('VALIDATION', actorErr);
28-
29-
const priorityErr = assertEnum(body.priority, MAP_PRIORITIES, 'priority');
30-
if (priorityErr) return err('VALIDATION', priorityErr);
31-
32-
const typeErr = assertEnum(body.type, INSTALLATION_TYPES, 'type');
33-
if (typeErr) return err('VALIDATION', typeErr);
34-
35-
if (body.status !== undefined && body.status !== null) {
36-
const statusErr = assertEnum(body.status, INSTALLATION_STATUSES, 'status');
37-
if (statusErr) return err('VALIDATION', statusErr);
38-
}
39-
4025
const geometry = normalizePointGeometry(body.geometry);
4126
if (!geometry) {
4227
return err('VALIDATION', 'Asset geometry requires position [lng, lat]');
@@ -79,7 +64,7 @@ export async function POST(
7964
status: body.status ?? null,
8065
timestamp,
8166
geometry,
82-
properties: body.properties ?? {},
67+
properties: toJsonValue(body.properties ?? {}),
8368
},
8469
});
8570

src/app/api/v1/admin/[conflictId]/map/features/[featureId]/route.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@ import { NextRequest, NextResponse } from 'next/server';
22

33
import { requireAdmin } from '@/server/lib/admin-auth';
44
import { validateOptionalEventId } from '@/server/lib/admin-relations';
5-
import { assertEnum, INSTALLATION_STATUSES,INSTALLATION_TYPES, KINETIC_STATUSES, KINETIC_TYPES, MAP_ACTOR_KEYS, MAP_PRIORITIES, parseISODate, safeJson, ZONE_TYPES } from '@/server/lib/admin-validate';
5+
import { parseBodyWithSchema, toJsonValue } from '@/server/lib/admin-schema-utils';
6+
import { adminMapFeatureUpdateSchema } from '@/server/lib/admin-schemas';
7+
import { assertEnum, INSTALLATION_STATUSES,INSTALLATION_TYPES, KINETIC_STATUSES, KINETIC_TYPES, MAP_ACTOR_KEYS, MAP_PRIORITIES, parseISODate, ZONE_TYPES } from '@/server/lib/admin-validate';
68
import { err,ok } from '@/server/lib/api-utils';
79
import { prisma } from '@/server/lib/db';
810
import { normalizeKineticGeometry, normalizePointGeometry, normalizePolygonGeometry } from '@/server/lib/map-feature-geometry';
@@ -15,7 +17,7 @@ export async function PUT(
1517
if (denied) return denied;
1618

1719
const { conflictId, featureId } = await params;
18-
const body = await safeJson(req);
20+
const body = await parseBodyWithSchema(req, adminMapFeatureUpdateSchema);
1921
if (body instanceof NextResponse) return body;
2022

2123
const feature = await prisma.mapFeature.findFirst({
@@ -68,7 +70,7 @@ export async function PUT(
6870
data.geometry = geometry;
6971
}
7072
}
71-
if (body.properties !== undefined) data.properties = body.properties;
73+
if (body.properties !== undefined) data.properties = toJsonValue(body.properties);
7274
if (body.timestamp !== undefined) {
7375
if (body.timestamp === null) {
7476
data.timestamp = null;

src/app/api/v1/admin/[conflictId]/map/heat-points/route.ts

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@ import { NextRequest, NextResponse } from 'next/server';
22

33
import { requireAdmin } from '@/server/lib/admin-auth';
44
import { validateOptionalEventId } from '@/server/lib/admin-relations';
5-
import { assertRequired, parseISODate , safeJson } from '@/server/lib/admin-validate';
5+
import { parseBodyWithSchema, toJsonValue } from '@/server/lib/admin-schema-utils';
6+
import { adminHeatPointCreateSchema } from '@/server/lib/admin-schemas';
7+
import { parseISODate } from '@/server/lib/admin-validate';
68
import { err,ok } from '@/server/lib/api-utils';
79
import { prisma } from '@/server/lib/db';
810
import { normalizePointGeometry } from '@/server/lib/map-feature-geometry';
@@ -15,12 +17,9 @@ export async function POST(
1517
if (denied) return denied;
1618

1719
const { conflictId } = await params;
18-
const body = await safeJson(req);
20+
const body = await parseBodyWithSchema(req, adminHeatPointCreateSchema);
1921
if (body instanceof NextResponse) return body;
2022

21-
const missing = assertRequired(body, ['id', 'actor', 'priority', 'category', 'type']);
22-
if (missing) return err('VALIDATION', missing);
23-
2423
const geometry = normalizePointGeometry(body.geometry);
2524
if (!geometry) {
2625
return err('VALIDATION', 'Heat point geometry requires position [lng, lat]');
@@ -55,7 +54,7 @@ export async function POST(
5554
status: body.status ?? null,
5655
timestamp,
5756
geometry,
58-
properties: body.properties ?? {},
57+
properties: toJsonValue(body.properties ?? {}),
5958
},
6059
});
6160

src/app/api/v1/admin/[conflictId]/map/missile-tracks/route.ts

Lines changed: 5 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@ import { NextRequest, NextResponse } from 'next/server';
22

33
import { requireAdmin } from '@/server/lib/admin-auth';
44
import { validateOptionalEventId } from '@/server/lib/admin-relations';
5-
import { assertEnum, assertRequired, KINETIC_STATUSES,KINETIC_TYPES, MAP_ACTOR_KEYS, MAP_PRIORITIES, parseISODate, safeJson } from '@/server/lib/admin-validate';
5+
import { parseBodyWithSchema, toJsonValue } from '@/server/lib/admin-schema-utils';
6+
import { adminMissileTrackCreateSchema } from '@/server/lib/admin-schemas';
7+
import { parseISODate } from '@/server/lib/admin-validate';
68
import { err,ok } from '@/server/lib/api-utils';
79
import { prisma } from '@/server/lib/db';
810
import { checkMapFeatureEnforcement } from '@/server/lib/enforcement';
@@ -17,26 +19,9 @@ export async function POST(
1719
if (denied) return denied;
1820

1921
const { conflictId } = await params;
20-
const body = await safeJson(req);
22+
const body = await parseBodyWithSchema(req, adminMissileTrackCreateSchema);
2123
if (body instanceof NextResponse) return body;
2224

23-
const missing = assertRequired(body, ['id', 'actor', 'priority', 'category', 'type']);
24-
if (missing) return err('VALIDATION', missing);
25-
26-
const actorErr = assertEnum(body.actor, MAP_ACTOR_KEYS, 'actor');
27-
if (actorErr) return err('VALIDATION', actorErr);
28-
29-
const priorityErr = assertEnum(body.priority, MAP_PRIORITIES, 'priority');
30-
if (priorityErr) return err('VALIDATION', priorityErr);
31-
32-
const typeErr = assertEnum(body.type, KINETIC_TYPES, 'type');
33-
if (typeErr) return err('VALIDATION', typeErr);
34-
35-
if (body.status !== undefined && body.status !== null) {
36-
const statusErr = assertEnum(body.status, KINETIC_STATUSES, 'status');
37-
if (statusErr) return err('VALIDATION', statusErr);
38-
}
39-
4025
const geometry = normalizeKineticGeometry(body.geometry);
4126
if (!geometry) {
4227
return err('VALIDATION', 'Missile track geometry requires from and to coordinates');
@@ -79,7 +64,7 @@ export async function POST(
7964
status: body.status ?? null,
8065
timestamp,
8166
geometry,
82-
properties: body.properties ?? {},
67+
properties: toJsonValue(body.properties ?? {}),
8368
},
8469
});
8570

src/app/api/v1/admin/[conflictId]/map/strike-arcs/route.ts

Lines changed: 5 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@ import { NextRequest, NextResponse } from 'next/server';
22

33
import { requireAdmin } from '@/server/lib/admin-auth';
44
import { validateOptionalEventId } from '@/server/lib/admin-relations';
5-
import { assertEnum, assertRequired, KINETIC_STATUSES,KINETIC_TYPES, MAP_ACTOR_KEYS, MAP_PRIORITIES, parseISODate, safeJson } from '@/server/lib/admin-validate';
5+
import { parseBodyWithSchema, toJsonValue } from '@/server/lib/admin-schema-utils';
6+
import { adminStrikeArcCreateSchema } from '@/server/lib/admin-schemas';
7+
import { parseISODate } from '@/server/lib/admin-validate';
68
import { err,ok } from '@/server/lib/api-utils';
79
import { prisma } from '@/server/lib/db';
810
import { checkMapFeatureEnforcement } from '@/server/lib/enforcement';
@@ -17,26 +19,9 @@ export async function POST(
1719
if (denied) return denied;
1820

1921
const { conflictId } = await params;
20-
const body = await safeJson(req);
22+
const body = await parseBodyWithSchema(req, adminStrikeArcCreateSchema);
2123
if (body instanceof NextResponse) return body;
2224

23-
const missing = assertRequired(body, ['id', 'actor', 'priority', 'category', 'type']);
24-
if (missing) return err('VALIDATION', missing);
25-
26-
const actorErr = assertEnum(body.actor, MAP_ACTOR_KEYS, 'actor');
27-
if (actorErr) return err('VALIDATION', actorErr);
28-
29-
const priorityErr = assertEnum(body.priority, MAP_PRIORITIES, 'priority');
30-
if (priorityErr) return err('VALIDATION', priorityErr);
31-
32-
const typeErr = assertEnum(body.type, KINETIC_TYPES, 'type');
33-
if (typeErr) return err('VALIDATION', typeErr);
34-
35-
if (body.status !== undefined && body.status !== null) {
36-
const statusErr = assertEnum(body.status, KINETIC_STATUSES, 'status');
37-
if (statusErr) return err('VALIDATION', statusErr);
38-
}
39-
4025
const geometry = normalizeKineticGeometry(body.geometry);
4126
if (!geometry) {
4227
return err('VALIDATION', 'Strike arc geometry requires from and to coordinates');
@@ -79,7 +64,7 @@ export async function POST(
7964
status: body.status ?? null,
8065
timestamp,
8166
geometry,
82-
properties: body.properties ?? {},
67+
properties: toJsonValue(body.properties ?? {}),
8368
},
8469
});
8570

src/app/api/v1/admin/[conflictId]/map/targets/route.ts

Lines changed: 5 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@ import { NextRequest, NextResponse } from 'next/server';
22

33
import { requireAdmin } from '@/server/lib/admin-auth';
44
import { validateOptionalEventId } from '@/server/lib/admin-relations';
5-
import { assertEnum, assertRequired, INSTALLATION_STATUSES,INSTALLATION_TYPES, MAP_ACTOR_KEYS, MAP_PRIORITIES, parseISODate, safeJson } from '@/server/lib/admin-validate';
5+
import { parseBodyWithSchema, toJsonValue } from '@/server/lib/admin-schema-utils';
6+
import { adminTargetCreateSchema } from '@/server/lib/admin-schemas';
7+
import { parseISODate } from '@/server/lib/admin-validate';
68
import { err,ok } from '@/server/lib/api-utils';
79
import { prisma } from '@/server/lib/db';
810
import { checkMapFeatureEnforcement } from '@/server/lib/enforcement';
@@ -17,26 +19,9 @@ export async function POST(
1719
if (denied) return denied;
1820

1921
const { conflictId } = await params;
20-
const body = await safeJson(req);
22+
const body = await parseBodyWithSchema(req, adminTargetCreateSchema);
2123
if (body instanceof NextResponse) return body;
2224

23-
const missing = assertRequired(body, ['id', 'actor', 'priority', 'category', 'type']);
24-
if (missing) return err('VALIDATION', missing);
25-
26-
const actorErr = assertEnum(body.actor, MAP_ACTOR_KEYS, 'actor');
27-
if (actorErr) return err('VALIDATION', actorErr);
28-
29-
const priorityErr = assertEnum(body.priority, MAP_PRIORITIES, 'priority');
30-
if (priorityErr) return err('VALIDATION', priorityErr);
31-
32-
const typeErr = assertEnum(body.type, INSTALLATION_TYPES, 'type');
33-
if (typeErr) return err('VALIDATION', typeErr);
34-
35-
if (body.status !== undefined && body.status !== null) {
36-
const statusErr = assertEnum(body.status, INSTALLATION_STATUSES, 'status');
37-
if (statusErr) return err('VALIDATION', statusErr);
38-
}
39-
4025
const geometry = normalizePointGeometry(body.geometry);
4126
if (!geometry) {
4227
return err('VALIDATION', 'Target geometry requires position [lng, lat]');
@@ -79,7 +64,7 @@ export async function POST(
7964
status: body.status ?? null,
8065
timestamp,
8166
geometry,
82-
properties: body.properties ?? {},
67+
properties: toJsonValue(body.properties ?? {}),
8368
},
8469
});
8570

src/app/api/v1/admin/[conflictId]/map/threat-zones/route.ts

Lines changed: 5 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@ import { NextRequest, NextResponse } from 'next/server';
22

33
import { requireAdmin } from '@/server/lib/admin-auth';
44
import { validateOptionalEventId } from '@/server/lib/admin-relations';
5-
import { assertEnum, assertRequired, MAP_ACTOR_KEYS, MAP_PRIORITIES, parseISODate, safeJson, ZONE_TYPES } from '@/server/lib/admin-validate';
5+
import { parseBodyWithSchema, toJsonValue } from '@/server/lib/admin-schema-utils';
6+
import { adminThreatZoneCreateSchema } from '@/server/lib/admin-schemas';
7+
import { parseISODate } from '@/server/lib/admin-validate';
68
import { err,ok } from '@/server/lib/api-utils';
79
import { prisma } from '@/server/lib/db';
810
import { normalizePolygonGeometry } from '@/server/lib/map-feature-geometry';
@@ -15,21 +17,9 @@ export async function POST(
1517
if (denied) return denied;
1618

1719
const { conflictId } = await params;
18-
const body = await safeJson(req);
20+
const body = await parseBodyWithSchema(req, adminThreatZoneCreateSchema);
1921
if (body instanceof NextResponse) return body;
2022

21-
const missing = assertRequired(body, ['id', 'actor', 'priority', 'category', 'type']);
22-
if (missing) return err('VALIDATION', missing);
23-
24-
const actorErr = assertEnum(body.actor, MAP_ACTOR_KEYS, 'actor');
25-
if (actorErr) return err('VALIDATION', actorErr);
26-
27-
const priorityErr = assertEnum(body.priority, MAP_PRIORITIES, 'priority');
28-
if (priorityErr) return err('VALIDATION', priorityErr);
29-
30-
const typeErr = assertEnum(body.type, ZONE_TYPES, 'type');
31-
if (typeErr) return err('VALIDATION', typeErr);
32-
3323
const geometry = normalizePolygonGeometry(body.geometry);
3424
if (!geometry) {
3525
return err('VALIDATION', 'Threat zone geometry requires coordinates array');
@@ -64,7 +54,7 @@ export async function POST(
6454
status: body.status ?? null,
6555
timestamp,
6656
geometry,
67-
properties: body.properties ?? {},
57+
properties: toJsonValue(body.properties ?? {}),
6858
},
6959
});
7060

0 commit comments

Comments
 (0)