// ABOUTME: API route for ICS calendar feed generation. // ABOUTME: Returns subscribable iCal feed with cycle phases and warnings. import { type NextRequest, NextResponse } from "next/server"; import { generateIcsFeed } from "@/lib/ics"; import { logger } from "@/lib/logger"; import { createPocketBaseClient } from "@/lib/pocketbase"; interface RouteParams { params: Promise<{ userId: string; token: string; }>; } export async function GET(_request: NextRequest, { params }: RouteParams) { const { userId, token: rawToken } = await params; // Strip .ics suffix if present (Next.js may include it in the param) const token = rawToken.endsWith(".ics") ? rawToken.slice(0, -4) : rawToken; const pb = createPocketBaseClient(); try { // Fetch user from database const user = await pb.collection("users").getOne(userId); // Check if user has a calendar token set if (!user.calendarToken) { return NextResponse.json( { error: "Unauthorized: Calendar not configured" }, { status: 401 }, ); } // Validate token (case-sensitive comparison) if (user.calendarToken !== token) { return NextResponse.json( { error: "Unauthorized: Invalid token" }, { status: 401 }, ); } // Fetch period logs for prediction accuracy display const periodLogs = await pb.collection("period_logs").getFullList({ filter: `user = "${userId}"`, sort: "-startDate", }); // Generate ICS feed with 90 days of events (3 months) const icsContent = generateIcsFeed({ lastPeriodDate: new Date(user.lastPeriodDate as string), cycleLength: user.cycleLength as number, monthsAhead: 3, periodLogs: periodLogs.map((log) => ({ id: log.id, user: log.user as string, startDate: new Date(log.startDate as string), predictedDate: log.predictedDate ? new Date(log.predictedDate as string) : null, created: new Date(log.created as string), })), }); // Return ICS content with appropriate headers return new NextResponse(icsContent, { status: 200, headers: { "Content-Type": "text/calendar; charset=utf-8", "Cache-Control": "public, max-age=3600", // Cache for 1 hour "Content-Disposition": "attachment; filename=phaseflow-calendar.ics", }, }); } catch (error) { // Check if error is a "not found" error from PocketBase if ( error instanceof Error && (error as unknown as { status?: number }).status === 404 ) { return NextResponse.json({ error: "User not found" }, { status: 404 }); } logger.error({ err: error, userId }, "Calendar feed error"); return NextResponse.json( { error: "Internal server error" }, { status: 500 }, ); } }