Spaces:
Running
Running
| import { NextRequest, NextResponse } from 'next/server' | |
| import fs from 'fs' | |
| import path from 'path' | |
| // Use /data for Hugging Face Spaces persistent storage | |
| const DATA_DIR = process.env.SPACE_ID | |
| ? '/data' | |
| : path.join(process.cwd(), 'public', 'data') | |
| const PUBLIC_DIR = path.join(DATA_DIR, 'public') | |
| // Ensure public directory exists | |
| if (!fs.existsSync(PUBLIC_DIR)) { | |
| fs.mkdirSync(PUBLIC_DIR, { recursive: true }) | |
| } | |
| interface FileItem { | |
| name: string | |
| type: 'file' | 'folder' | |
| size?: number | |
| modified?: string | |
| path: string | |
| extension?: string | |
| uploadedBy?: string | |
| uploadedAt?: string | |
| } | |
| function getFileExtension(filename: string): string { | |
| const ext = path.extname(filename).toLowerCase() | |
| return ext.startsWith('.') ? ext.substring(1) : ext | |
| } | |
| export async function GET(request: NextRequest) { | |
| const searchParams = request.nextUrl.searchParams | |
| const folder = searchParams.get('folder') || '' | |
| try { | |
| const targetDir = path.join(PUBLIC_DIR, folder) | |
| // Security check - prevent directory traversal | |
| if (!targetDir.startsWith(PUBLIC_DIR)) { | |
| return NextResponse.json({ error: 'Invalid path' }, { status: 400 }) | |
| } | |
| if (!fs.existsSync(targetDir)) { | |
| fs.mkdirSync(targetDir, { recursive: true }) | |
| } | |
| const files: FileItem[] = [] | |
| const items = fs.readdirSync(targetDir) | |
| for (const item of items) { | |
| // Skip hidden files | |
| if (item.startsWith('.')) continue | |
| const fullPath = path.join(targetDir, item) | |
| const relativePath = path.join(folder, item).replace(/\\/g, '/') | |
| const stats = fs.statSync(fullPath) | |
| if (stats.isDirectory()) { | |
| files.push({ | |
| name: item, | |
| type: 'folder', | |
| path: relativePath, | |
| modified: stats.mtime.toISOString() | |
| }) | |
| } else { | |
| // Try to read metadata if it exists | |
| const metadataPath = fullPath + '.meta.json' | |
| let metadata: any = {} | |
| if (fs.existsSync(metadataPath)) { | |
| try { | |
| metadata = JSON.parse(fs.readFileSync(metadataPath, 'utf-8')) | |
| } catch (e) { | |
| // Ignore metadata errors | |
| } | |
| } | |
| // Read content for text-based files | |
| let content = undefined | |
| const ext = path.extname(item).toLowerCase() | |
| const textExtensions = ['.json', '.tex', '.dart', '.txt', '.md', '.html', '.css', '.js', '.ts', '.jsx', '.tsx', '.py', '.java', '.c', '.cpp', '.h'] | |
| if (textExtensions.includes(ext)) { | |
| try { | |
| content = fs.readFileSync(fullPath, 'utf-8') | |
| } catch (err) { | |
| console.error(`Error reading ${item}:`, err) | |
| } | |
| } | |
| files.push({ | |
| name: item, | |
| type: 'file', | |
| size: stats.size, | |
| modified: stats.mtime.toISOString(), | |
| path: relativePath, | |
| extension: getFileExtension(item), | |
| uploadedBy: metadata.uploadedBy || 'Anonymous', | |
| uploadedAt: metadata.uploadedAt || stats.birthtime.toISOString(), | |
| ...(content !== undefined && { content }) | |
| }) | |
| } | |
| } | |
| return NextResponse.json({ | |
| files, | |
| currentPath: folder, | |
| isPublic: true | |
| }) | |
| } catch (error) { | |
| console.error('Error listing public files:', error) | |
| return NextResponse.json( | |
| { error: 'Failed to list public files' }, | |
| { status: 500 } | |
| ) | |
| } | |
| } | |
| // Upload to public folder | |
| export async function POST(request: NextRequest) { | |
| try { | |
| const contentType = request.headers.get('content-type') | |
| // Handle JSON body (for save_file action) | |
| if (contentType?.includes('application/json')) { | |
| const body = await request.json() | |
| const { action, fileName, content, folder = '' } = body | |
| if (action === 'save_file') { | |
| if (!fileName || content === undefined) { | |
| return NextResponse.json({ error: 'fileName and content are required' }, { status: 400 }) | |
| } | |
| const targetDir = path.join(PUBLIC_DIR, folder) | |
| // Security check | |
| if (!targetDir.startsWith(PUBLIC_DIR)) { | |
| return NextResponse.json({ error: 'Invalid path' }, { status: 400 }) | |
| } | |
| if (!fs.existsSync(targetDir)) { | |
| fs.mkdirSync(targetDir, { recursive: true }) | |
| } | |
| const filePath = path.join(targetDir, fileName) | |
| // Write file (overwrite if exists) | |
| fs.writeFileSync(filePath, content, 'utf-8') | |
| // Update or create metadata | |
| const metadataPath = filePath + '.meta.json' | |
| const existingMetadata = fs.existsSync(metadataPath) | |
| ? JSON.parse(fs.readFileSync(metadataPath, 'utf-8')) | |
| : {} | |
| fs.writeFileSync(metadataPath, JSON.stringify({ | |
| ...existingMetadata, | |
| lastModified: new Date().toISOString() | |
| })) | |
| return NextResponse.json({ success: true }) | |
| } | |
| } | |
| const formData = await request.formData() | |
| const file = formData.get('file') as File | |
| const folder = formData.get('folder') as string || '' | |
| const uploadedBy = formData.get('uploadedBy') as string || 'Anonymous' | |
| if (!file) { | |
| return NextResponse.json({ error: 'No file provided' }, { status: 400 }) | |
| } | |
| const targetDir = path.join(PUBLIC_DIR, folder) | |
| // Security check | |
| if (!targetDir.startsWith(PUBLIC_DIR)) { | |
| return NextResponse.json({ error: 'Invalid path' }, { status: 400 }) | |
| } | |
| if (!fs.existsSync(targetDir)) { | |
| fs.mkdirSync(targetDir, { recursive: true }) | |
| } | |
| const fileName = file.name | |
| const filePath = path.join(targetDir, fileName) | |
| // Check if file already exists | |
| if (fs.existsSync(filePath)) { | |
| // Add timestamp to filename | |
| const timestamp = Date.now() | |
| const ext = path.extname(fileName) | |
| const baseName = path.basename(fileName, ext) | |
| const newFileName = `${baseName}_${timestamp}${ext}` | |
| const newFilePath = path.join(targetDir, newFileName) | |
| const buffer = Buffer.from(await file.arrayBuffer()) | |
| fs.writeFileSync(newFilePath, buffer) | |
| // Save metadata | |
| const metadataPath = newFilePath + '.meta.json' | |
| fs.writeFileSync(metadataPath, JSON.stringify({ | |
| uploadedBy, | |
| uploadedAt: new Date().toISOString(), | |
| originalName: fileName | |
| })) | |
| return NextResponse.json({ | |
| success: true, | |
| message: 'File uploaded to public folder', | |
| path: path.join(folder, newFileName).replace(/\\/g, '/'), | |
| renamed: true | |
| }) | |
| } else { | |
| const buffer = Buffer.from(await file.arrayBuffer()) | |
| fs.writeFileSync(filePath, buffer) | |
| // Save metadata | |
| const metadataPath = filePath + '.meta.json' | |
| fs.writeFileSync(metadataPath, JSON.stringify({ | |
| uploadedBy, | |
| uploadedAt: new Date().toISOString() | |
| })) | |
| return NextResponse.json({ | |
| success: true, | |
| message: 'File uploaded to public folder', | |
| path: path.join(folder, fileName).replace(/\\/g, '/') | |
| }) | |
| } | |
| } catch (error) { | |
| console.error('Error uploading to public folder:', error) | |
| return NextResponse.json( | |
| { error: 'Failed to upload file' }, | |
| { status: 500 } | |
| ) | |
| } | |
| } |