127 lines
4.1 KiB
TypeScript
127 lines
4.1 KiB
TypeScript
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),
|
|
);
|
|
}
|
|
}
|