const md5 = require('md5'); const fs = require('fs-extra'); const sharp = require('sharp'); module.exports = Self => { Self.remoteMethod('upload', { description: 'Uploads an image', accessType: 'WRITE', accepts: [ { arg: 'ctx', type: 'Object', http: {source: 'context'} } ], returns: { type: Self.modelName, description: 'The resulting file instance', root: true, }, http: { path: `/upload`, verb: 'POST' } }); Self.upload = async ctx => { let app = Self.app; let $ = app.models; let storageConnector = app.dataSources.storage.connector; let tx = await Self.beginTransaction({}); let myOptions = {transaction: tx}; async function getContainer(name) { let container; try { container = await $.Container.getContainer(name); } catch (err) { if (err.code === 'ENOENT') { container = await $.Container.createContainer({ name: name }); } else throw err; } return container; } try { // Upload file to temporary path let tempContainer = await getContainer('temp'); let uploaded = await $.Container.upload(tempContainer.name, ctx.req, ctx.result, {}); let files = Object.values(uploaded.files).map(file => file[0]); let args = {}; for (let key in uploaded.fields) args[key] = uploaded.fields[key][0]; if (!/^[a-z0-9_]+$/.test(args.name)) throw new Error('Bad file name'); let collection = await $.ImageCollection.findOne({ fields: [ 'id', 'name', 'maxWidth', 'maxHeight', 'model', 'property' ], where: {name: args.collectionFk}, include: { relation: 'sizes', scope: { fields: ['width', 'height', 'crop'] } } }); let md5Hash = md5(args.name).substring(0, 4); let md5Path = md5Hash.match(/(..?)/g).join('/'); let rootPath = storageConnector.client.root; let file = files[0]; let data = { name: args.name, collectionFk: args.collectionFk }; let newImage = await Self.upsertWithWhere(data, { name: args.name, collectionFk: args.collectionFk, updated: (new Date).getTime() }, myOptions); // Resizes and saves the image let extension = file.name.split('.').pop().toLowerCase(); let srcPath = `${rootPath}/${tempContainer.name}/${file.name}`; let collectionDir = `${rootPath}/${args.collectionFk}`; let dstDir = `${collectionDir}/${md5Path}/${args.name}`; let dstFile = `full.${extension}`; let resizeOpts = { withoutEnlargement: true, fit: 'inside' }; await fs.mkdir(dstDir, {recursive: true}); await sharp(srcPath) .resize(collection.maxWidth, collection.maxHeight, resizeOpts) .toFile(`${dstDir}/${dstFile}`); let sizes = collection.sizes(); for (let size of sizes) { let dstFile = `${size.width}x${size.height}.${extension}`; let resizeOpts = { withoutEnlargement: true, fit: size.crop ? 'cover' : 'inside' }; await sharp(srcPath) .resize(size.width, size.height, resizeOpts) .toFile(`${dstDir}/${dstFile}`); } // Updates items with matching id, when option is checked if (args.updateMatching === 'true') { if (!collection.model || !collection.property) throw new Error('Matching model settings not defined'); let model = app.models[collection.model]; if (!model) throw new Error('Matching model not found'); let item = await model.findById(args.name, null, myOptions); if (item) await item.updateAttribute( collection.property, args.name, myOptions ); } await fs.unlink(srcPath); await tx.commit(); return newImage; } catch (e) { await tx.rollback(); throw e; } }; };