import fs from "fs" import { Controller } from "linebridge/dist/server" import ChunkedUpload from "@lib/chunkedUpload" import uploadBodyFiles from "./services/uploadBodyFiles" import { videoTranscode } from "@lib/videoTranscode" import Jimp from "jimp" const maximuns = { imageResolution: { width: 3840, height: 2160, }, imageQuality: 80, } async function processVideo(file, params = {}) { const result = await videoTranscode(file.filepath, global.uploadCachePath, { videoCodec: "libx264", format: "mp4", ...params }) file.filepath = result.filepath file.filename = result.filename return file } async function processImage(file) { const { width, height } = await new Promise((resolve, reject) => { Jimp.read(file.filepath) .then((image) => { resolve({ width: image.bitmap.width, height: image.bitmap.height, }) }) .catch((err) => { reject(err) }) }) if (width > maximuns.imageResolution.width || height > maximuns.imageResolution.height) { await new Promise((resolve, reject) => { // calculate max resolution respecting aspect ratio const resizedResolution = { width: maximuns.imageResolution.width, height: maximuns.imageResolution.height, } if (width > height) { resizedResolution.height = Math.floor((height / width) * maximuns.imageResolution.width) } if (height > width) { resizedResolution.width = Math.floor((width / height) * maximuns.imageResolution.height) } Jimp.read(file.filepath) .then((image) => { image .resize(resizedResolution.width, resizedResolution.height) .quality(maximuns.imageQuality) .write(file.filepath, resolve) }) .catch((err) => { reject(err) }) }) } return file } export default class FilesController extends Controller { static refName = "FilesController" static useRoute = "/files" chunkUploadEngine = new ChunkedUpload({ tmpPath: global.uploadCachePath, outputPath: global.uploadCachePath, maxFileSize: global.DEFAULT_POSTING_POLICY.maximumFileSize, acceptedMimeTypes: global.DEFAULT_POSTING_POLICY.acceptedMimeTypes, onExceedMaxFileSize: (req) => { // check if user has permission to upload big files if (!req.user) { return false } return req.user.roles.includes("admin") || req.user.roles.includes("moderator") || req.user.roles.includes("developer") } }) fileTransformer = { "video/avi": processVideo, "video/quicktime": processVideo, "video/mp4": processVideo, "video/webm": processVideo, "image/jpeg": processImage, "image/png": processImage, "image/gif": processImage, "image/bmp": processImage, "image/tiff": processImage, "image/webp": processImage, "image/jfif": processImage, } httpEndpoints = { get: { "/objects": { middlewares: ["withAuthentication"], fn: async (req, res) => { const user_id = req.user.id let totalSize = 0 const objectsPath = `${user_id}/` const objects = await new Promise((resolve, reject) => { const objects = [] const objectsStream = global.storage.listObjects(global.storage.defaultBucket, objectsPath, true) objectsStream.on("data", (obj) => { objects.push(obj) }) objectsStream.on("error", (err) => { return reject(err) }) objectsStream.on("end", () => { return resolve(objects) }) }) for await (const object of objects) { totalSize += object.size } return res.json({ totalSize, objects, }) } } }, post: { "/upload_chunk": { middlewares: ["withAuthentication", this.chunkUploadEngine.makeMiddleware()], fn: async (req, res) => { if (!req.isLastPart) { return res.json({ status: "ok", filePart: req.filePart, lastPart: req.isLastPart, }) } if (!req.fileResult) { return res.status(500).json({ error: "File upload failed", }) } console.log(req.fileResult) try { // check if mimetype has transformer if (typeof this.fileTransformer[req.fileResult.mimetype] === "function") { req.fileResult = await this.fileTransformer[req.fileResult.mimetype](req.fileResult) } } catch (error) { console.log(error) return res.status(500).json({ error: "File upload failed on transformation", reason: error.message, }) } // start upload to s3 const remoteUploadPath = req.user?._id ? `${req.user?._id.toString()}/${req.fileResult.filename}` : file.filename const remoteUploadResponse = await new Promise((_resolve, _reject) => { try { const fileStream = fs.createReadStream(req.fileResult.filepath) fs.stat(req.fileResult.filepath, (err, stats) => { try { if (err) { return _reject(new Error(`Failed to upload file to storage server > ${err.message}`)) } global.storage.putObject(global.storage.defaultBucket, remoteUploadPath, fileStream, stats.size, req.fileResult, (err, etag) => { if (err) { return _reject(new Error(`Failed to upload file to storage server > ${err.message}`)) } return _resolve({ etag, }) }) } catch (error) { return _reject(new Error(`Failed to upload file to storage server > ${error.message}`)) } }) } catch (error) { return _reject(new Error(`Failed to upload file to storage server > ${error.message}`)) } }).catch((err) => { res.status(500).json({ error: err.message, }) return false }) if (!remoteUploadResponse) { return false } try { // remove file from cache await fs.promises.unlink(req.fileResult.filepath) // programatically remove file from cache in the } catch (error) { console.log("Failed to remove file from cache", error) return res.status(500).json({ error: error.message, }) } // get url location const remoteUrlObj = global.storage.composeRemoteURL(remoteUploadPath) return res.json({ name: req.fileResult.filename, id: remoteUploadPath, url: remoteUrlObj, }) } }, "/upload": { middlewares: ["withAuthentication"], fn: async (req, res) => { const results = await uploadBodyFiles({ req, }).catch((err) => { res.status(400).json({ error: err.message, }) return false }) if (results) { return res.json(results) } } } } } }