Add seach plugin for products

This commit is contained in:
= 2021-01-04 20:55:02 +01:00
parent 78cee39ca9
commit a96c1d9c07
14 changed files with 170 additions and 59 deletions

View File

@ -10,9 +10,9 @@ const createProduct = catchAsync(async (req, res) => {
});
const getProducts = catchAsync(async (req, res) => {
const filter = pick(req.query, ['label', 'barcode']);
const options = pick(req.query, ['sortBy', 'limit', 'page']);
const result = await productService.queryProducts(filter, options);
const query = pick(req.query, ['label', 'barcode']);
const options = pick(req.query, ['limit']);
const result = await productService.queryProducts(query, options);
res.send(result);
});
@ -34,10 +34,16 @@ const deleteProduct = catchAsync(async (req, res) => {
res.status(httpStatus.NO_CONTENT).send();
});
const getAllProducts = catchAsync(async (req, res) => {
const products = await productService.getAllProducts();
res.send(products);
});
module.exports = {
createProduct,
getProducts,
getProduct,
updateProduct,
deleteProduct,
getAllProducts,
};

View File

@ -5,7 +5,7 @@ const catchAsync = require('../utils/catchAsync');
const { profileService } = require('../services');
const createProfile = catchAsync(async (req, res) => {
req.body.userId = req.user._id;
req.body.user = req.user._id;
const profile = await profileService.createProfile(req.body);
res.status(httpStatus.CREATED).send(profile);
});

View File

@ -1,2 +1,3 @@
module.exports.toJSON = require('./toJSON.plugin');
module.exports.paginate = require('./paginate.plugin');
module.exports.search = require('./search.plugin');

View File

@ -1,23 +1,6 @@
/* 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 {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) {

View File

@ -0,0 +1,18 @@
/* eslint-disable no-param-reassign */
const search = (schema) => {
schema.statics.search = async function (query, options) {
const [[key, pattern]] = Object.entries(query);
const searchRegex = Number.isInteger(pattern) ? pattern : new RegExp(`.*${pattern}.*`);
const limit = options.limit && parseInt(options.limit, 10) > 0 ? parseInt(options.limit, 10) : 10;
const docsPromise = this.find({ [key]: searchRegex })
.limit(limit)
.exec();
return Promise.resolve(docsPromise);
};
};
module.exports = search;

View File

@ -1,10 +1,5 @@
/* 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) {

View File

@ -1,5 +1,5 @@
const mongoose = require('mongoose');
const { toJSON, paginate } = require('./plugins');
const { toJSON, paginate, search } = require('./plugins');
const productSchema = mongoose.Schema(
{
@ -65,9 +65,10 @@ const productSchema = mongoose.Schema(
productSchema.plugin(toJSON);
productSchema.plugin(paginate);
productSchema.plugin(search);
productSchema.statics.isNameTaken = async function (name, excludeProductId) {
const product = await this.findOne({ name, _id: { $ne: excludeProductId } });
productSchema.statics.isNameTaken = async function (label, excludeProductId) {
const product = await this.findOne({ label, _id: { $ne: excludeProductId } });
return !!product;
};

View File

@ -1,16 +1,27 @@
const mongoose = require('mongoose');
const { toJSON, paginate } = require('./plugins');
const { genderTypes } = require('../config/genders');
const { goalTypes } = require('../config/goals');
const { activityTypes } = require('../config/activities');
const profileSchema = mongoose.Schema(
{
gender: {
type: String,
enum: ['male', 'female'],
enum: Object.values(genderTypes),
required: true,
},
goal: {
type: String,
enum: ['lose_weight', 'maintain_weight', 'put_on_weight'],
enum: Object.values(goalTypes),
required: true,
},
dailyCalories: {
type: Number,
required: true,
},
weeksToGoal: {
type: Number,
required: true,
},
birthday: {
@ -36,10 +47,10 @@ const profileSchema = mongoose.Schema(
},
activity: {
type: Number,
enum: [1.2, 1.375, 1.55, 1.725, 1.9],
enum: Object.values(activityTypes),
required: true,
},
userId: {
user: {
type: mongoose.SchemaTypes.ObjectId,
ref: 'User',
required: true,
@ -55,7 +66,7 @@ profileSchema.plugin(toJSON);
profileSchema.plugin(paginate);
profileSchema.statics.hasUser = async function (userId, excludeProfileId) {
const user = await this.findOne({ userId, _id: { $ne: excludeProfileId } });
const user = await this.findOne({ user: userId, _id: { $ne: excludeProfileId } });
return !!user;
};

View File

@ -11,6 +11,10 @@ router
.post(auth(), validate(productValidation.createProduct), productController.createProduct)
.get(auth(), validate(productValidation.getProducts), productController.getProducts);
router
.route('/all')
.get(productController.getAllProducts)
router
.route('/:productId')
.get(auth(), validate(productValidation.getProduct), productController.getProduct)

View File

@ -14,7 +14,7 @@ router
.delete(auth(), validate(profileValidation.deleteProfile), profileController.deleteProfile);
router
.route('/admin')
.route('/all')
.get(auth(), validate(profileValidation.getProfiles), profileController.getProfiles);
module.exports = router;

View File

@ -3,7 +3,7 @@ const { Product } = require('../models');
const data = [
{
label: 'cappy - multiwitamina',
label: 'multiwitamina',
brand: 'capy',
verified: true,
eco: false,
@ -17,7 +17,7 @@ const data = [
salt: 0,
},
{
label: 'star - chipsy o smaku smietana i cebula',
label: 'chipsy o smaku smietana i cebula',
brand: 'star',
verified: true,
eco: false,
@ -30,6 +30,62 @@ const data = [
protein: 6.2,
salt: 1.4,
},
{
label: 'Aloe',
brand: 'tropical',
verified: true,
eco: false,
barcode: 8712857004606,
unit: 'ml',
servingCapacity: 500,
calories: 20,
fat: 0,
carbohydrates: 4.9,
protein: 0,
salt: 0.042,
},
{
label: `chocolate m&m's`,
brand: `m&m's`,
verified: true,
eco: false,
barcode: 5000159471725,
unit: 'g',
servingCapacity: 150,
calories: 481,
fat: 19,
carbohydrates: 71,
protein: 5,
salt: 0.13,
},
{
label: `black`,
brand: `black`,
verified: true,
eco: false,
barcode: 5900552014713,
unit: 'ml',
servingCapacity: 250,
calories: 42,
fat: 0,
carbohydrates: 9.9,
protein: 0,
salt: 0.18,
},
{
label: `fantasia mleczna`,
brand: `e.wedel`,
verified: true,
eco: false,
barcode: 59074571,
unit: 'g',
servingCapacity: 104,
calories: 166,
fat: 9.3,
carbohydrates: 16.9,
protein: 0.3,
salt: 0.14,
},
];
class ProductSeeder extends Seeder {

View File

@ -13,8 +13,8 @@ const createProduct = async (productBody) => {
return product;
};
const queryProducts = async (filter, options) => {
const products = await Product.paginate(filter, options);
const queryProducts = async (query, options) => {
const products = await Product.search(query, options);
return products;
};
@ -45,6 +45,11 @@ const deleteProductById = async (productId) => {
return product;
};
const getAllProducts = async () => {
const products = await Product.find();
return products;
};
module.exports = {
createProduct,
queryProducts,
@ -52,4 +57,5 @@ module.exports = {
getProductByBarcode,
updateProductById,
deleteProductById,
getAllProducts,
};

View File

@ -5,10 +5,18 @@ const calculateWeeksToGetGoalWeight = require('../utils/weeksToGetGoalWeight');
const dailyCaloricRequirement = require('../utils/dailyCaloricRequirement');
const createProfile = async (profileBody) => {
if (await Profile.hasUser(profileBody.userId)) {
if (await Profile.hasUser(profileBody.user)) {
throw new ApiError(httpStatus.BAD_REQUEST, 'User already has a profile');
}
const profile = await Profile.create(profileBody);
const dailyCalories = dailyCaloricRequirement(profileBody);
const weeksToGoal = calculateWeeksToGetGoalWeight(profileBody);
const profile = await Profile.create({
...profileBody,
dailyCalories,
weeksToGoal,
});
return profile;
};
@ -19,13 +27,11 @@ const queryProfiles = async (filter, options) => {
const getProfileById = async (id) => {
const profile = await Profile.findById(id);
console.log(dailyCaloricRequirement(profile));
console.log(calculateWeeksToGetGoalWeight(profile));
return profile
return profile;
};
const getProfileByUserId = async (userId) => {
return Profile.findOne({ userId });
return Profile.findOne({ user: userId });
};
const updateProfileById = async (profileId, updateBody) => {
@ -33,7 +39,15 @@ const updateProfileById = async (profileId, updateBody) => {
if (!profile) {
throw new ApiError(httpStatus.NOT_FOUND, 'Profile not found');
}
Object.assign(profile, updateBody);
const dailyCalories = dailyCaloricRequirement(updateBody);
const weeksToGoal = calculateWeeksToGetGoalWeight(updateBody);
Object.assign(profile, {
...updateBody,
dailyCalories,
weeksToGoal,
});
await profile.save();
return profile;
};

View File

@ -6,14 +6,22 @@ const { activityTypes } = require('../config/activities');
const createProfile = {
body: Joi.object().keys({
gender: Joi.string().required().valid(Object.values(genderTypes)),
goal: Joi.string().required().valid(Object.values(goalTypes)),
gender: Joi.string()
.valid(...Object.values(genderTypes))
.required(),
goal: Joi.string()
.valid(...Object.values(goalTypes))
.required(),
dailyCalories: Joi.number(),
weeksToGoal: Joi.number(),
birthday: Joi.date().required(),
height: Joi.number().required(),
currentWeight: Joi.number().required(),
goalWeight: Joi.number().required(),
rateOfChange: Joi.number().required(),
activity: Joi.number().required().valid(Object.values(activityTypes)),
activity: Joi.number()
.valid(...Object.values(activityTypes))
.required(),
}),
};
@ -35,16 +43,24 @@ const updateProfile = {
params: Joi.object().keys({
profileId: Joi.required().custom(objectId),
}),
body: Joi.object().keys({
gender: Joi.string().required().valid(Object.values(genderTypes)),
goal: Joi.string().required().valid(Object.values(goalTypes)),
body: Joi.object()
.keys({
gender: Joi.string()
.valid(...Object.values(genderTypes))
.required(),
goal: Joi.string()
.valid(...Object.values(goalTypes))
.required(),
birthday: Joi.date().required(),
height: Joi.number().required(),
currentWeight: Joi.number().required(),
goalWeight: Joi.number().required(),
rateOfChange: Joi.number(),
activity: Joi.number().required().valid(Object.values(activityTypes)),
}).min(1),
activity: Joi.number()
.required()
.valid(...Object.values(activityTypes)),
})
.min(1),
};
const deleteProfile = {