chore: update server port variable to uppercase for clarity and consistency

This commit is contained in:
Mathias
2026-03-28 21:22:29 +02:00
parent 15360f2027
commit 0a28510945
8 changed files with 315 additions and 68 deletions
+30 -20
View File
@@ -1,22 +1,20 @@
import { NextRequest, NextResponse } from "next/server";
import { headers } from "next/headers";
import { prisma } from "@/shared/lib/prisma";
import { enrollInProgram } from "@/features/programs/actions/enroll-program.action";
import { auth } from "@/features/auth/lib/better-auth";
import { getMobileCompatibleSession } from "@/shared/api/mobile-auth";
export async function POST(req: NextRequest, { params }: { params: Promise<{ slug: string }> }) {
try {
const { slug } = await params;
const session = await auth.api.getSession({
headers: await headers(),
});
const session = await getMobileCompatibleSession(req);
if (!session) {
if (!session?.user) {
return NextResponse.json({ error: "Unauthorized", code: "UNAUTHORIZED" }, { status: 401 });
}
const userId = session.user.id;
const program = await prisma.program.findUnique({
where: { slug },
select: { id: true, participantCount: true },
@@ -26,17 +24,34 @@ export async function POST(req: NextRequest, { params }: { params: Promise<{ slu
return NextResponse.json({ error: "Program not found", code: "NOT_FOUND" }, { status: 404 });
}
const result = await enrollInProgram(program.id);
// Check if already enrolled
const existingEnrollment = await prisma.userProgramEnrollment.findUnique({
where: { userId_programId: { userId, programId: program.id } },
});
const updatedProgram = await prisma.program.findUnique({
if (existingEnrollment) {
return NextResponse.json({
enrollment: existingEnrollment,
isNew: false,
totalEnrollments: program.participantCount,
});
}
// Create enrollment + increment participant count
const enrollment = await prisma.userProgramEnrollment.create({
data: { userId, programId: program.id },
});
const updatedProgram = await prisma.program.update({
where: { id: program.id },
data: { participantCount: { increment: 1 } },
select: { participantCount: true },
});
return NextResponse.json({
enrollment: result.enrollment,
isNew: result.isNew,
totalEnrollments: updatedProgram?.participantCount || program.participantCount,
enrollment,
isNew: true,
totalEnrollments: updatedProgram.participantCount,
});
} catch (error) {
console.error("Error enrolling in program:", error);
@@ -51,20 +66,15 @@ export async function POST(req: NextRequest, { params }: { params: Promise<{ slu
export async function GET(req: NextRequest, { params }: { params: Promise<{ slug: string }> }) {
try {
const session = await auth.api.getSession({
headers: await headers(),
});
const session = await getMobileCompatibleSession(req);
const { slug } = await params;
if (!session) {
if (!session?.user) {
return NextResponse.json({ error: "Unauthorized", code: "UNAUTHORIZED" }, { status: 401 });
}
const userId = session.user?.id;
if (!userId) {
return NextResponse.json({ error: "User not found", code: "USER_NOT_FOUND" }, { status: 404 });
}
const userId = session.user.id;
const program = await prisma.program.findUnique({
where: { slug },
+61 -9
View File
@@ -1,18 +1,70 @@
import { NextResponse } from "next/server";
import { NextRequest, NextResponse } from "next/server";
import { ProgramVisibility } from "@prisma/client";
import { getProgramProgressBySlug } from "@/features/programs/actions/get-program-progress-by-slug.action";
import { prisma } from "@/shared/lib/prisma";
import { getMobileCompatibleSession } from "@/shared/api/mobile-auth";
export async function GET(request: Request, { params }: { params: Promise<{ slug: string }> }) {
export async function GET(request: NextRequest, { params }: { params: Promise<{ slug: string }> }) {
try {
const { slug } = await params;
const session = await getMobileCompatibleSession(request);
const progress = await getProgramProgressBySlug(slug);
if (!progress) {
return NextResponse.json({ error: "Program progress not found" }, { status: 404 });
if (!session?.user?.id) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
return NextResponse.json(progress);
const userId = session.user.id;
const { slug } = await params;
const program = await prisma.program.findFirst({
where: {
OR: [{ slug }, { slugEn: slug }, { slugEs: slug }, { slugPt: slug }, { slugRu: slug }, { slugZhCn: slug }],
visibility: ProgramVisibility.PUBLISHED,
isActive: true,
},
select: { id: true },
});
if (!program) {
return NextResponse.json({ error: "Program not found" }, { status: 404 });
}
const enrollment = await prisma.userProgramEnrollment.findUnique({
where: { userId_programId: { userId, programId: program.id } },
include: {
sessionProgress: {
include: { session: true, workoutSession: true },
},
program: {
include: {
weeks: {
include: { sessions: { orderBy: { sessionNumber: "asc" } } },
orderBy: { weekNumber: "asc" },
},
},
},
},
});
if (!enrollment) {
return NextResponse.json(null);
}
const totalSessions = enrollment.program.weeks.reduce((acc, week) => acc + week.sessions.length, 0);
const completedSessions = enrollment.sessionProgress.filter((p) => p.completedAt !== null).length;
const completionPercentage = totalSessions > 0 ? Math.round((completedSessions / totalSessions) * 100) : 0;
const isProgramCompleted = totalSessions > 0 && completedSessions === totalSessions;
return NextResponse.json({
enrollment,
stats: {
totalSessions,
completedSessions,
completionPercentage,
currentWeek: enrollment.currentWeek,
currentSession: enrollment.currentSession,
isProgramCompleted,
},
});
} catch (error) {
console.error("Error fetching program progress:", error);
return NextResponse.json({ error: "Failed to fetch program progress" }, { status: 500 });
@@ -1,9 +1,17 @@
import { NextRequest, NextResponse } from "next/server";
import { completeProgramSession } from "@/features/programs/actions/complete-program-session.action";
import { prisma } from "@/shared/lib/prisma";
import { getMobileCompatibleSession } from "@/shared/api/mobile-auth";
export async function POST(request: NextRequest, { params }: { params: Promise<{ progressId: string }> }) {
try {
const session = await getMobileCompatibleSession(request);
if (!session?.user?.id) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
const userId = session.user.id;
const { progressId } = await params;
const body = await request.json();
const { workoutSessionId } = body;
@@ -12,25 +20,79 @@ export async function POST(request: NextRequest, { params }: { params: Promise<{
return NextResponse.json({ error: "Missing workout session ID" }, { status: 400 });
}
// Use the existing server action
const result = await completeProgramSession(progressId, workoutSessionId);
// Get session progress with enrollment and program structure
const sessionProgress = await prisma.userSessionProgress.findUnique({
where: { id: progressId },
include: {
enrollment: {
include: {
program: {
include: {
weeks: {
include: { sessions: { orderBy: { sessionNumber: "asc" } } },
orderBy: { weekNumber: "asc" },
},
},
},
},
},
session: { include: { week: true } },
},
});
return NextResponse.json(result);
} catch (error: any) {
console.error("Error completing session progress:", error);
if (error.message === "Unauthorized") {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
if (error.message === "User not found") {
return NextResponse.json({ error: "User not found" }, { status: 401 });
}
if (error.message === "Session progress not found") {
if (!sessionProgress || sessionProgress.enrollment.userId !== userId) {
return NextResponse.json({ error: "Session progress not found" }, { status: 404 });
}
// Mark session as completed
const updatedProgress = await prisma.userSessionProgress.update({
where: { id: progressId },
data: { completedAt: new Date(), workoutSessionId },
});
// Count completed sessions
const completedSessionsCount = await prisma.userSessionProgress.count({
where: { enrollmentId: sessionProgress.enrollment.id, completedAt: { not: null } },
});
// Find next session
const currentWeek = sessionProgress.session.week.weekNumber;
const currentSession = sessionProgress.session.sessionNumber;
let nextWeek = currentWeek;
let nextSession = currentSession + 1;
const currentWeekSessions =
sessionProgress.enrollment.program.weeks.find((w) => w.weekNumber === currentWeek)?.sessions.length || 0;
if (nextSession > currentWeekSessions) {
nextWeek = currentWeek + 1;
nextSession = 1;
}
// Check if program is completed
const totalSessions = sessionProgress.enrollment.program.weeks.reduce((acc, week) => acc + week.sessions.length, 0);
const isCompleted = completedSessionsCount >= totalSessions;
// Update enrollment
await prisma.userProgramEnrollment.update({
where: { id: sessionProgress.enrollment.id },
data: {
completedSessions: completedSessionsCount,
currentWeek: isCompleted ? currentWeek : nextWeek,
currentSession: isCompleted ? currentSession : nextSession,
completedAt: isCompleted ? new Date() : null,
isActive: !isCompleted,
},
});
return NextResponse.json({
sessionProgress: updatedProgress,
isCompleted,
nextWeek: isCompleted ? null : nextWeek,
nextSession: isCompleted ? null : nextSession,
});
} catch (error) {
console.error("Error completing session progress:", error);
return NextResponse.json({ error: "Internal server error" }, { status: 500 });
}
}
@@ -1,9 +1,17 @@
import { NextRequest, NextResponse } from "next/server";
import { startProgramSession } from "@/features/programs/actions/start-program-session.action";
import { prisma } from "@/shared/lib/prisma";
import { getMobileCompatibleSession } from "@/shared/api/mobile-auth";
export async function POST(request: NextRequest) {
try {
const session = await getMobileCompatibleSession(request);
if (!session?.user?.id) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
const userId = session.user.id;
const body = await request.json();
const { enrollmentId, sessionId } = body;
@@ -11,29 +19,71 @@ export async function POST(request: NextRequest) {
return NextResponse.json({ error: "Missing required fields" }, { status: 400 });
}
// Use the existing server action
const result = await startProgramSession(enrollmentId, sessionId);
// Verify enrollment belongs to user
const enrollment = await prisma.userProgramEnrollment.findFirst({
where: { id: enrollmentId, userId },
include: { program: true },
});
return NextResponse.json(result);
} catch (error: any) {
console.error("Error starting session progress:", error);
if (error.message === "Unauthorized") {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
if (error.message === "User not found") {
return NextResponse.json({ error: "User not found" }, { status: 401 });
}
if (error.message === "Enrollment not found") {
if (!enrollment) {
return NextResponse.json({ error: "Enrollment not found" }, { status: 404 });
}
if (error.message === "Session not found") {
// Check if session already started
const existingProgress = await prisma.userSessionProgress.findUnique({
where: { enrollmentId_sessionId: { enrollmentId, sessionId } },
});
if (existingProgress) {
return NextResponse.json({ sessionProgress: existingProgress, isNew: false });
}
// Get session details
const programSession = await prisma.programSession.findUnique({
where: { id: sessionId },
include: {
week: true,
exercises: {
include: {
exercise: {
include: {
attributes: {
include: { attributeName: true, attributeValue: true },
},
},
},
suggestedSets: { orderBy: { setIndex: "asc" } },
},
orderBy: { order: "asc" },
},
},
});
if (!programSession) {
return NextResponse.json({ error: "Session not found" }, { status: 404 });
}
// Create session progress
const sessionProgress = await prisma.userSessionProgress.create({
data: { enrollmentId, sessionId },
});
// Update enrollment current position
await prisma.userProgramEnrollment.update({
where: { id: enrollmentId },
data: {
currentWeek: programSession.week.weekNumber,
currentSession: programSession.sessionNumber,
},
});
return NextResponse.json({
sessionProgress,
isNew: true,
sessionData: programSession,
});
} catch (error) {
console.error("Error starting session progress:", error);
return NextResponse.json({ error: "Internal server error" }, { status: 500 });
}
}
+8 -3
View File
@@ -3,7 +3,7 @@ import { NextRequest, NextResponse } from "next/server";
import { Platform } from "@prisma/client";
import { prisma } from "@/shared/lib/prisma";
import { serverRequiredUser } from "@/entities/user/model/get-server-session-user";
import { getMobileCompatibleSession } from "@/shared/api/mobile-auth";
/**
* Sync RevenueCat Status with Backend (Simplified)
@@ -25,8 +25,13 @@ const syncStatusSchema = z.object({
export async function POST(request: NextRequest) {
try {
// Get authenticated user
const user = await serverRequiredUser();
// Get authenticated user (supports mobile cookie format)
const session = await getMobileCompatibleSession(request);
const user = session?.user;
if (!user) {
return NextResponse.json({ error: "Authentication required" }, { status: 401 });
}
// Parse and validate request body
const body = await request.json();
+65
View File
@@ -0,0 +1,65 @@
import { z } from "zod";
import { NextRequest, NextResponse } from "next/server";
import { prisma } from "@/shared/lib/prisma";
import { getMobileCompatibleSession } from "@/shared/api/mobile-auth";
const preferencesSchema = z.object({
goals: z.array(z.string()),
fitnessLevel: z.enum(["beginner", "intermediate", "advanced"]).nullable(),
equipment: z.array(z.string()),
muscles: z.array(z.string()),
duration: z.number().nullable(),
weeklyFrequency: z.number().min(1).max(7),
notificationDays: z.array(z.number()).optional(),
notificationTime: z.string().optional(),
});
export async function GET(req: NextRequest) {
try {
const session = await getMobileCompatibleSession(req);
if (!session?.user) {
return NextResponse.json({ error: "UNAUTHORIZED" }, { status: 401 });
}
const user = await prisma.user.findUnique({
where: { id: session.user.id },
select: { onboardingPreferences: true },
});
return NextResponse.json({
preferences: user?.onboardingPreferences ?? null,
});
} catch (error) {
console.error("Get preferences error:", error);
return NextResponse.json({ error: "INTERNAL_SERVER_ERROR" }, { status: 500 });
}
}
export async function PUT(req: NextRequest) {
try {
const session = await getMobileCompatibleSession(req);
if (!session?.user) {
return NextResponse.json({ error: "UNAUTHORIZED" }, { status: 401 });
}
const body = await req.json();
const parsed = preferencesSchema.safeParse(body);
if (!parsed.success) {
return NextResponse.json({ error: "INVALID_INPUT", details: parsed.error.format() }, { status: 400 });
}
await prisma.user.update({
where: { id: session.user.id },
data: { onboardingPreferences: parsed.data },
});
return NextResponse.json({ success: true });
} catch (error) {
console.error("Update preferences error:", error);
return NextResponse.json({ error: "INTERNAL_SERVER_ERROR" }, { status: 500 });
}
}
+3
View File
@@ -42,6 +42,9 @@ model User {
licenses License[]
isPremium Boolean? @default(false)
// Onboarding preferences
onboardingPreferences Json?
// Training programs
programEnrollments UserProgramEnrollment[]
@@ -64,7 +64,7 @@ export function WorkoutSessionSets({
} else {
exerciseElement.scrollIntoView({
behavior: "smooth",
block: "center",
block: "start",
});
}
}