263 lines
7.3 KiB
TypeScript
263 lines
7.3 KiB
TypeScript
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();
|
|
}
|
|
}
|