Refactoring several mongoose models to similar collections - node.js

I have several collections that have the same documents type, except for the language.
Let's say imagesES, imagesEN, imagesFR, and so on....
I just thought about definig just one schema, but also one model that get the proper collection with a parameter:
var mongoose = require('mongoose')
var Schema = mongoose.Schema
let authorSchema = require('./Authors').authorSchema
const imageSchema = new Schema({
authors: [authorSchema],
status: Number, // published (1), unpublished (0)
created: { type: Date, default: Date.now },
lastUpdated: { type: Date, default: Date.now },
license: {
type: String,
enum: ['Creative Commons BY-NC-SA'], //just one license right now
default: 'Creative Commons BY-NC-SA'
},
downloads: {
type: Number,
default: 0
},
tags: [String]
})
module.exports = locale => {
return mongoose.model('Image', imageSchema, `image${locale}`)
}
However in the controller I should require the model inside the controller (when I know the locale):
getImageById: (req, res) => {
const id = req.swagger.params.id.value
const locale = req.swagger.params.locale.value
const Images = require('../models/Images')(locale)
Images.findOne({_id: id}).lean().exec( (err, image) => {
I'm not sure if this is the proper way as each request I get I have to require the model module (syncronously) or should I require all the different models previous to the use in the function.
const locales = ['es', 'en', 'fr']
const Images = []
locales.map(locale=>Images[locale] = require('../models/Images')(locale))
getImageById: (req, res) => {
const id = req.swagger.params.id.value
const locale = req.swagger.params.locale.value
Images[locale].findOne({_id: id}).lean().exec( (err, image) => {

Finally this is how I resolved it. Where it says Pictograms, could be Images as in the question
const setPictogramModel = require('../models/Pictograms')
const languages = [
'ar',
'bg',
'en',
'es',
'pl',
'ro',
'ru',
'zh'
]
const Pictograms = languages.reduce((dict, language)=> {
dict[language]= setPictogramModel(language)
return dict
}, {})
module.exports = {
getPictogramById: (req, res) => {
const id = req.swagger.params.idPictogram.value
const locale = req.swagger.params.locale.value
// Use lean to get a plain JS object to modify it, instead of a full model instance
Pictograms[locale].findOne({id_image: id}).exec( (err, pictogram) => {
if(err) {
return res.status(500).json({
message: 'Error getting pictograms. See error field for detail',
error: err
})
}
if(!pictogram) {
return res.status(404).json( {
message: `Error getting pictogram with id ${id}`,
err
})
}
return res.json(pictogram)
})
},

Related

Mongoose and express insert many many-to-many relationships in one request

I want to make a request in which I add several meals to order and a request that removes several meals from order. It is many-to-many relationship because order can have several meals and meal can have several orders.
This is my order model:
const mongoose = require("mongoose");
const orderObject = {
cook: { type: mongoose.Types.ObjectId, ref: "CookRoleUser" },
remark: { type: String },
delivery_address: { type: String, required: true },
orderer: {
type: mongoose.Types.ObjectId,
ref: "RegularRoleUser",
required: true,
},
meals: [{ type: mongoose.Types.ObjectId, ref: "Meal" }],
active: { type: Boolean, default: true },
date_ordered: { type: Date, default: () => Date.now() },
};
const Order = mongoose.model("Order", new mongoose.Schema(orderObject));
module.exports = { Order };
And this is my meal model:
const mongoose = require("mongoose");
const mealCategories = {
MAIN: "MAIN",
DESSERT: "DESSERT",
APPETIZER: "APPETIZER",
DRINK: "DRINK",
};
const mealTypes = {
REGULAR: "REGULAR",
SPECIAL: "SPECIAL",
};
const mealObject = {
name: { type: String },
ingredients: [
{ name: { type: String }, allergen: { type: Boolean, default: false } },
],
is_offered: { type: Boolean, default: false },
orders: [{ type: mongoose.Types.ObjectId, ref: "Order" }],
cook: { type: mongoose.Types.ObjectId, ref: "CookRoleUser" },
category: {
type: String,
enum: Object.values(mealCategories),
default: mealCategories.MAIN,
},
type: {
type: String,
enum: Object.values(mealTypes),
default: mealTypes.REGULAR,
},
date_created: { type: Date, default: () => Date.now() },
};
const mealSchema = new mongoose.Schema(mealObject);
const Meal = mongoose.model("Meal", mealSchema);
module.exports = { Meal, mealTypes };
I've tried to do this:
For addition:
const addMealsToOrder = async (req, res, next) => {
const order = await Order.findById(req.body.orderId);
let mealCount = 0;
await req.body.mealIds?.forEach(async (mealId, index) => {
const meal = await Meal.findById(mealId);
if (order.meals.includes(mealId)) {
return next(
new HttpError(
`Meal ${meal.name} already in order ${order.remark}.`,
400
)
);
}
if (order.cook.toString() != meal.cook.toString()) {
return next(
new HttpError(
`Meal ${meal.name} cook not same as order ${order.remark} cook.`
)
);
}
meal.orders.push(order.id);
order.meals.push(meal.id);
await meal.save();
mealCount = index;
});
if (mealCount == req.meals.length - 1) {
order.save();
res.json(order);
}
};
or without checking is are meals already in order and are cooks the same:
const addMealToOrderTest = async (req, res, next) => {
await req.body.mealIds.forEach(async (mealId) => {
const order = await Order.findById(req.body.orderId);
const meal = await Meal.findById(mealId);
order.meals.push(mealId);
meal.orders.push(order.id);
await order.save();
await meal.save();
});
res.json(2);
};
And for deletion:
const removeMealsFromOrder = async (req, res, next) => {
const { orderId, mealIds } = req.body;
const order = await Order.findById(orderId);
await req.body.mealIds.forEach(async (mealId, index) => {
const meal = Meal.findById(mealId);
meal.orders = meal.orders.filter((oId) => oId != order.id);
order.meals = order.meals.filter((mId) => mId != mealId);
await meal.save()
});
await order.save()
res.json(order);
};
I've tested adding and deleting in postman. Behaviour is very weird and random. Some times it does not add meals, or adds only few, etc. I test the result by fetching single order and/or single meal.
My question:
What is the best way to implement many-to-many update where you want to add for a single entity (order) multiple entities (meals)?
Furthermore I would also want to check, in addtion request, does the certain meal already exist in that order and are the order cook and certain meal cook not the same. If some of these condition is true, I want to inform the request issuer about it and stop all adding and in deletion request does the order contain certain meal and also inform the request issuer if that is true.
EDIT 04/08/22
Now i've tried to make my controllers as such
Addition
const addMealToOrderTest = async (req, res, next) => {
const { orderId, mealIds } = req.body;
await mealIds.forEach(async (mealId) => {
const order = await Order.findById(orderId);
const meal = await Meal.findById(mealId);
order.meals.push(mealId);
meal.orders.push(order.id);
await order.save();
await meal.save();
});
res.json(2);
};
Removal
const removeMealsFromOrder = async (req, res, next) => {
const { orderId, mealIds } = req.body;
await mealIds.forEach(async (mealId) => {
const order = await Order.findById(orderId);
const meal = await Meal.findById(mealId);
order.meals.pull(mealId);
meal.orders.pull(orderId);
await order.save();
await meal.save();
});
res.json(2);
};
Behaviour is very random. Sometimes it works all fine but sometimes I get:
VersionError if order.save() is in mealIds.forEach function or
ParrallelSaveError if the fetching and saving of the order are outside mealIds.forEach function.
EDIT no:2 04/08/22
Addition
const addMealToOrderTest = async (req, res, next) => {
const { orderId, mealIds } = req.body;
const order = await Order.findById(orderId);
let addedMealCount = 0;
mealIds.forEach((mealId) => {
if (!order.meals.includes(mealId)) {
order.meals.push(mealId);
addedMealCount += 1;
}
});
if (addedMealCount != mealIds.length) {
return res.json({ error: "Meal already in that order" });
}
await mealIds.forEach(async (mealId) => {
const meal = await Meal.findById(mealId);
if (!meal.orders.includes(mealId)) {
meal.orders.push(order.id);
await meal.save();
}
});
await order.save();
res.json(order);
};
Removal
const removeMealsFromOrder = async (req, res, next) => {
const { orderId, mealIds } = req.body;
const order = await Order.findById(orderId);
mealIds.forEach((mealId) => {
order.meals.pull(mealId);
});
await mealIds.forEach(async (mealId) => {
const meal = await Meal.findById(mealId);
meal.orders.pull(orderId);
await meal.save();
});
await order.save();
res.json(order);
};
Addition and removal both seem to work now. But I am wondering how could I check is meal cook and order cook the same when I add meals to order.
Problem with this is that I would have to fetch each meal, and then check are cooks the same as in the order.
When I do so, usually I get Cannot set headers or such. I've tried to initialize counter before forEach loop in which I fetch an meal for mealId and increment it only if cooks are the same. And if they are not, counter is not incremented and response is sent. I sent an ok response with order only if the counter is the same as mealIds length. But, counter does not seem to change if I fetch meal by id.

POST array of objects using req.body parser

I am trying to post a simple request which includes array of objects. I have created a model and passing the data as per the model.
I am having trouble accessing body parameters as it contains array of data.
I am able to store line item data by req.body.tasks[0]
which is not a standrad way of storing details in mongodb.
I am looking for a standrad way of storing array of data in mongodb
Controller:
let createBug = (req, res) => {
console.log(req.body.tasks[0].subtask[0].description)
for (var key in req.body) {
if (req.body.hasOwnProperty(key)) {
item = req.body[key];
console.log(item);
}
}
const createBug = new listModel({
title: req.body.title,
tasks: [{
title: req.body.tasks[0].title,
description: req.body.tasks[0].description,
subtask: [{
description: req.body.tasks[0].subtask[0].description
}]
}]
}).save((error, data) => {
if (data) {
let apiResponse = response.generate(false, null, 201, data);
res.status(201).send(apiResponse);
} else {
let apiResponse = response.generate(true, error, 404, null);
res.status(404).send(apiResponse);
}
});
};
body:
{
"title":"sample title",
"tasks":[{
"title": "task 1",
"description":"task1 description",
"subtask":[{
"description":"task3 description"
}]
}]
}
Model:
const mongoose = require("mongoose");
const mySchema = mongoose.Schema;
let subtask = new mySchema({
description: String
})
let taskdata = new mySchema({
title: String,
description: String,
subtask: [subtask]
});
let listSchema = new mySchema({
title: {
type: String,
require: true,
},
tasks: [taskdata],
owner: {
type: mongoose.Schema.Types.ObjectId,
ref: "users",
}
});
module.exports = mongoose.model("list", listSchema);
I think you're overcomplicating things here a little bit. The request body exactly matches the model definitions, so you can simply pass the req.body object to your mongoose model:
const createBug = new listModel(req.body).save((error, data) => { ... }

How to get the array of object instead of just _id in Mongoose

I am very new to mongoose.
I am currently building a backend using Node.js, express.js, GraphQL, and mongoose.
I have a Drink schema and DrinkType Schema. I defined DrinkType schema as "alcohol", "juice", "tea". And I have added many drinks and each drink has DrinkType reference. Then, I would like to reference all the drinks from DrinkType.
This is the schema for drinkType
const drinkTypeSchema = new Schema({
name: {
type: String,
required: true,
},
createdDrinks: [
{
type: Schema.Types.ObjectId,
ref: 'Drink',
},
],
Here is the schema for drink
const drinkSchema = new Schema({
name: {
type: String,
required: true,
},
drinkType: {
type: Schema.Types.ObjectId,
ref: 'DrinkType',
},
})
This is the drink resolver. Whenever a new drink is created, I am pushing it to drinkType.
try {
const result = await drink.save()
createdDrink = transformDrink(result)
const drinkType = await DrinkType.findById(args.addDrinkInput.drinkTypeId)
if (!drinkType) {
throw new Error('DrinkType not found.')
}
drinkType.createdDrinks.push(drink)
await drinkType.save()
const drinkLoader = new DataLoader(drinkIds => {
return drinks(drinkIds)
})
const drinks = async drinkIds => {
try {
const drinks = await Drink.find({ _id: { $in: drinkIds } })
return drinks.map(drink => {
return transformDrink(drink)
})
} catch (err) {
throw err
}
}
const transformDrink = drink => {
return {
...drink._doc,
_id: drink.id,
drinkType: drinkType.bind(this, drink.drinkType),
}
}
const drinkType = async drinkTypeId => {
try {
const drinkType = await drinkTypeLoader.load(drinkTypeId.toString())
return {
...drinkType._doc,
_id: drinkType.id,
createdDrinks: () => drinkLoader.loadMany(drinkType._doc.createdDrinks),
}
I want this createdDrinks part to return the array of drink objects, but it is only returning the array of _ids.
I have been reading the mongoose documentation and it seems that using ObjectId is the correct way. Would you mind helping me out?
Thank you in advance.

GraphQL Resolver for Interface on same Mongoose collection

I'm creating a GraphQL server that uses Mongoose and GraphQLInterfaceType. I have a GraphQLInterfaceType of Books and sub types of SchoolBooksType and ColoringBookType. in my Mongoose Schema I specified that both SchoolBooks and ColoringBooks are to be stored in the same books collection
const coloringSchema = new Schema({
title: String,//Interface
pages: String
});
module.exports = mongoose.model("ColoringBook", coloringSchema , "books");
const schoolSchema = new Schema({
title: String, //Interface
subject: String
});
module.exports = mongoose.model("SchoolBook", schoolSchema , "books");
Here is one of my types
const SchoolBookType = new GraphQLObjectType({
name: "SchoolBook",
interfaces: [BooksInterface],
isTypeOf: obj => obj instanceof SchoolBook,
fields: () => ({
title: { type: GraphQLString },
subject: { type: GraphQLString }
})
});
Here is my query: But I don't know what to return, if I need to combine the two collections into the same array?
books: {
type: new GraphQLList(BooksInterface),
resolve() {
return SchoolBook.find({}) //<---- What to return?
}
}
Here is my query:
{
books{
title
... on ColoringBook{
pages
}
... on SchoolBook{
subject
}
}
}
Any help would be great, Thank you.
I guess you can use an async resolver, and concat both queries.
resolve: async () => {
const schoolBooks = SchoolBook.find({}).exec()
const coloringBooks = ColoringBook.find({}).exec()
const [sbooks, cbooks] = await Promise.all([schoolBooks, coloringBooks])
return [...sbooks, ...cbooks]
}

Collection not saved mongodb nodejs

I have an issue on my application (mongodb/nodejs), my aim is to save a collection :
const Job = require("../models/Job");
exports.saveJob = (req, res, next) => {
const newJob = new Job(req.body);
newJob.gender = 'Male';
newJob.save((err, myjob) => {
myjob.code = '1234';
myjob.save((err, mysavedjob) => {
console.log(mysavedjob);
/** OUTPUT **
* { __v: 0,
updatedAt: 2018-07-31T08:31:47.664Z,
createdAt: 2018-07-31T08:31:47.664Z,
gender:'Male',
code:'1234',
...
}
*/
})
})
}
As you can see, the code output the saved document, but when I check the database , I don't have the code:'1234' I have just
{ __v: 0,
updatedAt: 2018-07-31T08:31:47.664Z,
createdAt: 2018-07-31T08:31:47.664Z,
gender:'Male',
...
}
==== UPDATE (adding the JobModel) ====
const mongoose = require("mongoose");
const JobSchema = new mongoose.Schema(
{
userId: { type: mongoose.Schema.Types.ObjectId, ref: "User" },
code: String,
gender: String,
},
{ timestamps: true }
);
module.exports = mongoose.model("Job", JobSchema, "jobs");
Someone could tell me why please ?
thank you.
I am not sure this is the correct way to alter a job after saving it but try this
const Job = require("../models/Job");
exports.saveJob = (req, res, next) => {
const newJob = new Job(req.body);
newJob.gender = 'Male';
newJob.save((err, myjob) => {
myjob.code = '1234';
Job.findOneAndUpdate({_id: myjob._id}, myjob, (err, mysavedjob) => {
console.log(mysavedjob);
});
})
}

Resources