Files
phaseflow/src/app/api/cycle/period/route.ts
Petru Paler 714194f2d3 Implement structured logging for API routes (P3.7)
Replace console.error with pino structured logger across API routes
and add key event logging per observability spec:
- Auth failure (warn): reason
- Period logged (info): userId, date
- Override toggled (info): userId, override, enabled
- Decision calculated (info): userId, decision, reason
- Error events (error): err object with stack trace

Files updated:
- auth-middleware.ts: Added structured logging for auth failures
- cycle/period/route.ts: Added Period logged event + error logging
- calendar/[userId]/[token].ics/route.ts: Replaced console.error
- overrides/route.ts: Added Override toggled events
- today/route.ts: Added Decision calculated event

Tests: 720 passing (added 3 new structured logging tests)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-11 09:19:55 +00:00

101 lines
2.8 KiB
TypeScript

// ABOUTME: API route for logging period start dates.
// ABOUTME: Recalculates all phase dates when period is logged.
import type { NextRequest } from "next/server";
import { NextResponse } from "next/server";
import { withAuth } from "@/lib/auth-middleware";
import { getCycleDay, getPhase } from "@/lib/cycle";
import { logger } from "@/lib/logger";
import { createPocketBaseClient } from "@/lib/pocketbase";
interface PeriodLogRequest {
startDate?: string;
}
/**
* Validates a date string is in YYYY-MM-DD format and represents a valid date.
*/
function isValidDateFormat(dateStr: string): boolean {
const regex = /^\d{4}-\d{2}-\d{2}$/;
if (!regex.test(dateStr)) {
return false;
}
const date = new Date(dateStr);
return !Number.isNaN(date.getTime());
}
/**
* Checks if a date is in the future (after today).
*/
function isFutureDate(dateStr: string): boolean {
const inputDate = new Date(dateStr);
const today = new Date();
today.setHours(0, 0, 0, 0);
inputDate.setHours(0, 0, 0, 0);
return inputDate > today;
}
export const POST = withAuth(async (request: NextRequest, user) => {
try {
const body = (await request.json()) as PeriodLogRequest;
// Validate startDate is present
if (!body.startDate) {
return NextResponse.json(
{ error: "startDate is required" },
{ status: 400 },
);
}
// Validate date format
if (!isValidDateFormat(body.startDate)) {
return NextResponse.json(
{ error: "Invalid date format. Use YYYY-MM-DD" },
{ status: 400 },
);
}
// Validate date is not in the future
if (isFutureDate(body.startDate)) {
return NextResponse.json(
{ error: "startDate cannot be in the future" },
{ status: 400 },
);
}
const pb = createPocketBaseClient();
// Update user's lastPeriodDate
await pb.collection("users").update(user.id, {
lastPeriodDate: body.startDate,
});
// Create PeriodLog record
await pb.collection("period_logs").create({
user: user.id,
startDate: body.startDate,
});
// Calculate updated cycle information
const lastPeriodDate = new Date(body.startDate);
const cycleDay = getCycleDay(lastPeriodDate, user.cycleLength, new Date());
const phase = getPhase(cycleDay);
// Log successful period logging per observability spec
logger.info({ userId: user.id, date: body.startDate }, "Period logged");
return NextResponse.json({
message: "Period start date logged successfully",
lastPeriodDate: body.startDate,
cycleDay,
phase,
});
} catch (error) {
logger.error({ err: error, userId: user.id }, "Period logging error");
return NextResponse.json(
{ error: "Failed to update period date" },
{ status: 500 },
);
}
});