diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000000..c6f2e0e370 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,14 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "node", + "request": "launch", + "name": "Launch Program", + "program": "${workspaceFolder}/demo/init.js" + } + ] +} diff --git a/src/controllers/media.controller.js b/src/controllers/media.controller.js new file mode 100644 index 0000000000..efc23fbab0 --- /dev/null +++ b/src/controllers/media.controller.js @@ -0,0 +1,69 @@ +import mkdirp from 'mkdirp'; +import { resizeAndSave } from '../utils/imageResizer'; +import httpStatus from 'http-status'; +import modelById from '../resolvers/modelById'; + +export async function update(req, res, next, config) { + req.model.setDefaultLocale(req.locale); + + let doc = await modelById(req, { returnRawDoc: true }); + if (!doc) + return res.status(httpStatus.NOT_FOUND).send('Not Found'); + + Object.keys(req.body).forEach(e => { + doc[e] = req.body[e]; + }); + + if (req.files && req.files.file) { + doc['filename'] = req.files.file.name; + let outputFilepath = `${config.staticDir}/${req.files.file.name}`; + let moveError = await req.files.file.mv(outputFilepath); + if (moveError) return res.status(500).send(moveError); + doc['sizes'] = await resizeAndSave(config, req.files.file); + } + + doc.save((saveError) => { + if (saveError) + return res.status(httpStatus.INTERNAL_SERVER_ERROR).json({ error: saveError }); + + return res.json({ + message: 'success', + result: doc.toJSON({ virtuals: true }) + }); + }); +} + +export async function upload(req, res, next, config) { + if (!req.files || Object.keys(req.files).length === 0) { + return res.status(400).send('No files were uploaded.'); + } + + mkdirp(config.staticDir, (err) => { + if (err) { + console.error(err); + res.status(500).send('Upload failed.'); + } + }); + + let outputFilepath = `${config.staticDir}/${req.files.file.name}`; + let moveError = await req.files.file.mv(outputFilepath); + if (moveError) return res.status(500).send(moveError); + let outputSizes = await resizeAndSave(config, req.files.file); + + req.model.create({ + name: req.body.name, + caption: req.body.caption, + description: req.body.description, + filename: req.files.file.name, + sizes: outputSizes + }, (mediaCreateError, result) => { + if (mediaCreateError) + return res.status(500).json({ error: mediaCreateError }); + + return res.status(201) + .json({ + message: 'success', + result: result.toJSON({ virtuals: true }) + }); + }); +} diff --git a/src/controllers/uploads.controller.js b/src/controllers/uploads.controller.js deleted file mode 100644 index 804929f310..0000000000 --- a/src/controllers/uploads.controller.js +++ /dev/null @@ -1,44 +0,0 @@ -import mkdirp from 'mkdirp'; -import { resize } from '../../src/utils/imageResizer'; -import Media from '../models/Media.model'; - -function upload(req, res, next, config) { - if (Object.keys(req.files).length === 0) { - return res.status(400).send('No files were uploaded.'); - } - - mkdirp(config.staticDir, (err) => { - if (err) { - console.error(err); - res.status(500).send('Upload failed.'); - } - }); - - let outputFilepath = `${config.staticDir}/${req.files.file.name}`; - req.files.file.mv(outputFilepath, (err) => { - if (err) return res.status(500).send(err); - - if (req.files.file.mimetype.split('/')[0] === 'image') { - resize(config, req.files.file); - } - - Media.create({ - name: req.files.file.name, - filename: req.files.file.name - }, (err, result) => { - if (err) - return res.status(500).json({ error: err }); - - return res.status(201) - .json({ - message: 'success', - result: { - id: result.id, - name: result.name - } - }); - }); - }) -} - -export default { upload }; diff --git a/src/models/Media.model.js b/src/models/Media.model.js index 77ed1dafeb..afed071ef8 100644 --- a/src/models/Media.model.js +++ b/src/models/Media.model.js @@ -1,15 +1,28 @@ import mongoose from 'mongoose'; import buildQuery from '../plugins/buildQuery'; import paginate from '../plugins/paginate'; +import internationalization from '../plugins/internationalization'; -const MediaSchema = new mongoose.Schema({ - name: { type: String }, - caption: { type: String }, - description: { type: String }, - filename: { type: String }, -}); +const mediaModelLoader = (config) => { + const MediaSchema = new mongoose.Schema({ + name: { type: String, intl: true }, + caption: { type: String, intl: true }, + description: { type: String, intl: true }, + filename: { type: String }, + sizes: [{ + height: { type: Number}, + width: { type: Number}, + _id: false + }] + }, + { timestamps: true } + ); -MediaSchema.plugin(paginate); -MediaSchema.plugin(buildQuery); + MediaSchema.plugin(paginate); + MediaSchema.plugin(buildQuery); + MediaSchema.plugin(internationalization, config.localization); -export default mongoose.model('Media', MediaSchema); + return mongoose.model('Media', MediaSchema); +}; + +export default mediaModelLoader; diff --git a/src/requestHandlers/findOne.js b/src/requestHandlers/findOne.js index 7b0b89b951..603d57216d 100644 --- a/src/requestHandlers/findOne.js +++ b/src/requestHandlers/findOne.js @@ -1,6 +1,6 @@ import httpStatus from 'http-status'; import { modelById } from '../resolvers'; -import {createAutopopulateOptions} from '../helpers/mongoose/createAutopopulateOptions'; +import { createAutopopulateOptions } from '../helpers/mongoose/createAutopopulateOptions'; const findOne = (req, res) => { @@ -10,7 +10,7 @@ const findOne = (req, res) => { locale: req.locale, fallback: req.query['fallback-locale'] }; - modelById(query, {...createAutopopulateOptions(req.query.depth)}) + modelById(query, { ...createAutopopulateOptions(req.query.depth) }) .then(doc => res.json(doc)) .catch(err => res.status(httpStatus.INTERNAL_SERVER_ERROR).json({ error: err })); }; diff --git a/src/resolvers/modelById.js b/src/resolvers/modelById.js index 4853daece5..91639075c4 100644 --- a/src/resolvers/modelById.js +++ b/src/resolvers/modelById.js @@ -1,4 +1,4 @@ -const modelById = (query, options)=> { +const modelById = (query, options) => { return new Promise((resolve, reject) => { query.Model.findOne({ _id: query.id }, {}, options, (err, doc) => { @@ -11,10 +11,12 @@ const modelById = (query, options)=> { if (query.locale) { doc.setLocale(query.locale, query.fallback); - const json = doc.toJSON({ virtuals: true }); - result = json; + result = doc.toJSON({ virtuals: true }); } - resolve(result); + + resolve(options.returnRawDoc + ? doc + : result); }) }) }; diff --git a/src/routes/media.routes.js b/src/routes/media.routes.js index d2e13f1f63..4f1cb82b99 100644 --- a/src/routes/media.routes.js +++ b/src/routes/media.routes.js @@ -1,25 +1,32 @@ import express from 'express'; import passport from 'passport'; -import uploadsCtrl from '../controllers/uploads.controller'; +import { upload, update } from '../controllers/media.controller'; import { query } from '../requestHandlers'; import bindModel from '../middleware/bindModel'; -import Media from '../models/Media.model'; +import mediaModelLoader from '../models/Media.model'; const router = express.Router(); const mediaRoutes = config => { - router.all('*', bindModel(Media)); + const mediaModel = mediaModelLoader(config); // Needs config for intl + router.all('*', bindModel(mediaModel)); router .route('') .post( passport.authenticate('jwt', { session: false }), - (req, res, next) => uploadsCtrl.upload(req, res, next, config) + (req, res, next) => upload(req, res, next, config) + ); + + router + .route('/:_id') + .put( + passport.authenticate('jwt', { session: false }), + (req, res, next) => update(req, res, next, config) ); router.route('') .get( - passport.authenticate('jwt', { session: false }), query ); diff --git a/src/utils/imageResizer.js b/src/utils/imageResizer.js index 6880385da6..4d719efd43 100644 --- a/src/utils/imageResizer.js +++ b/src/utils/imageResizer.js @@ -1,5 +1,6 @@ import sharp from 'sharp'; -import sizeOf from 'image-size'; +const { promisify } = require('util'); +const sizeOf = promisify(require('image-size')); function getOutputImageName(sourceImage, size) { let extension = sourceImage.split('.').pop(); @@ -7,25 +8,27 @@ function getOutputImageName(sourceImage, size) { return `${filenameWithoutExtension}-${size.width}x${size.height}.${extension}`; } -export function resize(config, file) { +export async function resizeAndSave(config, file) { let sourceImage = `${config.staticDir}/${file.name}`; - sizeOf(sourceImage, (err, dimensions) => { - for (let size of config.imageSizes) { - if (size.width > dimensions.width) { - console.log(`${size.width} is greater than actual width ${dimensions.width}`); + let outputSizes = []; + try { + const dimensions = await sizeOf(sourceImage); + for (let desiredSize of config.imageSizes) { + if (desiredSize.width > dimensions.width) { continue; } - let outputImageName = getOutputImageName(sourceImage, size); - sharp(sourceImage) - .resize(size.width, size.height, { - position: size.crop || 'centre' + let outputImageName = getOutputImageName(sourceImage, desiredSize); + await sharp(sourceImage) + .resize(desiredSize.width, desiredSize.height, { + position: desiredSize.crop || 'centre' }) - .toFile(outputImageName, (err) => { - if (err) console.log('Error writing resized file', err); - console.log(`Resized image from ${dimensions.width}x${dimensions.height} to ${size.width}x${size.height}`); - }); - + .toFile(outputImageName); + outputSizes.push({ height: desiredSize.height, width: desiredSize.width }); } - }); + } catch (e) { + console.log('error in resize and save', e.message); + } + + return outputSizes; }