updating and validating multiple files from different fields uploded with multer - node.js

I'm trying to update and validate a form consisting of two file inputs (one video and one image to be used as the video thumbnail) and two text inputs.
the validator and convertFiletoField were working fine when I was uploading a single file however, they have both stopped working, this is a long question but, I appreciate it, if anyone can help.
I'm using Node, epxress, mongodb and express-validator v.5.1.2
the Routes:
router.get('/mvs', mvController.index);
router.get('/mvs/create', mvController.create);
router.post('/mvs/create',
uploadVideo.fields([{
name: 'videos', maxCount: 1
}, {
name: 'images', maxCount: 1
}]),
convertFileToField.videoHandle,
mvValidator.handle(),
mvController.store
);
router.get('/mvs/:id/edit', mvController.edit);
router.put('/mvs/:id',
uploadVideo.fields([{
name: 'videos', maxCount: 1
}, {
name: 'images', maxCount: 1
}]),
convertFileToField.videoHandle,
mvValidator.handle(),
mvController.update
);
router.delete('/mvs/:id', mvController.destroy);
the Controller:
const fs = require('fs');
const path = require('path');
const controller = require('app/http/controllers/controller');
const Artist = require('app/models/artist');
const MV = require('app/models/mv');
class mvController extends controller {
async index(req , res) {
try {
let page = req.query.page || 1;
let mvs = await MV.paginate({} , { page , sort : { createdAt : 1 } , limit : 5 });
res.render('admin/mvs/index', { title : 'videos' , mvs });
} catch (err) {
next(err);
}
}
async create(req , res) {
let artists = await Artist.find({});
res.render('admin/mvs/create' , { artists });
}
async store(req , res , next) {
try {
let status = await this.validationData(req);
if(! status) {
if(req.file)
fs.unlinkSync(req.file.path);
return this.back(req,res);
}
// Create music video
let videos = this.videoPath(req.files['videos'][0]);
let images = this.videoPath(req.files['images'][0]);
let { title, artist} = req.body;
let newMV = new MV({
title,
slug : this.slug(title),
artist,
videos,
images
});
await newMV.save();
// update artist Times
this.updateArtistTime(req.body.artist);
return res.redirect('/admin/mvs');
} catch(err) {
next(err);
}
}
async edit(req, res ,next) {
try {
this.isMongoId(req.params.id);
let mv = await MV.findById(req.params.id);
let artists = await Artist.find({});
if( ! mv ) this.error('video does not exist' , 404);
return res.render('admin/mvs/edit' , { mv , artists });
} catch (err) {
next(err);
}
}
async update(req, res , next) {
try {
let status = await this.validationData(req);
if(! status) {
if(req.files)
fs.unlinkSync(req.files.path);
return this.back(req,res);
}
let objForUpdate = {};
// check video
if(req.files) {
objForUpdate.videos = this.videoPath(req.files['videos'][0]);
objForUpdate.images = this.videoPath(req.files['images'][0]);
}
delete req.body.videos;
delete req.body.images;
objForUpdate.slug = this.slug(req.body.title);
let mv = await MV.findByIdAndUpdate(req.params.id , { $set : { ...req.body, ...objForUpdate }});
// prev artist time update
this.updateArtistTime(mv.artist);
// now artist time update
this.updateArtistTime(req.body.artist);
return res.redirect('/admin/mvs');
} catch(err) {
next(err);
}
}
async destroy(req , res , next) {
try {
this.isMongoId(req.params.id);
let mv = await MV.findById(req.params.id);
if( ! mv ) this.error('video does not exist' , 404);
let artistId = mv.artist;
// delete music videos
fs.unlinkSync(`./public${mv.videos}`);
fs.unlinkSync(`./public${mv.images}`)
mv.remove();
// artist time update
this.updateArtistTime(artistId);
return res.redirect('/admin/mvs');
} catch (err) {
next(err);
}
}
async updateArtistTime(artistId) {
let artist = await Artist.findById(artistId).populate('mvs').exec();
artist.set({ time : this.getTime(artist.mvs)});
await artist.save();
}
videoPath(video) {
let addressVideos = this.getUrlVideo(`${video.destination}/${video.filename}`);
return addressVideos;
}
getUrlVideo(dir) {
return dir.substring(8);
}
slug(title) {
return title.replace(/([^۰-۹آ-یa-z0-9]|-)+/g , "-")
}
}
module.exports = new mvController();
the uploadVideo helper
const multer = require('multer');
const mkdirp = require('mkdirp');
const fs = require('fs');
const getDirVideo = () => {
let year = new Date().getFullYear();
let month = new Date().getMonth() + 1;
let day = new Date().getDay();
return `./public/uploads/mvs/${year}/${month}/${day}`;
}
const videoStorage = multer.diskStorage({
destination : (req , file , cb) => {
let dir = getDirVideo();
mkdirp(dir , (err) => cb(null , dir))
},
filename : (req , file , cb) => {
let filePath = getDirVideo() + '/' + file.originalname;
console.log(filePath);
if(!fs.existsSync(filePath))
cb(null , file.originalname);
else
cb(null , Date.now() + '-' + file.originalname);
}
})
const uploadVideo = multer({
storage : videoStorage,
limits : {
fileSize : 1024 * 1024 * 40
}
});
module.exports = uploadVideo;
converFiletoField
videoHandle(req , res , next) {
if(! req.files) {
req.body.videos = undefined;
req.body.images = undefined;
}
else {
req.body.videos = req.files.videos[0].filename;
req.body.images = req.files.images[0].filename;
}
next();
}
Validator:
const validator = require('./validator');
const { check } = require('express-validator/check');
const Artist = require('app/models/artist');
const MV = require('app/models/mv');
const path = require('path');
class mvValidator extends validator {
handle() {
return [
check('title')
.isLength({ min : 3 })
.withMessage('title can not be less than 3 characters'),
check('artist')
.not().isEmpty()
.withMessage('related artist can not remain empty'),
check('videos')
.custom(async (value , { req }) => {
if(req.query._method === 'put' && value === undefined) return;
if(! value)
throw new Error('video can not remain empty');
let fileExt = ['.webm' , '.mp4' , '.flv' , '.avi'];
if(! fileExt.includes(path.extname(value)))
throw new Error('file extention is not acceptable');
}),
check('images')
.custom(async (value , { req }) => {
if(req.query._method === 'put' && value === undefined) return;
if(! value)
throw new Error('video thumbnail can not remain empty');
let fileExt = ['.png' , '.jpg' , '.jpeg' , '.svg'];
if(! fileExt.includes(path.extname(value)))
throw new Error('file extention is not acceptable')
})
]
}
slug(title) {
return title.replace(/([^۰-۹آ-یa-z0-9]|-)+/g , "-")
}
}
module.exports = new mvValidator();
the error I'm getting when I'm trying to update/edit
TypeError: Cannot read property '0' of undefined
at convertFileToField.videoHandle (test\app\http\middleware\convertFileToField.js:22:47)
at Layer.handle [as handle_request] (test\node_modules\express\lib\router\layer.js:95:5)
at next (E:\test\node_modules\express\lib\router\route.js:137:13)
at Immediate._onImmediate (test\node_modules\multer\lib\make-middleware.js:53:37)
at processImmediate (internal/timers.js:463:21
the error I'm getting from validator when I enter title with 2 characters instead on min of 3
TypeError [ERR_INVALID_ARG_TYPE]: The "path" argument must be of type string or an instance of Buffer or URL. Received undefined

just try create a middleware like this :
videoHandle.js
module.exports = (req, res, next) => {
try {
if (!req.files) {
req.body.videos = undefined;
req.body.images = undefined;
} else {
req.body.videos = (req.files.videos) ? req.files.videos[0].filename : undefined;
req.body.images = ( req.files.images)? req.files.images[0].filename : undefined;
}
next();
} catch (err) {
console.log(err)
const error = new HttpError("uploading failed!", 403);
return next(error);
}
};
require the middleware in the router
const videoHandle = require('./videoHandle');//path of videoHandler
router.post('/mvs/create',
uploadVideo.fields([{
name: 'videos', maxCount: 1
}, {
name: 'images', maxCount: 1
}]),
videoHandle,(req,res)=>{
//do somtethings
console.log(req.body)
}
);

Related

[ERR_INVALID_ARG_TYPE]: The "path" argument must be of type string or an instance of Buffer or URL. Received null

I'm having this problem while trying to upload phtos and I don't know why. I'm using multer v. 2.0.0 and express. Is my multer version too old ? Or is it a problem with the await pipeline()?
In upload.js :
const UserModel = require("../models/user.model");
const fs = require("fs");
const { promisify } = require("util");
const pipeline = promisify(require("stream").pipeline);
const { uploadErrors } = require("../utils/errors.utils");
module.exports.uploadProfile = async (req, res) => {
try {
if (
req.file.detectedMimeType != "image/jpg" &&
req.file.detectedMimeType != "image/png" &&
req.file.detectedMimeType != "image/jpeg"
)
throw Error("invalid file");
if (req.file.size > 500000) throw Error("max size");
} catch (err) {
const errors = uploadErrors(err);
return res.status(201).json({ errors });
}
const fileName = req.body.name + ".jpg";
await pipeline(
req.file.stream,
fs.createWriteStream(
`${__dirname}/../client/public/uploads/profil/${fileName}`
)
);
};
In user.routes.js :
const router = require("express").Router();
const multer = require("multer");
const upload = multer();
const userController = require("../controllers/user.controller");
const authController = require("../controllers/auth.controller");
const uploadController = require("../controllers/upload.controller");
// auth
router.post("/register", authController.signUp);
router.post("/login", authController.signIn);
router.get("/logout", authController.logout);
//user display block
router.get("/", userController.getAllUsers);
router.get("/:id", userController.userInfo);
router.put("/:id", userController.updateUser);
router.delete("/:id", userController.deleteUser);
router.patch("/follow/:id", userController.follow);
router.patch("/unfollow/:id", userController.unfollow);
// upload
router.post("/upload", upload.single("file"), uploadController.uploadProfile);
module.exports = router;
Can anyone help me ?
I think we are on the same problem.
You have to go back to the previous version of multer not on 2.0.0 and add sharp js to replace pipeline
npm i multer#1.4.5-lts.1 sharp
upload.controller.js
const { uploadErrors } = require("../utils/errors.utils.js");
const sharp = require("sharp");
//upload image profil utilisateur
module.exports.uploadProfil = async (req, res) => {
//console.log(req.file);
//renome le fichier avec extension .jpg
const fileName = req.body.name +".jpg";
try {
if (
req.file.mimetype != "image/jpg" &&
req.file.mimetype != "image/png" &&
req.file.mimetype != "image/jpeg"
)
throw Error("invalid file");
if (req.file.size > 500000) throw Error("max size");
} catch (err) {
const errors = uploadErrors(err);
return res.status(201).json({ errors });
}
try {
await sharp(req.file.buffer)
.resize({ width: 150, height: 150 })
.toFile(`${__dirname}/../client/public/uploads/profil/${fileName}`
);
res.status(201).send("Photo de profil chargé avec succés");
} catch (err) {
res.status(400).send(err);
}
}

google cloud function error DEADLINE EXCEEDED

I'm trying to write a simple pub/sub triggered cloud function to update my Firestore collection of stocks.I'm getting a bunch of weird error messages with one prominent of Error: 4 DEADLINE_EXCEEDED: Deadline exceeded. Whats even more strange that some stocks gets updated correctly while others don't. Im new to Javascript/Typescript so obviously I have some misunderstanding about how to return promises in this case here. The idea here is very simple loop through each ticker in the collection make a request to updated data then update existing document data and save it
export const updateChart = functions.pubsub.schedule('35 16 * * 1-5').timeZone('America/New_York').onRun(async(_context) => {
const key = functions.config().services.key as string
const db = admin.firestore()
const charts5DRef = db.collection("charts5D")
var needsUpdate : boolean = true
// I cut off some unrelated code for brevity sake
if (needsUpdate){
const snapshot = await charts5DRef.get()
var path = ``
const timestamp = Date.now() / 1000
return snapshot.forEach(async function(document){ // this could contain 100's of stock tickers
const ticker = document.id
const docData = document.data()
var labels = docData.labels as [string]
var marketNotional = docData.marketNotional as [number]
var marketTrades = docData.marketNumberOfTrades as [number]
var dates = docData.dates as [string]
var closings = docData.close as [number]
var volume = docData.marketVolume as [number]
path = `apiUrl to get data`
const options = {
method : 'GET',
uri : path,
resolveWithFullResponse : true,
}
await req(options).then(async(response)=>{
if(response.statusCode === 200){
const resultData = JSON.parse(response.body)
const updatedPrices = resultData as [IntradayPrice]
updatedPrices.forEach(function(value){
if(value.close !== undefined){
closings.splice(0,1)
marketTrades.splice(0,1)
marketNotional.splice(0,1)
labels.splice(0,1)
dates.splice(0,1)
volume.splice(0,1)
closings.push(value.close)
dates.push(value.date)
if(value.label !== undefined){ labels.push(value.label) } else { labels.push("") }
if(value.marketNotional !== undefined) { marketNotional.push(value.marketNotional) } else { marketNotional.push(0) }
if(value.marketNumberOfTrades !== undefined) { marketTrades.push(value.marketNumberOfTrades) } else { marketTrades.push(0) }
if(value.marketVolume !== undefined) { volume.push(value.marketVolume) } else { volume.push(0) }
}
})
await charts5DRef.doc(ticker).set({lastUpdate : timestamp,close : closings, labels : labels, marketVolume : volume,marketNumberOfTrades : marketTrades, marketNotional : marketNotional, dates : dates}).then(()=>{
console.log(`Updated ${ticker} 5Dchart successfully`)
}).catch((error)=>{
console.log(error)
})
}
}).catch((error)=>{
console.log(error)
})
})
}else{
console.log("Markets closed")
return
}
})
ok so this solved the errors
export const updateChart = functions.pubsub.schedule('35 16 * * 1-5').timeZone('America/New_York').onRun(async(_context) => {
const key = functions.config().services.key as string
const db = admin.firestore()
const charts5DRef = db.collection("charts5D")
var needsUpdate : boolean = false
if (needsUpdate){
const snapshot = await charts5DRef.get()
var path = ``
const timestamp = Date.now() / 1000
const promises : bird<void>[] = []
snapshot.forEach(async function(document){
const ticker = document.id
const docData = document.data()
var labels = docData.labels as [string]
var marketNotional = docData.marketNotional as [number]
var marketTrades = docData.marketNumberOfTrades as [number]
var dates = docData.dates as [string]
var closings = docData.close as [number]
var volume = docData.marketVolume as [number]
path = `https://cloud.iexapis.com/stable/stock/${ticker}/intraday-prices?token=${key}&chartInterval=10`
const options = {
method : 'GET',
uri : path,
resolveWithFullResponse : true,
}
const promise = req(options).then(async(response)=>{
if(response.statusCode === 200){
const resultData = JSON.parse(response.body)
const updatedPrices = resultData as [IntradayPrice]
updatedPrices.forEach(function(value){
if(value.close !== undefined){
closings.splice(0,1)
marketTrades.splice(0,1)
marketNotional.splice(0,1)
labels.splice(0,1)
dates.splice(0,1)
volume.splice(0,1)
closings.push(value.close)
dates.push(value.date)
if(value.label !== undefined){ labels.push(value.label) } else { labels.push("") }
if(value.marketNotional !== undefined) { marketNotional.push(value.marketNotional) } else { marketNotional.push(0) }
if(value.marketNumberOfTrades !== undefined) { marketTrades.push(value.marketNumberOfTrades) } else { marketTrades.push(0) }
if(value.marketVolume !== undefined) { volume.push(value.marketVolume) } else { volume.push(0) }
}
})
await charts5DRef.doc(ticker).set({lastUpdate : timestamp,close : closings, labels : labels, marketVolume : volume,marketNumberOfTrades : marketTrades, marketNotional : marketNotional, dates : dates}).then(()=>{
console.log(`Updated ${ticker} 5Dchart successfully`)
}).catch((error)=>{
console.log(error)
})
}
}).catch((error)=>{
console.log(error)
})
promises.push(promise)
})
return Promise.all(promises).then(()=>{
console.log("All good")
}).catch((error)=>{
console.log(error)
})
}else{
console.log("Markets closed")
return
}
})

Asynchronicity issue: why does the second part of my function run before a loop event finishes?

I have a route on an Express server that updates a User profile. The User profile is updated before I have finished to parse the data to update. How so?
I want to update two const: newProfilePicture & newOtherPictures. They are correctly updated, but after the user has been updated, so it's useless. How to fix this asynchronicity issue?
Here is the function:
router.post("/upload-images", upload.array("image"), async (req, res) => {
const { userId } = req.body;
try {
if (req.files) {
let newProfilePicture = null;
let newOtherPictures = [];
req.files.forEach(({ path, originalname }) => {
cloudinary.uploader.upload(
path,
{
resource_type: "image",
public_id: `myapp/users/${userId}/${originalname}`,
crop: "scale",
quality: "auto",
},
(err, res) => {
if (err) {
return fs.unlinkSync("./" + path);
}
fs.unlinkSync("./" + path);
if (originalname === "main") {
return (newProfilePicture = res.secure_url);
}
return newOtherPictures.push({
id: originalname,
url: res.secure_url,
});
}
);
});
// THIS PART IS COMPLETE BEFORE THE req.files.forEach IS DONE
const user = await User.findById(userId);
const { otherPictures, profilePicture } = updatePictures(
newProfilePicture,
newOtherPictures,
user
);
User.findByIdAndUpdate(
userId,
{ profilePicture, otherPictures },
{ new: true }
);
res.send("upload images success");
}
} catch (err) {
console.log("err", err);
return res.status(500).send("upload images failed");
}
});
It happens because cloudinary.uploader.upload() runs asynchronously. Since you mentioned it doesn't have promise interface, you can convert the callback to promise using NodeJS's util.promise function as it's error first callback.
const { promisify } = require("util");
const fs = require("fs");
const cloudinaryUpload = promisify(cloudinary.uploader.upload.bind(cloudinary.uploader))
router.post("/upload-images", upload.array("image"), async (req, res) => {
try {
if (!req.files) {
return res.send("no images in the request body");
}
let newProfilePicture = null;
let newOtherPictures = [];
for (const { path, originalName } of req.files) {
try {
const response = await cloudinaryUpload(path, {
resource_type: "image",
public_id: `myapp/users/${userId}/${originalName}`,
crop: "scale",
quality: "auto",
});
await fs.promises.unlink("./" + path);
if (originalname === "main") {
newProfilePicture = response.secure_url;
continue;
}
newOtherPictures.push({
id: originalName,
url: response.secure_url,
});
} catch (error) {
//do what you want if there is an error
//throw error if you want
await fs.promises.unlink("./" + path);
}
}
const user = await User.findById(userId);
const { otherPictures, profilePicture } = updatePictures(
newProfilePicture,
newOtherPictures,
user
);
//use User.updateOne() as you don't need the doc back
await User.findByIdAndUpdate(
userId,
{ profilePicture, otherPictures },
{ new: true }
);
return res.send("upload images success");
} catch (error) {
console.log("err", err);
return res.status(500).send("upload images failed");
}
});

Reading all JSONs in a folder and getting their strings

There is a folder with a lot of JSON files and all of them got an object called "name"
I want to get their strings and turn them into a string like this
name0=UsernameExample;name1=Flowers;name2=Test; ...
the number after name is the index/count of the json, like if its name48, its the 48th json
This far I only tried to read the JSONs from the folder but I failed of course
let s = "";
fs.readdir('/tmp/userdb/', (files) => {
files.each(file => {
name = file[file.keys()[0]];
})})
I can already convert this
var other_users = (serialize({
"sid0": 0,
"name0": "user1",
"pays0": "8521",
"avatar0": "357",
"onlinescore0": "50"
}));
to this:
sid0=0;name0=user1;pays0=8521;avatar0=357;onlinescore0=50
with this const
const serialize = obj =>
Object.entries(obj).map(([k, v]) => `${k}=${v}`).join(';')
And I want to send the result to the user with this way
if (req.query.d === 'getRandomPlayers') {
var sessionid = req.body.player_sid
let user = require("./tmp/userdb/" + sessionid + ".json")
var current_user = (serialize({
player_name: user.name
}));
res.send("method_id=1665;" + current_user);
}
It should be like res.send("method_id=1665;" + current_user + thefinalresult);
thefinalresult is what this all should go. current_user and other stuff is not related to this question.
Assuming an example JSON files inside /tmp/userdb/ has the following structure,
{
"53874745": {
"avatar": "372",
"name": "BILLY",
"onlinescore": "1",
"pays": "8758"
}
}
you could do something like the following:
const { promisify } = require("util");
const fs = require("fs");
const path = require("path");
const readdir = promisify(fs.readdir);
const readFile = promisify(fs.readFile);
async function process(excludedSessionId) {
try {
const entries = [];
// get a list of all `JSON` files
const jsonFiles = await readdir(
path.join(__dirname, "./tmp/userdb/")
).then(
(files) => files.filter(
(file) => path.extname(file) === ".json" && !file.includes(excludedSessionId)
)
);
// iterate through a list of all `JSON` files & read their content
for (const [index, file] of jsonFiles.entries()) {
const content = await readFile(
path.join(__dirname, "./tmp/userdb/", file)
).then(JSON.parse);
// create an object for a new entry
const key = `sid${index}`;
const keyValue = Object.keys(content)[0];
// use the `spread syntax` to include the rest of the
// properties in a new entry
const entry = {
[key]: keyValue,
...content[keyValue],
};
entries.push(entry);
}
console.log(entries[0]);
// {
// sid0: '53874745',
// avatar: '372',
// name: 'BILLY',
// onlinescore: '1',
// pays: '8758'
// }
const result = entries.map((entry) => serialize(entry)).join(";");
console.log(result);
// sid0=53874745;avatar=372;name=BILLY;onlinescore=1;pays=8758;
// sid1=154261758;avatar=480;name=JESSEY;onlinescore=30;pays=8521;
return result;
} catch (error) {
console.error(error);
throw error;
}
}
process("154261742");
Then, if you'd want to use this function in a callback of your route controller, you could do something like the following:
app.get("/user", (req, res) => {
// ...
const excludedSessionId = req.body.player_sid;
process(excludedSessionId)
.then(result => {
res.send(result);
})
.catch(error => {
res.status(500).send("Something went wrong.");
});
});
References:
Spread syntax (...) - MDN
async function - MDN

How to fix reference error: logger not defined

I am currently getting a "ReferenceError: logger is not defined" error when I am running some tests. These errors did not occur while my endpoints were in my app.js. They only started occurring when I moved my endpoints into my router file.
My endpoints all work in postman and can retrieve the data but for some reason all my tests are failing. Is there something I am missing in my router file?
const express = require("express");
const uuid = require("uuid/v4");
const logger = require("../logger");
const { bookmarks } = require("../store");
const BookmarksService = require("../bookmarks-service");
const bookmarkRouter = express.Router();
const bodyParser = express.json();
bookmarkRouter
.route("/bookmarks")
.get((req, res, next) => {
const knexInstance = req.app.get("db");
BookmarksService.getAllBookmarks(knexInstance)
.then(bookmarks => {
res.json(bookmarks);
})
.catch(next);
})
.post(bodyParser, (req, res) => {
//implementation
for (const field of ["title", "url", "rating"]) {
if (!req.body[field]) {
logger.error(`${field} is required`);
return res.status(400).send(`${field} is required`);
}
}
const { title, url, rating, desc } = req.body;
if (!title) {
logger.error("Title is required");
return res.status(400).send("Invalid data");
}
if (!url) {
logger.error("Url is required");
return res.status(400).send("Invalid data");
}
if (!desc) {
logger.error("Desc is required");
return res.status(400).send("Invalid data");
}
const id = uuid();
const bookmark = {
id,
title,
url,
rating,
desc
};
bookmarks.push(bookmark);
logger.info(`Bookmark with bookmark ${id} created`);
res
.status(201)
.location(`http://localhost.8000/bookmark/${id}`)
.json(bookmark);
});
bookmarkRouter
.route("/bookmarks/:id")
.get((req, res, next) => {
//implementation
const { id } = req.params;
const bookmark = bookmarks.find(b => b.id == id);
const knexInstance = req.app.get("db");
BookmarksService.getById(knexInstance, id)
.then(bookmark => {
if (!bookmark) {
logger.error(`Bookmark with id ${id} not found`);
return res.status(404).send({
error: { message: `Bookmark doesn't exist` }
});
}
res.json(bookmark);
})
.catch(next);
})
.delete((req, res) => {
//implementation
const { id } = req.params;
const bookmarkIndex = bookmarks.findIndex(li => li.id == id);
if (bookmarkIndex === -1) {
logger.error(`Bookmark with id ${id} not found.`);
return res.status(400).send("Not found");
}
bookmarks.splice(bookmarkIndex, 1);
logger.info(`Bookmark with id ${id} deleted`);
res.status(204).end();
});
module.exports = bookmarkRouter;

Resources