Last active
October 30, 2022 21:16
-
-
Save irvanherz/11a0b9fa30d74101c5ea7ca8d099d868 to your computer and use it in GitHub Desktop.
Streaming multer storage engine with Sharp image transformation for Minio
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* global Express */ | |
import { Request } from 'express' | |
import { Client, ClientOptions, ItemBucketMetadata } from 'minio' | |
import multer from 'multer' | |
import path from 'path' | |
import sharp from 'sharp' | |
type TransformerType = { | |
id: string | |
sharp: sharp.Sharp | |
filename?: (req: Request, file: Express.Multer.File, transformer: TransformerType) => string | |
meta?: (req: Request, file: Express.Multer.File, transformer: TransformerType) => ItemBucketMetadata | |
} | |
type FilenameCallbackType = (req: Request, file: Express.Multer.File, transformer: TransformerType) => string | |
type ObjectMetaCallbackType = (req: Request, file: Express.Multer.File, transformer: TransformerType) => ItemBucketMetadata | |
type TransformedType = Partial<Express.Multer.File> & { id: string, status: 'success' | 'error', meta?: any } | |
type HandleFileCallbackInfoType = Partial<Express.Multer.File> & { transformed: TransformedType[] } | |
type HandleFileCallbackType = (error?: any, info?: HandleFileCallbackInfoType) => void | |
type MulterSharpMinioStorageOptions = { | |
filename?: FilenameCallbackType | |
meta?: ObjectMetaCallbackType | |
bucket: string | |
clientOptions: ClientOptions | |
transformers: TransformerType[] | |
} | |
const defaultFilenameCallback: FilenameCallbackType = (_req, file) => { | |
const fileExt = path.extname(file.originalname) | |
return `${Date.now()}${fileExt}` | |
} | |
const defaultObjectMetaCallback: ObjectMetaCallbackType = (_req, _file, _transformer) => ({}) | |
class MulterSharpMinioStorage implements multer.StorageEngine { | |
private filename?: FilenameCallbackType | |
private meta?: ObjectMetaCallbackType | |
private bucket: string | |
private minioClient: Client | |
private transformers: TransformerType[] | |
private minioClientOptions: ClientOptions | |
constructor (opts: MulterSharpMinioStorageOptions) { | |
this.filename = opts.filename | |
this.meta = opts.meta | |
this.bucket = opts.bucket | |
this.transformers = opts.transformers | |
this.minioClient = new Client(opts.clientOptions) | |
this.minioClientOptions = opts.clientOptions | |
} | |
_handleFile ( | |
req: Request, | |
file: Express.Multer.File, | |
callback: HandleFileCallbackType | |
) { | |
const t = this | |
async function transformation () { | |
const untransformed = file.stream | |
const res: TransformedType[] = await Promise.all( | |
t.transformers.map(async transformer => { | |
const filenameFn = transformer.filename || t.filename || defaultFilenameCallback | |
const filename = filenameFn(req, file, transformer) | |
const media = sharp() | |
const transformed = sharp() | |
untransformed.pipe(media).pipe(transformer.sharp).pipe(transformed) | |
const transformedMetadata = await transformed.metadata() | |
const objectMetaFn = transformer.meta || t.meta || defaultObjectMetaCallback | |
const objectMeta = objectMetaFn(req, file, transformer) | |
return await t.minioClient | |
.putObject(t.bucket, filename, transformed, objectMeta) | |
.then(objectInfo => { | |
const co = t.minioClientOptions | |
const destination = `${co.useSSL ? 'https' : 'http'}://${co.endPoint}/${t.bucket}/${filename}` | |
const res: TransformedType = { | |
id: transformer.id, | |
status: 'success', | |
filename, | |
destination, | |
fieldname: file.fieldname, | |
meta: { ...transformedMetadata, objectInfo } | |
} | |
return res | |
}) | |
// eslint-disable-next-line n/handle-callback-err | |
.catch(err => { | |
const res: TransformedType = { | |
id: transformer.id, | |
status: 'error' | |
} | |
return res | |
}) | |
}) | |
) | |
return res | |
} | |
transformation().then(res => { | |
callback(null, { transformed: res }) | |
}) | |
} | |
_removeFile = (_req: Request, file: Express.Multer.File, callback: (error: Error | null) => void): void => { | |
callback(null) | |
// NOT YET IMPLEMENTED | |
// this.minioClient.removeObject(this.bucket, file.filename, () => callback(null)) | |
} | |
} | |
export default MulterSharpMinioStorage | |
// USAGE EXAMPLE: | |
// import multer from 'multer' | |
// import path from 'path' | |
// import sharp from 'sharp' | |
// import slugify from 'slugify' | |
// import MulterSharpMinioStorage from '../lib/multer-sharp-minio-storage' | |
// const upload = multer({ | |
// storage: new MulterSharpMinioStorage({ | |
// filename: (_req, file, t) => { | |
// const name = path.parse(file.originalname).name | |
// return slugify(name, { lower: true, trim: true }) + '_' + Date.now() + '_' + t.id + '.jpg' | |
// }, | |
// meta: (_req, _file, _t) => ({ 'Content-Type': 'image/jpeg' }), | |
// bucket: process.env.S3_BUCKET, | |
// transformers: [ | |
// { | |
// id: '320', | |
// sharp: sharp().resize(320).jpeg() | |
// }, | |
// { | |
// id: '640', | |
// sharp: sharp().resize(640).jpeg() | |
// } | |
// ], | |
// clientOptions: { | |
// endPoint: process.env.S3_ENDPOINT, | |
// accessKey: process.env.S3_ACCESS_KEY_ID, | |
// secretKey: process.env.S3_SECRET_ACCESS_KEY | |
// } | |
// }) | |
// }) | |
// export const uploadImage = upload.single('file') | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment