import { NextRequest, NextResponse } from 'next/server'; import { FileExampleManager } from '../../../src/data/fileExamples'; // Simple ZIP creation without external dependencies class SimpleZipCreator { private files: Array<{ filename: string; content: string }> = []; addFile(filename: string, content: string) { this.files.push({ filename, content }); } // Create a basic ZIP file structure create(): number[] { const encoder = new TextEncoder(); const chunks: number[][] = []; let offset = 0; const centralDirectory: Array<{ name: string; offset: number; size: number; compressedSize: number; }> = []; // Process each file for (const file of this.files) { const contentBytes = Array.from(encoder.encode(file.content)); const filenameBytes = Array.from(encoder.encode(file.filename)); // Local file header const localHeader: number[] = []; // Local file header signature (little endian) localHeader.push(0x50, 0x4b, 0x03, 0x04); // Version needed to extract localHeader.push(20, 0); // General purpose bit flag localHeader.push(0, 0); // Compression method (0 = store) localHeader.push(0, 0); // Last modified time/date localHeader.push(0, 0, 0, 0); // CRC32 (0 for simplicity) localHeader.push(0, 0, 0, 0); // Compressed size localHeader.push(...intToLittleEndian(contentBytes.length, 4)); // Uncompressed size localHeader.push(...intToLittleEndian(contentBytes.length, 4)); // Filename length localHeader.push(...intToLittleEndian(filenameBytes.length, 2)); // Extra field length localHeader.push(0, 0); // Add filename localHeader.push(...filenameBytes); chunks.push(localHeader); chunks.push(contentBytes); // Store info for central directory centralDirectory.push({ name: file.filename, offset: offset, size: contentBytes.length, compressedSize: contentBytes.length }); offset += localHeader.length + contentBytes.length; } // Central directory const centralDirectoryChunks: number[][] = []; let centralDirectoryOffset = offset; for (const entry of centralDirectory) { const filenameBytes = Array.from(encoder.encode(entry.name)); const centralHeader: number[] = []; // Central directory header signature centralHeader.push(0x50, 0x4b, 0x01, 0x02); // Version made by centralHeader.push(20, 0); // Version needed to extract centralHeader.push(20, 0); // General purpose bit flag centralHeader.push(0, 0); // Compression method centralHeader.push(0, 0); // Last modified time/date centralHeader.push(0, 0, 0, 0); // CRC32 centralHeader.push(0, 0, 0, 0); // Compressed size centralHeader.push(...intToLittleEndian(entry.compressedSize, 4)); // Uncompressed size centralHeader.push(...intToLittleEndian(entry.size, 4)); // Filename length centralHeader.push(...intToLittleEndian(filenameBytes.length, 2)); // Extra field length centralHeader.push(0, 0); // File comment length centralHeader.push(0, 0); // Disk number start centralHeader.push(0, 0); // Internal file attributes centralHeader.push(0, 0); // External file attributes centralHeader.push(0, 0, 0, 0); // Relative offset of local header centralHeader.push(...intToLittleEndian(entry.offset, 4)); // Add filename centralHeader.push(...filenameBytes); centralDirectoryChunks.push(centralHeader); } const centralDirectorySize = centralDirectoryChunks.reduce((sum, chunk) => sum + chunk.length, 0); // End of central directory const endOfCentralDirectory: number[] = []; // End of central directory signature endOfCentralDirectory.push(0x50, 0x4b, 0x05, 0x06); // Number of this disk endOfCentralDirectory.push(0, 0); // Number of the disk with the start of the central directory endOfCentralDirectory.push(0, 0); // Total number of entries on this disk endOfCentralDirectory.push(...intToLittleEndian(centralDirectory.length, 2)); // Total number of entries endOfCentralDirectory.push(...intToLittleEndian(centralDirectory.length, 2)); // Size of the central directory endOfCentralDirectory.push(...intToLittleEndian(centralDirectorySize, 4)); // Offset of start of central directory endOfCentralDirectory.push(...intToLittleEndian(centralDirectoryOffset, 4)); // ZIP file comment length endOfCentralDirectory.push(0, 0); // Combine all chunks const result: number[] = []; chunks.forEach(chunk => result.push(...chunk)); centralDirectoryChunks.forEach(chunk => result.push(...chunk)); result.push(...endOfCentralDirectory); return result; } } // Helper function to convert integer to little endian bytes function intToLittleEndian(value: number, bytes: number): number[] { const result: number[] = []; for (let i = 0; i < bytes; i++) { result.push((value >> (i * 8)) & 0xff); } return result; } export async function POST(request: NextRequest) { try { const body = await request.json(); const { fileIds } = body; if (!Array.isArray(fileIds) || fileIds.length === 0) { return NextResponse.json( { error: 'fileIds array is required and must not be empty' }, { status: 400 } ); } // Get file contents const files = await Promise.all( fileIds.map(async (id) => { const file = await FileExampleManager.getFileExample(id); if (!file) { throw new Error(`File with id ${id} not found`); } return file; }) ); // Create ZIP const zipCreator = new SimpleZipCreator(); files.forEach(file => { zipCreator.addFile(file.filename, file.content); }); const zipData = zipCreator.create(); const buffer = Buffer.from(new Uint8Array(zipData)); // Return ZIP file return new Response(buffer, { headers: { 'Content-Type': 'application/zip', 'Content-Disposition': `attachment; filename="code-examples-${Date.now()}.zip"`, 'Cache-Control': 'no-cache', } }); } catch (error) { console.error('ZIP download error:', error); const errorMessage = error instanceof Error ? error.message : 'Unknown error'; return NextResponse.json( { error: 'Failed to create zip file', details: errorMessage }, { status: 500 } ); } } export async function GET(request: NextRequest) { try { const { searchParams } = new URL(request.url); const fileId = searchParams.get('id'); if (!fileId) { return NextResponse.json( { error: 'id parameter is required' }, { status: 400 } ); } const file = await FileExampleManager.getFileExample(fileId); if (!file) { return NextResponse.json( { error: 'File not found' }, { status: 404 } ); } const encoder = new TextEncoder(); const content = encoder.encode(file.content); const buffer = Buffer.from(content); return new Response(buffer, { headers: { 'Content-Type': getMimeType(file.language), 'Content-Disposition': `attachment; filename="${file.filename}"`, 'Cache-Control': 'no-cache', } }); } catch (error) { console.error('File download error:', error); return NextResponse.json( { error: 'Failed to download file' }, { status: 500 } ); } } // Helper function to get MIME type function getMimeType(language: string): string { const mimeTypes: Record = { 'python': 'text/x-python', 'typescript': 'text/x-typescript', 'javascript': 'text/javascript', 'dockerfile': 'text/x-dockerfile', 'yaml': 'text/yaml', 'json': 'application/json', 'html': 'text/html', 'css': 'text/css', 'sql': 'text/x-sql', 'bash': 'text/x-shellscript', 'text': 'text/plain' }; return mimeTypes[language] || 'text/plain'; }