import { NextRequest, NextResponse } from "next/server"; import { z } from "zod"; import { getCurrentUser } from "@/lib/auth"; import { prisma, setCaloriesBurned, getCaloriesBurnedBulk } from "@/lib/prisma"; // Schema now supports creating empty workouts (just date) or with sets const createWorkoutSchema = z.object({ name: z.string().optional(), notes: z.string().optional(), durationMinutes: z.number().int().positive().optional(), difficulty: z.number().int().min(1).max(10).optional(), caloriesBurned: z.number().int().positive().optional(), date: z.string().optional(), // ISO date string or date-only string sets: z .array( z.object({ exerciseId: z.string(), setNumber: z.number().int().positive(), reps: z.number().int().positive().optional(), weight: z.number().positive().optional(), weightUnit: z.string().default("lbs"), rpe: z.number().int().min(1).max(10).optional(), durationSeconds: z.number().int().positive().optional(), distance: z.number().positive().optional(), distanceUnit: z.string().optional(), calories: z.number().int().positive().optional(), notes: z.string().optional(), }) ) .optional() .default([]), }); // GET: List workouts with search/date filters export async function GET(request: NextRequest) { try { const user = await getCurrentUser(); if (!user) { return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); } const searchParams = request.nextUrl.searchParams; const query = searchParams.get("q"); const dateFrom = searchParams.get("dateFrom"); const dateTo = searchParams.get("dateTo"); const limit = Math.min(parseInt(searchParams.get("limit") || "50"), 100); const offset = parseInt(searchParams.get("offset") || "0"); const where: any = { userId: user.id, }; if (query) { where.name = { contains: query, }; } if (dateFrom || dateTo) { where.date = {}; if (dateFrom) { where.date.gte = new Date(dateFrom); } if (dateTo) { const toDate = new Date(dateTo); toDate.setHours(23, 59, 59, 999); where.date.lte = toDate; } } const [workouts, total] = await Promise.all([ prisma.workout.findMany({ where, include: { setLogs: { include: { exercise: true, }, orderBy: { setNumber: "asc", }, }, }, orderBy: { date: "desc", }, take: limit, skip: offset, }), prisma.workout.count({ where }), ]); // Supplement with caloriesBurned from raw SQL const ids = workouts.map((w) => w.id); const caloriesMap = await getCaloriesBurnedBulk(ids); const enriched = workouts.map((w) => ({ ...w, caloriesBurned: caloriesMap[w.id] ?? null, })); return NextResponse.json({ data: enriched, meta: { total, limit, offset, hasMore: offset + limit < total, }, }); } catch (error) { console.error("Failed to fetch workouts:", error); return NextResponse.json( { error: "Failed to fetch workouts" }, { status: 500 } ); } } // POST: Create workout (can be empty or with sets) export async function POST(request: NextRequest) { try { const user = await getCurrentUser(); if (!user) { return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); } const body = await request.json(); const validated = createWorkoutSchema.parse(body); const workoutDate = validated.date ? new Date(validated.date) : new Date(); // Extract caloriesBurned — handled via raw SQL after creation const caloriesValue = validated.caloriesBurned; const createData: any = { userId: user.id, name: validated.name || null, notes: validated.notes, durationMinutes: validated.durationMinutes, difficulty: validated.difficulty, // caloriesBurned handled separately via raw SQL date: workoutDate, setLogs: validated.sets.length > 0 ? { create: validated.sets.map((set) => ({ exerciseId: set.exerciseId, setNumber: set.setNumber, reps: set.reps, weight: set.weight, weightUnit: set.weightUnit, rpe: set.rpe, durationSeconds: set.durationSeconds, distance: set.distance, distanceUnit: set.distanceUnit, calories: set.calories, notes: set.notes, } as any)), } : undefined, }; const includeOpts = { setLogs: { include: { exercise: true }, orderBy: { setNumber: "asc" as const }, }, }; const workout = await prisma.workout.create({ data: createData, include: includeOpts }); // Set caloriesBurned via raw SQL if (caloriesValue !== undefined) { await setCaloriesBurned(workout.id, caloriesValue); } return NextResponse.json({ ...workout, caloriesBurned: caloriesValue ?? null }, { status: 201 }); } catch (error) { if (error instanceof z.ZodError) { return NextResponse.json( { error: "Invalid request data", details: error.errors }, { status: 400 } ); } console.error("Failed to create workout:", error); return NextResponse.json( { error: "Failed to create workout" }, { status: 500 } ); } }