Nelze vybrat více než 25 témat Téma musí začínat písmenem nebo číslem, může obsahovat pomlčky („-“) a může být dlouhé až 35 znaků.
 
 

72 řádky
2.3 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({ error: "Binario 'zip' non disponibile sul server." }, { status: 503 });
  31. }
  32. await mkdir(FACTORY_DIR, { recursive: true });
  33. // Scrive prima su .tmp poi rename atomico → mai un preset.zip parziale leggibile.
  34. const tmpPath = path.join(FACTORY_DIR, `preset.zip.${crypto.randomUUID()}.tmp`);
  35. const child = spawn(
  36. 'zip',
  37. ['-r', '-', 'cards.txt', 'portals.txt', 'uploads', 'fonts', '-x', 'uploads/.tmp/*'],
  38. { cwd: DATA_DIR },
  39. );
  40. const out = createWriteStream(tmpPath);
  41. child.stdout.pipe(out);
  42. const exit: number = await new Promise(resolve => {
  43. let exitCode = -1;
  44. child.on('error', () => resolve(-1));
  45. child.on('exit', code => { exitCode = code ?? -1; });
  46. out.on('finish', () => resolve(exitCode));
  47. out.on('error', () => resolve(-1));
  48. });
  49. // 0 = ok, 12 = "nothing to do" (data/ vuoto: accettiamo, sarà uno zip minimale)
  50. if (exit !== 0 && exit !== 12) {
  51. try { await unlink(tmpPath); } catch { /* ignore */ }
  52. return NextResponse.json({ error: `zip terminato con codice ${exit}` }, { status: 500 });
  53. }
  54. await rename(tmpPath, PRESET_PATH);
  55. const s = await stat(PRESET_PATH);
  56. return NextResponse.json({
  57. ok: true,
  58. sizeBytes: s.size,
  59. modifiedAt: s.mtime.toISOString(),
  60. });
  61. }