Add goal service

This commit is contained in:
= 2020-12-09 18:05:36 +01:00
parent 2c73524597
commit 0674b1aa2c
21 changed files with 158 additions and 323 deletions

View File

@ -1,5 +1,5 @@
const mongoose = require('mongoose'); 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'); const config = require('./src/config/config');
@ -11,9 +11,11 @@ const mongoURL = config.mongoose.url
* @type {Object} * @type {Object}
*/ */
module.exports.seedersList = { module.exports.seedersList = {
GoalSeeder,
GenderSeeder, GenderSeeder,
UnitSeeder, UnitSeeder,
ActivitySeeder, ActivitySeeder,
// ProductSeeder,
}; };
/** /**
* Connect to mongodb implementation * Connect to mongodb implementation

View File

@ -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,
};

View File

@ -3,3 +3,4 @@ module.exports.userController = require('./user.controller');
module.exports.profileController = require('./profile.controller'); module.exports.profileController = require('./profile.controller');
module.exports.activityController = require('./activity.controller'); module.exports.activityController = require('./activity.controller');
module.exports.unitController = require('./unit.controller'); module.exports.unitController = require('./unit.controller');
module.exports.goalController = require('./goal.controller');

View File

@ -10,7 +10,7 @@ const createUser = catchAsync(async (req, res) => {
}); });
const getUsers = 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 options = pick(req.query, ['sortBy', 'limit', 'page']);
const result = await userService.queryUsers(filter, options); const result = await userService.queryUsers(filter, options);
res.send(result); res.send(result);

33
src/models/goal.model.js Normal file
View File

@ -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;

View File

@ -5,3 +5,4 @@ module.exports.Activity = require('./activity.model');
module.exports.Product = require('./product.model'); module.exports.Product = require('./product.model');
module.exports.Unit = require('./unit.model'); module.exports.Unit = require('./unit.model');
module.exports.Gender = require('./gender.model'); module.exports.Gender = require('./gender.model');
module.exports.Goal = require('./goal.model');

View File

@ -6,12 +6,6 @@ const { roles } = require('../config/roles');
const userSchema = mongoose.Schema( const userSchema = mongoose.Schema(
{ {
name: {
type: String,
required: true,
trim: true,
index: true,
},
email: { email: {
type: String, type: String,
required: true, required: true,
@ -48,26 +42,14 @@ const userSchema = mongoose.Schema(
} }
); );
// add plugin that converts mongoose to json
userSchema.plugin(toJSON); userSchema.plugin(toJSON);
userSchema.plugin(paginate); 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) { userSchema.statics.isEmailTaken = async function (email, excludeUserId) {
const user = await this.findOne({ email, _id: { $ne: excludeUserId } }); const user = await this.findOne({ email, _id: { $ne: excludeUserId } });
return !!user; return !!user;
}; };
/**
* Check if password matches the user's password
* @param {string} password
* @returns {Promise<boolean>}
*/
userSchema.methods.isPasswordMatch = async function (password) { userSchema.methods.isPasswordMatch = async function (password) {
const user = this; const user = this;
return bcrypt.compare(password, user.password); return bcrypt.compare(password, user.password);
@ -81,9 +63,6 @@ userSchema.pre('save', async function (next) {
next(); next();
}); });
/**
* @typedef User
*/
const User = mongoose.model('User', userSchema); const User = mongoose.model('User', userSchema);
module.exports = User; module.exports = User;

View File

@ -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;

View File

@ -5,6 +5,7 @@ const profileRoute = require('./profile.route');
const activityRoute = require('./activity.route'); const activityRoute = require('./activity.route');
const unitRoute = require('./unit.route'); const unitRoute = require('./unit.route');
const genderRoute = require('./gender.route'); const genderRoute = require('./gender.route');
const goalRoute = require('./goal.route');
const docsRoute = require('./docs.route'); const docsRoute = require('./docs.route');
const router = express.Router(); const router = express.Router();
@ -15,6 +16,7 @@ router.use('/profiles', profileRoute);
router.use('/activities', activityRoute); router.use('/activities', activityRoute);
router.use('/units', unitRoute); router.use('/units', unitRoute);
router.use('/genders', genderRoute); router.use('/genders', genderRoute);
router.use('/goals', goalRoute);
router.use('/docs', docsRoute); router.use('/docs', docsRoute);
module.exports = router; module.exports = router;

View File

@ -18,237 +18,3 @@ router
.delete(auth('manageUsers'), validate(userValidation.deleteUser), userController.deleteUser); .delete(auth('manageUsers'), validate(userValidation.deleteUser), userController.deleteUser);
module.exports = router; 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'
*/

View File

@ -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;

View File

@ -2,3 +2,4 @@ module.exports.ActivitySeeder = require('./activity.seeder');
module.exports.UnitSeeder = require('./unit.seeder'); module.exports.UnitSeeder = require('./unit.seeder');
module.exports.GenderSeeder = require('./gender.seeder'); module.exports.GenderSeeder = require('./gender.seeder');
module.exports.ProductSeeder = require('./product.seeder'); module.exports.ProductSeeder = require('./product.seeder');
module.exports.GoalSeeder = require('./goal.seeder');

View File

@ -5,12 +5,6 @@ const Token = require('../models/token.model');
const ApiError = require('../utils/ApiError'); const ApiError = require('../utils/ApiError');
const { tokenTypes } = require('../config/tokens'); const { tokenTypes } = require('../config/tokens');
/**
* Login with username and password
* @param {string} email
* @param {string} password
* @returns {Promise<User>}
*/
const loginUserWithEmailAndPassword = async (email, password) => { const loginUserWithEmailAndPassword = async (email, password) => {
const user = await userService.getUserByEmail(email); const user = await userService.getUserByEmail(email);
if (!user || !(await user.isPasswordMatch(password))) { if (!user || !(await user.isPasswordMatch(password))) {
@ -19,11 +13,6 @@ const loginUserWithEmailAndPassword = async (email, password) => {
return user; return user;
}; };
/**
* Logout
* @param {string} refreshToken
* @returns {Promise}
*/
const logout = async (refreshToken) => { const logout = async (refreshToken) => {
const refreshTokenDoc = await Token.findOne({ token: refreshToken, type: tokenTypes.REFRESH, blacklisted: false }); const refreshTokenDoc = await Token.findOne({ token: refreshToken, type: tokenTypes.REFRESH, blacklisted: false });
if (!refreshTokenDoc) { if (!refreshTokenDoc) {
@ -31,12 +20,6 @@ const logout = async (refreshToken) => {
} }
await refreshTokenDoc.remove(); await refreshTokenDoc.remove();
}; };
/**
* Refresh auth tokens
* @param {string} refreshToken
* @returns {Promise<Object>}
*/
const refreshAuth = async (refreshToken) => { const refreshAuth = async (refreshToken) => {
try { try {
const refreshTokenDoc = await tokenService.verifyToken(refreshToken, tokenTypes.REFRESH); 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) => { const resetPassword = async (resetPasswordToken, newPassword) => {
try { try {
const resetPasswordTokenDoc = await tokenService.verifyToken(resetPasswordToken, tokenTypes.RESET_PASSWORD); const resetPasswordTokenDoc = await tokenService.verifyToken(resetPasswordToken, tokenTypes.RESET_PASSWORD);

View File

@ -1,12 +1,12 @@
const { Unit } = require('../models'); const { Gender } = require('../models');
const queryGenders = async (filter, options) => { const queryGenders = async (filter, options) => {
const units = await Unit.paginate(filter, options); const genders = await Gender.paginate(filter, options);
return units; return genders;
}; };
const getGenderById = async (id) => { const getGenderById = async (id) => {
return Unit.findById(id); return Gender.findById(id);
}; };
module.exports = { module.exports = {

View File

@ -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,
};

View File

@ -6,3 +6,4 @@ module.exports.profileService = require('./profile.service');
module.exports.activityService = require('./activity.service'); module.exports.activityService = require('./activity.service');
module.exports.unitService = require('./unit.service'); module.exports.unitService = require('./unit.service');
module.exports.genderService = require('./gender.service'); module.exports.genderService = require('./gender.service');
module.exports.goalService = require('./goal.service');

View File

@ -2,11 +2,6 @@ const httpStatus = require('http-status');
const { User } = require('../models'); const { User } = require('../models');
const ApiError = require('../utils/ApiError'); const ApiError = require('../utils/ApiError');
/**
* Create a user
* @param {Object} userBody
* @returns {Promise<User>}
*/
const createUser = async (userBody) => { const createUser = async (userBody) => {
if (await User.isEmailTaken(userBody.email)) { if (await User.isEmailTaken(userBody.email)) {
throw new ApiError(httpStatus.BAD_REQUEST, 'Email already taken'); throw new ApiError(httpStatus.BAD_REQUEST, 'Email already taken');
@ -15,44 +10,19 @@ const createUser = async (userBody) => {
return user; 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<QueryResult>}
*/
const queryUsers = async (filter, options) => { const queryUsers = async (filter, options) => {
const users = await User.paginate(filter, options); const users = await User.paginate(filter, options);
return users; return users;
}; };
/**
* Get user by id
* @param {ObjectId} id
* @returns {Promise<User>}
*/
const getUserById = async (id) => { const getUserById = async (id) => {
return User.findById(id); return User.findById(id);
}; };
/**
* Get user by email
* @param {string} email
* @returns {Promise<User>}
*/
const getUserByEmail = async (email) => { const getUserByEmail = async (email) => {
return User.findOne({ email }); return User.findOne({ email });
}; };
/**
* Update user by id
* @param {ObjectId} userId
* @param {Object} updateBody
* @returns {Promise<User>}
*/
const updateUserById = async (userId, updateBody) => { const updateUserById = async (userId, updateBody) => {
const user = await getUserById(userId); const user = await getUserById(userId);
if (!user) { if (!user) {
@ -66,11 +36,6 @@ const updateUserById = async (userId, updateBody) => {
return user; return user;
}; };
/**
* Delete user by id
* @param {ObjectId} userId
* @returns {Promise<User>}
*/
const deleteUserById = async (userId) => { const deleteUserById = async (userId) => {
const user = await getUserById(userId); const user = await getUserById(userId);
if (!user) { if (!user) {

View File

@ -5,7 +5,6 @@ const register = {
body: Joi.object().keys({ body: Joi.object().keys({
email: Joi.string().required().email(), email: Joi.string().required().email(),
password: Joi.string().required().custom(password), password: Joi.string().required().custom(password),
name: Joi.string().required(),
}), }),
}; };

View File

@ -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,
};

View File

@ -4,3 +4,4 @@ module.exports.profileValidation = require('./profile.validation');
module.exports.activityValidation = require('./activity.validation'); module.exports.activityValidation = require('./activity.validation');
module.exports.unitValidation = require('./unit.validation'); module.exports.unitValidation = require('./unit.validation');
module.exports.productValidation = require('./product.validation'); module.exports.productValidation = require('./product.validation');
module.exports.goalValidation = require('./goal.validation');

View File

@ -5,14 +5,12 @@ const createUser = {
body: Joi.object().keys({ body: Joi.object().keys({
email: Joi.string().required().email(), email: Joi.string().required().email(),
password: Joi.string().required().custom(password), password: Joi.string().required().custom(password),
name: Joi.string().required(),
role: Joi.string().required().valid('user', 'admin'), role: Joi.string().required().valid('user', 'admin'),
}), }),
}; };
const getUsers = { const getUsers = {
query: Joi.object().keys({ query: Joi.object().keys({
name: Joi.string(),
role: Joi.string(), role: Joi.string(),
sortBy: Joi.string(), sortBy: Joi.string(),
limit: Joi.number().integer(), limit: Joi.number().integer(),
@ -34,7 +32,6 @@ const updateUser = {
.keys({ .keys({
email: Joi.string().email(), email: Joi.string().email(),
password: Joi.string().custom(password), password: Joi.string().custom(password),
name: Joi.string(),
}) })
.min(1), .min(1),
}; };