import { type NextRequest } from 'next/server'; import { randomUUID } from 'crypto'; import { apiSuccess, apiError, API_ERROR_CODES } from '@/lib/server/api-response'; import { buildRequestOrigin, isValidClassroomId, persistClassroom, readClassroom, } from '@/lib/server/classroom-storage'; import { createLogger } from '@/lib/logger'; import { mayAccessRawClassroom } from '@/lib/server/published-classroom-access'; import { authorizeClassroomCreation, authorizeClassroomRequest, classroomAccessError, } from '@/lib/server/authz/classroom-access'; import type { PersistClassroomOwnership } from '@/lib/server/classroom-storage'; const log = createLogger('Classroom API'); export async function POST(request: NextRequest) { let stageId: string | undefined; let sceneCount: number | undefined; try { const body = await request.json(); const { stage, scenes } = body; stageId = stage?.id; sceneCount = scenes?.length; if (!stage || typeof stage !== 'object' || !Array.isArray(scenes)) { return apiError( API_ERROR_CODES.MISSING_REQUIRED_FIELD, 400, 'Missing required fields: stage, scenes', ); } const id = stage.id || randomUUID(); if (!isValidClassroomId(id)) { return apiError(API_ERROR_CODES.INVALID_REQUEST, 400, 'Invalid classroom id'); } if (!(await mayAccessRawClassroom(id))) { return apiError(API_ERROR_CODES.INVALID_REQUEST, 404, 'Classroom not found'); } const existing = await readClassroom(id); let ownership: PersistClassroomOwnership; if (existing) { const access = await authorizeClassroomRequest(request, existing, 'write'); if (!access.allowed) return classroomAccessError(access); ownership = { ...(existing.ownerPrincipalId ? { ownerPrincipalId: existing.ownerPrincipalId } : {}), ...(existing.guestPrincipalId ? { guestPrincipalId: existing.guestPrincipalId } : {}), ...(existing.ownershipBoundAt ? { ownershipBoundAt: existing.ownershipBoundAt } : {}), ...(existing.ownershipMigratedAt ? { ownershipMigratedAt: existing.ownershipMigratedAt } : {}), }; } else { const access = await authorizeClassroomCreation(request); if (!access.allowed) return classroomAccessError(access); ownership = access.ownership; } const baseUrl = buildRequestOrigin(request); const persisted = await persistClassroom( { id, stage: { ...stage, id }, scenes }, baseUrl, ownership, ); return apiSuccess({ id: persisted.id, url: persisted.url }, 201); } catch (error) { log.error( `Classroom storage failed [stageId=${stageId ?? 'unknown'}, scenes=${sceneCount ?? 0}]:`, error, ); return apiError( API_ERROR_CODES.INTERNAL_ERROR, 500, 'Failed to store classroom', error instanceof Error ? error.message : String(error), ); } } export async function GET(request: NextRequest) { try { const id = request.nextUrl.searchParams.get('id'); if (!id) { return apiError( API_ERROR_CODES.MISSING_REQUIRED_FIELD, 400, 'Missing required parameter: id', ); } if (!isValidClassroomId(id)) { return apiError(API_ERROR_CODES.INVALID_REQUEST, 400, 'Invalid classroom id'); } if (!(await mayAccessRawClassroom(id))) { return apiError(API_ERROR_CODES.INVALID_REQUEST, 404, 'Classroom not found'); } const classroom = await readClassroom(id); if (!classroom) { return apiError(API_ERROR_CODES.INVALID_REQUEST, 404, 'Classroom not found'); } const access = await authorizeClassroomRequest(request, classroom, 'read'); if (!access.allowed) return classroomAccessError(access); return apiSuccess({ classroom }); } catch (error) { log.error( `Classroom retrieval failed [id=${request.nextUrl.searchParams.get('id') ?? 'unknown'}]:`, error, ); return apiError( API_ERROR_CODES.INTERNAL_ERROR, 500, 'Failed to retrieve classroom', error instanceof Error ? error.message : String(error), ); } }