import { NextRequest, NextResponse } from "next/server"; import { auth } from "@clerk/nextjs/server"; import { getDatabase } from "@/lib/database"; import log from "@/lib/logger"; import { legacyRecommendationSchema, recommendationUpdateRequestSchema, } from "@/lib/validation/schemas"; import { validateRequestBody, validationErrorResponse, } from "@/lib/validation/helpers"; import { successResponse, unauthorizedResponse, forbiddenResponse, badRequestResponse, internalErrorResponse, } from "@/lib/api/responses"; import { getRecommendationsByGym } from "@/lib/gym-context"; import { ensureUserSynced } from "@/lib/sync-user"; export async function GET(request: NextRequest) { try { const { userId: currentUserId } = await auth(); if (!currentUserId) { return unauthorizedResponse(); } const db = await getDatabase(); const currentUser = await ensureUserSynced(currentUserId, db); if (!currentUser) { return forbiddenResponse("User not found"); } const { searchParams } = new URL(request.url); const targetUserId = searchParams.get("userId"); // If no userId provided, staff gets gym-scoped recommendations if (!targetUserId) { const isStaff = currentUser.role === "admin" || currentUser.role === "superAdmin" || currentUser.role === "trainer"; if (!isStaff) { return badRequestResponse("User ID is required"); } // Get target gym based on role const targetGymId = currentUser.role === "superAdmin" ? (searchParams.get("gymId") ?? undefined) : (currentUser.gymId ?? undefined); // Get recommendations filtered by gym const recommendations = targetGymId ? await getRecommendationsByGym(targetGymId) : await db.getAllRecommendations(); return successResponse({ recommendations }); } // Check permissions: Users can view their own, Admins/Trainers can view anyone's in their gym const isStaff = currentUser.role === "admin" || currentUser.role === "superAdmin" || currentUser.role === "trainer"; if (currentUserId !== targetUserId) { if (!isStaff) { return forbiddenResponse(); } // Staff need to verify target user is in same gym if (currentUser.role !== "superAdmin") { const targetUser = await db.getUserById(targetUserId); if (!targetUser || targetUser.gymId !== currentUser.gymId) { return forbiddenResponse("Cannot access users from other gyms"); } } } let recommendations = await db.getRecommendationsByUserId(targetUserId); // Non-staff users should only see approved recommendations if (!isStaff) { recommendations = recommendations.filter( (rec: any) => rec.status === "approved", ); } return successResponse(recommendations); } catch (error) { console.error("Error fetching recommendations:", error); return internalErrorResponse(); } } export async function POST(request: NextRequest) { try { const { userId: currentUserId } = await auth(); if (!currentUserId) { return unauthorizedResponse(); } const db = await getDatabase(); const currentUser = await ensureUserSynced(currentUserId, db); const isStaff = currentUser?.role === "admin" || currentUser?.role === "superAdmin" || currentUser?.role === "trainer"; if (!isStaff) { return forbiddenResponse(); } // Validate request body const validation = await validateRequestBody( request, legacyRecommendationSchema, ); if (!validation.success) { log.warn("Recommendation creation validation failed", { errors: validation.errors, }); return validationErrorResponse(validation.errors); } const { userId, fitnessProfileId, recommendationText, activityPlan, dietPlan, status, type, content, } = validation.data; const targetUser = await db.getUserById(userId); if (!targetUser) { return badRequestResponse("Target user not found"); } if ( currentUser?.role !== "superAdmin" && (!currentUser?.gymId || targetUser.gymId !== currentUser.gymId) ) { return forbiddenResponse("Cannot create recommendations for other gyms"); } // Handle AI Plan (Legacy/Specific) if (recommendationText && activityPlan && dietPlan && fitnessProfileId) { const recommendation = await db.createRecommendation({ id: crypto.randomUUID(), userId, fitnessProfileId, type: "ai_plan", recommendationText: recommendationText, activityPlan, dietPlan, status: status || "pending", } as any); return successResponse(recommendation); } // Handle User Goal (Generic) if (type && content) { const recommendation = await db.createRecommendation({ id: crypto.randomUUID(), userId, type, recommendationText: content, status: status || "pending", } as any); return successResponse(recommendation); } return badRequestResponse("Missing required fields"); } catch (error) { log.error("Failed to create recommendation", error); return internalErrorResponse(); } } export async function PUT(request: NextRequest) { try { const { userId: currentUserId } = await auth(); if (!currentUserId) { return unauthorizedResponse(); } // Validate request body const validation = await validateRequestBody( request, recommendationUpdateRequestSchema, ); if (!validation.success) { log.warn("Recommendation update validation failed", { errors: validation.errors, }); return validationErrorResponse(validation.errors); } const { id, status, recommendationText, activityPlan, dietPlan, content } = validation.data; const db = await getDatabase(); const currentUser = await ensureUserSynced(currentUserId, db); if (!currentUser) { return forbiddenResponse("User not found"); } const isStaff = currentUser.role === "admin" || currentUser.role === "superAdmin" || currentUser.role === "trainer"; if (!isStaff) { return forbiddenResponse(); } const existingRecommendation = (await db.getAllRecommendations()).find( (recommendation) => recommendation.id === id, ); if (!existingRecommendation) { return badRequestResponse("Recommendation not found"); } if (currentUser.role !== "superAdmin") { const targetUser = await db.getUserById(existingRecommendation.userId); if ( !currentUser.gymId || !targetUser || targetUser.gymId !== currentUser.gymId ) { return forbiddenResponse( "Cannot modify recommendations for other gyms", ); } } const updated = await db.updateRecommendation(id, { ...(status && { status }), ...(recommendationText && { recommendationText }), ...(content && { recommendationText: content }), ...(activityPlan && { activityPlan }), ...(dietPlan && { dietPlan }), } as any); return successResponse(updated); } catch (error) { log.error("Failed to update recommendation", error); return internalErrorResponse(); } }