How to chain functions in Parse CloudCode? - javascript

I've done a parse job that checks every "X" time if "emailSent" is false, for each user. If it is, I call a function to send a email and change the "emailSent" to true. That works.
My problem is with the function "getMaxId". I need to return the maxid value to change each user "id_client" column, but I don't know how. I've tried this but it doesn't work. This is writing nothing: "console.log("Write somethingggg"); "
Here is the code...
Parse.Cloud.job("test", function(request, status) {
// Set up to modify user data
Parse.Cloud.useMasterKey();
var texto = "New verified emails:\n\t";
// Query for all users
var query = new Parse.Query(Parse.User);
//query.equalTo("emailVerified", true);
query.equalTo("emailSent", false);
query.each(function(user) {
user.set("emailSent", true);
user.save();
var datos = user.get("email")+"\n";
texto=texto+datos;
Parse.Cloud.run("getMaxId", {},{
success: function(results) {
console.log("Write somethingggg");
user.set("id_client", "gofoadasda");
user.save();
var datos = user.get("id_client")+"\n";
//console.log("id_client: "+datos);
response.success();
},
error: function(results, error) {
response.error(errorMessageMaker("running chained function",error));
}
}).then(function() {
// Set the job's success status
}, function(error) {
// Set the job's error status
status.error("Uh oh, something went wrong.");
});
Parse.Cloud.run("sendEmail",{
success: function(results) {
response.success(results);
},
error: function(results, error) {
response.error(errorMessageMaker("running chained function",error));
}
});
}).then(function() {
// Set the job's success status
console.log("texto: "+texto);
status.success("Migration completed successfully.");
}, function(error) {
// Set the job's error status
status.error("Uh oh, something went wrong.");
});
});
Parse.Cloud.define("sendEmail", function(request, response) {
Parse.Cloud.httpRequest({
url: 'http://www.example.com/sendemail.php',
params: {
email : 'email#email.com'
},
success: function(httpResponse) {
console.log(httpResponse.text);
},
error: function(httpResponse) {
console.error('Request failed with response code ' + httpResponse.status);
}
});
});
Parse.Cloud.define("getMaxId", function(request,response) {
var query = new Parse.Query(Parse.User);
query.descending("id_client");
query.find({
success: function(results) {
var idmax=results[0].get("id_client")
console.log("idmax: "+idmax);
response.success(idmax);
},
error: function() {
response.error(" is an error");
}
});
});
FIRST CHANGES:
After #danh help, I tried to do what I need, changing some code:
Important: id_client is a int value which it's unique for each user, it starts at 20000.
get all the users with the flag sentEmail=false.
For each of those users, getMaxId (this returns the actual max "id_client" value for all the users).
Change value of sentEmail to true, set user id_client to the actual max id.
Send email.
New code (sendEmail has no changes):
var _ = require('underscore');
// return a promise to get the max value of id_client in the user table
function getMaxId(user) {
var query = new Parse.Query(Parse.User);
//return query.count();
query.descending("id_client");
query.limit(1);
return query.find().then(function(users) {
if(users[0].get("id_client")<20000){ //No users yet.
user.set("id_client", 20000); //First id:20000
user.save();
return 20000;
}
else{ //There are users. Get the maxId and increment +1.
user.set("id_client", users[0].get("id_client")+1);
user.save();
return (users.length)? users[0].get("id_client")+1 : 0;
}
});
}
// return a promise for users with emailSent flag == false
function usersWithUnsentEmail() {
var query = new Parse.Query(Parse.User);
query.equalTo("emailSent", false);
return query.find();
}
// return a promise to send email to the given user, and to set its
// emailSent flag = true
function sendEmailToUser(user) {
return sendEmail(user.get("email")).then(function() {
user.set("emailSent", true);
return user.save();
});
}
Parse.Cloud.job("test", function(request, response) {
// Set up to modify user data
Parse.Cloud.useMasterKey();
usersWithUnsentEmail().then(function (users){
var emailPromises = _.map(users, function(user) {
//what I understand is that here, for each user, we call getMaxId, getting the actual max id_client, and then, we pass it to "sendEmailToUser".
return getMaxId(user).then(function(max){
return sendEmailToUser(user);
});
});
return Parse.Promise.when(emailPromises);//This means that we have looped all users, is it?
}).then(function(results) {
response.success(results);
}, function(error) {
response.error(error);
});
});
I've tested this with 2 users with the flag "sentEmail" = false and actual max id_client was 20001
Result:
sentEmail flags changed correctly.
2 emails sent correctly.
Error here: id_client for both users changed to 20002. It has to be 20002 and 20003.
Logs in parse:
I2015-04-22T09:44:13.433Z] v90: Ran job test with:
Input: {}
Result: undefined
E2015-04-22T09:44:29.005Z] v90: Ran job test with:
Input: {}
Failed with: Error: Job status message must be a string
at updateJobMessageAndReturn (<anonymous>:790:7)
at Object.success (<anonymous>:828:9)
at main.js:217:18
at e (Parse.js:3:8736)
at Parse.js:3:8185
at Array.forEach (native)
at Object.x.each.x.forEach [as _arrayEach] (Parse.js:1:661)
at c.extend.resolve (Parse.js:3:8136)
at Parse.js:3:8815
at e (Parse.js:3:8736)
EDITED:
We need their email and the id_client that we will assign them.
May be I haven't explained well, the email won't be sent to the user email, the email will be sent to a email that I've determined in the sendemail.php script, and it will be always the same.
I'll explain: You have a local database at home, and parse database. When this Parse.job is called, it will send an email to you (email of php) with a list of the email and the id_client of each user updated. Now you can manually update your local database with the email received info.
So, for this reason, it will be better to send only one email, at the end of all the updates. (I didn't say that because I had a lot of problems yet trying to understand how cloudCode works...)

There are a few things that need fixing in the code: (1) as a rule, use promises if you're doing more than two consecutive asynchronous things, (2) don't call Parse.Cloud.run from cloud code, it's what you call from clients who wish to invoke cloud functions, (3) style-wise, you'll go nuts trying to figure it out later on unless you break the code into small, promise-returning steps.
I've applied all three bits of advice to your code. I don't fully understand the logic as described in code and text, but hopefully I got close enough for you to make sense of it.
// using underscore js, which provides _.map below as well as loads of other useful stuff
var _ = require('underscore');
// return a promise to get the max value of id_client in the user table
function getMaxId() {
var query = new Parse.Query(Parse.User);
query.descending("id_client");
query.limit(1);
return query.find().then(function(users) {
return (users.length)? users[0].get("id_client") : 0;
});
}
// return a promise for users with emailSent flag == false
function usersWithUnsentEmail() {
var query = new Parse.Query(Parse.User);
query.equalTo("emailSent", false);
return query.find();
}
// return a promise to send email to the given user, and to set its
// emailSent flag = true, and to set its clientId to the passed value
function sendEmailToUser(user, idClient) {
return sendEmail(user.get("email")).then(function() {
user.set("emailSent", true);
user.set("id_client", idClient);
return user.save();
});
}
// return a promise to send email to the given email address via an http service
function sendEmail(email) {
var params = {url: 'http://www.example.com/sendemail.php', params: {email : email} };
return Parse.Cloud.httpRequest(params);
}
Parse.Cloud.job("test", function(request, response) {
// Set up to modify user data
Parse.Cloud.useMasterKey();
var maxIdClient;
getMaxId().then(function(result) {
maxIdClient = result;
return usersWithUnsentEmail();
}).then(function(users) {
var emailPromises = _.map(users, function(user) {
return sendEmailToUser(user, maxIdClient);
});
return Parse.Promise.when(emailPromises);
}).then(function(results) {
response.success(results);
}, function(error) {
response.error(error);
});
});
EDIT - we're kind of working on logic here particular to the app, as opposed to the concept of promises, but here goes anyway. To restate the functional requirement: We want a job to find users who have not yet been recorded in another database, represented by a flag called "emailSent". Our goal is to assign these users a unique id, and send their info (for now, we'll say email address and that id) via email to some fixed destination.
So
// getMaxId() from above is almost ready, except the minimum id_client
// value is 20000, so change the line that set this to:
return (users.length)? users[0].get("id_client") : 20000;
// usersWithUnsentEmail() from above is fine
// delete sendEmailToUser() since we're not doing that
// change sendEmail() to take an array of users to be conveyed to
// the other database. Send email about them, then change each user's
// emailSent status and save them
function sendEmail(users) {
var params = {url: 'http://www.example.com/sendemail.php', params: {users : JSON.stringify(users)} };
return Parse.Cloud.httpRequest(params).then(function() {
_.each(users, function(user) {user.set("emailSent", true);});
return Parse.Object.saveAll(users);
});
}
// add a function that takes an array of users, updates their
// id_client to be a new, unique value, and sends mail about them
// to a remote server
function synchUsers(users, idMax) {
_.each(users, function(user) {
user.set("id_client", idMax);
idMax += 1;
});
return sendEmail(users);
}
// update the job taking all this into account
Parse.Cloud.job("test", function(request, response) {
// Set up to modify user data
Parse.Cloud.useMasterKey();
var maxIdClient;
getMaxId().then(function(result) {
maxIdClient = result;
return usersWithUnsentEmail();
}).then(function(users) {
return synchUsers(users, maxIdClient+1);
}).then(function(results) {
response.success(results);
}, function(error) {
response.error(error);
});
});

Related

Cloud function returning nil value

I had a freelancer do some work in cloud code however I can no longer contact them due to an argument that occurred. I do not know javascript nor am I familiar with Parse cloud code and I was hoping someone could shed light on whether or not I am calling this function correctly considering it returns as if its parameter was equal to nil although I do believe I am giving it a value. Below is the javascript cloud code function as well as my swift code where I am calling it. For instance it is returning the value (-5).
Parse.Cloud.define("AddFriendRequest", function (request, response) {
var FriendRequest = Parse.Object.extend("FriendsIncoming");
var FRequest = new FriendRequest();
var user = request.user;
var query = new Parse.Query(Parse.User);
query.equalTo("username", request.params.username);
query.find({
success: function (people) {
if(people.length == 0)
{
response.success(-5);
return;
}
var person = people[0];
FRequest.set("OwnerID", user.id);
FRequest.set("TargetFriend", person.id);
FRequest.set("Status", 0);
var query = new Parse.Query("FriendsIncoming");
query.equalTo("OwnerID", user.id);
query.equalTo("TargetFriendID", person.id);
query.find({
success: function (results) {
if (results.length > 0) {
response.success(1);
return;
}
FRequest.save(null, {
success: function (Friend) {
response.success(2);
},
error: function (Friend, error) {
response.error(3);
}
});
response.error(-2);
},
error: function () {
response.error(-1);
}
});
}
,
error: function (Friend, error) {
response.error(-4);
}
});
});
func textFieldShouldReturn(textField: UITextField) -> Bool {
if textField == NewRequest {
textField.resignFirstResponder()
print(NewRequest)
var name : NSString
name = NewRequest.text!
print(name)
//let parameters : [NSObject : AnyObject]
let params = ["TargetFriendID" : name]
PFCloud.callFunctionInBackground("AddFriendRequest", withParameters: params) { results, error in
if error != nil {
//Your error handling here
} else {
print(results)
}
}
return false
}
return true
}
The parameter from the client is named "TargetFriendID", but the cloud function runs the query on request.params.username.
Either rename the parameter in swift to username, or rename the parameter in the cloud to request.params.TargetFriendID.

Send bulk email using Parse.com Cloud Code

In the app I'm creating a user will be able to send a description of an object to a number of recipients (from 1 to 200). Using Parse Cloud Code I'll have to use a promise to wait for the email server response for every email (Mailgun).
Is there any other way where I stock all those created emails in some kind of array and send them in 1 shot to the email server? Otherwise I hit the max of 15 seconds a function can run.
Right now I use this:
Parse.Cloud.define("emailobject", function(request, response) {
// ... some company info parameters
var theTraders = request.params.traders;
var objectQ = new Parse.Query("objects");
objectQ.equalTo("objectId", theObjectId);
objectQ.first({
success:function(theObject) {
// ... more code
searchObjectPictures(theObject, {
success:function(pictureObjects) {
for (var a = 0; a < theTraders.length; a++) {
// create the parameters to create the email
var mailingParameters = {};
// ... create the parameters
// when the email html has been compiled
mailgun.sendWelcomeEmail({
to: traderEmail,
from: toString,
subject: subjectString,
html: mailing.getMailingHTML(mailingParameters)
}, {
success:function(httpResponse) {
console.log(httpResponse);
},
error:function(httpResponse) {
console.log(httpResponse);
}
});
}
// emailedObjectsToSave is an array of analytics objects
Parse.Object.saveAll(emailedObjectsToSave, {
success:function(list) {
response.success(true);
},
error:function(error) {
response.error(error);
}
});
},
error:function(error) {
response.error(error);
}
});
},
error:function(error){
response.error(error);
}
});
});
I know promises would be better for nested queries, but I'm still wrapping my head around this.
Thank you
After a lot of searching, trial and error, I have finally found the solution. As #danh said it's better to add these requests to a job queue. The objects are now saved to a "notifications" class that has a bool "notified" to check if this object has already been sent or not.
I'll post the code here for people who might also search for it.
First you add your objects to the new "notifications" class (queue).
Parse.Cloud.define("addToNotificationsQueue", function(request, response) {
var roleName = request.params.role;
var objects = request.params.objects;
var newEmailObjectsToSave = [];
var EmailedObjectClass = Parse.Object.extend("notifications");
var emailObjectACL = new Parse.ACL();
emailObjectACL.setPublicReadAccess(false);
emailObjectACL.setRoleReadAccess(roleName, true);
emailObjectACL.setRoleWriteAccess(roleName, true);
for (var i = 0; i < objects.length; i++) {
// define the parameters for the new objects based on the request params
var emailObject = new EmailedObjectClass();
// set all the details to your new emailObject
emailObject.setACL(emailObjectACL);
newEmailObjectsToSave.push(emailObject);
}
Parse.Object.saveAll(newEmailObjectsToSave, {
success:function(list) {
response.success(true);
},
error:function(error) {
response.error(error);
}
});
});
Then define your job. I run it every 15 minutes for now to test. When the mailserver starts acting faster, I'll set the limit and frequency higher.
Parse.Cloud.job("sendNotifications", function(request, status) {
Parse.Cloud.useMasterKey();
var numberOfNotificationsHandled;
var query = new Parse.Query("notifications");
query.limit(10); // limit because max 15 minutes, slow email server...
query.notEqualTo("notified", true);
query.find().then(function(notifications) {
numberOfNotificationsHandled = notifications.length;
var promise = Parse.Promise.as();
_.each(notifications, function(notification) {
var parameterDict = {};
// add all parameters the emailObject function needs
promise = promise.then(function(){
return Parse.Cloud.run("emailObject", parameterDict).then(function(result) {
notification.set("notified", true);
notification.save();
}, function(error) {
notification.set("notified", false);
notification.save();
});
});
});
return promise;
}).then(function() {
console.log("JOB COMPLETED");
status.success("Sent " + numberOfNotificationsHandled + " emails");
}, function(error) {
console.log("JOB ERROR " + error);
status.error("Job error " + error);
});
});
The emailObject method is just an httpRequest like any other. This method waits for the answers of those httpRequests.

Parse [Error]: success/error was not called (Code: 141, Version: 1.9.0)

I am trying to write a Cloud Code function that will allow me to edit the data of another user as I cannot do that in the application it self. What the code does (I should say tries to do as I don't know JS) is fetch a User object and a Group (a class I created) object using two separate queries based on the two object IDs inputed. Here is my code
Parse.Cloud.define("addInvite", function(request, response) {
Parse.Cloud.useMasterKey();
var userID = request.params.user;
var groupID = request.params.group;
var user;
var group;
var userQuery = new Parse.Query(Parse.User);
userQuery.equalTo("objectId", userID);
return userQuery.first
({
success: function(userRetrieved)
{
user = userRetrieved;
},
error: function(error)
{
response.error(error.message);
}
});
var groupObject = Parse.Object.extend("Group");
var groupQuery = new Parse.Query(groupObject);
groupQuery.equalTo("objectId", groupID);
return groupQuery.first
({
success: function(groupRetrieved)
{
group = groupRetrieved;
},
error: function(error)
{
response.error(error.message);
}
});
var relations = user.relation("invited");
relations.add(group);
user.save();
response.success();
});
Every time I execute the method I get the error:
[Error]: success/error was not called (Code: 141, Version: 1.9.0)
Can anyone help with this? Thanks.
Every function in Parse Cloud returns a Promise. This also includes any query functions which you run to retrieve some data. Clearly in your code you are returning a Promise when you execute a query which abruptly ends your cloud function when your query completes. As you do not call a response.success() or response.error() in any of the success blocks, your cloud function returns without setting a suitable response, something that Parse requires and hence the error. Your code needs to chain all the promises to ensure your code is executed correctly and return success/error in the last step:
Parse.Cloud.define("addInvite", function(request, response) {
Parse.Cloud.useMasterKey();
var userID = request.params.user;
var groupID = request.params.group;
var user;
var group;
var userQuery = new Parse.Query(Parse.User);
userQuery.equalTo("objectId", userID);
userQuery.first().then(function(userRetrieved) {
user = userRetrieved;
var groupObject = Parse.Object.extend("Group");
var groupQuery = new Parse.Query(groupObject);
groupQuery.equalTo("objectId", groupID);
return groupQuery.first();
}).then( function(groupRetrieved) {
//group = groupRetrieved;
var relations = user.relation("invited");
relations.add(groupRetrieved);
return user.save();
}).then(function() {
response.success();
}, function(error) {
response.error(error.message);
});
});

Meteor: Async Callback issues

Currently I have the user click submit and then a click event occurs where a token is created and a method is called. What I am trying to do is after the charge get a callback which says if it is successfully or not. If successful it will run router.go to the confirmation page. If it is not successful then it will let the user know the card has been declined. All the above I can code out except despite non stop tinkering, I can't seem to figure out how to pass the message back to the event.
Here is my server side method:
Meteor.methods({
'chargeCard': function(token,amount,email) {
var Stripe = StripeAPI('where the key info guys');
// get a sync version of our API async func
var stripeCustomersCreateSync=Meteor.wrapAsync(Stripe.customers.create,Stripe.customers);
// call the sync version of our API func with the parameters from the method call
var result=stripeCustomersCreateSync({
description: 'Woot! A new customer!',
card: token,
email: email
}, function(error,result) {
if(error) {
return error;
}
return 'Success';
});
return result;
}
});
and my Client side method:
Stripe.card.createToken({
number: $('#cc-number').val(),
cvc: $('#card-cvc').val(),
exp_month: expM,
exp_year: expY,
name: $('#fn').val(),
address_zip: $('#postCode').val()
}, stripeResponseHandler);
}
function stripeResponseHandler(status, response) {
var $form = $('form');
if (response.error) {
// Show the errors on the form
$form.find('.validation').text(response.error.message);
return false;
} else {
var token = response.id;
var amount = 15000;
var Payid = $('#pid').text();
var userEmail = Leaguemembers.findOne({_id: Payid}).Email;
Meteor.call('chargeCard', token, amount,userEmail, function (error, result) {
console.log(error,result); alert(result); alert(error);
}
);
}
};
Any help would be greatly appreciated.
EDIT:
I went back into the backend and I can see the errors being generated through console.log but still am unable to pass it back to where the call was made to display those errors to the user or pass them to the confirmation page. All I seem to get is undefined.
The meteor.call should look like this
Meteor.call('chargeCard',token,amount,username,function(err,result){
if(!err){
Router.go("theRoute") //if there is not error go to the route
}else{
console.log(err.reason) // show the error
}
})

Parse cloudcode beforeSave obtain pre-updated object

In a beforeSave hook I want to obtain the state of the object prior to the update. In this particular case it is to stop a user from changing their choice once they have made it. Pseudo-code looks something like:
If (user has already voted) {
deny;
} else {
accept;
}
And the code that I have so far is:
Parse.Cloud.beforeSave('votes', function(request, response) {
if (!request.object.isNew()) {
// This is an update. See if the user already voted
if (request.object.get('choice') !== null) {
response.error('Not allowed to change your choice once submitted');
}
}
response.success();
}
But request.object is the state of the object with the update already applied.
Note that the 'votes' object is created separately so this isn't allowing an insert but not an update will not suffice; I need to know if a given field is already set in the database.
While Krodmannix's response is correct (and was helpful to me) it has the overhead of a full query. If you are doing things in beforeSave, you really want to streamline them. As a result, I believe a fetch command is much preferable.
Parse.Cloud.beforeSave('votes', function(request, response) {
if (!request.object.isNew()) {
var Votes = Parse.Object.extend("votes");
var oldVote = new Votes();
oldVote.set("objectId",request.object.id);
oldVote.fetch({
success: function(oldVote) {
if (oldVote('choice') !== null) {
response.error('Not allowed to change your choice once submitted');
}
else {
response.success(); // Only after we check for error do we call success
}
},
error: function(oldVote, error) {
response.error(error.message);
}
});
});
If you are using the self hosted Parse Server, there is a property on request called "original" that is the object before changes.
Parse.Cloud.beforeSave("Post", function(request, response) {
console.log(request.object); //contains changes
console.log(request.original); //contains original
response.success();
});
You can use Parse DirtyKeys to identify which field has changed.
Parse.Cloud.beforeSave(Parse.User, function(request, response) {
for (dirtyKey in request.object.dirtyKeys()) {
if (dirtyKey === "yourfieldname") {
response.error("User is not allowed to modify " + dirtyKey);
return;
}
}
response.success();
});
The request variable is the updated row itself. You can get it's object id through request.object.idand use this to grab the current row from the database and check the current value, like so:
Parse.Cloud.beforeSave('votes', function(request, response) {
if (!request.object.isNew()) {
var query = new Parse.Query("votes");
query.get(request.object.id, { // Gets row you're trying to update
success: function(row) {
if (row.get('choice') !== null)
response.error('Not allowed to change your choice once submitted');
response.success(); // Only after we check for error do we call success
},
error: function(row, error) {
response.error(error.message);
}
});
}
This Worked :
var dirtyKeys = request.object.dirtyKeys();
var query = new Parse.Query("Question");
var clonedData = null;
query.equalTo("objectId", request.object.id);
query.find().then(function(data){
var clonedPatch = request.object.toJSON();
clonedData = data[0];
clonedData = clonedData.toJSON();
console.log("this is the data : ", clonedData, clonedPatch, dirtyKeys);
response.success();
}).then(null, function(err){
console.log("the error is : ", err);
});

Categories