diff --git a/src/app/api/download-filtered-entries/route.ts b/src/app/api/download-filtered-entries/route.ts index 82cf1b4..8586c49 100644 --- a/src/app/api/download-filtered-entries/route.ts +++ b/src/app/api/download-filtered-entries/route.ts @@ -2,12 +2,46 @@ import { NextRequest, NextResponse } from 'next/server'; import { downloadFilteredEntries } from '@/app/actions/download-filtered-entries'; import JSZip from 'jszip'; import { join } from 'path'; -import { readFile, existsSync } from 'fs'; +import { readFile, existsSync, writeFileSync, mkdirSync, readdirSync, unlinkSync, statSync } from 'fs'; import { promisify } from 'util'; import { requireAdmin } from '@/lib/auth'; const readFileAsync = promisify(readFile); +// Clean up old ZIP files older than 24 hours +async function cleanupOldZips(downloadsDir: string, maxAgeHours: number = 24) { + try { + if (!existsSync(downloadsDir)) { + return; + } + + const files = readdirSync(downloadsDir); + const now = Date.now(); + const maxAgeMs = maxAgeHours * 60 * 60 * 1000; + + for (const file of files) { + if (!file.startsWith('dataset-') || !file.endsWith('.zip')) { + continue; + } + + const filePath = join(downloadsDir, file); + try { + const stats = statSync(filePath); + const fileAge = now - stats.mtimeMs; + + if (fileAge > maxAgeMs) { + unlinkSync(filePath); + console.log(`Cleaned up old ZIP file: ${file} (age: ${(fileAge / (60 * 60 * 1000)).toFixed(1)} hours)`); + } + } catch (error) { + console.error(`Error cleaning up file ${file}:`, error); + } + } + } catch (error) { + console.error('Error during cleanup:', error); + } +} + interface FilterParams { speakerId?: string; modelName?: string; @@ -84,16 +118,37 @@ export async function POST(request: NextRequest) { // Generate ZIP blob const zipBuffer = await zip.generateAsync({ type: 'nodebuffer' }); - // Create response with ZIP file - const fileName = `dataset-${datasetId}-export-${new Date().toISOString().split('T')[0]}.zip`; + // Save ZIP to public folder + const downloadsDir = join(process.cwd(), 'public', 'downloads'); + + // Ensure downloads directory exists + try { + mkdirSync(downloadsDir, { recursive: true }); + } catch (error) { + console.error('Error creating downloads directory:', error); + } - return new NextResponse(new Uint8Array(zipBuffer), { - status: 200, - headers: { - 'Content-Type': 'application/zip', - 'Content-Disposition': `attachment; filename="${fileName}"`, - 'Content-Length': zipBuffer.length.toString(), - }, + // Create filename with timestamp for uniqueness + const timestamp = Date.now(); + const fileName = `dataset-${datasetId}-export-${new Date().toISOString().split('T')[0]}-${timestamp}.zip`; + const filePath = join(downloadsDir, fileName); + + // Write ZIP file to disk + writeFileSync(filePath, zipBuffer); + console.log(`ZIP file saved to: ${filePath}`); + + // Clean up old ZIP files in the background + cleanupOldZips(downloadsDir).catch(error => { + console.error('Cleanup failed:', error); + }); + + // Return download URL + const downloadUrl = `/downloads/${fileName}`; + + return NextResponse.json({ + success: true, + downloadUrl, + fileName, }); } catch (error) { console.error('Error creating ZIP:', error); diff --git a/src/lib/download-utils.ts b/src/lib/download-utils.ts index f027001..84a7eb7 100644 --- a/src/lib/download-utils.ts +++ b/src/lib/download-utils.ts @@ -24,21 +24,17 @@ export async function downloadFilteredEntriesAsZip( if (!response.ok) { const errorData = await response.json(); - throw new Error(errorData.error || 'Failed to download ZIP'); + throw new Error(errorData.error || 'Failed to prepare ZIP'); } - // Get the ZIP blob from response - const blob = await response.blob(); + const data = await response.json(); + + if (!data.success || !data.downloadUrl) { + throw new Error('Invalid response from server'); + } - // Trigger download - const url = URL.createObjectURL(blob); - const a = document.createElement('a'); - a.href = url; - a.download = `dataset-${datasetId}-export-${new Date().toISOString().split('T')[0]}.zip`; - document.body.appendChild(a); - a.click(); - document.body.removeChild(a); - URL.revokeObjectURL(url); + // Redirect to the generated ZIP file + window.location.href = data.downloadUrl; } catch (error) { console.error('Error downloading ZIP:', error); throw error;