From f8ba3f619ac42f2030c358fb44095b72fb37013b Mon Sep 17 00:00:00 2001 From: Hagop Jamkojian Date: Sat, 16 May 2020 20:53:52 +0200 Subject: [PATCH] Handle mongoose to json conversions in a custom plugin --- src/controllers/auth.controller.js | 4 ++-- src/controllers/user.controller.js | 8 +++---- src/models/token.model.js | 6 +++-- src/models/user.model.js | 18 +++++---------- src/models/utils.js | 36 ++++++++++++++++++++++++++++++ 5 files changed, 51 insertions(+), 21 deletions(-) create mode 100644 src/models/utils.js diff --git a/src/controllers/auth.controller.js b/src/controllers/auth.controller.js index 9e922651..039330be 100644 --- a/src/controllers/auth.controller.js +++ b/src/controllers/auth.controller.js @@ -10,7 +10,7 @@ const register = catchAsync(async (req, res) => { } const user = await User.create(req.body); const tokens = await tokenService.generateAuthTokens(user.id); - const response = { user: user.transform(), tokens }; + const response = { user, tokens }; res.status(httpStatus.CREATED).send(response); }); @@ -21,7 +21,7 @@ const login = catchAsync(async (req, res) => { throw new ApiError(httpStatus.UNAUTHORIZED, 'Incorrect email or password'); } const tokens = await tokenService.generateAuthTokens(user.id); - const response = { user: user.transform(), tokens }; + const response = { user, tokens }; res.send(response); }); diff --git a/src/controllers/user.controller.js b/src/controllers/user.controller.js index 195d144d..aa35f282 100644 --- a/src/controllers/user.controller.js +++ b/src/controllers/user.controller.js @@ -10,14 +10,14 @@ const createUser = catchAsync(async (req, res) => { throw new ApiError(httpStatus.BAD_REQUEST, 'Email already taken'); } const user = await User.create(req.body); - res.status(httpStatus.CREATED).send(user.transform()); + res.status(httpStatus.CREATED).send(user); }); const getUsers = catchAsync(async (req, res) => { const filter = pick(req.query, ['name', 'role']); const options = getQueryOptions(req.query); const users = await User.find(filter, null, options); - const response = users.map((user) => user.transform()); + const response = users; res.send(response); }); @@ -26,7 +26,7 @@ const getUser = catchAsync(async (req, res) => { if (!user) { throw new ApiError(httpStatus.NOT_FOUND, 'User not found'); } - res.send(user.transform()); + res.send(user); }); const updateUser = catchAsync(async (req, res) => { @@ -39,7 +39,7 @@ const updateUser = catchAsync(async (req, res) => { } Object.assign(user, req.body); await user.save(); - res.send(user.transform()); + res.send(user); }); const deleteUser = catchAsync(async (req, res) => { diff --git a/src/models/token.model.js b/src/models/token.model.js index 071f1641..bfc634e5 100644 --- a/src/models/token.model.js +++ b/src/models/token.model.js @@ -1,4 +1,5 @@ const mongoose = require('mongoose'); +const { toJSON } = require('./utils'); const tokenSchema = mongoose.Schema( { @@ -28,11 +29,12 @@ const tokenSchema = mongoose.Schema( }, { timestamps: true, - toObject: { getters: true }, - toJSON: { getters: true }, } ); +// add plugin that converts mongoose to json +tokenSchema.plugin(toJSON); + const Token = mongoose.model('Token', tokenSchema); module.exports = Token; diff --git a/src/models/user.model.js b/src/models/user.model.js index 4823623c..e09a2df1 100644 --- a/src/models/user.model.js +++ b/src/models/user.model.js @@ -1,7 +1,7 @@ const mongoose = require('mongoose'); const validator = require('validator'); const bcrypt = require('bcryptjs'); -const { omit, pick } = require('lodash'); +const { toJSON } = require('./utils'); const { roles } = require('../config/roles'); const userSchema = mongoose.Schema( @@ -33,6 +33,7 @@ const userSchema = mongoose.Schema( throw new Error('Password must contain at least one letter and one number'); } }, + private: true, // used by the toJSON plugin }, role: { type: String, @@ -42,11 +43,12 @@ const userSchema = mongoose.Schema( }, { timestamps: true, - toObject: { getters: true }, - toJSON: { getters: true }, } ); +// add plugin that converts mongoose to json +userSchema.plugin(toJSON); + userSchema.statics.isEmailTaken = async function (email, excludeUserId) { const user = await this.findOne({ email, _id: { $ne: excludeUserId } }); return !!user; @@ -57,16 +59,6 @@ userSchema.methods.isPasswordMatch = async function (password) { return bcrypt.compare(password, user.password); }; -userSchema.methods.toJSON = function () { - const user = this; - return omit(user.toObject(), ['password']); -}; - -userSchema.methods.transform = function () { - const user = this; - return pick(user.toJSON(), ['id', 'email', 'name', 'role']); -}; - userSchema.pre('save', async function (next) { const user = this; if (user.isModified('password')) { diff --git a/src/models/utils.js b/src/models/utils.js new file mode 100644 index 00000000..9dd55e8c --- /dev/null +++ b/src/models/utils.js @@ -0,0 +1,36 @@ +/* eslint-disable no-param-reassign */ + +/** + * A mongoose schema plugin which applies the following in the toJSON transform call: + * - removes __v, createdAt, updatedAt, and any path that has private: true + * - replaces _id with id + */ +const toJSON = (schema) => { + let transform; + if (schema.options.toJSON && schema.options.toJSON.transform) { + transform = schema.options.toJSON.transform; + } + + schema.options.toJSON = Object.assign(schema.options.toJSON || {}, { + transform(doc, ret, options) { + Object.keys(schema.paths).forEach((path) => { + if (schema.paths[path].options && schema.paths[path].options.private) { + delete ret[path]; + } + }); + + ret.id = ret._id.toString(); + delete ret._id; + delete ret.__v; + delete ret.createdAt; + delete ret.updatedAt; + if (transform) { + return transform(doc, ret, options); + } + }, + }); +}; + +module.exports = { + toJSON, +};