No puede seleccionar más de 25 temas Los temas deben comenzar con una letra o número, pueden incluir guiones ('-') y pueden tener hasta 35 caracteres de largo.
 
 

78 líneas
2.5 KiB

  1. import { NextResponse } from 'next/server';
  2. import { spawn } from 'node:child_process';
  3. import { mkdir, stat, unlink, rename } from 'node:fs/promises';
  4. import { createWriteStream } from 'node:fs';
  5. import path from 'node:path';
  6. import crypto from 'node:crypto';
  7. import { checkSystemBin } from '@/lib/system-bins';
  8. export const dynamic = 'force-dynamic';
  9. export const maxDuration = 600;
  10. const PROJECT_ROOT = process.cwd();
  11. const DATA_DIR = path.join(PROJECT_ROOT, 'data');
  12. const FACTORY_DIR = path.join(PROJECT_ROOT, 'factory');
  13. const PRESET_PATH = path.join(FACTORY_DIR, 'preset.zip');
  14. // GET — stato del preset (per UI: presente o no, dimensione, data).
  15. export async function GET() {
  16. try {
  17. const s = await stat(PRESET_PATH);
  18. return NextResponse.json({
  19. exists: true,
  20. sizeBytes: s.size,
  21. modifiedAt: s.mtime.toISOString(),
  22. });
  23. } catch {
  24. return NextResponse.json({ exists: false });
  25. }
  26. }
  27. // POST — crea il preset zippando lo stato corrente di data/.
  28. export async function POST() {
  29. if (!(await checkSystemBin('zip', '-v'))) {
  30. return NextResponse.json(
  31. { error: "Cannot create the preset: the 'zip' command is not installed on the server. Ask the administrator to install it." },
  32. { status: 503 },
  33. );
  34. }
  35. await mkdir(FACTORY_DIR, { recursive: true });
  36. // Scrive prima su .tmp poi rename atomico → mai un preset.zip parziale leggibile.
  37. const tmpPath = path.join(FACTORY_DIR, `preset.zip.${crypto.randomUUID()}.tmp`);
  38. const child = spawn(
  39. 'zip',
  40. ['-r', '-', 'cards.txt', 'portals.txt', 'uploads', 'fonts', '-x', 'uploads/.tmp/*'],
  41. { cwd: DATA_DIR },
  42. );
  43. const out = createWriteStream(tmpPath);
  44. child.stdout.pipe(out);
  45. const exit: number = await new Promise(resolve => {
  46. let exitCode = -1;
  47. child.on('error', () => resolve(-1));
  48. child.on('exit', code => { exitCode = code ?? -1; });
  49. out.on('finish', () => resolve(exitCode));
  50. out.on('error', () => resolve(-1));
  51. });
  52. // 0 = ok, 12 = "nothing to do" (data/ vuoto: accettiamo, sarà uno zip minimale)
  53. if (exit !== 0 && exit !== 12) {
  54. try { await unlink(tmpPath); } catch { /* ignore */ }
  55. return NextResponse.json(
  56. { error: `Failed to create the preset archive: the 'zip' command exited with code ${exit}. Check the server logs for details. The previous preset (if any) has not been modified.` },
  57. { status: 500 },
  58. );
  59. }
  60. await rename(tmpPath, PRESET_PATH);
  61. const s = await stat(PRESET_PATH);
  62. return NextResponse.json({
  63. ok: true,
  64. sizeBytes: s.size,
  65. modifiedAt: s.mtime.toISOString(),
  66. });
  67. }