My Parse cloud code is not responding with an error or success. It just times out, I don't know what I'm doing wrong. It should save multiple values in different tables, and finish with sending a push message to a user.
The push message and user table are adjusted and send, but the street and the ledger objects are not being saved correctly. Also the response is not being called.
I did work in the past (or I was just lucky). Any thought on what I'm doing wrong?
var buyerId = request.params.buyerid;
var sellerName = request.params.sellername;
var streetId = request.params.streetid;
var amount = request.params.amount;
var pushMessageTemplate = request.params.pushMessage;
var log = request ? request.log : console;
var Streets = Parse.Object.extend('SHStreets');
var streetQuery = new Parse.Query(Streets);
streetQuery.equalTo("objectId", streetId);
streetQuery.first({
useMasterKey: true,
success: function (street) {
var streetName = street.get("name");
var query = new Parse.Query(Parse.User);
query.equalTo("objectId", buyerId);
query.first({
useMasterKey: true,
success: function (user) {
var promises = [];
var now = new Date();
var buyerName = user.get("username");
// Set and save the change
user.set("balance", user.get("balance") - amount);
user.set("streets", user.get("streets") + 1);
street.set("current_owner", user);
street.set("owned_since", now);
street.set("for_sale", false);
street.set("price_bought", amount);
var acl = new Parse.ACL();
acl.setPublicReadAccess(true);
acl.setWriteAccess(user, true);
street.setACL(acl);
//update ledger
var Ledger = Parse.Object.extend("Ledger");
var ledger = new Ledger();
ledger.set("type", "buy");
ledger.set("amount", amount);
ledger.set('ledger_time', now);
ledger.set("user", user);
ledger.set("description", "x");
promises.push(Parse.Object.saveAll([street, user, ledger], { useMasterKey: true }));
// Find users with a given email
var userQuery = new Parse.Query(Parse.User);
userQuery.equalTo("objectId", user.id);
var pushQuery = new Parse.Query(Parse.Installation);
pushQuery.exists("user"); // filter out installations without users
pushQuery.include('user'); // expand the user pointer
pushQuery.matchesQuery("user", userQuery);
// Send push notification to query
promises.push(Parse.Push.send({
where: pushQuery, // Set our installation query
data: {
alert: _.template(pushMessageTemplate)({
sellername: sellerName,
streetname: streetName,
amount: amount
})
}
}, {
useMasterKey: true,
}));
return Parse.Promise.when(promises).then(function () {
response.success("success");
});
},
error: function (error) {
log.error('buy-street error', error.message);
response.error("Uh oh, buy request success failed." + error.message);
}
});
},
error: function (error) {
log.error('buy-street error', error.message);
response.error("Uh oh, buy request success failed." + error.message);
}
});
It looks like your first query's success function doesn't return anything:
streetQuery.first({
useMasterKey: true,
success: function (street) { // this function doesn't return anything
// ...
query.first({ // You probably want to return this promise
useMasterKey: true,
success: function (user) { // this is the function that you're returning to
// ...
return Parse.Promise.when(promises).then(function () {
response.success("success");
});
},
});
}
});
Javascript will return undefined by default if you don't have a return statement.
I was missing a function error for the return of the promises.
return Parse.Promise.when(promises).then(function () {
// Set the job's sucess status
response.success('success');
},function(error) {
response.error(error);
});
Related
I tried setting up cloud code on my new parse server (on AWS EB) to send push notifications with the following code:
Parse.Cloud.define("sendPush", function(request, response) {
var currentUser = request.user;
var recipient = request.params.recipient;
if (!currentUser) {
response.error("Must be logged in.");
return;
}
if (!recipient) {
response.error("Must specify recipient.");
return;
}
var userQuery = new Parse.Query(Parse.User);
userQuery.equalTo('objectId', recipient);
response.success(userQuery.get('name'))
var pushQuery = new Parse.Query(Parse.Installation);
pushQuery.matchesQuery('user', userQuery);
Parse.Push.send({
where: pushQuery,
data: {
alert: "Hello, World",
badge: "Increment"
}
}, {
success: function() {
response.success("Push sent to " + recipient + " from " + request.user.get("username"));
},
error: function(error) {
response.error('Not working')
}
});
});
It doesn't return back an error but the notification doesn't go through either. Instead, I get the following error:
Optional({
"_rejected" = 0;
"_rejectedCallbacks" = (
);
"_resolved" = 0;
"_resolvedCallbacks" = (
);
})
Any idea why this is happening?
EDIT
I decided to change the code to see if the function is even retrieving results, but looks like it doesn't. Here's the new code:
Parse.Cloud.define("sendPush", function(request, response) {
var currentUser = request.user;
var recipient = request.params.recipient;
if (!currentUser) {
response.error("Must be logged in.");
return;
}
if (!recipient) {
response.error("Must specify recipient.");
return;
}
var userQuery = new Parse.Query(Parse.User);
userQuery.equalTo('objectId', recipient);
userQuery.find
({
success: function(results)
{
if (results.length > 0)
{
var objectId = results[0].id;
var gate = results[0].get("name");
response.success(JSON.stringify(results[0]));
}
else
{
response.error("wont work");
};
}
})
});
and get the following error:
Optional(Error Domain=NSCocoaErrorDomain Code=3840 "The operation couldn’t be completed. (Cocoa error 3840.)" (No value.) UserInfo=0x7ff163c587d0 {NSDebugDescription=No value.})
I would like to share a variable that is set in the client with the Meteor.onCreateUser function call on the server.
I have this code that sets some user properties before a user is created
Accounts.onCreateUser(function(options, user, err) {
if (options.profile) {
user.profile = options.profile;
// Images
var picturelrg = "http://graph.facebook.com/" + user.services.facebook.id + "/picture/?type=large";
var picturesm = "http://graph.facebook.com/" + user.services.facebook.id + "/picture/?type=small";
options.profile.picturelrg = picturelrg;
options.profile.picturesm = picturesm;
options.profile.upvotes = 0;
options.profile.neutralvotes = 0;
options.profile.downvotes = 0;
// ip = response.ip;
return user;
}
});
Here is the client code
if (Meteor.isClient) {
fbLogin = function() {
Meteor.loginWithFacebook({
requestPermissions: ['public_profile', 'email', 'user_location']
}, function(err) {
if (err)
// redirect to register if popup comes and user isn't on register
Session.set('errorMessage', err.reason || 'Unknown Eror');
console.log(Session.get('errorMessage'));
});
}
locate = function(){
function ipLocate(whenDone) {
var api = "http://ipinfo.io?callback=?";
$.getJSON(api, {
format: "jsonp"
})
.done(function(response) {
var result = ""
// show all the props returned
for (var prop in response) {
result += prop + ": " + response[prop] + "<br>";
}
var selectedResponse = {
city: response.city,
region: response.region,
country: response.country,
ip: response.ip,
latLng: response.loc
}
console.log(selectedResponse);
whenDone(selectedResponse);
return selectedResponse
});
}
// HACK: Async
function ipDone(selectedResponse) {
response = selectedResponse;
}
// Set response
ipLocate(ipDone);
return response
}
Template.ModalJoin.events({
'click .modJoinFB-Btn ': function() {
locate();
fbLogin();
}
});
}
On the client I have an event handler that sets some values when the user clicks the "Sign Up with Facebook" button. How can I send these values to the onCreateUser function to be accessed.
Ex: I want to store user geolocation info ( city, state) when the user registers but I don't know how this can be sent from the client to server.
I'm not sure how I would use Meteor.call() if I could
Looks like you should run a Meteor.call function inside fbLogin, passing that location data, if no error is returned. Something like this:
fbLogin = function() {
Meteor.loginWithFacebook({
requestPermissions: ['public_profile', 'email', 'user_location']
}, function(err) {
if (err) {
Session.set('errorMessage', err.reason || 'Unknown Eror');
console.log(Session.get('errorMessage'));
} else {
//if no error was returned, then Meteor.call the location
var userId = Meteor.userId(); //you should send that userId for the method.
Meteor.call('storeLocation', locationData, userId, function(err,res){
if (err) {
console.log(err);
}
});
}
});
}
And on server, you create a Method for updating that user profile data with the location. Maybe something like this:
Meteor.methods({
'storeLocation': function(locationData, userId) {
var locationData = {
// based on what you have gathered on your client location function
'city': response.city,
'region': response.region,
'country': response.country,
'ip': response.ip,
'latLng': response.loc
}
Meteor.users.update(
//I suggest placing it inside profile, but do as it is better to you
{'_id': userId},
{$addToSet: {'profile.locations': locationData }}
);
}
});
Not sure if you will store like that, but this is how I have done for myself. Let me know if any problems or doubts, we can try to solve it together.
I've turned my hand from game dev to writing a supporting back-end. Thus far everything seems to work out but when i got to writing a friend system i ran into this function flow which seemed very dirty to me. And i'm certain i'm just hacking it together at this point. Any node.js wizards about to tell me how I can improve this?
Fairly certain i should be caching player lookups in Redis as well.
acceptfriend: function(req, res){
//Find our user
User.findById( req.decoded._id, function(err, user){
//error occured
if(err){
return res.status(401).send(err);
}
//no user found
if(!user){
return res.status(401).json({
succes: false,
message: 'no user found with that id'
} );
}
//Does the request exist?
if( !_.any( user.social.friendRequests, {id: req.params.id} ) ){
return res.status(401).json( {
succes: false,
message: 'friend request not found'
} );
}
//find the user that belongs to the request
User.findById( req.params.id, function(err, friend){
//error occured
if(err){
return res.send(err);
}
//user doesnt exist
if(!friend){
return res.status(401).json({
succes: false,
message: 'no user found with that id'
} );
}
//Pull the request from the friendRequests array
user.social.friendRequests.pull( req.params.id );
//Add the friend
user.social.friends.addToSet( {
user_id: friend._id,
name: friend.username,
corp: 'n/a'
} );
//Add the user to the friends list as well
friend.social.friends.addToSet({
user_id: user._id,
name: user.username,
corp: 'n/a'
});
//save the docs
user.save();
friend.save();
} );
//return success
return res.status(200).json({
success: true,
message: 'friend succesfully added'
});
} );
}
1- First of all, you have a big function. You have to split it into some functions. Doing this you gain the possibility to test them with any testing framework.
2- Delegate the handle of error responses to the controller.
from -> return res.status(401).send(err);
to (with Promises)-> deferred.reject(err);
to (normal way) -> throw new Error(err);
3- You can use Promises to manage the asynchronous behaviour of node to clear the code.
I created an example, maybe is not working at first time, feel free to fix the incorrent references. The User ref, the 'acceptfriend' method...
Gist: https://gist.github.com/aitoraznar/b7099ad88ead0cdab256
var Promise = require('bluebird');
var _ = require('lodash');
//var User = app.models.User;
var ERRORS = {
userNotFoundError: {
code: 401,
success: false,
message: 'no user found with that id'
},
friendRequestNotFoundError: {
code: 401,
success: false,
message: 'friend request not found'
},
friendNotFoundError: {
code: 401,
success: false,
message: 'no friend found with that id'
}
}
var SUCCESS_MESSAGES= {
friendAddedSuccessfully: {
success: true,
message: 'friend succesfully added'
}
};
var userDAO = {
/*
*
*/
getUserById: function(id) {
var deferred = Promise.pending();
User.findById(id, function(err, user) {
//error occured
if (err) {
err.code = 401;
return deferred.reject(err);
}
//no user found
if (!user) {
return deferred.reject(ERRORS.userNotFoundError);
}
deferred.resolve(user);
});
return deferred.promise;
},
/*
* Does the request exist?
*/
checkFriendRequest: function(user, friendId) {
var deferred = Promise.pending();
if (userDAO.haveFriendRequestFrom(user, friendId)) {
deferred.resolve(user, friendId);
} else {
return deferred.reject(ERRORS.friendRequestNotFoundError);
}
return deferred.promise;
},
/*
*
*/
haveFriendRequestFrom: function(user, friendId) {
return _.any(user.social.friendRequests, {id: friendId });
},
/*
*
*/
getFriend: function(user, friendId) {
var deferred = Promise.pending();
userDAO.getUserById(friendId)
.then(function(friend) {
deferred.resolve(user, friend);
},
function(error) {
if (error === ERRORS.userNotFoundError) {
// Then the error is friend not found
// Override the error
error = ERRORS.friendNotFoundError;
}
return deferred.reject(error);
});
return deferred.promise;
},
/*
*
*/
makeFriendship: function(user, friend) {
var deferred = Promise.pending();
//Pull the request from the friendRequests array
user.social.friendRequests.pull(friend._id);
//Add the friend
user.social.friends.addToSet( {
user_id: friend._id,
name: friend.username,
corp: 'n/a'
} );
//Add the user to the friends list as well
friend.social.friends.addToSet({
user_id: user._id,
name: user.username,
corp: 'n/a'
});
//save the docs
user.save();
friend.save();
// Return the new friendship
var friendship = {
user: user,
friend:friend
};
deferred.resolve(friendship);
return deferred.promise;
},
/*
*
*/
friendRequestError: function(err) {
var deferred = Promise.pending();
// Propagate de error
deferred.reject(err);
return deferred.promise;
},
/*
*
*/
friendRequest: function(userId, friendId) {
var deferred = Promise.pending();
// Get user by ID
userDAO.getUserById(userId)
// Check if the user is able to add the friend
.then(userDAO.checkFriendRequest, userDAO.friendRequestError)
// Get the friend to add
.then(userDAO.getFriend, userDAO.friendRequestError)
// Make the friendship
.then(userDAO.makeFriendship, userDAO.friendRequestError)
// Response to the controller
.then(
function(friendship) {
// Resolve with new friendship
// This goes to 'success' function in controller
deferred.resolve(friendship);
}, function(error) {
// This goes to 'error' function in controller
deferred.reject(error);
})
return deferred.promise;
}
};
// Controller
var acceptfriend = function(req, res, next) {
var userId = req.decoded._id;
var friendId = req.params.id;
userDAO.friendRequest(userId, friendId)
.then(function(friendRequest) {
console.log('---> SUCCESS');
//return success
return res.status(200)
.json(SUCCESS_MESSAGES.friendAddedSuccessfully);
}, function(error) {
console.error('---> ERROR', error);
return res.status(error.code).json(error);
});
}
4- Create database indexes in the collection/table
Regards,
Aitor
Writing a Parse Cloud Function (which uses Parse Javascript SDK) and I am having trouble checking to see if the current user has role "Admin". I'm looking at the web view of the Role class and a role with the name "Admin" exists, if I click "View Relations" for users, it shows the current user. I doubt it should matter, but "Admin" is the only role and the current user is the only user with a role. Lastly, the "Admin" role has an ACL of Public Read, so that shouldn't be causing any issues either.
Code is as follows:
...
var queryRole = new Parse.Query(Parse.Role);
queryRole.equalTo('name', 'Admin');
queryRole.equalTo("users", Parse.User.current());
queryRole.first({
success: function(result) { // Role Object
var role = result;
role ? authorized = true : console.log('Shiet, user not Admin');
},
error: function(error) {
console.log("Bruh, queryRole error");
}
})
console.log('After test: Auth = ' + authorized);
if (!authorized) {
response.error("You ain't no admin, measly user");
return;
}
...
This results in the following in the log:
Before test: Auth = false
After test: Auth = false
Give this a shot:
var authorized = false;
console.log('Before test: Auth = ' + authorized);
var queryRole = new Parse.Query(Parse.Role);
queryRole.equalTo('name', 'Admin');
queryRole.first({
success: function(result) { // Role Object
console.log("Okay, that's a start... in success 1 with results: " + result);
var role = result;
var adminRelation = new Parse.Relation(role, 'users');
var queryAdmins = adminRelation.query();
queryAdmins.equalTo('objectId', Parse.User.current().id);
queryAdmins.first({
success: function(result) { // User Object
var user = result;
user ? authorized = true : console.log('Shiet, user not Admin');
}
});
},
error: function(error) {
console.log("Bruh, can't find the Admin role");
}
}).then(function() {
console.log('After test: Auth = ' + authorized);
});
I got a simpler solution, give this a try:
var adminRoleQuery = new Parse.Query(Parse.Role);
adminRoleQuery.equalTo('name', 'admin');
adminRoleQuery.equalTo('users', req.user);
return adminRoleQuery.first().then(function(adminRole) {
if (!adminRole) {
throw new Error('Not an admin');
}
});
For those of you looking for a Parse Server (2018) answer, see below:
Parse.Cloud.define('authorizedUserTest', function(request, response) {
if(!request.params.username){
//response.error('no username');
response.error(request.params);
}
var queryRole = new Parse.Query(Parse.Role);
queryRole.equalTo('name','Admin');
queryRole.first({ useMasterKey: true }).then(function(promise){
var role = promise;
var relation = new Parse.Relation(role, 'users');
var admins = relation.query();
admins.equalTo('username', request.user.get('username'));
admins.first({ useMasterKey: true }).then(function(user){
if(user){
response.success(true)
}
else {
response.success(false)
}
}, function(err){
response.error('User is not an admin')
})
}, function(err){
response.error(err)
})
});
request.params is equal to a dictionary {"username":inputUsernameHere}.
Feel free to comment if you have questions.
I'm trying to send push notifications to my users via Parse Background Job if they are in proximity of the pet that was created.
Every user in range gets crosschecked with the pets (confirmed via log) but the push notifications are sent to the wrong user or most of the time not even sent at all. I'm pretty sure I messed the promises up but can't the problem here.
Any help would be much appreciated, thanks!
Parse.Cloud.job("locationPush", function(request, status) {
Parse.Cloud.useMasterKey();
var Pet = Parse.Object.extend("Pet");
var petQuery = new Parse.Query(Pet);
petQuery.equalTo("initialPushSent", false);
petQuery.equalTo("status", "missing");
petQuery.equalTo("deleted", false);
petQuery.find().then(function(pets) {
var petPromises = [];
_.each(pets, function(pet) {
console.log("checking pet: " + pet.id);
var petLocation = pet.get("lastSeenLocation");
var query = new Parse.Query(Parse.User);
query.withinKilometers("lastLocation", petLocation, 50);
query.find().then(function(users) {
var userPromises = [];
_.each(users, function(user) {
var userPromise = new Parse.Promise();
userPromises.push(userPromise);
console.log("check user " + user.id + " with pet: " + pet.id);
var pushPromises = [];
if(petLocation.kilometersTo(user.get("lastLocation")) <= user.get("pushRadius")){
console.log("send push to" + user.id);
var promise = new Parse.Promise();
pushPromises.push(promise);
Parse.Push.send({
channels: [ "user_" + user.id ],
data: {
alert : "Neues vermisstes Tier im Umkreis"
}},
{ success: function() {
console.log("push sent to: " + user.id)
},
error: function(error) {
console.log("error sending push: " + error)
}}).then (function(result){
promise.resolve();
}, function(error) {
promise.reject();
});
}
return Parse.Promise.when(pushPromises);
});
return Parse.Promise.when(userPromises);
});
petPromises.push(pet.save());
});
return Parse.Promise.when(petPromises);
}).then(function() {
status.success("location Send complete");
}, function(error) {
status.error("location Send Error");
});
You need to return a promise from really absolutely every function that does something asynchronous. In your case, you dropped the promise that was returned by query.find(), and called pet.save() immediately. I guess you wanted to chain them.
Also, your userPromises were never resolved, which likely is the reason that your chain failed. And your pushPromises array is quite unnecessary, as it only will contain at most one promise.
Also, I've used _.map instead of pushing to arrays, and removed the deferred antipattern that you had used. It makes the returns more prominent, so that it's easier to spot if you forgot one.
Parse.Cloud.job("locationPush", function(request, status) {
Parse.Cloud.useMasterKey();
var Pet = Parse.Object.extend("Pet");
var petQuery = new Parse.Query(Pet);
petQuery.equalTo("initialPushSent", false);
petQuery.equalTo("status", "missing");
petQuery.equalTo("deleted", false);
return petQuery.find().then(function(pets) {
return Parse.Promise.when(_.map(pets, function(pet) {
console.log("checking pet: " + pet.id);
var petLocation = pet.get("lastSeenLocation");
var query = new Parse.Query(Parse.User);
query.withinKilometers("lastLocation", petLocation, 50);
query.find().then(function(users) {
return Parse.Promise.when(_.map(users, function(user) {
console.log("check user " + user.id + " with pet: " + pet.id);
if (petLocation.kilometersTo(user.get("lastLocation")) <= user.get("pushRadius")) {
console.log("send push to" + user.id);
return Parse.Push.send({
channels: ["user_" + user.id],
data: {
alert: "Neues vermisstes Tier im Umkreis"
}
}, {
success: function() {
console.log("push sent to: " + user.id)
},
error: function(error) {
console.log("error sending push: " + error)
}
}); // we already got a promise here!
} else
return null;
}));
}).then(function() {
return pet.save();
});
}));
}).then(function() {
status.success("location Send complete");
}, function(error) {
status.error("location Send Error");
});
});