Shopify. Problems updating Metafields with shopify-node-api - node.js

I'm having difficulty in updating an existing metafield with Shopify API. Each time I receive an error, advising me that the variant already exists... so it must be thinking I'm trying to create a new one (and not update).
I thought this may be an issue with 'put' and 'post' - so changed my method to put, however the error persists. I've hardwired in all my variables to make it easier to test.
I'm working with Cloudinary. I'm using https://github.com/sinechris/shopify-node-api with Express.js
app.post('/upload', function(req, res){
// upload page... assume we have uploaded our image - but have hard-wired a local file in for now
cloudinary.uploader.upload('/Users/Rob/Pictures/testimg.jpg', function(savedImg) {
var imageURL = savedImg.url;
console.log(imageURL)
},
{
public_id: "testimg"
});
// the saved image is returned - so we add it to our updateMetafieldData json object
var updateMetafieldData = {
"variant": {
"id": '253818949',
"metafields": [
{
"key": "variant_image_0",
"value": 'testimg', // whatever the public id of our image is.
"value_type": "string",
"namespace": "variant_image"
}
]
}
}
// and post the result to shopify - then redirect to /getvariants
Shopify.put('/admin/variants/253818949.json', updateMetafieldData, function(data){
// res.redirect('/getvariants')
});
});

I actually created Shopify Node API and was just now happened upon this months later but thought I'd answer for anyone else coming along.
Take a look at the shopify API here: https://docs.shopify.com/api/metafield#update
You can update the metafield directly by performing a PUT request against the metafield resource instead of the variant like so:
/admin/metafields/#{id}.json
You would of course need to know the ID of the metafield first so that would require a call to the variant first or you could simply store the id in your local database for reference.

Related

MongoDB not saving value, call responds fine on postman, no errors

I have a NodeJS backend call with MongoDB. This particular api call was working fine for a long time, and when testing out something on the frontend, I realized the call was not going through. I've checked the routes, controller, index files, and tested the call through Postman which works fine, no errors, and even returns an ObjectId (which means it must be interacting with the database, right?). However, when I search the mongo shell nothing comes back, which tells me it is not saving. I cannot find anything wrong and get no errors anywhere along the way. I checked on MongoDB Atlas, the collection only has 4kb of data so it is not that it is 'too full' and have tested all the other api calls (get, patch, delete) which work fine and have no issues like this in my other collections.
Even weirder, during the save call I push the ID to 2 other collection's documents as a ref. The Mongo shell does show that the new Id is populating to the other documents, but yet it is not saving the actual document... which should be happening prior to the push and needs to happen in order to get the ObjectId
Below is the controller for adding a new Visit document, the route for it, and the response from postman. I really have no idea of how else to determine what is taking place.
Controller
exports.addVisit = async (req, res) => {
const hoursValue = getTotalHours(req.body.visitStart, req.body.visitEnd)
try {
const visit = new Visit({
totalHours: hoursValue,
user: req.body.user,
client: req.body.client,
visitStart: req.body.visitStart,
visitEnd: req.body.visitEnd,
location: req.body.location
});
const user = await User.findById(req.body.user);
const client = await Client.findById(req.body.client);
visit.user = user._id
visit.client = client._id
visit.save();
user.visits.push(visit._id);
user.save();
client.visits.push(visit._id);
client.save();
console.log(visit)
console.log(user)
console.log(client)
res.status(201).send(visit);
} catch (error) {
res.status(400)
res.send({ error: "Error adding visit", error})
}
}
Route
router.route("/visits").post(addVisit)
Postman call to: http://localhost:5000/visitapi/visits
{
"client": "6205a8313fe12d6b4ec354c4",
"location": "Home",
"user": "62410a1dcaac9a3d0528de7a",
"visitStart": "2022-10-12T17:00:00.000Z",
"visitEnd": "2022-10-12T19:00:11.000Z"
}
Postman response
{
"client": "6205a8313fe12d6b4ec354c4",
"user": "62410a1dcaac9a3d0528de7a",
"location": "Home",
"visitStart": "2022-10-12T17:00:00.000Z",
"visitEnd": "2022-10-12T19:00:11.000Z",
"totalHours": 2,
"goals": [],
"id": "635302bb48e85ff6ad17ee59"
}
NodeJs console logging the same new document with no errors:
{
client: new ObjectId("6205a8313fe12d6b4ec354c4"),
user: new ObjectId("62410a1dcaac9a3d0528de7a"),
location: 'Home',
visitStart: 2022-10-12T17:00:00.000Z,
visitEnd: 2022-10-12T19:00:11.000Z,
totalHours: 2,
_id: new ObjectId("635302bb48e85ff6ad17ee59"),
goals: []
}
MongoShell showing the Client collection document stored the new Visit document Id:
visits: [
ObjectId("6257158d157e807e51c7e009"),
ObjectId("62fd852a252b83f4bc8f9782"),
ObjectId("63056cee252b83f4bc8f97e9"),
ObjectId("634ee01ec582da494032c73e"),
ObjectId("634ee09cc582da494032c7aa"),
ObjectId("634ee3d6ddbe3f7e6641d69e"),
ObjectId("634efcf1ddbe3f7e6641d6f9"),
ObjectId("634efdd3ddbe3f7e6641d71b"),
ObjectId("635029937da8972360d907c1"),
ObjectId("6350a0e37da8972360d9084f"),
ObjectId("635302bb48e85ff6ad17ee59") //_id matches the same returned by Postman/ NodeJS
],
Again, everything goes through with no errors on postman or the front or back end, and the backend even logs the returns the new document but no new document is being saved to the DB unless I manually pass it in the Mongosh shell, and only for this one collection. Totally lost, I'd appreciate any guidance on how to explore/ resolve this. Thanks
Edited to include the solution based on the discussion in comment
The problem could be in the mongoose schema. If the property names do not match, mongoose will simply ignore the mismatched properties.
Previous answer
Those mongodb calls are expected to be async. You might want to add await to all those.
visit.user = user._id
visit.client = client._id
await visit.save(); // <- this
user.visits.push(visit._id);
await user.save(); // <- this
client.visits.push(visit._id);
await client.save(); // <- and this

Validate Request Body variables through Swagger and Express

I am currently using Swagger (2.0) on Node.js. My YAML file validates correctly and the requests are going through. My particular call has a request body using the following format -
{
"postalCode": "invalidpostalcode",
"phone": "invalidnumber",
"website": "invalidwebsite"
}
I would like to validate each body variable before continuing to the next piece of code. To accomplish validating the data I would like to use validator.js.
Now my basic validation code looks something like that -
if(!validator.isURL(req.body.website)){
var tmp = {
"message": req.__('error.url', {
field: req.body.website
})
}
res.status(400).json(tmp);
return;
}
if(!validator.isPostalCode(req.body.postalCode,'any')){
var tmp = {
"message": req.__('error.postalCode', {
field: req.body.postalCode
})
}
res.status(400).json(tmp);
return;
}
Is there there any better way to validate data sent in through the API that would require less code? For example by using a function to control the majority of the logic?
while this is one example of the endpoint I will have some in the future that will require upwards of 20 body parameters.
I have tried create a function that will return the response code. However, that returns an error Cannot set headers after they are sent to the client.

loopback remote method return variable other than request data

I have a generic SendMail route which I want to create multiple remote methods to handle multiple request templates. Any ideas on how to return a Email_Type from the remote method back to the base route. I know I could add a default with a code in it, but would like a more elegant solution.
Mail.genericSendMail = function genericEmail(response, callback) {
console.log(response);
let templateId=0;
//PROBLEM: HOW TO KNOW WHICH REMOTE WAS USED
switch (response.emailType) {
case "Template-1":
templateId= 1234;
break;
case "Template-2":
tempalteId = 456;
break;
default:
templateId = 789l
} //switch
console.log(templateId);
};
//Want multiple routes like this to support various templates
Mail.remoteMethod("genericEmail", {
http: {
path: "/emailTemplate1",
verb: "POST"
},
accepts [
{arg: "request", type:"object",http: {source:"body"},
default: {firstName:"", lastName:"",emailAddress:""}
}],
returns: RESTResponseStatic.loopbackAdapterCommonRestResponseDefinition()
});
//Want multiple routes like this to support various templates
Mail.remoteMethod("genericEmail", {
http: {
path: "/emailTemplate2",
verb: "POST"
},
accepts [
{arg: "request", type:"object",http: {source:"body"},
default: {emailAddress:"", promoCode:""}
}],
returns: RESTResponseStatic.loopbackAdapterCommonRestResponseDefinition()
});
There are a couple of different ways to do this. Since it happens to be a POST request, I usually go with attaching data to the body using a before remote hook.
Let's say you have a model method for logging in users.
Say we have a multi realm platform, so we need to know what platform we are logging in. If you don't use realms or don't know what they are, don't worry. This just shows you how to populate the data to the model method.
User.login = function(data, cb) {
if (data.realm == 'platform1) {
return logUserIntoPlatform1(data, cb);
}
return logUserIntoDefaultPlatform(data, cb);
}
Now let's say you don't want the client/frontend to send the realm and you don't want to do the lookup for realm in the model. We can add a beforeRemote hook like so:
User.beforeRemote('login', function (context, user, next) {
context.args.data.realm = lookUpRealmSync(context); // 1
next();
});
This will be called before the login method. Note the next() call: this is how you could do error detection before actually hitting the model method. Something like next({ status: 422, message: 'Parameter missing: password }); would return an error and not execute the User.login method.
You may have to look carefully at your context object (i.e. the line marked with 1 may not work exactly as I've shown for you).
If you want to read more about this stuff, I LoopBack's docs are pretty good. It seems they've been updated since I've last used them so I can't link you to the more useful pages. I found the remote method documentation here though.
Edit: I took a closer look at your question. You should be able to retrieve the path from the context object and pass data accordingly. I'm not going to try to code that since I don't know where it would actually be within the object.

Unable to make PUT request to Cloudant-bluemix database using Nodejs-express

I'm trying to implement a Password reset. So I'm taking the phone number of the user, getting the document from the database using the phone number to find it, and I'm taking the new password and trying to update the corresponding document using a PUT request in my Cloudant database.
app.post('/pass_rst', function(req,response){
var log='';
//log is just for me to see what's happening
var phone= req.body.phone;
log+=phone+'\n';
db.find({selector:{'phone':phone}}, function(err, result){
if(err){
throw err;
}
if(result.docs.length==0){
response.send('User doesnt exist');
}else{
var existing_data=result.docs[0];
log+=JSON.stringify(existing_data)+'\n';
var upd_pswd= req.body.new_password;
log+=upd_pswd+'\n';
var new_data=existing_data;
new_data.password=upd_pswd;
log+=JSON.stringify(new_data)+'\n';
var id= result.docs[0]._id;
log+=id+'\n';
//make PUT request to db
var options={
host:dbCredentials.host,
port:dbCredentials.port,
path:'/'+dbCredentials.dbName+'/'+id,
//url: dbCredentials.url+'/'+dbCredentials.dbName+'/'+id,
method:'PUT',
json:new_data,
headers:{
'Content-Type':'application/json',
'accept':'*/*'
}
};
log+=JSON.stringify(options)+'\n';
var httpreq= http.request(options);
//log+=JSON.stringify(httpreq);
httpreq.on('error', function(e){
response.send('Error'+e.message);
});
response.send(log+'\n\n\nUpdated');
}
});
});
dbCredentials is defined above as follows:
dbCredentials.host = vcapServices.cloudantNoSQLDB[0].credentials.host;
dbCredentials.port = vcapServices.cloudantNoSQLDB[0].credentials.port;
dbCredentials.user = vcapServices.cloudantNoSQLDB[0].credentials.username;
dbCredentials.password = vcapServices.cloudantNoSQLDB[0].credentials.password;
dbCredentials.url = vcapServices.cloudantNoSQLDB[0].credentials.url;
I've tried tinkering around with it, but in the best case scenario, I don't get an error and I see "Updated" but nothing actually happens in the database. Sometimes I get an error saying : 502 Bad Gateway: Registered endpoint failed to handle the request.
If you see what's going wrong, please let me know. Thank you.
This is the documentation on how to update documents in cloudant
UPDATE
Updating a document
PUT /$DATABASE/$DOCUMENT_ID HTTP/1.1 { "_id": "apple", "_rev":
"1-2902191555", "item": "Malus domestica", "prices": {
"Fresh Mart": 1.59,
"Price Max": 5.99,
"Apples Express": 0.79,
"Gentlefop's Shackmart": 0.49 } }
To update (or create) a document, make a PUT request with the updated
JSON content and the latest _rev value (not needed for creating new
documents) to https://$USERNAME.cloudant.com/$DATABASE/$DOCUMENT_ID.
If you fail to provide the latest _rev, Cloudant responds with a 409
error. This error prevents you overwriting data changed by other
processes. If the write quorum cannot be met, a 202 response is
returned.
Example response: { "ok":true, "id":"apple",
"rev":"2-9176459034" }
The response contains the ID and the new revision of the document or
an error message in case the update failed.
I am using bluemix -nodejs and cloudant. The best way that worked for me to do the update is to use the nano package for db interaction from node.js.
You can refer to the post here:
The summary is - By making use of nano api, you can easily update the record. You need to make sure to use - the _id and the right _rev number, while you use nano. This inturn uses PUT Method underneath.
Updating and Deleting documents using NodeJS on Cloudant DB
When you are including nano, make sure to update the package.json to have the nano dependency added. Let me know if you have further questions on the update/delete
When using the cloudant node.js module there is no separate update function.
You need to use the db.insert function also for the update with the right doc revision, so you need to read the latest revision before the insert.
https://github.com/apache/couchdb-nano#document-functions
"and also used to update an existing document, by including the _rev token in the document being saved:"
// read existing document from db
db.get(key, function(error, existing) {
if (!error)
// use revision of existing doc for new doc to update
obj._rev = existing._rev;
// call db insert
db.insert(obj, key, cb);
});

How to pull attachments from Google Glass item?

I created NodeJS server that communicate with Google Glass, I want to know how to pull attachment from item, below you can see the item with attachments:
Note: in my project I already have:
*Send item item to glass(contact, card, location, etc..)
*Subscription to timeline collection
*Contact with callback to let Glass user share content - for more info Visit How to add another option to the share functionality of Google Glass?
Do I need to use the selfLink to pull the attachment? if yes then how I can execute HTTP request for the selfLink while including the token?
The selfLink refers to the URL of the timelineItem itself. You want to look at the attachments attribute of the object. It might look something like this:
{ "kind": "mirror#timelineItem",
"id": "da61598c-2890-4852-2123-031011dfa004",
...
"attachments": [
"id": ...
"contentType": "image/jpeg".
"contentUrl": "https://www.googleapis.com/mirror/v1/timeline/da61598c-2890-4852-2123-031011dfa004/attachments/ps:605507433604363824",
"isProcessingContent": false
]
}
You should check to make sure isProcessingContent is false before you try to fetch it, otherwise the fetch will fail. This is usually pretty quick for images, but can take longer for video.
See more at https://developers.google.com/glass/v1/reference/timeline/attachments
To fetch it, you can issue an HTTPS request to that URL with an Authorization header with a value of Bearer auth_token (replacing auth_token with the actual value of the auth token).
To make the request itself, you'll probably want to use the http.request() method. So something like this (untested) might work:
var item = {the item you got sent above};
var attachment = item.attachments[0];
if( !attachment.isProcessingContent ){
var contentUrl = url.parse( attachment.contentUrl );
var options = {
"hostname": contentUrl.hostname,
"path": contentUrl.path,
"headers": {
"Authorization": 'Bearer '+authToken;
}
}
https.request( options, function(res){
// Get the image from the res object
});
}
See the documentation for URL.parse and HTTPS.request for details.

Resources