| import { promises as fs } from 'fs'; |
| import path from 'path'; |
| import type { NextRequest } from 'next/server'; |
| import type { Scene, Stage } from '@/lib/types/stage'; |
|
|
| export const CLASSROOMS_DIR = path.join(process.cwd(), 'data', 'classrooms'); |
| export const CLASSROOM_JOBS_DIR = path.join(process.cwd(), 'data', 'classroom-jobs'); |
|
|
| async function ensureDir(dir: string) { |
| await fs.mkdir(dir, { recursive: true }); |
| } |
|
|
| export async function ensureClassroomsDir() { |
| await ensureDir(CLASSROOMS_DIR); |
| } |
|
|
| export async function ensureClassroomJobsDir() { |
| await ensureDir(CLASSROOM_JOBS_DIR); |
| } |
|
|
| export async function writeJsonFileAtomic(filePath: string, data: unknown) { |
| const dir = path.dirname(filePath); |
| await ensureDir(dir); |
|
|
| const tempFilePath = `${filePath}.${process.pid}.${Date.now()}.tmp`; |
| const content = JSON.stringify(data, null, 2); |
| await fs.writeFile(tempFilePath, content, 'utf-8'); |
| await fs.rename(tempFilePath, filePath); |
| } |
|
|
| export function buildRequestOrigin(req: NextRequest): string { |
| return req.headers.get('x-forwarded-host') |
| ? `${req.headers.get('x-forwarded-proto') || 'http'}://${req.headers.get('x-forwarded-host')}` |
| : req.nextUrl.origin; |
| } |
|
|
| export interface PersistedClassroomData { |
| id: string; |
| stage: Stage; |
| scenes: Scene[]; |
| createdAt: string; |
| } |
|
|
| export function isValidClassroomId(id: string): boolean { |
| return /^[a-zA-Z0-9_-]+$/.test(id); |
| } |
|
|
| export async function readClassroom(id: string): Promise<PersistedClassroomData | null> { |
| const filePath = path.join(CLASSROOMS_DIR, `${id}.json`); |
| try { |
| const content = await fs.readFile(filePath, 'utf-8'); |
| return JSON.parse(content) as PersistedClassroomData; |
| } catch (error) { |
| if ((error as NodeJS.ErrnoException).code === 'ENOENT') { |
| return null; |
| } |
| throw error; |
| } |
| } |
|
|
| export async function persistClassroom( |
| data: { |
| id: string; |
| stage: Stage; |
| scenes: Scene[]; |
| }, |
| baseUrl: string, |
| ): Promise<PersistedClassroomData & { url: string }> { |
| const classroomData: PersistedClassroomData = { |
| id: data.id, |
| stage: data.stage, |
| scenes: data.scenes, |
| createdAt: new Date().toISOString(), |
| }; |
|
|
| await ensureClassroomsDir(); |
| const filePath = path.join(CLASSROOMS_DIR, `${data.id}.json`); |
| await writeJsonFileAtomic(filePath, classroomData); |
|
|
| return { |
| ...classroomData, |
| url: `${baseUrl}/classroom/${data.id}`, |
| }; |
| } |
|
|