MongoDB - addToSet/push only on upsert - mongodb

I would like to add a subdocument to an array if it doesn't already exist and then return the newly added subdocument (or at least the array of subdocuments) within one query. Here is an example document structure:
{
"name": "John Smith",
"folders": [
{
"folderName": "Breweries"
"updatedAt": 1450210046338,
"checkins": [
{
"facebookID": "123",
"checkinID": "3480809",
"addedOn": 1450210046338
},
{
"facebookID": "234",
"checkinID": "345254",
"addedOn": 1450210046339
}
],
},
{
"folderName": "Food"
"updatedAt": 1450210160277,
"checkins": [
{
"facebookID": "432",
"checkinID": "123545426",
"addedOn": 1450210160277
}
],
}
],
}
The nested query below checks to see if the new folder's name already exists in the folders array. If it doesn't already exist, it adds the new folder to the folders array:
(using mongoskin here)
mongodb.collection('users').findOne(
{facebookID: facebookID, 'folders.folderName': folderName},
function (err, result) {
if (err) {
deferred.reject(err);
} else if (result !== null) {
deferred.reject(new Error('Folder name already taken'));
} else {
mongodb.collection('users').findOne(
{facebookID: facebookID, 'folders.folderName': folderName},
function (err, result) {
if (err) {
deferred.reject(err);
} else if (result !== null) {
deferred.reject(new Error('Folder name already taken'));
} else {
mongodb.collection('users').findAndModify(
{facebookID: facebookID},
[],
{$addToSet: {folders: newFolder}},
{fields:{'folders': 1}, new: true},
function (err, result) {
if (err) {
deferred.reject(err);
} else {
deferred.resolve(result);
}
});
}
});
It seems like you should be able to do this in one query - but I couldn't find a way to achieve $setOnInsert functionality with array operators ($addToSet/$push).

Related

Search MongoDB autocomplete filtered

I want to search autocomplete on the following fields :contactfirstname, contactlastname and name
Also, want to filter based on userid(s) first then perform autocomplete search
Issue:
Without filter criteria, autocomplete is working fine
With filter criteria in compound query not working as getting empty array
Can anyone help please?
exports.userNameCitySearchAutocomplete = async function (req, res) {
try {
const { userNameCityQueryparam } = req.query;
console.log("search query param", userNameCityQueryparam);
const agg = [
{
$search: {
index: 'userNameCity',
'compound': {
"filter": [{
"text": {
"query": ["6271f2bb79cd80194c81f631"],
"path": "_id",
}
}],
"should": [
{
//search on user name
autocomplete: {
query: userNameCityQueryparam,
path: 'name',
fuzzy: {
maxEdits: 2,
prefixLength: 3
}
}},
//search on user city
{
autocomplete: {
query: userNameCityQueryparam,
path: 'city',
fuzzy: {
maxEdits: 2,
prefixLength: 3
}
},
}
,
//search on user contact first name
{
autocomplete: {
query: userNameCityQueryparam,
path: 'contactfirstname',
fuzzy: {
maxEdits: 2,
prefixLength: 3
}
},
}
,
//search on user contact last name
{
autocomplete: {
query: userNameCityQueryparam,
path: 'contactlastname',
fuzzy: {
maxEdits: 2,
prefixLength: 3
}
},
}
],
"minimumShouldMatch": 1
}
}
}
]
const response = await User.aggregate(agg);
return res.json(response);
// res.send(response);
} catch (error) {
console.log("autocomplete search error", error);
return res.json([]);
}
};
Index details in mongodb:
{
"mappings": {
"dynamic": false,
"fields": {
"_id": {
"type": "string"
},
"city": {
"type": "autocomplete"
},
"contactfirstname": {
"type": "autocomplete"
},
"contactlastname": {
"type": "autocomplete"
},
"name": {
"type": "autocomplete"
}
}
}
}
Image of collection in mongodb
image of empty array
for anyone looking for solution to this,
You can use the MQL where clause in your pipeline stage definition.
{
$match: {
email:"email#domain.com"
}
}
check here for an example

Updating a nested array object in Mongo

I have a StudentClass collection that looks like this:
[
{
"_id": ObjectId("60923c997b4d3205009a981a"),
"attended": [
{
"in": "2021-05-05T06:35:05.226+00:00",
"out": "2021-05-05T06:45:05.226+00:00"
},
{
"in": "2021-05-05T06:47:05.226+00:00",
"out": "2021-05-05T06:55:05.226+00:00"
},
{
"in": "2021-05-05T06:56:05.226+00:00"
},
{
"in": "2021-03-03T07:10:00.628Z"
}
],
"studentId": ObjectId("608a42e8224c549ad9a9ab51"),
"active": true
},
{
"_id": ObjectId("6098f6f974af29682772fbe6"),
"attended": [
{
"in": "2021-05-05T06:35:05.226+00:00",
"out": "2021-05-05T06:55:05.226+00:00"
},
{
"in": "2021-05-05T06:59:05.226+00:00",
"out": "2021-05-05T07:20:05.226+00:00"
}
],
"studentId": ObjectId("608a42e8224c549ad9a9ab51"),
"active": true
},
{
"_id": ObjectId("6098f6f974af29682772fbe7"),
"attended": [],
"studentId": ObjectId("608a42e8224c549ad9a9ab51"),
"active": true
}]
For a give _id, I need to update the attended array as per the following conditions:
Need to find the last array element with a missing out key. That array element of the attended should get updated with the out key just like other array elements.
This is what I've tried:
const markStudentExitFromClass = (studentClassId, exitTime) => {
return new Promise((resolve, reject) => {
StudentClasses.updateOne(
{ _id: new mongoose.Types.ObjectId(studentClassId), "attended.out" : {$exists: false}},
{ $set: { "attended.$.out": new Date(exitTime) } },
function (err, updatedData) {
if (err) {
reject(err);
} else {
resolve(updatedData);
}
}
)
})
}
This is not updating any array element of attended array for the give _id. Just like $ positional operator finds the first array element, is there anything for the last array element?
What am I doing wrong?
Updated:
So, I got the update part working by changing the match clause of updateOne to:
const markStudentExitFromClass = (studentClassId, exitTime) => {
return new Promise((resolve, reject) => {
StudentClasses.updateOne(
{ _id: new mongoose.Types.ObjectId(studentClassId), "attended" :{"$elemMatch":{"out":{$exists: false}}} },
{ $set: { "attended.$.out": new Date(exitTime) } },
function (err, updatedData) {
if (err) {
reject(err);
} else {
resolve(updatedData);
}
}
)
})
}
But, I still cant figure out how to get the last matching element of the attended array instead of the first. Any pointers?

How to return the a formatted response from a mongo query/projection?

I'm trying to create an API to validate a promocode. I have minimal experience with mongo and the backend in general so I'm a bit confused in what is the best approach to do what I'm trying to accomplish.
I have this PromoCode form in the client. When a user types a promocode I would like for my backend to
verify if the code exists in one of the docs.
if it exists then return that code, the value for that code and the couponId
if the code doesn't exist then return an error.
My db is structured like this. The user will type one of those codes inside the codes: []
{
"_id": {
"$oid": "603f7a3b52e0233dd23bef79"
},
"couponId": "rate50",
"value": 50,
"codes": ["K3D01XJ50", "2PACYFN50", "COKRHEQ50"]
},
{
"_id": {
"$oid": "603f799d52e0233dd23bef78"
},
"couponId": "rate100",
"value": 100,
"codes": ["rdJ2ZMF100", "GKAAYLP100", "B9QZILN100"]
}
My route is structure like this:
router.post('/promoCode', (req, res, next) => {
const { promoCode } = req.body;
console.log('this is the req.body.promoCode on /promoCode', promoCode)
if (!promoCode) {
throw new Error('A promoCode needs to be passed')
}
promoCodesModel
.validatePromoCode(req.body.promoCode)
.then((response) => {
console.log('response inside /promoCode', response)
res.status(200).json({ data: response })
})
.catch((error) => {
res.status(400).json({ result: 'nok', error: error })
})
})
The validatePromoCode function is the following:
const validatePromoCode = async (code) => {
try {
let promoCode = await PromoCodesModel.find(
{"codes": code},
{_id: 0, codes: { $elemMatch: { $eq: code }} })
console.log('This is the promocode', promoCode)
return promoCode
} catch (err) {
throw new Error (err.stack)
}
}
All this seems to sort of work since I get the following response when the code is typed correctly
{
"data": [
{
"codes": [
"COKRHEQ50"
]
}
]
}
when typed incorrectly I get
{
"data": []
}
What I would like to get back is. (How can I accomplish this ?). Thanks
// when typed correctly
{
"data": { value: 50, couponId: "rate50", code: "COKRHEQ50" }
}
// when typed incorrectly
{
"error": "this is not valid code"
}
TL;DR: I would like to return a formatted query with specific values from a mongo query or an error object if that value does not exist on the document object.
Ok just figured it out
To be able to get the this responsed (what I wanted):
{
"data": [
{
"codes": [
"K3D01XJ50"
],
"couponId": "rate50",
"value": 50
}
]
}
I ended up having to do this on validatePromoCode
onst validatePromoCode = async (code) => {
try {
let promoCode = await PromoCodesModel.find(
{ codes: code },
{ _id: 0, codes: { $elemMatch: { $eq: code } }, couponId: 1, value: 1 },
)
return promoCode
} catch (err) {
throw new Error(err.stack)
}
}
But is there a better way on doing this ? Thanks

Mongoose loop through findOneAndUpdate condition statement

I want to know if this part of code can be written differently, only with Mongoose helper methods of models ? Can I return a success and error if no stock are greater then 0 ?
ProductSchema.statics.substractStock = function (products) {
_.map(products, updateStock)
function updateStock(o) {
mongoose.model('Product').findById(o._id, function (err, product) {
return product
}).then(function(productDB){
if(productDB.stock > o.stock && productDB.stock > 0){
mongoose.model('Product').findOneAndUpdate(o._id, {$inc: {stock: -(o.stock)}}, {},
function (err, doc) {
//return success ??
}
);
} else {
//return 'no update'
}
});
}
};
This could be done with an atomic update where you can ditch the initial findById() call and include the comparison logic
if (productDB.stock > o.stock && productDB.stock > 0) { ... }
within the query as in the following:
function updateStock(o) {
mongoose.model('Product').findOneAndUpdate(
{
"_id": o._id,
"$and": [
{ "stock": { "$gt": o.stock } } ,
{ "stock": { "$gt": 0 } }
]
},
{ "$inc": { "stock": -(o.stock) } },
{ "new": true }, // <-- returns modified document
function (err, doc) {
// check whether there was an update
}
);
}

how to write findOneAndUpdate query in express.js?

i have shown my data , which is stored in database like this
{
"_id": {
"$oid": "5799995943d643600fabd6b7"
},
"Username": "xx",
"Email": "xx#gmail.com",
"Info": "Deactivate",
"Description": "aajdjdjddjdkjddjdjdhdj",
"VerificationCode": "594565",
"VerificationExpires": {
"$date": "2016-10-07T10:20:20.077Z"
}
}
My controller:
if Username, Email, Info are matched I need to update " Info = 'Active' " this is working at the same time i need to delete 'VerificationCode' field and 'VerificationExpires' field how can i achieve this?
exports.updatearticle = function(req, res) {
Article.findOneAndUpdate(
{ "Username":'xx', "Email":'xx#gmail.com', "Info": "Deactivate" },
{ "$set": { "Info": "Active" } },
{ "new": true }
function (err, doc) {
if (err) { // err: any errors that occurred
console.log(err);
} else { // doc: the document before updates are applied if `new: false`
console.log(doc); // , the document returned after updates if `new true`
console.log(doc.Info);
}
}
);
};
above condtion matched and info getting changed but i want to delete VerificationCode,VerificationExpires some one help me out
exports.updatearticle = function(req, res) {
Article.findOne( { "Username":'xx', "Email":'xx#gmail.com', "Info": "Deactivate" }, function(err, result){
if (!err && result) {
result.Info = "Active"; // update ur values goes here
result.VerificationCode = "";
result.VerificationExpires = {};
var article = new Article(result);
article.save(function(err, result2){
if(!err) {
res.send(result2);
} else res.send(err);
})
} else res.send(err);
});
}
home this may help