|
- import { NextResponse } from 'next/server';
- import { spawn } from 'node:child_process';
- import { mkdir, writeFile, readFile, rename, rm } from 'node:fs/promises';
- import path from 'node:path';
- import crypto from 'node:crypto';
- import { checkSystemBin } from '@/lib/system-bins';
-
- export const dynamic = 'force-dynamic';
- export const maxDuration = 600;
-
- const PROJECT_ROOT = process.cwd();
- const DATA_DIR = path.join(PROJECT_ROOT, 'data');
- const RESTORE_STAGING = path.join(PROJECT_ROOT, '.restore-staging');
-
- function runUnzip(zipPath: string, destDir: string): Promise<{ code: number; stderr: string }> {
- return new Promise(resolve => {
- const child = spawn('unzip', ['-o', '-q', zipPath, '-d', destDir]);
- let stderr = '';
- child.stderr.on('data', d => { stderr += d.toString(); });
- child.on('error', () => resolve({ code: -1, stderr: stderr || 'unzip non avviato' }));
- child.on('exit', code => resolve({ code: code ?? -1, stderr }));
- });
- }
-
- export async function POST(request: Request) {
- if (!(await checkSystemBin('unzip', '-v'))) {
- return NextResponse.json({ error: "Binario 'unzip' non disponibile sul server." }, { status: 503 });
- }
-
- const sessionId = crypto.randomUUID();
- const sessionDir = path.join(RESTORE_STAGING, sessionId);
- const zipPath = path.join(sessionDir, 'backup.zip');
- const extractDir = path.join(sessionDir, 'extract');
-
- try {
- const formData = await request.formData();
- const file = formData.get('file') as File | null;
- if (!file) {
- return NextResponse.json({ error: 'Nessun file ricevuto.' }, { status: 400 });
- }
-
- await mkdir(extractDir, { recursive: true });
- const buf = Buffer.from(await file.arrayBuffer());
- await writeFile(zipPath, buf);
-
- const { code, stderr } = await runUnzip(zipPath, extractDir);
- if (code !== 0) {
- return NextResponse.json(
- { error: 'Impossibile estrarre lo ZIP.', detail: stderr.split('\n').slice(0, 3).join(' ') },
- { status: 400 },
- );
- }
-
- // Validazione struttura: cards.txt e portals.txt devono esistere E essere JSON validi.
- let cardsCount = 0;
- let portalsCount = 0;
- try {
- const cardsRaw = await readFile(path.join(extractDir, 'cards.txt'), 'utf-8');
- const cardsParsed = JSON.parse(cardsRaw || '[]');
- if (!Array.isArray(cardsParsed)) throw new Error('cards.txt non è un array JSON');
- cardsCount = cardsParsed.length;
- } catch (e) {
- return NextResponse.json(
- { error: `Backup non valido: cards.txt assente o malformato (${(e as Error).message}).` },
- { status: 400 },
- );
- }
- try {
- const portalsRaw = await readFile(path.join(extractDir, 'portals.txt'), 'utf-8');
- const portalsParsed = JSON.parse(portalsRaw || '[]');
- if (!Array.isArray(portalsParsed)) throw new Error('portals.txt non è un array JSON');
- portalsCount = portalsParsed.length;
- } catch (e) {
- return NextResponse.json(
- { error: `Backup non valido: portals.txt assente o malformato (${(e as Error).message}).` },
- { status: 400 },
- );
- }
-
- // Atomic swap: data → data.bak-<ts>, extract → data.
- const ts = Date.now();
- const backupOldDir = path.join(PROJECT_ROOT, `data.bak-${ts}`);
- try {
- await rename(DATA_DIR, backupOldDir);
- } catch (err) {
- const e = err as NodeJS.ErrnoException;
- if (e.code !== 'ENOENT') throw err;
- // data/ non esisteva: ok, andiamo avanti
- }
- try {
- await rename(extractDir, DATA_DIR);
- } catch (err) {
- // Revert: rimettiamo data/ al suo posto.
- try { await rename(backupOldDir, DATA_DIR); } catch { /* ignore */ }
- throw err;
- }
-
- // Cleanup zip + cartella staging della sessione (l'extract è già stato spostato).
- try { await rm(sessionDir, { recursive: true, force: true }); } catch { /* ignore */ }
-
- return NextResponse.json({
- ok: true,
- restored: { cards: cardsCount, portals: portalsCount },
- previousDataBackup: path.basename(backupOldDir),
- });
- } catch (error) {
- console.error('Restore error:', error);
- // Cleanup staging in caso di errore non gestito sopra.
- try { await rm(sessionDir, { recursive: true, force: true }); } catch { /* ignore */ }
- return NextResponse.json({ error: 'Errore durante il ripristino.' }, { status: 500 });
- }
- }
|