DynamoDB: Dynamoose update ($PUT default behaviour) not working as expected? - node.js

I'm trying to implement a PUT (update) API endpoint, passing the primary key of an element as a parameter in the path and the attributes I want to update as parameter in the body. But is not working as expected, instead of updating the attributes of an existing element, is creating a new element in the database with wrong attributes.
As far as I understand from the Dynamoose API documentation, Model.update(key, update, options, callback) updates and existing item in the table. For example, if we have a Dog model where age is one of the attributes, then this code
Dog.update({ownerId: 4, name: 'Odie'}, {age: 1}, function (err) {
if(err) { return console.log(err); }
console.log('Just a puppy');
})
would update the age of a Dog called 'Odie' with ownerId: 4
Now, I tried to do a similar update for my own API. I have a data model called InvoiceConfig where the primary key corresponds to a unique name/id (string) attribute and contains another attribute called providerVariants (an array of objects)
This is my API definition in swagger:
put:
description: Updates the invoice config matching the id (providerName)
operationId: updateInvoiceConfigByName
consumes:
- application/json
produces:
- application/json
parameters:
- name: id
in: path
required: true
type: string
- name: providerVariants
description: array of JSON objects
in: body
required: true
schema:
$ref: "#/definitions/ProviderVariantsDataList"
responses:
"200":
description: A confirmation message
schema:
$ref: "#/definitions/ResponseMessage"
"500":
description: Error message
schema:
$ref: "#/definitions/ErrorResponse"
And this is the function that implements the dynamoose update in my code:
updateInvoiceConfigByName: function(req, res) {
var name = req.swagger.params.id.value;
var providerVariants = req.body;
console.log("UPDATE /invoice-config");
InvoiceConfig.update({provideName: name}, providerVariants, function(err) {
if (err) {
console.log("Update InvoiceConfig error");
throw err;
}
res.status(200).send({
providerName: `${name}`,
message: 'provider invoice cfg updated'
});
});
}
I have an object in the database:
{
"providerName": "ID01",
"providerVariants": [
{
"displayName": "My 1st Provider ",
"phone": "915698471",
"availableTemplates": [
"default"
]
}
]
}
and I try to update it from swagger-ui passing the following paramenters:
ID01 in the endpoint path itself and a modified providerVariants array in the body:
[
{
"displayName": "My new name",
"phone": "913333333",
"availableTemplates": [
"default"
]
}
]
But as I said at the beginning, if I check the contents of my table, I see that the item with providerName "ID01" has not changed, and there is a new item created:
{
"providerName": {
"S": "[object Object]"
}
}
I suspect that in this new object the providerName (primary key) was populated with the providerVariants array, which is totally wrong. Any hints about how to fix this are welcome, I don't know how to proceed with the update. Other endpoints (get, delete, post) in my API are working fine, but I'm blocked with the update/put

There is a typo in your update.
InvoiceConfig.update({provideName: name}, providerVariants, function(err)
You're missing the 'r' in providerName :)

Related

mongoDb database form-data empty response

I am sending Rest POST Request(form-data) using postman to mongoDb. Even after providing all the key-value pairs in the Model, only the product _id gets stored into the database not other array of objects. Here's my model schema:
const mongoose = require('mongoose');
const productSchema = mongoose.Schema({
name: String,
price: Number,
editor1: String,
year: String,
quantity: Number,
subject: String,
newProduct: String,
relatedProduct: String,
//coverImage: { type: String, required: false }
});
module.exports = mongoose.model('Product', productSchema);
And here's my POST request for the products:
exports.products_create_product = (req, res, next) => {
const product = new Product(req.body);
product
.save()
.then(result => {
console.log(result);
res.status(201).json({
message: "Created product successfully",
createdProduct: {
name: result.name,
price: result.price,
_id: result._id,
request: {
type: "GET",
url: "http://localhost:3000/products/" + result._id
}
}
});
})
.catch(err => {
console.log(err);
res.status(500).json({
error: err
});
});
};
And this is my result output:
{
"message": "Created product successfully",
"createdProduct": {
"_id": "5b2df3420e8b7d1150f6f7f6",
"request": {
"type": "GET",
"url": "http://localhost:3000/products/5b2df3420e8b7d1150f6f7f6"
}
}
}
Tried every possible way to solve this but in vain.
try the callback
product.save((err,result) =>{
if(err){
res.status(500).json({
error: err
});
return false
}
res.status(201).json({
message: "Created product successfully",
createdProduct: {
name: result.name,
price: result.price,
_id: result._id,
request: {
type: "GET",
url: "http://localhost:3000/products/" + result._id
}
}
})
First let's try to understand why this happens ? ,
When you use .json() method inside this method it uses JSON.stringify() function what this function does is that take any JavaScript value and convert it to JSON string for example
let product = {
name : 'p1' ,
price : 200
}
console.log(JSON.stringify(product)); // {"name":"p1","price":200}
that's good , but you should know that if this function during the conversion see any undefined values it will be omitted ,
for example
let product = {
id:"23213214214214"
} // product doesn't have name property and its undefind
console.log(JSON.stringify({
id:product.id ,
name : product.name
})); // {"id":"23213214214214"}
if you look at the example above you'll see that because the value of name is **undefined ** you get a JSON output but only with the id value , and any value that is undefined will not be in the result .
that's the main reason why you get the result with only the product._id because other values like price and name are undefined .
so what to do to solve this problem ?
log the req.body and make sure that it has properties like name and price and I think this is the main reason of the problem because when you create the product variable it will only has the _id prop because other props are not exist .
after you create the product log it to the console and make sure that it has other properties like name and price
#mostafa yes I did tried.
{
"count": 3,
"products": [
{
"_id": "5b2e2d0cb70f5f0020e72cb6",
"request": {
"type": "GET",
"url": "https://aa-backend.herokuapp.com/products/5b2e2d0cb70f5f0020e72cb6"
}
},
{
"_id": "5b2e2d37b70f5f0020e72cb7",
"request": {
"type": "GET",
"url": "https://aa-backend.herokuapp.com/products/5b2e2d37b70f5f0020e72cb7"
}
},
{
this is the only output I am getting.

MongoDB Query Returns Empty Nested Object

I've got a 'conversations' collection in MongoDB which I'm querying from NodeJS to use the returned data to render the conversation's page.
The data has been stored in the database correctly as far as I can see, when I query it everything comes back as I'd expect, apart from a couple of nested objects - the two users that the conversation belongs to.
Here's what I get when I console.log a conversation (note the 'participants' field:
[ { _id: 57f96549cc4b1211abadf28e,
__v: 1,
messages: [ 57f96549cc4b1211abadf28d ],
participants: { user2: [Object], user1: [Object] } } ]
In Mongo shell the participants has the correct info - the id and username for both participants.
Here's the Schema:
var ConversationSchema = new mongoose.Schema({
participants: {
user1:
{
id: String,
username: String
},
user2:
{
id: String,
username: String
},
},
started: Number,
messages: [
{
type: mongoose.Schema.Types.ObjectId,
ref: "Message"
}
]
});
Here's the creation of the conversation document:
var conv = {
participants : {
"user1" : {
"id" : req.body.senderId,
"username" : req.body.senderName
},
"user2" : {
"id" : req.body.recipientId,
"username" : req.body.recipientName
}
},
created : Date.now(),
messages : [] // The message _id is pushed in later.
}
Conversation.create(conv, function(err, newConvo){
if(err){
console.log(err);
} else {
newConvo.messages.push(newMessage);
newConvo.save();
}
})
And lastly, in case it's useful, here's the query to Mongo:
// view all conversations a user belongs to
app.get('/messages', function(req, res){
Conversation.find({
$or : [
{"participants.user1.id" : req.user._id},
{"participants.user2.id" : req.user._id}
]
}, function(err, convos){
if(err){
console.log('Error getting Convos ' + err)
} else {
res.render('messages', {convos: convos, currentUser: req.user});
}
});
});
Thanks a lot for any help that!
It seems that everything is alright, the console.log just doesn't print nested objects by default. Try using:
console.log(JSON.stringify(conversation))
When logging a conversation in order to see the participants objects.
Fixed it!
Andresk's answer above was a big shove in the right direction. As he said, everything was OK, but I wasn't accessing the returned object in the correct way. It's obvious now, but I wasn't providing the index number for the 'convos' object.
I simply needed to do this, even though I was only getting one 'conversation' document back from MongoDB:
console.log(convos[0].participants.user1.username);

How to update document with dynamic fields in node.js

Assume that I have a document in database which has the following structure;
id: 1,
name: alex,
age: 21
There are some updates in client side and the same document with id:1 returns as;
id: 1,
name: alex,
age: 21,
location: usa
where a new field location is added.
I want to update the document in database. I want to add this new inserted field but I couldn't find out how can I handle this situation. Because location is not static, it can be surname, job and so on. So new inserted fields are dynamic and I don't know in advance.
If the field location was static, I could write a query as;
db.collection("user").updateOne( {id: 1},
{$set: {name: "alex", age: "21", location: "usa"}}, function(err, result){
if(err){}
else{}
});
But now, how can I update the document with this newly added fields?
If you are saying that you have an updated object:
id: 1,
name: alex,
age: 21,
location: usa
then I assume that you have some object that has a value of:
{
id: 1,
name: 'alex',
age: 21,
location: 'usa'
}
e.g. as a result of parsing this JSON sent from the client:
{
"id": 1,
"name": "alex",
"age": 21,
"location": "usa"
}
If you have that object in a variable called object then all you have to do is:
db.collection("user")
.updateOne({id: object.id}, {$set: object}, function (err, result) {
if (err) {
// handle success
} else {
// handle success
}
});
But most likely you will need to use _id instead of id:
db.collection("user")
.updateOne({_id: object._id}, {$set: object}, function (err, result) {
if (err) {
// handle success
} else {
// handle success
}
});
If you want to be able to remove properties as well then maybe even:
db.collection("user")
.replaceOne({_id: object._id}, object, function (err, result) {
if (err) {
// handle success
} else {
// handle success
}
});
I wouldn't recommend putting data into the database without at least some sort of validation but it's possible to do what you're asking about in the way shown above.
For more info see:
Update Specific Fields
Replace a Document
in the Introduction to MongoDB in Node.js.

Mongoose Validation Error occurs on OpenShift but not local version

I am migrating my Node.js server with Mongoose to OpenShift and an error occurs on the live server that I cannot reproduce on my local WebStorm built-in server.
I get the error message:
undefined: {
properties: {
message: "Cannot read property 'options' of undefined"
type: "cast"
}-
message: "Cannot read property 'options' of undefined"
name: "ValidatorError"
kind: "cast"
}
This occurs when I try to push an element into the items array and save, for the following schema:
var listSchema = new mongoose.Schema({
owner: {type: mongoose.Schema.Types.ObjectId, ref: 'User', required: true},
name: {type: String, required: true},
items: [
{
name:{
type: String,
required:true
},
quantity:Number,
check: Boolean
}
]
});
The local version that works, and the OpenShift version use the exact same code. The code that adds the new element is:
var listId = req.params["id"];
if (sessions.verifyToken(userId, token)) {
var data = req.body;
var query = List.findOne({
owner: userId,
"_id": listId
});
query.exec(function(err, list) {
...
//handle error and null (omitted for brevity)
...
list.items.push({ // error thrown here
name: req.body["name"],
quantity: req.body["quantity"],
check: false
});
list.save(function(err, list) {
if (err) {
var message = "Unable save appended list to persistent memory";
console.log(message, err);
res.setHeader("content-type", "application/json");
res.send(JSON.stringify({success: false, message: message, error: err}));
return;
}
res.setHeader("content-type", "application/json");
res.send(JSON.stringify(list));
});
});
I thought that maybe an earlier version of the schema had added a constraint, so I dropped the lists collection, but the problem did not go away.
What might be different on the OpenShift PaaS that could be causing the error?
[Edit]
Just for fun, I removed all required fields from items and now the error message is this:
"undefined": {
"properties": {
"message": "Cannot read property 'options' of undefined",
"type": "cast"
},
"message": "Cannot read property 'options' of undefined",
"name": "ValidatorError",
"kind": "cast"
},
"owner": {
"properties": {
"type": "required",
"message": "Path `{PATH}` is required.",
"path": "owner"
},
"message": "Path `owner` is required.",
"name": "ValidatorError",
"kind": "required",
"path": "owner"
}
This seems to suggest that the Model loses its owner field somewhere between finding the list and saving it again.
[/Edit]
On OpenShift, when you find or findOne a model that has a required reference to another entity, that field will not be automatically filled in. Thus, when save is called, the field will be missing. Change
var query = List.findOne({
owner: userId,
"_id": listId
});
to
var query = List.findOne({
owner: userId,
"_id": listId
}).populate("owner");
For some reason, this does not work the same in every environment. For some, either it does automatically populate the reference field, or it assumed it unchanged when saving. I'm not sure which.

ExpressJS and MongooseJS: can I query array values in a subdocument?

I've got a web api written using expressjs and mongoosejs.
The main schema in the app contains a subdocument, permissions, which contains array fields. Those arrays contain ids of users who can perform an action on that document.
I'm trying to limit results of a query on that collection by the values in the read subdocument array field:
Here's the main schema:
var MainSchema = new Schema({
uri: { type: String, required: false },
user: { type: String, required: false },
groups: [String],
tags: [String],
permissions: {
read: [String],
update: [String],
delete: [String]
}
});
I want to return documents that have a specific value in the permissions.read array or where that array is empty.
My code doesn't throw an error, but doesn't limit the results; I still get documents that don't match the given value, and aren't empty.
Here's what I've got:
var MainModel = mongoose.model('MainSchema', MainSchema);
app.get('/api/search', function (req, res) {
var query = MainModel.find({'uri': req.query.uri });
// This works fine
if (req.query.groups) {
query.where('groups').in(req.query.groups);
}
else if (req.query.user) {
query.where('user').equals(req.query.user);
}
// How can I return only documents that match req.query.user or ""?
query.where('permissions.read').in([req.query.user, ""]);
query.exec(function (err, results) {
if (!err) {
return res.send(results);
} else {
return console.log(err);
}
});
});
I have a hunch that the where clause is not testing the value of each of the elements of permissions.read against each of the values of the array passed to the in clause.
Thanks.
EDIT: Here's a document that shouldn't be returned, but is (note, permissions.read array includes a value that's not the current user's ID):
{
"user": "firstname#gmail.com",
"uri": "http://localhost:3000/documents/test",
"permissions": {
"delete": [
"firstname#gmail.com"
],
"update": [
"firstname#gmail.com"
],
"read": [
"firstname#gmail.com"
]
},
"tags": [],
"groups": [
"demo",
"2013"
]
}
EDITED: Corrected Model/Schema confusion, which wasn't in my code, but was left out in the copy/paste. Thanks.
I have looked for and not found an example of a mongodb/mongoose query that tests either for a specified value in a subdocument array field or an empty array field.
Instead, since I control both the API (which this code comes from) and the client application, I refactored.
I now add one of three where clauses to the query, based on client-side user selection:
if (req.query.mode === 'user') {
query.where('user').equals(req.query.user);
}
else if (req.query.mode === 'group') {
query.where('groups').in(req.query.groups);
query.$where('this.permissions.read.length === 0');
}
else if (req.query.mode === 'class') {
query.$where('this.permissions.read.length === 0');
}
Thanks for your input, #JohnnyHK.

Resources