Mongoose NodeJS Express - Edit a specific sub document field - node.js

I have the following schemas designed in my Node server
SCHEMAS
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const dataSchema = new Schema({
time: Date,
value: String
});
const nodeSchema = new Schema({
name: String,
description: String,
number: Number,
status: String,
lastSeen: Date,
data: [dataSchema]
});
const siteSchema = new Schema({
code: String,
name: String,
description: String,
totalNodes: Number,
nodes: [nodeSchema]
});
const Site = mongoose.model('site',siteSchema);
module.exports = Site;
They basically look like this. You can see there are two nodes with some demo data.
EXAMPLE
{
"_id": "5fa169473a394829bc485069",
"code": "xfx3090",
"name": "Name of this site",
"description": "Some description",
"totalNodes": 2,
"__v": 0,
"nodes": [
{
"_id": "5fa1af361e085b516066d7e2",
"name": "device name",
"description": "device description",
"number": 1,
"status": "Offline",
"lastSeen": "2020-11-03T19:27:50.062Z",
"data": [
{
"Date": "2019-01-01T00:00:00.000Z",
"value": "12"
},
{
"Date": "2019-01-01T00:00:00.000Z",
"Value": "146"
}
]
},
{
"_id": "5fa1b10f4f24051520f85a58",
"name": "device name",
"description": "device description",
"number": 2,
"status": "Offline",
"lastSeen": "2020-11-03T19:35:43.409Z",
"data": [
{
"Date": "2019-01-01T00:00:00.000Z",
"Value": "555"
}
]
}
]
}
]
My question is how can I update a specific field of a node, in particular how I can update the last seen or the status. It is important to mention that the client making the request will only have access the the site code and the node number. The Object Id's of sites and nodes will not be known.
So far this is what I have, but it only creates one new Object Id for some reason.
Any advice will be appreciated
updateNode: async (req,res,next) => {
const {siteCode} = req.params;
const { nodeNumber } = req.params;
const status = req.body.status;
const nodeStatus = await Site.findOneAndUpdate({'code': siteCode, 'nodes.number':nodeNumber}, { '$set': {'nodes.$.status': {'status':status}}});
res.status(200).json({message: 'success'});
}

You'll need to do it this way.
I have predefined the ._ids.
You can do this dynamically if you want. If you are using express you could just use queries. Example req.query.documentID. The URL to access it will be localhost:p/?documentID=5fa169473a394829bc485069&nodeID=5fa1af361e085b516066d7e2
p in localhost is for port
await Site
.findOne({
"_id": "5fa169473a394829bc485069",
"nodes._id": "5fa1af361e085b516066d7e2"
})
.update({ "lastSeen": Date })
.then(doc => res.json(doc))
.catch(e => console.log(e))
Basically finding a doc with id of 5fa169473a394829bc485069
Then a node with _id of 5fa1af361e085b516066d7e2
And then update() method and { "lastSeen": Date } parameter to Date.
That's it!
EDIT
You'll have to create a VALID MongoDB object by doing this
app.get("/new", async (req, res) => {
let Site = new model({
code: "String",
name: "String",
description: "String",
totalNodes: 2,
nodes: [
{
_id: new mongoose.Types.ObjectId,
name: "String",
description: "String",
number: 1,
status: "offline",
lastSeen: Date.now(),
data: [{ "someData": "someData" }]
},
{
_id: new mongoose.Types.ObjectId,
name: "String",
description: "String",
number: 2,
status: "offline",
lastSeen: Date.now(),
data: [{ "someData": "someData" }]
}
]
});
await Site
.save()
.then(doc => {
console.log(doc);
res.json(doc);
})
.catch(e => console.error(e));
});
Everything is loaded with dummy data. Then you update the data like this.
app.get("/", async (req, res) => {
await model
.findOne({ "code": "String" })
.update({
"nodes.0.status": "online"
})
.then(doc => {
console.log(doc);
res.json(doc);
})
.catch(e => console.error(e));
})
Basically you access the object at the index position 0 ( that means the first post ) like this nodes.0 and then the status of that object will be respectively nodes.0.status. Then you just save the object and that's it!

Related

how to get specified info from a collection

i have this schema
const todoSchema = new mongoose.Schema({
name: String,
details: String
})
const userSchema = new mongoose.Schema({
name: String,
todo: [todoSchema]
})
i want a rout that gets a specified todo (based on name) for a specified user
this code
await User.findOne({ 'todo._id': req.body.todo_id, _id: req.body.id })
its get all the information of the user not a Single ToDo
such as if i got this
"user": {
"todo": [
{
"_id": "60fc3bd454b38c19a0afd09a",
"name": "hi",
"details": "hello"
} , {
"_id": "60fc3bd454b38c19a0afd09b",
"name": "bye",
"details": "goodbye"
}
]
}
how can i get the todo[1] details
how should i fix it
$ positional operator is what you want.
await User.findOne(
{ 'todo._id': req.body.todo_id,
_id: req.body.id },{
'todo.$': 1 }
})

What is the best approach for referencing objectIds plus additional parameters in a schema?

I am creating a schema for an Order model that will track the items ordered along with the quantity purchased. I want to keep the itemId references and the quantity tied together as an array in one parameter.
I have created an Array that includes a reference to the ObjectId plus an additional Number type. I am currently unable to populate the product information using a .populate() query.
Order Schema
const mongoose = require("mongoose");
const { Schema } = mongoose;
const orderSchema = new Schema({
orderNumber: String,
_itemsOrdered: [
{
itemId: {
type: mongoose.Schema.Types.ObjectId,
ref: "menuItems"
},
quantity: Number
}
]
});
mongoose.model("orders", orderSchema);
MenuItem Schema
const mongoose = require("mongoose");
const { Schema } = mongoose;
const MenuItemSchema = new Schema({
imageURL: String,
name_en: String,
name_es: String,
type_en: String,
type_es: String,
description_en: String,
description_es: String,
dietaryCallouts: [String],
price: Number
});
mongoose.model("menuItems", MenuItemSchema);
module.export = MenuItemSchema;
I am able to save the record but cannot populate the MenuItem information with the following query:
Order Controller
async show(req, res, next) {
try {
const orderId = req.params.id;
let order = await Order.findById({ _id: orderId }).populate(
"_itemsOrdered.itemId"
);
res.send(order);
} catch (err) {
res.status(402).send(err);
}
}
Here it the order object that is being saved to the DB.
Order Object
{
"_id": "5dc93b9c0085b8045e0c8aa3",
"orderNumber": "Order 3",
"_itemsOrdered": [
{
"_id": "5dc93b9c0085b8045e0c8aa5",
"itemId": "5dc7f814a2679b47319a79a4",
"quantity": 1
},
{
"_id": "5dc93b9c0085b8045e0c8aa4",
"itemId": "5dc7e5c7de590744c46f93da",
"quantity": 2
}
],
"__v": 0
}
Your order schema must be like this:
const orderSchema = new Schema({
orderNumber: String,
_itemsOrdered: [
{
itemId: { type: mongoose.Schema.Types.ObjectId, ref: "menuItems" },
quantity: Number
}
]
});
And you can use the following route to create an order document.
router.post("/order", async (req, res, next) => {
try {
const { orderNumber, _itemsOrdered } = req.body;
let order = new Order({ orderNumber, _itemsOrdered });
order = await order.save();
res.status(201).send(order);
} catch (err) {
console.log(err);
res.status(500).send(err);
}
});
Sample body: (you need to change ids according to yours)
{
"orderNumber": "Order 1",
"_itemsOrdered": [
{"itemId": "5dc90346222b892434e4675a", "quantity" : 1 },
{"itemId": "5dc90359222b892434e4675b", "quantity" : 2 }
]
}
To get the order and its items you can use populate like this:
router.get("/orders/:id", async (req, res) => {
try {
const orderAndItems = await Order.findById(req.params.id).populate(
"_itemsOrdered.itemId"
);
res.send(orderAndItems);
} catch (err) {
console.log(err);
res.status(500).send(err);
}
});
This will give you a result like this:
{
"_id": "5dc904db8407a217b4dfe6f4",
"orderNumber": "Order 1",
"_itemsOrdered": [
{
"_id": "5dc904db8407a217b4dfe6f6",
"itemId": {
"_id": "5dc90346222b892434e4675a",
"name_en": "item 1",
"price": 1,
"__v": 0
},
"quantity": 1
},
{
"_id": "5dc904db8407a217b4dfe6f5",
"itemId": {
"_id": "5dc90359222b892434e4675b",
"name_en": "item 2",
"price": 2,
"__v": 0
},
"quantity": 2
}
],
"__v": 0
}

How to post and populate bill

I've got a relational json called "client" inside Bill's model. This is my code:
const mongoose = require("mongoose");
const { Schema } = mongoose;
const billSchema = new Schema({
number: Number,
date: { type: Date, default: Date.now() },
type: String,
local: String,
client: { type: mongoose.Schema.Types.ObjectId, ref: "clients", required: true },
detail: [
{
quantity: Number,
product: { code: Number, name: String, price: Number },
undertotal: Number
}
],
total: Number
});
mongoose.model("bills", billSchema);
this is my post route:
app.post("/api/bills", async (req, res) => {
const { number, type, local, client, detail, total } = req.body;
await Client.findById(req.body.client._id).then(client => {
if (!client) {
return res.status(404).json({
message: "client not found"
});
}
});
const bill = new Bill({
number,
date: new Date(),
type,
local,
client,
detail,
total
});
try {
let newBill = await bill.save();
res.status(201).send(newBill);
} catch (err) {
if (err.name === "MongoError") {
res.status(409).send(err.message);
}
res.status(500).send(err);
}
});
//my get route
app.get("/api/bills", function(req, res) {
Bill.find({}, function(err, bills) {
Client.populate(bills, { path: "clients" }, function(err, bills) {
res.status(200).send(bills);
});
});
});
I want something like this:
{
"number": 302,
"type": "c",
"local": "porstmouth",
"client": {
"address": {
"street": "victoria street",
"number": 1001,
"floor": "2",
"flat": 4
},
"_id": "5dab929613fb682b48e4ca6b",
"name": "luke skywalker",
"mail": "l.skywalker#yahoo.com",
"cuil": "39193219",
"phone": 128391,
"__v": 0
},
"detail": [
{
"quantity": 500,
"product": {
"code": 300,
"name": "P2",
"price": 800
},
"undertotal": 5000
}
],
"total": 11000
}
But I see this result:
{
"date": "2019-10-20T12:27:17.162Z",
"_id": "5dac52a577e09b4acc45718d",
"number": 302,
"type": "c",
"local": "porstmouth ",
"client": "5dab929613fb682b48e4ca6b",
"detail": [
{
"_id": "5dac52a577e09b4acc45718e",
"quantity": 500,
"product": {
"code": 300,
"name": "P2",
"price": 800
},
"undertotal": 5000
}
],
"total": 11000,
"__v": 0
}
I don't want to see id client only. I want to see all content from client inside bill.
I tried to do with populate method, but I haven't results.
So, Which is form to post and populate a nested json relational object in this case?
While posting only clientId is enough.
So your post route can be like this (you both used await and then, which is incorrect, so I refactored it to use only await)
app.post('/api/bills', async (req, res) => {
const { number, type, local, client, detail, total } = req.body;
let existingClient = await Client.findById(req.body.client._id)
if (!existingClient) {
return res.status(404).json({
message: "client not found"
});
}
const bill = new Bill({
number,
date: new Date(),
type,
local,
client: req.body.client._id
detail,
total
})
try {
let newBill = await bill.save();
res.status(201).send(newBill);
} catch (err) {
if (err.name === 'MongoError') {
res.status(409).send(err.message);
}
res.status(500).send(err);
}
});
And in the get route to retrieve all the client info you need to populate it like this:
app.get('/api/bills', async (req, res) => {
try {
const bills = await Bill.find({}).populate("clients");
res.status(200).send(bills);
} catch (err) {
console.log(err);
res.status(500).send(err);
}
}
)

Problem with ottoman not resolving the references

I have two models in my ottoman 1.0.5 setup. One holds contact info which includes an emails array of docs and then the email doc. I can insert new contacts fine as well as emails in docs and the corresponding link in the contact doc for the new email.
Here is my model
const ottoman = require("ottoman")
ottoman.bucket = require("../app").bucket
var ContactModel = ottoman.model("Contact",{
timestamp: {
type: "Date",
default: function() {return new Date()}
},
first_name : "string",
last_name : "string",
emails: [
{
ref:"Email"
}
]} )
var EmailModel = ottoman.model("Email",{
timestamp: {
type: "Date",
default: function() {return new Date()}
},
type : "string",
address : "string",
name: "string"
} )
module.exports = {
ContactModel : ContactModel,
EmailModel : EmailModel
}
Now to get an contact and all its emails i use this function
app.get("/contacts/:id", function(req, res){
model.ContactModel.getById(req.params.id,{load: ["emails"]}, function(error, contact){
if(error) {
res.status(400).json({ Success: false , Error: error, Message: ""})
}
res.status(200).json({ Success: true , Error: "", Message: "", Data : contact})
})
})
Which returns me this
{
"Success": true,
"Error": "",
"Message": "",
"Data": {
"timestamp": "2019-01-30T23:59:59.188Z",
"emails": [
{
"$ref": "Email",
"$id": "3ec07ba0-aaec-4fd4-a207-c4272cef8d66"
}
],
"_id": "0112f774-4b5d-4b73-b784-60fa9fa2f9ff",
"first_name": "Test",
"last_name": "User"
}
}
if i go and log the contact to my console i get this
OttomanModel(`Contact`, loaded, key:Contact|0112f774-4b5d-4b73-b784-60fa9fa2f9ff, {
timestamp: 2019-01-30T23:59:59.188Z,
emails: [ OttomanModel(`Email`, loaded, key:Email|3ec07ba0-aaec-4fd4-a207-c4272cef8d66, {
timestamp: 2019-01-31T00:36:01.264Z,
_id: '3ec07ba0-aaec-4fd4-a207-c4272cef8d66',
type: 'work',
address: 'test#outlook.com',
name: 'Test Outlook',
}),
OttomanModel(`Email`, loaded, key:Email|93848b71-7696-4ef5-979d-05c19be9d593, {
timestamp: 2019-01-31T04:12:40.603Z,
_id: '93848b71-7696-4ef5-979d-05c19be9d593',
type: 'work',
address: 'newTest#outlook.com',
name: 'Test2 Outlook',
}) ],
_id: '0112f774-4b5d-4b73-b784-60fa9fa2f9ff',
first_name: 'Test',
last_name: 'User',
})
This shows that emails was resolved but why does it not show up in the returned json. On the other hand if i return contact.emails i get the resolved emails just fine. So i hope someone can shed some light on what i am missing here
I asked a similar question on the couchbase forum, and I also found out the solution:
(a slight difference that the result of my search is an array not an object like in your case)
forum.couchbase.com
app.get("/assets", (req, res) => {
AssetModel.find({}, { load: ["assetModelId", "assetGroupId", "assetTypeId"] }, (err, results) => {
if (err) return res.status(400).send("no asset found");
const assets = [];
results.map(asset => {
assets.push({...asset});
});
res.status(200).send(assets)
});
});

How to use different types of References and populate

So i have two schemas, Article and Event
Both have an image field.
For Article,
featured_image: {
type: String,
default: '',
}
For Event,
featured_image: {
type: Schema.ObjectId,
ref: 'Medium'
}
I have another schema, Card, like this
type: {
type: String,
enum: ['Article', 'Event']
},
data: {
type: Schema.ObjectId,
refPath: 'type'
}
I am trying to populate the cards, like this
Card
.find(query)
.populate({
path: 'data',
populate: [{
path: 'featured_image',
model: 'Medium',
select: 'source type'
}]
};)
However, it keeps giving me a cast error, because when card is of type Event, it populates fine, but when it's of type 'Article', featured_image field is of string type and hence cannot be populated.
How do i populate featured_image field only if card is of type Event or it's a reference id, instead of string.
Instead of what you are attempting to do you should be using "discriminators", which is in fact the correct way to handle a relationship where the object types vary in the reference given.
You use discriminators by the different way in which you define the model, which instead constructs from a "base model" and schema as in:
const contentSchema = new Schema({
name: String
});
const articleSchema = new Schema({
image: String,
});
const eventSchema = new Schema({
image: { type: Schema.Types.ObjectId, ref: 'Medium' }
});
const cardSchema = new Schema({
name: String,
data: { type: Schema.Types.ObjectId, ref: 'Content' }
});
const Medium = mongoose.model('Medium', mediumSchema);
const Card = mongoose.model('Card', cardSchema )
const Content = mongoose.model('Content', contentSchema);
const Article = Content.discriminator('Article', articleSchema);
const Event = Content.discriminator('Event', eventSchema);
So instead you define a "base model" such as Content here which you actually point the references to within Event.
The next part is that the differing schema are actually registered to this model via the .discriminator() method from the base model, as opposed to the .model() method. This registers the schema with the general Content model in such a way that when you refer to any model instance defined with .discriminator() that a special __t field is implied to exist in that data, using the registered model name.
Aside from enabling mongoose to .populate() on different types, this also has the advantage of being a "full schema" attached to the different types of items. So you have have different validation and other methods as well if you like. It is indeed "polymorphism" at work in a database context, with helpful schema objects attached.
Therefore we can demonstrate both the varied "joins" that are done, as well as that you can now both use the individual models for Article and Event which would deal with only those items in all queries and operations. And not only can you use "individually", but since the mechanism for this actually stores the data in the same collection, there is also a Content model which gives access to both these types. Which is in essence how the main relation works in the definition to the Event schema.
As a full listing
const async = require('async'),
mongoose = require('mongoose'),
Schema = mongoose.Schema;
mongoose.set('debug',true);
mongoose.Promise = global.Promise;
mongoose.connect('mongodb://localhost/cards');
const mediumSchema = new Schema({
title: String
});
const contentSchema = new Schema({
name: String
});
const articleSchema = new Schema({
image: String,
});
const eventSchema = new Schema({
image: { type: Schema.Types.ObjectId, ref: 'Medium' }
});
const cardSchema = new Schema({
name: String,
data: { type: Schema.Types.ObjectId, ref: 'Content' }
});
const Medium = mongoose.model('Medium', mediumSchema);
const Card = mongoose.model('Card', cardSchema )
const Content = mongoose.model('Content', contentSchema);
const Article = Content.discriminator('Article', articleSchema);
const Event = Content.discriminator('Event', eventSchema);
function log(data) {
console.log(JSON.stringify(data, undefined, 2))
}
async.series(
[
// Clean data
(callback) =>
async.each(mongoose.models,(model,callback) =>
model.remove({},callback),callback),
// Insert some data
(callback) =>
async.waterfall(
[
(callback) =>
Medium.create({ title: 'An Image' },callback),
(medium,callback) =>
Content.create(
[
{ name: "An Event", image: medium, __t: 'Event' },
{ name: "An Article", image: "A String", __t: 'Article' }
],
callback
),
(content,callback) =>
Card.create(
[
{ name: 'Card 1', data: content[0] },
{ name: 'Card 2', data: content[1] }
],
callback
)
],
callback
),
// Query and populate
(callback) =>
Card.find()
.populate({
path: 'data',
populate: [{
path: 'image'
}]
})
.exec((err,cards) => {
if (err) callback(err);
log(cards);
callback();
}),
// Query on the model for the discriminator
(callback) =>
Article.findOne({},(err,article) => {
if (err) callback(err);
log(article);
callback();
}),
// Query on the general Content model
(callback) =>
Content.find({},(err,contents) => {
if (err) callback(err);
log(contents);
callback();
}),
],
(err) => {
if (err) throw err;
mongoose.disconnect();
}
);
And the sample output for different queries
Mongoose: cards.find({}, { fields: {} })
Mongoose: contents.find({ _id: { '$in': [ ObjectId("595ef117175f6850dcf657d7"), ObjectId("595ef117175f6850dcf657d6") ] } }, { fields: {} })
Mongoose: media.find({ _id: { '$in': [ ObjectId("595ef117175f6850dcf657d5") ] } }, { fields: {} })
[
{
"_id": "595ef117175f6850dcf657d9",
"name": "Card 2",
"data": {
"_id": "595ef117175f6850dcf657d7",
"name": "An Article",
"image": "A String",
"__v": 0,
"__t": "Article"
},
"__v": 0
},
{
"_id": "595ef117175f6850dcf657d8",
"name": "Card 1",
"data": {
"_id": "595ef117175f6850dcf657d6",
"name": "An Event",
"image": {
"_id": "595ef117175f6850dcf657d5",
"title": "An Image",
"__v": 0
},
"__v": 0,
"__t": "Event"
},
"__v": 0
}
]
Mongoose: contents.findOne({ __t: 'Article' }, { fields: {} })
{
"_id": "595ef117175f6850dcf657d7",
"name": "An Article",
"image": "A String",
"__v": 0,
"__t": "Article"
}
Mongoose: contents.find({}, { fields: {} })
[
{
"_id": "595ef117175f6850dcf657d6",
"name": "An Event",
"image": "595ef117175f6850dcf657d5",
"__v": 0,
"__t": "Event"
},
{
"_id": "595ef117175f6850dcf657d7",
"name": "An Article",
"image": "A String",
"__v": 0,
"__t": "Article"
}
]

Resources