Related
so here is my stories model,
const storySchema = new mongoose.Schema(
{
avatar: { type: String, default: null },
handle: { type: String, default: null },
new: { type: Boolean, default: true },
isLive: {type: Boolean, default: false},
url: { type: String },
type: { type: String },
userName: { type: String, default: null },
userId: { type: mongoose.Schema.Types.ObjectId, ref: "user_details" },
isDeleted: { type: Boolean, default: false}
},
{
timestamps: true,
minimize: false
}
)
userId is refered to user_details, so currently when i list stories they get listed like this ,
one story at a time and sorted by userId and createdAt,
As you can see the first 2 stories has the same userId, and what i want to do is that i group the stories by the user Object.
"status": true,
"data": [
{
"_id": "633564ab793cf2a65f7c5dad",
"avatar": null,
"handle": null,
"new": true,
"isLive": false,
"url": "https://ellingsen-group.s3.amazonaws.com/media-1664443562856.png",
"type": "",
"userName": null,
"userId": "62eb5d58512ef25f1352830b",
"isDeleted": false,
"createdAt": "2022-09-29T09:26:03.846Z",
"updatedAt": "2022-09-29T09:26:03.846Z",
"__v": 0
},
{
"_id": "633564a9793cf2a65f7c5daa",
"avatar": null,
"handle": null,
"new": true,
"isLive": false,
"url": "https://ellingsen-group.s3.amazonaws.com/media-1664443559395.png",
"type": "",
"userName": null,
"userId": "62eb5d58512ef25f1352830b",
"isDeleted": false,
"createdAt": "2022-09-29T09:26:01.032Z",
"updatedAt": "2022-09-29T09:26:01.032Z",
"__v": 0
},
{
"_id": "633564e6793cf2a65f7c5dba",
"avatar": null,
"handle": null,
"new": true,
"isLive": false,
"url": "https://ellingsen-group.s3.amazonaws.com/media-1664443621607.png",
"type": "",
"userName": null,
"userId": "6290a0e7f03b0b3585e0f740",
"isDeleted": false,
"createdAt": "2022-09-29T09:27:02.608Z",
"updatedAt": "2022-09-29T09:27:02.608Z",
"__v": 0
},
{
"_id": "633564bf793cf2a65f7c5db0",
"avatar": null,
"handle": null,
"new": true,
"isLive": false,
"url": "https://ellingsen-group.s3.amazonaws.com/media-1664443582519.png",
"type": "",
"userName": null,
"userId": "6290a0e7f03b0b3585e0f740",
"isDeleted": false,
"createdAt": "2022-09-29T09:26:23.519Z",
"updatedAt": "2022-09-29T09:26:23.519Z",
"__v": 0
}
],
"totalPages": 1,
"message": "Get user story Feed Success"
want to change this, so for user 1 ( story 1, 2, 3) user 2 ( story 1,2 ) etc,
here is the query for the result above.
const stories: any = await Story.StoryModel.aggregate([{ $match: { '_id': { $in: combined } } }, { $sort: { userId: -1, createdAt: -1 } }, listStoriesFacetPagination]).exec()
I tried grouping them like this (below) but i get at error saying that stories.groupBy is not a function, I'm stuck at this point and been trying to work this out for the past week.
const groupByUserId = stories.groupBy((userId: any) => {
return story.userId;
});
and it would not work.
You can achieve this by using reduce method, the code will be like this:
const list = [{
'name': 'John',
'userId': '1',
},
{
'name': 'Anne',
'userId': '2',
},
{
'name': 'John',
'userId': '1',
},
{
'name': 'Anne',
'userId': '2',
},
];
const groups = list.reduce((groups, item) => ({
...groups,
[item.userId]: [...(groups[item.userId] || []), item]
}), {});
This code will result in a object like this:
{
"1": [
{'name': 'John', 'userId': '1'},
{'name': 'John', 'userId': '1'}
],
"2": [
{'name': 'Anne', 'userId': '2'},
{'name': 'Anne', 'userId': '2'}
]
}
Hope it help you :D
Here is the solution i found,
const stories: any = await Story.StoryModel.aggregate([
{ $match: { '_id': { $in: combined } } },
{ $group: {
_id: '$userId',
stories: { $push: { url: '$url', _id: '$_id' , isLive: '$isLive', avatar: '$avatar', type:'$type', handle:'$handle', new:'$new', userName:'$userName', userId:'$userId', isDeleted:'$isDeleted', createdAt:'$createdAt' } } } },
{ $sort: { createdAt: 1 } },
listStoriesFacetPagination]).exec()
I'm trying to get an object which has isDraft value true, but I'm also getting objects which have isDraft value false. I need only objects having isDraft value true. I have tried all possible ways but am not able to find a solution for this. Can anyone help me with this?
Below are the schema, query and response.
Schema
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const Contract = new Schema({
name: {
type: String,
unqiue: true,
required: true
},
version: [
{
no: {
type: Number,
required: true
},
sections: [
{
sectionName: {
type: String,
required: true
},
clause: [{
description: {
type: String,
required: true
},
}]
}
],
approvedBy: [
{
user: {
type: Schema.Types.ObjectId,
ref: 'user'
},
}
],
acceptedBy: [
{
name: {
type: String,
},
eamil: {
type: String,
},
}
],
isDraft: {
type: Boolean,
required: true
},
date: {
type: Date,
default: Date.now
}
}
],
createdBy: {
type: Schema.Types.ObjectId,
ref: 'user',
required: true
},
});
module.exports = mongoose.model('contract', Contract);
Query
query = {
$and: [
{ createdBy: clientAdminDetails._id },
{ "version.isDraft": true }
],
};
await Contract
.find(query)
.skip(req.body.noOfItems * (req.body.pageNumber - 1))
.limit(req.body.noOfItems)
.exec((err, contract) => {
if (err) {
return res.json(err);
}
Contract.countDocuments(query).exec((count_error, count) => {
if (err) {
return res.json(count_error);
}
return res.json({
total: count,
page: req.body.pageNumber,
pageSize: contract.length,
contracts: contract
});
});
});
Response
{
"total": 1,
"page": 1,
"pageSize": 1,
"contracts": [
{
"_id": "61449469775..",
"name": "Octavia Blankenship",
"version": [
{
"_id": "614496593cc..",
"sections": [
{
"_id": "61449469775..",
"sectionName": "Est dolore dolorem n Updated `1323",
"clause": [
{
"_id": "614494697..",
"description": "Numquam nostrud et a"
}
]
}
],
"isDraft": false,
"no": 1,
"approvedBy": [],
"acceptedBy": [],
"date": "2021-09-17T13:21:29.509Z"
},
{
"_id": "614496122904ee4e046fbee8",
"sections": [
{
"_id": "6144955a8c0061025499606f",
"sectionName": "Praesentium suscipit",
"clause": [
{
"_id": "6144955a8c00610254996070",
"description": "Velit aperiam ut vel"
}
]
}
],
"isDraft": true,
"no": 2,
"approvedBy": [],
"acceptedBy": [],
"date": "2021-09-17T13:20:18.128Z"
}
],
"createdBy": "614367e980b29e6c...",
"__v": 0
}
]
}
This is why using your query you are telling mongo "Give me a document where createdBy is desired id and version.isdraft is true" So, as the DOCUMENT contains both values, is returned, even existing false into the array.
To solve this you have many ways.
First one is using $elemMatch into projection (docs here). But using this way only the first element is returned, so I think you prefer other ways.
So you can use an aggregation query using $filter like this:
First $match by values you want (as in your query).
Then override version array filtering by values where isDraft = true.
db.collection.aggregate([
{
"$match": {
"createdBy": "",
"version.isDraft": true
}
},
{
"$set": {
"version": {
"$filter": {
"input": "$version",
"as": "v",
"cond": {
"$eq": [
"$$v.isDraft",
true
]
}
}
}
}
}
])
Example here
For a project where we have actions and donations. We store the donations in an array in the related action. For the connection we use Mongoose.
The schema for an action is as follows, for readability I've removed some fields which are not related to this problem:
const donationSchema = new Schema(
{
id: {
type: String,
unique: true,
required: true,
index: true,
},
amount: { type: Number },
status: {
type: String,
enum: ['pending', 'collected', 'failed'],
default: 'pending',
},
},
{ timestamps: true, versionKey: false, _id: false },
);
const schema = new Schema(
{
donations: { type: [donationSchema], default: [] },
target: { type: Number, default: 0 },
collected: { type: Number, default: 0 },
},
{
timestamps: true,
versionKey: false,
},
);
const Action = model<IAction>('Action', schema);
Let say I have an Action with three donations, one in every state:
{
"_id": "6098fb22101f22cfcbd31e3b"
"target": 10000,
"collected": 25,
"donations": [
{
"uuid": "dd90f6f1-56d7-4d8b-a51f-f9e5382d3cd9",
"amount": 25,
"status": "collected"
},
{
"uuid": "eea0ac5e-1e52-4eba-aa1f-c1f4d072a37a",
"amount": 10,
"status": "failed"
},
{
"uuid": "215237bd-bfe6-4d5a-934f-90e3ec9d2aa1",
"amount": 50,
"status": "pending"
}
]
}
Now I want to update the pending donation to collected.
This would be
Action.findOneAndUpdate(
{
_id: '6098fb22101f22cfcbd31e3b',
'donations.id': '215237bd-bfe6-4d5a-934f-90e3ec9d2aa1',
},
{
$set: {
'donations.$.status': 'collected',
},
},
{
upsert: false,
returnOriginal: false,
}
).then((action) => console.log(action);
I want to update the status to collected, but also update the collected so that it is the same as all the donations with status equal to collected. I thought of using the $inc operator, but this keeps saying that donations.$.amount is not a number and therefore not able to increment collected.
Is there a way to do this in the same update call? The reason why I cannot get the object and just count collected amount is that maybe two donation callbacks occur at the same time, so we don't want the to overwrite the previous given amount.
This aggregation can help you I believe:
db.collection.aggregate([
{
"$match": {
_id: "6098fb22101f22cfcbd31e3b"
}
},
{
"$set": {
"donations.status": {
"$reduce": {
"input": "$donations",
"initialValue": {
uuid: "215237bd-bfe6-4d5a-934f-90e3ec9d2aa1"
},
"in": {
$cond: [
{
$eq: [
"$$this.uuid",
"$$value.uuid"
]
},
"collected",
"$$this.status"
]
}
}
}
}
},
{
"$set": {
"collected": {
"$reduce": {
"input": "$donations",
"initialValue": "$collected",
"in": {
$cond: [
{
$eq: [
"$$this.status",
"collected"
]
},
{
$sum: [
"$$value",
"$$this.amount"
]
},
"$$value"
]
}
}
}
}
}
])
Edit: Above aggregation wasn't properly update status field to "collected" dunno why..
But update query below should work. I couldn't test it too. So, please let me know if something goes wrong.
db.collection.update({
"_id": "6098fb22101f22cfcbd31e3b"
},
{
"$set": {
"donations.$[element].status": "collected",
"$inc": {
"donations.$[element].amount": {
"$cond": [
{
"$eq": [
"donations.$[element].status",
"collected"
]
},
"donations.$[element].amount",
"collected"
]
}
}
}
},
{
"arrayFilters": [
{
"element.uuid": "215237bd-bfe6-4d5a-934f-90e3ec9d2aa1"
}
]
})
I have a product document that looks like this:
{
"_index": "productss",
"_type": "_doc",
"_id": "2fb60b1880f0251af4340af009",
"_score": 5.0262785,
"_source": {
"prepTime": {
"durationType": "Min",
"value": 8,
"imageUrl": ""
},
"shopId": "CCXow8ALRDrALRSKFC",
"productTimings": [
{
"startHour": 8,
"endHour": 9,
"startMin": 30,
"endMin": 45,
"dayOfWeek": [
"Mon",
"Tue",
"Wed",
"Thu",
"Fri"
]
},
{
"startHour": 16,
"endHour": 18,
"startMin": 30,
"endMin": 45,
"dayOfWeek": [
"Sat",
"Sun"
]
}
]
}
}
My model looks like this.
const mongoosastic = require('mongoosastic');
const { Schema } = mongoose;
const Timing = new Schema({
startHour: { type: Number, es_indexed: true },
endHour: { type: Number, es_indexed: true },
startMin: { type: Number, es_indexed: true },
endMin: { type: Number, es_indexed: true },
dayOfWeek: [{ type: String, es_indexed: true }],
});
const productsSchema = mongoose.Schema({
_id: { type: String },
prepTime : {
durationType : { type: String, es_indexed: true },
value : { type: Number, es_indexed: true },
imageUrl : { type: String, es_indexed: true }
},
shopId: { type: String, es_indexed: true },
productTimings: {
type: [Timing],
es_indexed: true,
es_type: 'nested',
es_include_in_parent: true,
}
});
productsSchema.plugin(mongoosastic);
module.exports = mongoose.model('Products', productsSchema, 'Products');
I need to fetch all products whose startHours:Startminute is less than the current time and endHour:endMinute is less than the current time. It should also match the dayOfWeek from current date.
Note that, there could be a morning slot and a evening slot in the productTimings.
I have tried the following and got this far, but cannot proceed further:
prodDetails = await client.search({
index: 'productss',
body:
{
query:
{
bool: {
must: [
{ match: { shopId } },
{
"nested": {
"path": "productTimings",
"query": {
"range": {
"productTimings.startHour": {
"lte": 12,
}
},
"range": {
"productTimings.endHour":{
"gte": 11,
}
}
}
}
}
]
}
}
},
},
from: pageno * size,
size,
});```
You were almost there but the query was heavily malformed.
Try this — I've adapted it to match the sample document from above:
const body = {
size,
from: pageno * size,
"query": {
"bool": {
"must": [
{
"match": { shopId }
},
{
"nested": {
"path": "productTimings",
"query": {
"bool": {
"must": [
{
"range": {
"productTimings.startHour": {
"gte": 8
}
}
},
{
"range": {
"productTimings.endHour": {
"lte": 9
}
}
},
{
"match": {
"productTimings.dayOfWeek": "Fri"
}
}
]
}
}
}
}
]
}
}
}
prodDetails = await client.search({
index: 'productss',
body: body
})
💡 Tip: I'd recommend using a term query instead of match. Even before that, I'd convert the shopId and dayOfWeek to keywords. When they're mapped as String (= text), they'll be lowercased by the standard analyzer — and you don't want that. You likely want case-sensitive, exact matches that the keyword mapping guarantees.
The goal of my code is to upsert an object into my database. I created the Schema which is a bit complex
const mongoose = require("mongoose");
const Schema = mongoose.Schema;
const objectSchema = new Schema(
{ message: String },
{ discriminatorKey: "type", _id: false }
);
const cameraSchema = new Schema(
{
number: { type: Number, required: true },
name: { type: String, required: true } //Do not update if exists
},
{ _id: false }
);
const mainSchema = new Schema(
{
name: { type: String, required: true, trim: true },
lastUpdateTime: { type: Date, default: Date.now },
objects: [objectSchema]
},
{ collection: "mainCollection" }
);
mainSchema.path("objects").discriminator(
"TypeA",
new Schema(
{
cameras: [cameraSchema],
enabled: { type: Boolean } //Do not update if exists
},
{ _id: false }
)
);
mainSchema.path("objects").discriminator(
"TypeB",
new Schema(
{
available: { type: Boolean, required: true },
enabled: { type: Boolean } //Do not update if exists
},
{ _id: false }
)
);
module.exports = mongoose.model("mainModel", mainSchema);
I am receiving the following object from an external source
{
"name": "040C7",
"objects": [
{
"type": "TypeA",
"cameras": [
{
"number": 1
},
{
"number": 2
},
{
"number": 3
}
]
},
{
"type": "TypeB",
"available": false
}
]
}
If the name exists in my database, I should update every field that I received without removing/updating the fields :
enabled in TypeA and TypeB
name in the camera objects
Otherwise, I should insert it in the database with all the fields and default values for enabled (true) and name (Camera)
I already tried the UpdateOne() function but it is deleting my fields enabled and name when they already are in my database
const obj = new myModel(message);
let upsertObj = obj.toObject();
delete upsertRcm._id;
MyModel.updateOne(
{ name: obj.name },
upsertObj,
{ upsert: true },
err => {
console.log(err)
}
);
How can I say to mongoose to keep fields that are already in my database but not in my received object ?
Document in database
{
"lastUpdateTime": "2018-11-07T09:12:30.750Z",
"name": "040C7",
"objects": [
{
"type": "TypeA",
"enabled": true,
"cameras": [
{
"number": 1,
"name": "Camera",
}
]
},
{
"type": "TypeB",
"enabled": true,
"available": false
}
]
}
After the update with the received object
{
"lastUpdateTime": "2018-11-07T09:14:30.600Z",
"name": "040C7",
"objects": [
{
"type": "TypeA",
"cameras": [
{
"number": 1,
},
{
"number": 2,
},
{
"number": 3,
}
]
},
{
"type": "TypeB",
"available": false
}
]
}
But it should be
{
"lastUpdateTime": "2018-11-07T09:14:30.600Z",
"name": "040C7",
"objects": [
{
"type": "TypeA",
"enabled": true
"cameras": [
{
"number": 1,
"name": "Camera"
},
{
"number": 2,
},
{
"number": 3,
}
]
},
{
"type": "TypeB",
"enabled": true,
"available": false
}
]
}
MongoDB version: 4.0.2
The name field is my unique identifier. Only one type "TypeA", "TypeB", ... will be present in the array. I will never receive 2 "TypeA" in my objects
The list of fields that have to be updated :
objects["TypeA"].cameras.number (value from new object)
objects["TypeB"].available (value from new object)
lastUpdateTime (Date now)
The list of fields that are in the database and should kept :
objects["TypeA"].enabled
objects["TypeA"].cameras.name
objects["TypeB"].enabled