|
- import { NextResponse } from 'next/server';
- import { spawn } from 'node:child_process';
- import path from 'node:path';
- import { checkSystemBin } from '@/lib/system-bins';
-
- export const dynamic = 'force-dynamic';
- export const maxDuration = 600;
-
- const DATA_DIR = path.join(process.cwd(), 'data');
-
- function timestamp(): string {
- const d = new Date();
- const pad = (n: number) => String(n).padStart(2, '0');
- return `${d.getFullYear()}${pad(d.getMonth() + 1)}${pad(d.getDate())}-${pad(d.getHours())}${pad(d.getMinutes())}${pad(d.getSeconds())}`;
- }
-
- export async function GET() {
- if (!(await checkSystemBin('zip', '-v'))) {
- return NextResponse.json(
- { error: "Cannot create the backup: the 'zip' command is not installed on the server. Ask the administrator to install it." },
- { status: 503 },
- );
- }
-
- // -r ricorsivo, - stdout, -x esclude pattern relativi al cwd.
- // Stato runtime escluso: .tmp/, transcode-jobs.json.
- const child = spawn(
- 'zip',
- [
- '-r', '-',
- 'cards.txt', 'portals.txt', 'uploads', 'fonts',
- '-x', 'uploads/.tmp/*',
- ],
- { cwd: DATA_DIR },
- );
-
- // Adatta stdout di Node a Web ReadableStream per la Response di Next.
- const stream = new ReadableStream<Uint8Array>({
- start(controller) {
- child.stdout.on('data', (chunk: Buffer) => controller.enqueue(new Uint8Array(chunk)));
- child.stdout.on('end', () => controller.close());
- child.on('error', (err) => controller.error(err));
- child.on('exit', code => {
- if (code !== 0 && code !== null) {
- // 12 = "nothing to do" (cartella vuota). Tutto il resto è errore vero.
- if (code !== 12) controller.error(new Error(`zip exited with code ${code}`));
- }
- });
- },
- cancel() {
- try { child.kill('SIGTERM'); } catch { /* ignore */ }
- },
- });
-
- return new Response(stream, {
- headers: {
- 'Content-Type': 'application/zip',
- 'Content-Disposition': `attachment; filename="interceptor-backup-${timestamp()}.zip"`,
- 'Cache-Control': 'no-store',
- },
- });
- }
|