init
This commit is contained in:
2
src/models/index.js
Normal file
2
src/models/index.js
Normal file
@@ -0,0 +1,2 @@
|
||||
module.exports.Token = require('./token.model');
|
||||
module.exports.User = require('./user.model');
|
||||
2
src/models/plugins/index.js
Normal file
2
src/models/plugins/index.js
Normal file
@@ -0,0 +1,2 @@
|
||||
module.exports.toJSON = require('./toJSON.plugin');
|
||||
module.exports.paginate = require('./paginate.plugin');
|
||||
70
src/models/plugins/paginate.plugin.js
Normal file
70
src/models/plugins/paginate.plugin.js
Normal file
@@ -0,0 +1,70 @@
|
||||
/* eslint-disable no-param-reassign */
|
||||
|
||||
const paginate = (schema) => {
|
||||
/**
|
||||
* @typedef {Object} QueryResult
|
||||
* @property {Document[]} results - Results found
|
||||
* @property {number} page - Current page
|
||||
* @property {number} limit - Maximum number of results per page
|
||||
* @property {number} totalPages - Total number of pages
|
||||
* @property {number} totalResults - Total number of documents
|
||||
*/
|
||||
/**
|
||||
* Query for documents with pagination
|
||||
* @param {Object} [filter] - Mongo filter
|
||||
* @param {Object} [options] - Query options
|
||||
* @param {string} [options.sortBy] - Sorting criteria using the format: sortField:(desc|asc). Multiple sorting criteria should be separated by commas (,)
|
||||
* @param {string} [options.populate] - Populate data fields. Hierarchy of fields should be separated by (.). Multiple populating criteria should be separated by commas (,)
|
||||
* @param {number} [options.limit] - Maximum number of results per page (default = 10)
|
||||
* @param {number} [options.page] - Current page (default = 1)
|
||||
* @returns {Promise<QueryResult>}
|
||||
*/
|
||||
schema.statics.paginate = async function (filter, options) {
|
||||
let sort = '';
|
||||
if (options.sortBy) {
|
||||
const sortingCriteria = [];
|
||||
options.sortBy.split(',').forEach((sortOption) => {
|
||||
const [key, order] = sortOption.split(':');
|
||||
sortingCriteria.push((order === 'desc' ? '-' : '') + key);
|
||||
});
|
||||
sort = sortingCriteria.join(' ');
|
||||
} else {
|
||||
sort = 'createdAt';
|
||||
}
|
||||
|
||||
const limit = options.limit && parseInt(options.limit, 10) > 0 ? parseInt(options.limit, 10) : 10;
|
||||
const page = options.page && parseInt(options.page, 10) > 0 ? parseInt(options.page, 10) : 1;
|
||||
const skip = (page - 1) * limit;
|
||||
|
||||
const countPromise = this.countDocuments(filter).exec();
|
||||
let docsPromise = this.find(filter).sort(sort).skip(skip).limit(limit);
|
||||
|
||||
if (options.populate) {
|
||||
options.populate.split(',').forEach((populateOption) => {
|
||||
docsPromise = docsPromise.populate(
|
||||
populateOption
|
||||
.split('.')
|
||||
.reverse()
|
||||
.reduce((a, b) => ({ path: b, populate: a }))
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
docsPromise = docsPromise.exec();
|
||||
|
||||
return Promise.all([countPromise, docsPromise]).then((values) => {
|
||||
const [totalResults, results] = values;
|
||||
const totalPages = Math.ceil(totalResults / limit);
|
||||
const result = {
|
||||
results,
|
||||
page,
|
||||
limit,
|
||||
totalPages,
|
||||
totalResults,
|
||||
};
|
||||
return Promise.resolve(result);
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
module.exports = paginate;
|
||||
43
src/models/plugins/toJSON.plugin.js
Normal file
43
src/models/plugins/toJSON.plugin.js
Normal file
@@ -0,0 +1,43 @@
|
||||
/* 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 deleteAtPath = (obj, path, index) => {
|
||||
if (index === path.length - 1) {
|
||||
delete obj[path[index]];
|
||||
return;
|
||||
}
|
||||
deleteAtPath(obj[path[index]], path, index + 1);
|
||||
};
|
||||
|
||||
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) {
|
||||
deleteAtPath(ret, path.split('.'), 0);
|
||||
}
|
||||
});
|
||||
|
||||
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;
|
||||
44
src/models/token.model.js
Normal file
44
src/models/token.model.js
Normal file
@@ -0,0 +1,44 @@
|
||||
const mongoose = require('mongoose');
|
||||
const { toJSON } = require('./plugins');
|
||||
const { tokenTypes } = require('../config/tokens');
|
||||
|
||||
const tokenSchema = mongoose.Schema(
|
||||
{
|
||||
token: {
|
||||
type: String,
|
||||
required: true,
|
||||
index: true,
|
||||
},
|
||||
user: {
|
||||
type: mongoose.SchemaTypes.ObjectId,
|
||||
ref: 'User',
|
||||
required: true,
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
enum: [tokenTypes.REFRESH, tokenTypes.RESET_PASSWORD, tokenTypes.VERIFY_EMAIL],
|
||||
required: true,
|
||||
},
|
||||
expires: {
|
||||
type: Date,
|
||||
required: true,
|
||||
},
|
||||
blacklisted: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
timestamps: true,
|
||||
}
|
||||
);
|
||||
|
||||
// add plugin that converts mongoose to json
|
||||
tokenSchema.plugin(toJSON);
|
||||
|
||||
/**
|
||||
* @typedef Token
|
||||
*/
|
||||
const Token = mongoose.model('Token', tokenSchema);
|
||||
|
||||
module.exports = Token;
|
||||
91
src/models/user.model.js
Normal file
91
src/models/user.model.js
Normal file
@@ -0,0 +1,91 @@
|
||||
const mongoose = require('mongoose');
|
||||
const validator = require('validator');
|
||||
const bcrypt = require('bcryptjs');
|
||||
const { toJSON, paginate } = require('./plugins');
|
||||
const { roles } = require('../config/roles');
|
||||
|
||||
const userSchema = mongoose.Schema(
|
||||
{
|
||||
name: {
|
||||
type: String,
|
||||
required: true,
|
||||
trim: true,
|
||||
},
|
||||
email: {
|
||||
type: String,
|
||||
required: true,
|
||||
unique: true,
|
||||
trim: true,
|
||||
lowercase: true,
|
||||
validate(value) {
|
||||
if (!validator.isEmail(value)) {
|
||||
throw new Error('Invalid email');
|
||||
}
|
||||
},
|
||||
},
|
||||
password: {
|
||||
type: String,
|
||||
required: true,
|
||||
trim: true,
|
||||
minlength: 8,
|
||||
validate(value) {
|
||||
if (!value.match(/\d/) || !value.match(/[a-zA-Z]/)) {
|
||||
throw new Error('Password must contain at least one letter and one number');
|
||||
}
|
||||
},
|
||||
private: true, // used by the toJSON plugin
|
||||
},
|
||||
role: {
|
||||
type: String,
|
||||
enum: roles,
|
||||
default: 'user',
|
||||
},
|
||||
isEmailVerified: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
timestamps: true,
|
||||
}
|
||||
);
|
||||
|
||||
// add plugin that converts mongoose to json
|
||||
userSchema.plugin(toJSON);
|
||||
userSchema.plugin(paginate);
|
||||
|
||||
/**
|
||||
* Check if email is taken
|
||||
* @param {string} email - The user's email
|
||||
* @param {ObjectId} [excludeUserId] - The id of the user to be excluded
|
||||
* @returns {Promise<boolean>}
|
||||
*/
|
||||
userSchema.statics.isEmailTaken = async function (email, excludeUserId) {
|
||||
const user = await this.findOne({ email, _id: { $ne: excludeUserId } });
|
||||
return !!user;
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if password matches the user's password
|
||||
* @param {string} password
|
||||
* @returns {Promise<boolean>}
|
||||
*/
|
||||
userSchema.methods.isPasswordMatch = async function (password) {
|
||||
const user = this;
|
||||
return bcrypt.compare(password, user.password);
|
||||
};
|
||||
|
||||
userSchema.pre('save', async function (next) {
|
||||
const user = this;
|
||||
if (user.isModified('password')) {
|
||||
user.password = await bcrypt.hash(user.password, 8);
|
||||
}
|
||||
next();
|
||||
});
|
||||
|
||||
/**
|
||||
* @typedef User
|
||||
*/
|
||||
const User = mongoose.model('User', userSchema);
|
||||
|
||||
module.exports = User;
|
||||
Reference in New Issue
Block a user