Add override management API for the training decision system: - POST /api/overrides adds an override (flare, stress, sleep, pms) - DELETE /api/overrides removes an override - Both endpoints use withAuth middleware - Validation for override types, idempotent operations - 14 tests covering auth, validation, and persistence Also fix type error in today/route.ts where DailyLog body battery fields could be null but biometrics object expected numbers. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
103 lines
2.9 KiB
TypeScript
103 lines
2.9 KiB
TypeScript
// ABOUTME: API route for managing training overrides.
|
|
// ABOUTME: Handles flare, stress, sleep, and PMS override toggles.
|
|
import type { NextRequest } from "next/server";
|
|
import { NextResponse } from "next/server";
|
|
|
|
import { withAuth } from "@/lib/auth-middleware";
|
|
import { createPocketBaseClient } from "@/lib/pocketbase";
|
|
import type { OverrideType } from "@/types";
|
|
|
|
const VALID_OVERRIDE_TYPES: OverrideType[] = [
|
|
"flare",
|
|
"stress",
|
|
"sleep",
|
|
"pms",
|
|
];
|
|
|
|
function isValidOverrideType(value: unknown): value is OverrideType {
|
|
return (
|
|
typeof value === "string" &&
|
|
VALID_OVERRIDE_TYPES.includes(value as OverrideType)
|
|
);
|
|
}
|
|
|
|
/**
|
|
* POST /api/overrides - Add an override to the user's active overrides.
|
|
* Request body: { override: OverrideType }
|
|
* Response: { activeOverrides: OverrideType[] }
|
|
*/
|
|
export const POST = withAuth(async (request: NextRequest, user) => {
|
|
const body = await request.json();
|
|
|
|
if (!body.override) {
|
|
return NextResponse.json(
|
|
{ error: "Missing required field: override" },
|
|
{ status: 400 },
|
|
);
|
|
}
|
|
|
|
if (!isValidOverrideType(body.override)) {
|
|
return NextResponse.json(
|
|
{
|
|
error: `Invalid override type: ${body.override}. Valid types: ${VALID_OVERRIDE_TYPES.join(", ")}`,
|
|
},
|
|
{ status: 400 },
|
|
);
|
|
}
|
|
|
|
const overrideToAdd: OverrideType = body.override;
|
|
|
|
// Build the new array, avoiding duplicates
|
|
const currentOverrides = user.activeOverrides || [];
|
|
const newOverrides = currentOverrides.includes(overrideToAdd)
|
|
? currentOverrides
|
|
: [...currentOverrides, overrideToAdd];
|
|
|
|
// Update the user record in PocketBase
|
|
const pb = createPocketBaseClient();
|
|
await pb
|
|
.collection("users")
|
|
.update(user.id, { activeOverrides: newOverrides });
|
|
|
|
return NextResponse.json({ activeOverrides: newOverrides });
|
|
});
|
|
|
|
/**
|
|
* DELETE /api/overrides - Remove an override from the user's active overrides.
|
|
* Request body: { override: OverrideType }
|
|
* Response: { activeOverrides: OverrideType[] }
|
|
*/
|
|
export const DELETE = withAuth(async (request: NextRequest, user) => {
|
|
const body = await request.json();
|
|
|
|
if (!body.override) {
|
|
return NextResponse.json(
|
|
{ error: "Missing required field: override" },
|
|
{ status: 400 },
|
|
);
|
|
}
|
|
|
|
if (!isValidOverrideType(body.override)) {
|
|
return NextResponse.json(
|
|
{
|
|
error: `Invalid override type: ${body.override}. Valid types: ${VALID_OVERRIDE_TYPES.join(", ")}`,
|
|
},
|
|
{ status: 400 },
|
|
);
|
|
}
|
|
|
|
const overrideToRemove: OverrideType = body.override;
|
|
|
|
// Remove the override from the array
|
|
const currentOverrides = user.activeOverrides || [];
|
|
const newOverrides = currentOverrides.filter((o) => o !== overrideToRemove);
|
|
|
|
// Update the user record in PocketBase
|
|
const pb = createPocketBaseClient();
|
|
await pb
|
|
.collection("users")
|
|
.update(user.id, { activeOverrides: newOverrides });
|
|
|
|
return NextResponse.json({ activeOverrides: newOverrides });
|
|
});
|