Skip to content

Instantly share code, notes, and snippets.

@SinanAkkoyun
Last active July 5, 2023 13:54
Show Gist options
  • Save SinanAkkoyun/1a72dabfd498e27f7d525b835d41c097 to your computer and use it in GitHub Desktop.
Save SinanAkkoyun/1a72dabfd498e27f7d525b835d41c097 to your computer and use it in GitHub Desktop.
Curveball TS Multiform Middleware
import Controller from '@curveball/controller'
import { Context } from '@curveball/core'
import Busboy, { FileInfo } from 'busboy'
import { IncomingHttpHeaders } from 'http'
import { Readable, Writable } from 'stream'
export interface MultiformFile {
name: string
info: FileInfo
stream?: NodeJS.ReadableStream
buffer?: Buffer
}
export interface CheckParamsOptions {
requiredParams?: string[]
requiredFiles?: string[]
}
export const checkParams = (ctx: Context, options: CheckParamsOptions) => {
const { requiredParams, requiredFiles } = options
if(!ctx.state.parameters || !ctx.state.files) throw new Error('Multiform middleware not used')
// check required params
for (const param of requiredParams || []) {
if (!ctx.state.parameters.has(param)) {
throw new Error(`Missing required parameter: ${param}`)
}
}
// check required files
for (const file of requiredFiles || []) {
if (!ctx.state.files.has(file)) {
throw new Error(`Missing required file: ${file}`)
}
}
}
export const multiformMiddleware = () => {
return async (ctx: Context, next: Function) => {
await new Promise<void>(async (resolve, reject) => {
if(ctx.request.headers.get('content-type')?.startsWith('multipart/form-data')) {
//ctx.state.params = new Map()
//ctx.state.files = new Map()
const headersObject = ctx.request.headers.getAll()
const headers: IncomingHttpHeaders = {}
for (const key in headersObject) {
const value = headersObject[key]
headers[key] = Array.isArray(value) ? value.map(String) : String(value)
}
const busboy = Busboy({ headers })
//const params: Map<string, string> = new Map()
//const files: Map<string, MultiformFile> = new Map()
ctx.state.parameters = new Map()
ctx.state.files = new Map()
busboy.on('file', async function(fieldname: string, file: NodeJS.ReadableStream, info: FileInfo) {
console.log('file', info.filename)
//file.emit('end')
// instead of stream, load into buffer... (big files don't work with passing the stream)
let readStream = new Writable()
let buffer: Buffer = Buffer.alloc(0)
readStream._write = function(chunk, encoding, done) {
buffer = Buffer.concat([buffer, chunk])
done()
}
file.pipe(readStream)
await new Promise<void>((resolve, reject) => {
file.on('end', () => {
resolve()
})
})
ctx.state.files.set(fieldname, {
name: info.filename,
info,
//stream: file
buffer
})
console.log('filesize', buffer.length)
})
busboy.on('field', function(fieldname: string, val: string) {
console.log('field', fieldname)
ctx.state.parameters.set(fieldname, val)
//console.log('params', ctx.state.params)
})
busboy.on('error', function(err: Error) {
// console.log('Error parsing form: ' + err.stack)
ctx.state.error = err
})
busboy.on('close', function() {
//ctx.state.params = params
//ctx.state.files = files
resolve()
})
ctx.request.getStream().pipe(busboy)
} else {
resolve()
}
})
console.log('pass to next')
return next()
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment