import { NextRequest, NextResponse } from "next/server"; import { getCurrentUser } from "@/lib/auth"; import { prisma, getCaloriesBurned, setCaloriesBurned } from "@/lib/prisma"; import { z } from "zod"; // GET: Get workout by ID export async function GET( _request: NextRequest, { params }: { params: { id: string } } ) { try { const user = await getCurrentUser(); if (!user) { return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); } const workout = await prisma.workout.findUnique({ where: { id: params.id }, include: { setLogs: { include: { exercise: true, }, orderBy: { setNumber: "asc", }, }, }, }); if (!workout) { return NextResponse.json({ error: "Workout not found" }, { status: 404 }); } if (workout.userId !== user.id) { return NextResponse.json({ error: "Unauthorized" }, { status: 403 }); } // Prisma client doesn't know about caloriesBurned — fetch via raw SQL const caloriesBurned = await getCaloriesBurned(workout.id); return NextResponse.json({ ...workout, caloriesBurned }); } catch (error) { console.error("Failed to fetch workout:", error); return NextResponse.json( { error: "Failed to fetch workout" }, { status: 500 } ); } } // PATCH: Update workout — supports metadata-only or full update with sets const setSchema = z.object({ exerciseId: z.string().min(1), setNumber: z.number().int().positive(), reps: z.number().int().positive().optional().nullable(), weight: z.number().optional().nullable(), weightUnit: z.string().default("lbs"), rpe: z.number().int().min(1).max(10).optional().nullable(), notes: z.string().optional().nullable(), }); const updateWorkoutSchema = z.object({ name: z.string().optional(), notes: z.string().optional().nullable(), date: z.string().optional(), // ISO date string durationMinutes: z.number().int().positive().optional().nullable(), difficulty: z.number().int().min(1).max(10).optional().nullable(), caloriesBurned: z.number().int().positive().optional().nullable(), sets: z.array(setSchema).optional(), // if provided, replaces all sets }); export async function PATCH( request: NextRequest, { params }: { params: { id: string } } ) { try { const user = await getCurrentUser(); if (!user) { return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); } const workout = await prisma.workout.findUnique({ where: { id: params.id }, select: { userId: true }, }); if (!workout) { return NextResponse.json({ error: "Workout not found" }, { status: 404 }); } if (workout.userId !== user.id) { return NextResponse.json({ error: "Unauthorized" }, { status: 403 }); } const body = await request.json(); const validated = updateWorkoutSchema.parse(body); // Extract caloriesBurned separately — handled via raw SQL const caloriesValue = validated.caloriesBurned; const hasCaloriesUpdate = validated.caloriesBurned !== undefined; // Build the Prisma-compatible workout update data (no caloriesBurned) const workoutData: Record = {}; if (validated.name !== undefined) workoutData.name = validated.name; if (validated.notes !== undefined) workoutData.notes = validated.notes || null; if (validated.date !== undefined) workoutData.date = new Date(validated.date); if (validated.durationMinutes !== undefined) workoutData.durationMinutes = validated.durationMinutes; if (validated.difficulty !== undefined) workoutData.difficulty = validated.difficulty; // If sets are provided, do a full replace inside a transaction if (validated.sets) { const result = await prisma.$transaction(async (tx) => { // Update workout metadata (without caloriesBurned) if (Object.keys(workoutData).length > 0) { await tx.workout.update({ where: { id: params.id }, data: workoutData }); } // Delete all existing sets await tx.setLog.deleteMany({ where: { workoutId: params.id }, }); // Create new sets if (validated.sets!.length > 0) { await tx.setLog.createMany({ data: validated.sets!.map((set) => ({ workoutId: params.id, exerciseId: set.exerciseId, setNumber: set.setNumber, reps: set.reps ?? undefined, weight: set.weight ?? undefined, weightUnit: set.weightUnit, rpe: set.rpe ?? undefined, notes: set.notes ?? undefined, } as any)), }); } // Return full updated workout return tx.workout.findUnique({ where: { id: params.id }, include: { setLogs: { include: { exercise: true }, orderBy: { setNumber: "asc" }, }, }, }); }); // Update caloriesBurned via raw SQL (outside transaction since Prisma doesn't know this column) if (hasCaloriesUpdate) { await setCaloriesBurned(params.id, caloriesValue ?? null); } const calories = await getCaloriesBurned(params.id); return NextResponse.json({ ...result, caloriesBurned: calories }); } // Metadata-only update if (Object.keys(workoutData).length > 0) { await prisma.workout.update({ where: { id: params.id }, data: workoutData, }); } // Update caloriesBurned via raw SQL if (hasCaloriesUpdate) { await setCaloriesBurned(params.id, caloriesValue ?? null); } const updated = await prisma.workout.findUnique({ where: { id: params.id }, include: { setLogs: { include: { exercise: true }, orderBy: { setNumber: "asc" }, }, }, }); const calories = await getCaloriesBurned(params.id); return NextResponse.json({ ...updated, caloriesBurned: calories }); } catch (error) { if (error instanceof z.ZodError) { return NextResponse.json( { error: "Invalid data", details: error.errors }, { status: 400 } ); } console.error("Failed to update workout:", error); return NextResponse.json( { error: "Failed to update workout" }, { status: 500 } ); } } // DELETE: Delete workout export async function DELETE( _request: NextRequest, { params }: { params: { id: string } } ) { try { const user = await getCurrentUser(); if (!user) { return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); } const workout = await prisma.workout.findUnique({ where: { id: params.id }, select: { userId: true }, }); if (!workout) { return NextResponse.json({ error: "Workout not found" }, { status: 404 }); } if (workout.userId !== user.id) { return NextResponse.json({ error: "Unauthorized" }, { status: 403 }); } await prisma.workout.delete({ where: { id: params.id }, }); return NextResponse.json({ success: true }); } catch (error) { console.error("Failed to delete workout:", error); return NextResponse.json( { error: "Failed to delete workout" }, { status: 500 } ); } }