diff --git a/src/controllers/storage.controller.ts b/src/controllers/storage.controller.ts index 7ae616324..4ba102cc7 100644 --- a/src/controllers/storage.controller.ts +++ b/src/controllers/storage.controller.ts @@ -10,8 +10,8 @@ import { import {FCSService} from '../services/fcs.service'; import {FILE_UPLOAD_SERVICE} from '../keys'; import {FileUploadHandler} from '../types'; -import {unlinkSync} from 'fs'; import {config} from '../config'; +import {UploadType} from '../enums'; export class StorageController { constructor( @@ -45,40 +45,36 @@ export class StorageController { this.handler(request, response, (err: unknown) => { if (err) reject(err); else { - resolve(this.getFilesAndFields(userId, kind, request)); + const targetDir = `users/${userId}/${kind}`; + resolve(this.getFilesAndFields(targetDir, request)); } }); }); } - /** - * Get files and fields for the request - * @param request - Http request - */ - private async getFilesAndFields( - userId: string, - kind: string, - request: Request, - ) { + private async getFilesAndFields(targetDir: string, request: Request) { const uploadedFiles = request.files; - const mapper = async (f: globalThis.Express.Multer.File) => { - let downloadURL: String = ''; + const mapper = async (file: globalThis.Express.Multer.File) => { + let fileURL: String = ''; if (config.FIREBAE_STORAGE_BUCKET) { - if (f.mimetype.toLowerCase().startsWith('image')) { - downloadURL = await this.fcsService.uploadImage(userId, kind, f.path); - } else { - downloadURL = await this.fcsService.uploadVideo(userId, kind, f.path); + let uploadType = UploadType.IMAGE; + if (file.mimetype.toLowerCase().startsWith('video')) { + uploadType = UploadType.VIDEO; } - unlinkSync(f.path); + fileURL = await this.fcsService.upload( + uploadType, + targetDir, + file.path, + ); } return { - fieldname: f.fieldname, - originalname: f.originalname, - mimetype: f.mimetype, - size: f.size, - url: downloadURL, + fieldname: file.fieldname, + originalname: file.originalname, + mimetype: file.mimetype, + size: file.size, + url: fileURL, }; }; let files: object[] = []; diff --git a/src/enums/index.ts b/src/enums/index.ts index f6999a2c3..c44245eb9 100644 --- a/src/enums/index.ts +++ b/src/enums/index.ts @@ -12,3 +12,4 @@ export * from './reference-type.enum'; export * from './report-type.enum'; export * from './account-setting-type.enum'; export * from './post-status-type.enum'; +export * from './upload-type.enum'; diff --git a/src/enums/upload-type.enum.ts b/src/enums/upload-type.enum.ts new file mode 100644 index 000000000..c2422eadf --- /dev/null +++ b/src/enums/upload-type.enum.ts @@ -0,0 +1,4 @@ +export enum UploadType { + IMAGE = 'image', + VIDEO = 'video', +} diff --git a/src/services/fcs.service.ts b/src/services/fcs.service.ts index 25739b962..1f2fe453c 100644 --- a/src/services/fcs.service.ts +++ b/src/services/fcs.service.ts @@ -1,10 +1,12 @@ import {BindingScope, injectable} from '@loopback/core'; import * as firebaseAdmin from 'firebase-admin'; -import sharp from 'sharp'; import ffmpegInstaller from '@ffmpeg-installer/ffmpeg'; import ffmpeg from 'fluent-ffmpeg'; +import fs from 'fs'; +import os from 'os'; import path from 'path'; -import {unlinkSync} from 'fs'; +import sharp from 'sharp'; +import {UploadType} from '../enums'; ffmpeg.setFfmpegPath(ffmpegInstaller.path); @@ -12,99 +14,95 @@ ffmpeg.setFfmpegPath(ffmpegInstaller.path); export class FCSService { constructor() {} - async uploadImage( - userId: string, - kind: string, + async upload( + type: UploadType, + targetDir: string, filePath: string, ): Promise { - const parsed = path.parse(filePath); - const format = 'jpg'; - const mutations = [ + const bucket = firebaseAdmin.storage().bucket(); + const tempDir = os.tmpdir(); + const baseName = path.basename(filePath); + let format = 'jpg'; + let mutations = [ { type: 'thumbnail', + suffix: '_thumbnail', width: 200, - height: 200, }, { type: 'small', + suffix: '_small', width: 400, - height: 400, }, { type: 'medium', + suffix: '_medium', width: 600, - height: 600, + }, + { + type: 'origin', + suffix: '', + width: 0, }, ]; - const bucket = firebaseAdmin.storage().bucket(); - const options = {resumable: false, metadata: {contentType: 'image/jpg'}}; - - const buffer = await sharp(filePath).toBuffer(); + if (type === UploadType.VIDEO) { + format = 'mp4'; + mutations = [ + { + type: 'origin', + suffix: '', + width: 0, + }, + ]; + } + let result = ''; for (const mutation of mutations) { - const file = bucket.file( - `${userId}/${kind}/${parsed.name}_${mutation.type}.${format}`, - ); - - const resized = await sharp(buffer) - .resize({width: mutation.width}) - .toFormat(format) - .toBuffer({resolveWithObject: true}); - - await file.save(resized.data, options); - await file.makePublic(); + const formattedFilePath = `${tempDir}/${baseName}${mutation.suffix}_formatted.${format}`; + const uploadFilePath = `${targetDir}/${baseName}${mutation.suffix}.${format}`; + + if (mutation.type === 'origin') { + if (type === UploadType.IMAGE) { + await sharp(filePath) + .toFormat(sharp.format.jpg) + .toFile(formattedFilePath); + } else { + await new Promise((resolve, reject) => { + ffmpeg(filePath) + .videoCodec('libx264') + .audioCodec('libmp3lame') + .format(format) + .on('error', err => { + reject(err); + }) + .on('end', () => { + resolve(formattedFilePath); + }) + .saveToFile(formattedFilePath); + }); + } + } else { + if (type === UploadType.IMAGE) { + await sharp(filePath) + .resize({width: mutation.width}) + .toFormat(sharp.format.jpg) + .toFile(formattedFilePath); + } + } + + const [file] = await bucket.upload(formattedFilePath, { + resumable: false, + public: true, + destination: uploadFilePath, + }); + result = file.publicUrl(); + + fs.unlinkSync(formattedFilePath); } - const file = bucket.file(`${userId}/${kind}/${parsed.name}.${format}`); - const resized = await sharp(buffer) - .toFormat(format) - .toBuffer({resolveWithObject: true}); - - await file.save(resized.data, options); - - await file.makePublic(); - - return file.publicUrl(); - } - - async uploadVideo( - userId: string, - kind: string, - filePath: string, - ): Promise { - const parsed = path.parse(filePath); - const format = 'mp4'; - - const bucket = firebaseAdmin.storage().bucket(); - const options = { - destination: `${userId}/${kind}/${parsed.name}.${format}`, - resumable: false, - metadata: {contentType: 'video/mp4'}, - }; - - const convertedFilePath = `/tmp/convert_${parsed.name}.${format}`; - - const result: string = await new Promise((resolve, reject) => { - ffmpeg(filePath) - .videoCodec('libx264') - .audioCodec('libmp3lame') - .format('mp4') - .on('error', err => { - reject(err); - }) - .on('end', () => { - resolve(convertedFilePath); - }) - .saveToFile(convertedFilePath); - }); - - const [file] = await bucket.upload(result, options); - - unlinkSync(result); - - await file.makePublic(); + fs.unlinkSync(filePath); - return file.publicUrl(); + return result; } }