From 0674b1aa2cc330c6af1a308518fb079e961837d7 Mon Sep 17 00:00:00 2001 From: = <=> Date: Wed, 9 Dec 2020 18:05:36 +0100 Subject: [PATCH] Add goal service --- md-seed-config.js | 4 +- src/controllers/goal.controller.js | 25 +++ src/controllers/index.js | 1 + src/controllers/user.controller.js | 2 +- src/models/goal.model.js | 33 ++++ src/models/index.js | 1 + src/models/user.model.js | 21 --- src/routes/v1/goal.route.js | 17 +++ src/routes/v1/index.js | 2 + src/routes/v1/user.route.js | 234 ----------------------------- src/seeders/goal.seeder.js | 31 ++++ src/seeders/index.js | 1 + src/services/auth.service.js | 23 --- src/services/gender.service.js | 8 +- src/services/goal.service.js | 15 ++ src/services/index.js | 1 + src/services/user.service.js | 35 ----- src/validations/auth.validation.js | 1 - src/validations/goal.validation.js | 22 +++ src/validations/index.js | 1 + src/validations/user.validation.js | 3 - 21 files changed, 158 insertions(+), 323 deletions(-) create mode 100644 src/controllers/goal.controller.js create mode 100644 src/models/goal.model.js create mode 100644 src/routes/v1/goal.route.js create mode 100644 src/seeders/goal.seeder.js create mode 100644 src/services/goal.service.js create mode 100644 src/validations/goal.validation.js diff --git a/md-seed-config.js b/md-seed-config.js index 48dcf92..adc28c9 100644 --- a/md-seed-config.js +++ b/md-seed-config.js @@ -1,5 +1,5 @@ const mongoose = require('mongoose'); -const { GenderSeeder, UnitSeeder, ActivitySeeder, ProductSeeder } = require('./src/seeders'); +const { GenderSeeder, UnitSeeder, ActivitySeeder, ProductSeeder, GoalSeeder } = require('./src/seeders'); const config = require('./src/config/config'); @@ -11,9 +11,11 @@ const mongoURL = config.mongoose.url * @type {Object} */ module.exports.seedersList = { + GoalSeeder, GenderSeeder, UnitSeeder, ActivitySeeder, + // ProductSeeder, }; /** * Connect to mongodb implementation diff --git a/src/controllers/goal.controller.js b/src/controllers/goal.controller.js new file mode 100644 index 0000000..176a5ce --- /dev/null +++ b/src/controllers/goal.controller.js @@ -0,0 +1,25 @@ +const httpStatus = require('http-status'); +const pick = require('../utils/pick'); +const ApiError = require('../utils/ApiError'); +const catchAsync = require('../utils/catchAsync'); +const { goalService } = require('../services'); + +const getGoals = catchAsync(async (req, res) => { + const filter = pick(req.query, ['name', 'value']); + const options = pick(req.query, ['sortBy', 'limit', 'page']); + const result = await goalService.queryGoals(filter, options); + res.send(result); +}); + +const getGoal = catchAsync(async (req, res) => { + const goal = await goalService.getGoalById(req.params.goalId); + if (!goal) { + throw new ApiError(httpStatus.NOT_FOUND, 'Gaol not found'); + } + res.send(goal); +}); + +module.exports = { + getGoals, + getGoal, +}; diff --git a/src/controllers/index.js b/src/controllers/index.js index 345435a..99c5da2 100644 --- a/src/controllers/index.js +++ b/src/controllers/index.js @@ -3,3 +3,4 @@ module.exports.userController = require('./user.controller'); module.exports.profileController = require('./profile.controller'); module.exports.activityController = require('./activity.controller'); module.exports.unitController = require('./unit.controller'); +module.exports.goalController = require('./goal.controller'); diff --git a/src/controllers/user.controller.js b/src/controllers/user.controller.js index 66b83fa..b5e39bd 100644 --- a/src/controllers/user.controller.js +++ b/src/controllers/user.controller.js @@ -10,7 +10,7 @@ const createUser = catchAsync(async (req, res) => { }); const getUsers = catchAsync(async (req, res) => { - const filter = pick(req.query, ['name', 'role']); + const filter = pick(req.query, ['role']); const options = pick(req.query, ['sortBy', 'limit', 'page']); const result = await userService.queryUsers(filter, options); res.send(result); diff --git a/src/models/goal.model.js b/src/models/goal.model.js new file mode 100644 index 0000000..9f46c0a --- /dev/null +++ b/src/models/goal.model.js @@ -0,0 +1,33 @@ +const mongoose = require('mongoose'); +const { toJSON, paginate } = require('./plugins'); + +const goalSchema = mongoose.Schema( + { + name: { + type: String, + unique: true, + required: true, + trim: true, + index: true, + }, + value: { + type: Number, + required: true, + }, + }, + { + timestamps: true, + } +); + +goalSchema.plugin(toJSON); +goalSchema.plugin(paginate); + +goalSchema.statics.isNameTaken = async function (name, excludeGoalId) { + const goal = await this.findOne({ name, _id: { $ne: excludeGoalId } }); + return !!goal; +}; + +const Goal = mongoose.model('Goal', goalSchema); + +module.exports = Goal; diff --git a/src/models/index.js b/src/models/index.js index 7dc8036..d324133 100644 --- a/src/models/index.js +++ b/src/models/index.js @@ -5,3 +5,4 @@ module.exports.Activity = require('./activity.model'); module.exports.Product = require('./product.model'); module.exports.Unit = require('./unit.model'); module.exports.Gender = require('./gender.model'); +module.exports.Goal = require('./goal.model'); diff --git a/src/models/user.model.js b/src/models/user.model.js index 93c1ce4..d099751 100644 --- a/src/models/user.model.js +++ b/src/models/user.model.js @@ -6,12 +6,6 @@ const { roles } = require('../config/roles'); const userSchema = mongoose.Schema( { - name: { - type: String, - required: true, - trim: true, - index: true, - }, email: { type: String, required: true, @@ -48,26 +42,14 @@ const userSchema = mongoose.Schema( } ); -// 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} - */ 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} - */ userSchema.methods.isPasswordMatch = async function (password) { const user = this; return bcrypt.compare(password, user.password); @@ -81,9 +63,6 @@ userSchema.pre('save', async function (next) { next(); }); -/** - * @typedef User - */ const User = mongoose.model('User', userSchema); module.exports = User; diff --git a/src/routes/v1/goal.route.js b/src/routes/v1/goal.route.js new file mode 100644 index 0000000..632f6c3 --- /dev/null +++ b/src/routes/v1/goal.route.js @@ -0,0 +1,17 @@ +const express = require('express'); +const auth = require('../../middlewares/auth'); +const validate = require('../../middlewares/validate'); +const goalValidation = require('../../validations/goal.validation'); +const goalController = require('../../controllers/goal.controller'); + +const router = express.Router(); + +router + .route('/') + .get(auth(), validate(goalValidation.getGoals), goalController.getGoals); + +router + .route('/:goalId') + .get(auth(), validate(goalValidation.getGoal), goalController.getGoal) + +module.exports = router; diff --git a/src/routes/v1/index.js b/src/routes/v1/index.js index 065ec29..cb6829a 100644 --- a/src/routes/v1/index.js +++ b/src/routes/v1/index.js @@ -5,6 +5,7 @@ const profileRoute = require('./profile.route'); const activityRoute = require('./activity.route'); const unitRoute = require('./unit.route'); const genderRoute = require('./gender.route'); +const goalRoute = require('./goal.route'); const docsRoute = require('./docs.route'); const router = express.Router(); @@ -15,6 +16,7 @@ router.use('/profiles', profileRoute); router.use('/activities', activityRoute); router.use('/units', unitRoute); router.use('/genders', genderRoute); +router.use('/goals', goalRoute); router.use('/docs', docsRoute); module.exports = router; diff --git a/src/routes/v1/user.route.js b/src/routes/v1/user.route.js index c4cfee8..56cf9dc 100644 --- a/src/routes/v1/user.route.js +++ b/src/routes/v1/user.route.js @@ -18,237 +18,3 @@ router .delete(auth('manageUsers'), validate(userValidation.deleteUser), userController.deleteUser); module.exports = router; - -/** - * @swagger - * tags: - * name: Users - * description: User management and retrieval - */ - -/** - * @swagger - * path: - * /users: - * post: - * summary: Create a user - * description: Only admins can create other users. - * tags: [Users] - * security: - * - bearerAuth: [] - * requestBody: - * required: true - * content: - * application/json: - * schema: - * type: object - * required: - * - name - * - email - * - password - * - role - * properties: - * name: - * type: string - * email: - * type: string - * format: email - * description: must be unique - * password: - * type: string - * format: password - * minLength: 8 - * description: At least one number and one letter - * role: - * type: string - * enum: [user, admin] - * example: - * name: fake name - * email: fake@example.com - * password: password1 - * role: user - * responses: - * "201": - * description: Created - * content: - * application/json: - * schema: - * $ref: '#/components/schemas/User' - * "400": - * $ref: '#/components/responses/DuplicateEmail' - * "401": - * $ref: '#/components/responses/Unauthorized' - * "403": - * $ref: '#/components/responses/Forbidden' - * - * get: - * summary: Get all users - * description: Only admins can retrieve all users. - * tags: [Users] - * security: - * - bearerAuth: [] - * parameters: - * - in: query - * name: name - * schema: - * type: string - * description: User name - * - in: query - * name: role - * schema: - * type: string - * description: User role - * - in: query - * name: sortBy - * schema: - * type: string - * description: sort by query in the form of field:desc/asc (ex. name:asc) - * - in: query - * name: limit - * schema: - * type: integer - * minimum: 1 - * default: 10 - * description: Maximum number of users - * - in: query - * name: page - * schema: - * type: integer - * minimum: 1 - * default: 1 - * description: Page number - * responses: - * "200": - * description: OK - * content: - * application/json: - * schema: - * type: object - * properties: - * results: - * type: array - * items: - * $ref: '#/components/schemas/User' - * page: - * type: integer - * example: 1 - * limit: - * type: integer - * example: 10 - * totalPages: - * type: integer - * example: 1 - * totalResults: - * type: integer - * example: 1 - * "401": - * $ref: '#/components/responses/Unauthorized' - * "403": - * $ref: '#/components/responses/Forbidden' - */ - -/** - * @swagger - * path: - * /users/{id}: - * get: - * summary: Get a user - * description: Logged in users can fetch only their own user information. Only admins can fetch other users. - * tags: [Users] - * security: - * - bearerAuth: [] - * parameters: - * - in: path - * name: id - * required: true - * schema: - * type: string - * description: User id - * responses: - * "200": - * description: OK - * content: - * application/json: - * schema: - * $ref: '#/components/schemas/User' - * "401": - * $ref: '#/components/responses/Unauthorized' - * "403": - * $ref: '#/components/responses/Forbidden' - * "404": - * $ref: '#/components/responses/NotFound' - * - * patch: - * summary: Update a user - * description: Logged in users can only update their own information. Only admins can update other users. - * tags: [Users] - * security: - * - bearerAuth: [] - * parameters: - * - in: path - * name: id - * required: true - * schema: - * type: string - * description: User id - * requestBody: - * required: true - * content: - * application/json: - * schema: - * type: object - * properties: - * name: - * type: string - * email: - * type: string - * format: email - * description: must be unique - * password: - * type: string - * format: password - * minLength: 8 - * description: At least one number and one letter - * example: - * name: fake name - * email: fake@example.com - * password: password1 - * responses: - * "200": - * description: OK - * content: - * application/json: - * schema: - * $ref: '#/components/schemas/User' - * "400": - * $ref: '#/components/responses/DuplicateEmail' - * "401": - * $ref: '#/components/responses/Unauthorized' - * "403": - * $ref: '#/components/responses/Forbidden' - * "404": - * $ref: '#/components/responses/NotFound' - * - * delete: - * summary: Delete a user - * description: Logged in users can delete only themselves. Only admins can delete other users. - * tags: [Users] - * security: - * - bearerAuth: [] - * parameters: - * - in: path - * name: id - * required: true - * schema: - * type: string - * description: User id - * responses: - * "200": - * description: No content - * "401": - * $ref: '#/components/responses/Unauthorized' - * "403": - * $ref: '#/components/responses/Forbidden' - * "404": - * $ref: '#/components/responses/NotFound' - */ diff --git a/src/seeders/goal.seeder.js b/src/seeders/goal.seeder.js new file mode 100644 index 0000000..1401ad3 --- /dev/null +++ b/src/seeders/goal.seeder.js @@ -0,0 +1,31 @@ +const { Seeder } = require('mongoose-data-seed'); +const { Goal } = require('../models'); + +const data = [ + { + name: 'lose weight', + value: -1, + }, + { + name: 'maintain weight', + value: 0, + }, + { + name: 'put on weight', + value: 1, + }, +]; + +class GoalSeeder extends Seeder { + async shouldRun() { + return Goal.countDocuments() + .exec() + .then((count) => count === 0); + } + + async run() { + return Goal.create(data); + } +} + +module.exports = GoalSeeder; diff --git a/src/seeders/index.js b/src/seeders/index.js index 0b00267..b80c65f 100644 --- a/src/seeders/index.js +++ b/src/seeders/index.js @@ -2,3 +2,4 @@ module.exports.ActivitySeeder = require('./activity.seeder'); module.exports.UnitSeeder = require('./unit.seeder'); module.exports.GenderSeeder = require('./gender.seeder'); module.exports.ProductSeeder = require('./product.seeder'); +module.exports.GoalSeeder = require('./goal.seeder'); diff --git a/src/services/auth.service.js b/src/services/auth.service.js index 6ef5eb6..5177fc0 100644 --- a/src/services/auth.service.js +++ b/src/services/auth.service.js @@ -5,12 +5,6 @@ const Token = require('../models/token.model'); const ApiError = require('../utils/ApiError'); const { tokenTypes } = require('../config/tokens'); -/** - * Login with username and password - * @param {string} email - * @param {string} password - * @returns {Promise} - */ const loginUserWithEmailAndPassword = async (email, password) => { const user = await userService.getUserByEmail(email); if (!user || !(await user.isPasswordMatch(password))) { @@ -19,11 +13,6 @@ const loginUserWithEmailAndPassword = async (email, password) => { return user; }; -/** - * Logout - * @param {string} refreshToken - * @returns {Promise} - */ const logout = async (refreshToken) => { const refreshTokenDoc = await Token.findOne({ token: refreshToken, type: tokenTypes.REFRESH, blacklisted: false }); if (!refreshTokenDoc) { @@ -31,12 +20,6 @@ const logout = async (refreshToken) => { } await refreshTokenDoc.remove(); }; - -/** - * Refresh auth tokens - * @param {string} refreshToken - * @returns {Promise} - */ const refreshAuth = async (refreshToken) => { try { const refreshTokenDoc = await tokenService.verifyToken(refreshToken, tokenTypes.REFRESH); @@ -51,12 +34,6 @@ const refreshAuth = async (refreshToken) => { } }; -/** - * Reset password - * @param {string} resetPasswordToken - * @param {string} newPassword - * @returns {Promise} - */ const resetPassword = async (resetPasswordToken, newPassword) => { try { const resetPasswordTokenDoc = await tokenService.verifyToken(resetPasswordToken, tokenTypes.RESET_PASSWORD); diff --git a/src/services/gender.service.js b/src/services/gender.service.js index f119b29..b388378 100644 --- a/src/services/gender.service.js +++ b/src/services/gender.service.js @@ -1,12 +1,12 @@ -const { Unit } = require('../models'); +const { Gender } = require('../models'); const queryGenders = async (filter, options) => { - const units = await Unit.paginate(filter, options); - return units; + const genders = await Gender.paginate(filter, options); + return genders; }; const getGenderById = async (id) => { - return Unit.findById(id); + return Gender.findById(id); }; module.exports = { diff --git a/src/services/goal.service.js b/src/services/goal.service.js new file mode 100644 index 0000000..0fec4f7 --- /dev/null +++ b/src/services/goal.service.js @@ -0,0 +1,15 @@ +const { Goal } = require('../models'); + +const queryGoals = async (filter, options) => { + const goals = await Goal.paginate(filter, options); + return goals; +}; + +const getGoalById = async (id) => { + return Goal.findById(id); +}; + +module.exports = { + queryGoals, + getGoalById, +}; diff --git a/src/services/index.js b/src/services/index.js index 0b80ffa..6af240c 100644 --- a/src/services/index.js +++ b/src/services/index.js @@ -6,3 +6,4 @@ module.exports.profileService = require('./profile.service'); module.exports.activityService = require('./activity.service'); module.exports.unitService = require('./unit.service'); module.exports.genderService = require('./gender.service'); +module.exports.goalService = require('./goal.service'); diff --git a/src/services/user.service.js b/src/services/user.service.js index 3488a71..ed89b61 100644 --- a/src/services/user.service.js +++ b/src/services/user.service.js @@ -2,11 +2,6 @@ const httpStatus = require('http-status'); const { User } = require('../models'); const ApiError = require('../utils/ApiError'); -/** - * Create a user - * @param {Object} userBody - * @returns {Promise} - */ const createUser = async (userBody) => { if (await User.isEmailTaken(userBody.email)) { throw new ApiError(httpStatus.BAD_REQUEST, 'Email already taken'); @@ -15,44 +10,19 @@ const createUser = async (userBody) => { return user; }; -/** - * Query for users - * @param {Object} filter - Mongo filter - * @param {Object} options - Query options - * @param {string} [options.sortBy] - Sort option in the format: sortField:(desc|asc) - * @param {number} [options.limit] - Maximum number of results per page (default = 10) - * @param {number} [options.page] - Current page (default = 1) - * @returns {Promise} - */ const queryUsers = async (filter, options) => { const users = await User.paginate(filter, options); return users; }; -/** - * Get user by id - * @param {ObjectId} id - * @returns {Promise} - */ const getUserById = async (id) => { return User.findById(id); }; -/** - * Get user by email - * @param {string} email - * @returns {Promise} - */ const getUserByEmail = async (email) => { return User.findOne({ email }); }; -/** - * Update user by id - * @param {ObjectId} userId - * @param {Object} updateBody - * @returns {Promise} - */ const updateUserById = async (userId, updateBody) => { const user = await getUserById(userId); if (!user) { @@ -66,11 +36,6 @@ const updateUserById = async (userId, updateBody) => { return user; }; -/** - * Delete user by id - * @param {ObjectId} userId - * @returns {Promise} - */ const deleteUserById = async (userId) => { const user = await getUserById(userId); if (!user) { diff --git a/src/validations/auth.validation.js b/src/validations/auth.validation.js index be688df..50f1816 100644 --- a/src/validations/auth.validation.js +++ b/src/validations/auth.validation.js @@ -5,7 +5,6 @@ const register = { body: Joi.object().keys({ email: Joi.string().required().email(), password: Joi.string().required().custom(password), - name: Joi.string().required(), }), }; diff --git a/src/validations/goal.validation.js b/src/validations/goal.validation.js new file mode 100644 index 0000000..e072758 --- /dev/null +++ b/src/validations/goal.validation.js @@ -0,0 +1,22 @@ +const Joi = require('joi'); +const { objectId } = require('./custom.validation'); + +const getGoals = { + query: Joi.object().keys({ + name: Joi.string(), + value: Joi.number(), + limit: Joi.number().integer(), + page: Joi.number().integer(), + }), +}; + +const getGaol = { + params: Joi.object().keys({ + goalId: Joi.string().custom(objectId), + }), +}; + +module.exports = { + getGoals, + getGaol, +}; diff --git a/src/validations/index.js b/src/validations/index.js index cdc457a..d4e4537 100644 --- a/src/validations/index.js +++ b/src/validations/index.js @@ -4,3 +4,4 @@ module.exports.profileValidation = require('./profile.validation'); module.exports.activityValidation = require('./activity.validation'); module.exports.unitValidation = require('./unit.validation'); module.exports.productValidation = require('./product.validation'); +module.exports.goalValidation = require('./goal.validation'); diff --git a/src/validations/user.validation.js b/src/validations/user.validation.js index 1be6ce4..007c156 100644 --- a/src/validations/user.validation.js +++ b/src/validations/user.validation.js @@ -5,14 +5,12 @@ const createUser = { body: Joi.object().keys({ email: Joi.string().required().email(), password: Joi.string().required().custom(password), - name: Joi.string().required(), role: Joi.string().required().valid('user', 'admin'), }), }; const getUsers = { query: Joi.object().keys({ - name: Joi.string(), role: Joi.string(), sortBy: Joi.string(), limit: Joi.number().integer(), @@ -34,7 +32,6 @@ const updateUser = { .keys({ email: Joi.string().email(), password: Joi.string().custom(password), - name: Joi.string(), }) .min(1), };