story-research-zapwall/pages/api/nip95-upload.ts

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',
})
}
}