Files
phaseflow/src/app/api/overrides/route.ts
Petru Paler e4d123704d Implement POST/DELETE /api/overrides endpoints (P1.5)
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>
2026-01-10 19:09:08 +00:00

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 });
});