'use strict'; // modules const fs = require('fs'); const express = require('express'); const multer = require('multer'); const storage = multer.memoryStorage(); const upload = multer({storage: storage}); // HTTP status codes by name const codes = require('./http-codes'); const apiAuthenticationMiddleware = require('../middleware/auth-middleware'); const checkHl = require('../middleware/permission-check').checkHl; const genericGetById = require('./_generic').genericGetById; const routerHandling = require('../middleware/router-handling'); const idValidator = require('../middleware/validators').idValidator; const resourceLocation = require('../middleware/resource-location').resourceLocation().concat('/decoration/'); // Mongoose Model using mongoDB const DecorationModel = require('../models/decoration'); const AwardingsModel = require('../models/awarding'); // util const getFileBuffer = require('../tools/util').getFileBuffer; const imageDimensionValidator = require('../middleware/validators').imageDimensionValidator; const MAX_MEDAL_IMAGE_WIDTH = 100; const MAX_MEDAL_IMAGE_HEIGHT = 150; const MAX_RIBBON_IMAGE_WIDTH = 150; const MAX_RIBBON_IMAGE_HEIGHT = 50; const decorationRouter = new express.Router(); // routes ********************** decorationRouter.route('/') .get((req, res, next) => { const filter = {}; if (req.query.fractFilter) { filter.fraction = { '$in': req.query.fractFilter .toUpperCase() .split(','), }; } if (req.query.q) { filter.name = {$regex: req.query.q, $options: 'i'}; } DecorationModel.find(filter, {}, { sort: { fraction: 'asc', isMedal: 'asc', sortingNumber: 'asc', name: 'asc', }, }, (err, items) => { if (err) { err.status = codes.servererror; return next(err); } if (items && items.length > 0) { res.locals.items = items; } else { res.locals.items = []; } res.locals.processed = true; next(); }); }) .post(apiAuthenticationMiddleware, checkHl, upload.single('image'), (req, res, next) => { if (req.file) { const decoration = new DecorationModel(req.body); const imageFileBuffer = getFileBuffer(req.file); const err = imageDimensionValidator(imageFileBuffer, (decoration.isMedal ? MAX_MEDAL_IMAGE_WIDTH : MAX_RIBBON_IMAGE_WIDTH), (decoration.isMedal ? MAX_MEDAL_IMAGE_HEIGHT : MAX_RIBBON_IMAGE_HEIGHT)); if (err) { return next(err); } // timestamp and default are set automatically by Mongoose Schema Validation decoration.save((err) => { if (err) { err.status = codes.wrongrequest; err.message += ' in fields: ' + Object.getOwnPropertyNames(err.errors); return next(err); } res.status(codes.created); res.locals.items = decoration; fs.appendFile(resourceLocation + decoration._id + '.png', imageFileBuffer, (err) => { if (err) next(err); }); next(); }); } else { const err = new Error('no image file provided'); err.status = codes.wrongmediasend; next(err); } }) .all( routerHandling.httpMethodNotAllowed ); decorationRouter.route('/:id') .get(idValidator, (req, res, next) => { return genericGetById(req, res, next, DecorationModel); }) .patch(apiAuthenticationMiddleware, checkHl, upload.single('image'), (req, res, next) => { if (!req.body || (req.body._id && req.body._id !== req.params.id)) { // little bit different as in PUT. :id does not need to be in data, but if the _id and url id must // match const err = new Error( `id of PATCH resource and send JSON body are not equal: ${req.params.id} vs ${req.body._id}`); err.status = codes.notfound; next(err); return; // prevent node to process this function further after next() has finished. } // optional task 3: increment version manually as we do not use .save(.) req.body.updatedAt = new Date(); req.body.$inc = {__v: 1}; DecorationModel.findById(req.params.id, (err, item) => { if (err) { return next(err); } if (req.file) { const imageFileBuffer = getFileBuffer(req.file); const imageDimensionError = imageDimensionValidator(imageFileBuffer, (item.isMedal ? MAX_MEDAL_IMAGE_WIDTH : MAX_RIBBON_IMAGE_WIDTH), (item.isMedal ? MAX_MEDAL_IMAGE_HEIGHT : MAX_RIBBON_IMAGE_HEIGHT)); if (imageDimensionError) { return next(imageDimensionError); } const file = resourceLocation + req.params.id + '.png'; fs.unlink(file, (err) => { if (err) next(err); fs.appendFile(file, imageFileBuffer, (err) => { if (err) next(err); }); }); } // PATCH is easier with mongoose than PUT. You simply update by all data that comes from outside. no // need to reset attributes that are missing. DecorationModel.findByIdAndUpdate(req.params.id, req.body, {new: true}, (err, item) => { if (err) { err.status = codes.wrongrequest; } else if (!item) { err = new Error('item not found'); err.status = codes.notfound; } else { res.locals.items = item; } next(err); }); }); }) .delete(apiAuthenticationMiddleware, checkHl, (req, res, next) => { const id = req.params.id; DecorationModel.findByIdAndRemove(id, (err, item) => { if (err) { err.status = codes.wrongrequest; } else if (!item) { err = new Error('item not found'); err.status = codes.notfound; } // deleted all awardings linked to this decoration AwardingsModel.find({decorationId: id}).deleteMany(); // delete graphic fs.unlink(resourceLocation.concat(id).concat('.png'), (err) => { // we don't set res.locals.items and thus it will send a 204 (no content) at the end. see last // handler res.locals.processed = true; next(err); }); }); }) .all( routerHandling.httpMethodNotAllowed ); // this middleware function can be used, if you like or remove it // it looks for object(s) in res.locals.items and if they exist, they are send to the client as json decorationRouter.use(routerHandling.emptyResponse); module.exports = decorationRouter;