Reubencf's picture
downloading fixes
ff732bb
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 }
)
}
}