96 lines
2.9 KiB
TypeScript
96 lines
2.9 KiB
TypeScript
import { NextApiRequest, NextApiResponse } from 'next'
|
|
import { IncomingForm, File as FormidableFile } from 'formidable'
|
|
import FormData from 'form-data'
|
|
import fs from 'fs'
|
|
import { Readable } from 'stream'
|
|
|
|
const MAX_FILE_SIZE = 50 * 1024 * 1024 // 50MB
|
|
|
|
export const config = {
|
|
api: {
|
|
bodyParser: false, // Disable bodyParser to handle multipart
|
|
},
|
|
}
|
|
|
|
interface ParseResult {
|
|
fields: Record<string, string[]>
|
|
files: Record<string, FormidableFile[]>
|
|
}
|
|
|
|
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
|
if (req.method !== 'POST') {
|
|
return res.status(405).json({ error: 'Method not allowed' })
|
|
}
|
|
|
|
// Get target endpoint from query or use default
|
|
const targetEndpoint = (req.query.endpoint as string) || 'https://picstr.build/api/v1/upload'
|
|
|
|
try {
|
|
// Parse multipart form data
|
|
// formidable needs the raw Node.js IncomingMessage, which NextApiRequest extends
|
|
const form = new IncomingForm({
|
|
maxFileSize: MAX_FILE_SIZE,
|
|
keepExtensions: true,
|
|
})
|
|
|
|
const parseResult = await new Promise<ParseResult>((resolve, reject) => {
|
|
// Cast req to any to work with formidable - NextApiRequest extends IncomingMessage
|
|
form.parse(req as any, (err, fields, files) => {
|
|
if (err) {
|
|
console.error('Formidable parse error:', err)
|
|
reject(err)
|
|
} else {
|
|
resolve({ fields: fields as Record<string, string[]>, files: files as Record<string, FormidableFile[]> })
|
|
}
|
|
})
|
|
})
|
|
|
|
const { fields, files } = parseResult
|
|
|
|
// Get the file from the parsed form
|
|
const fileField = files.file?.[0]
|
|
if (!fileField) {
|
|
return res.status(400).json({ error: 'No file provided' })
|
|
}
|
|
|
|
// Create FormData for the target endpoint
|
|
const formData = new FormData()
|
|
const fileStream = fs.createReadStream(fileField.filepath)
|
|
formData.append('file', fileStream, {
|
|
filename: fileField.originalFilename || fileField.newFilename || 'upload',
|
|
contentType: fileField.mimetype || 'application/octet-stream',
|
|
})
|
|
|
|
// Forward to target endpoint
|
|
const response = await fetch(targetEndpoint, {
|
|
method: 'POST',
|
|
body: formData as unknown as BodyInit,
|
|
headers: {
|
|
...formData.getHeaders(),
|
|
},
|
|
})
|
|
|
|
// Clean up temporary file
|
|
try {
|
|
fs.unlinkSync(fileField.filepath)
|
|
} catch (unlinkError) {
|
|
console.error('Error deleting temp file:', unlinkError)
|
|
}
|
|
|
|
if (!response.ok) {
|
|
const errorText = await response.text()
|
|
return res.status(response.status).json({
|
|
error: errorText || `Upload failed: ${response.status} ${response.statusText}`,
|
|
})
|
|
}
|
|
|
|
const result = await response.json()
|
|
return res.status(200).json(result)
|
|
} catch (error) {
|
|
console.error('NIP-95 proxy error:', error)
|
|
return res.status(500).json({
|
|
error: error instanceof Error ? error.message : 'Internal server error',
|
|
})
|
|
}
|
|
}
|