classroom-storage.ts 2.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384
  1. import { promises as fs } from 'fs';
  2. import path from 'path';
  3. import type { NextRequest } from 'next/server';
  4. import type { Scene, Stage } from '@/lib/types/stage';
  5. export const CLASSROOMS_DIR = path.join(process.cwd(), 'data', 'classrooms');
  6. export const CLASSROOM_JOBS_DIR = path.join(process.cwd(), 'data', 'classroom-jobs');
  7. async function ensureDir(dir: string) {
  8. await fs.mkdir(dir, { recursive: true });
  9. }
  10. export async function ensureClassroomsDir() {
  11. await ensureDir(CLASSROOMS_DIR);
  12. }
  13. export async function ensureClassroomJobsDir() {
  14. await ensureDir(CLASSROOM_JOBS_DIR);
  15. }
  16. export async function writeJsonFileAtomic(filePath: string, data: unknown) {
  17. const dir = path.dirname(filePath);
  18. await ensureDir(dir);
  19. const tempFilePath = `${filePath}.${process.pid}.${Date.now()}.tmp`;
  20. const content = JSON.stringify(data, null, 2);
  21. await fs.writeFile(tempFilePath, content, 'utf-8');
  22. await fs.rename(tempFilePath, filePath);
  23. }
  24. export function buildRequestOrigin(req: NextRequest): string {
  25. return req.headers.get('x-forwarded-host')
  26. ? `${req.headers.get('x-forwarded-proto') || 'http'}://${req.headers.get('x-forwarded-host')}`
  27. : req.nextUrl.origin;
  28. }
  29. export interface PersistedClassroomData {
  30. id: string;
  31. stage: Stage;
  32. scenes: Scene[];
  33. createdAt: string;
  34. }
  35. export function isValidClassroomId(id: string): boolean {
  36. return /^[a-zA-Z0-9_-]+$/.test(id);
  37. }
  38. export async function readClassroom(id: string): Promise<PersistedClassroomData | null> {
  39. const filePath = path.join(CLASSROOMS_DIR, `${id}.json`);
  40. try {
  41. const content = await fs.readFile(filePath, 'utf-8');
  42. return JSON.parse(content) as PersistedClassroomData;
  43. } catch (error) {
  44. if ((error as NodeJS.ErrnoException).code === 'ENOENT') {
  45. return null;
  46. }
  47. throw error;
  48. }
  49. }
  50. export async function persistClassroom(
  51. data: {
  52. id: string;
  53. stage: Stage;
  54. scenes: Scene[];
  55. },
  56. baseUrl: string,
  57. ): Promise<PersistedClassroomData & { url: string }> {
  58. const classroomData: PersistedClassroomData = {
  59. id: data.id,
  60. stage: data.stage,
  61. scenes: data.scenes,
  62. createdAt: new Date().toISOString(),
  63. };
  64. await ensureClassroomsDir();
  65. const filePath = path.join(CLASSROOMS_DIR, `${data.id}.json`);
  66. await writeJsonFileAtomic(filePath, classroomData);
  67. return {
  68. ...classroomData,
  69. url: `${baseUrl}/classroom/${data.id}`,
  70. };
  71. }