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.})
Related
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);
});
Whenever I try to run the JS code with node in CMD I get this error:
Oh and the bot probably cannot be run since stackoverflow needs permission from mySQL database.
TypeError: Cannot read property 'length' of undefined
at Query._callback (C:\Users\George\Desktop\bot2\websitebot.js:138:9)
at Query.Sequence.end (C:\Users\George\node_modules\mysql\lib\protocol\sequences\Sequence.js:86:24)
at Query.ErrorPacket (C:\Users\George\node_modules\mysql\lib\protocol\sequences\Query.js:94:8)
at Protocol._parsePacket (C:\Users\George\node_modules\mysql\lib\protocol\Protocol.js:280:23)
at Parser.write (C:\Users\George\node_modules\mysql\lib\protocol\Parser.js:74:12)
at Protocol.write (C:\Users\George\node_modules\mysql\lib\protocol\Protocol.js:39:16)
at Socket.<anonymous> (C:\Users\George\node_modules\mysql\lib\Connection.js:109:28)
at emitOne (events.js:96:13)
at Socket.emit (events.js:188:7)
at readableAddChunk (_stream_readable.js:176:18)
This is the code I'm trying to run, I honestly have no idea why I am getting these errors. Incase someone here might help me:
//The required liberys for the bot to work
var Steam = require('steam')
var SteamUser = require('steam-user');
var SteamTotp = require('steam-totp');
var SteamConfirm = require('steamcommunity-mobile-confirmations');
var SteamTradeOffers = require('steam-tradeoffers');
var TradeOfferManager = require('steam-tradeoffer-manager');
var TOTP = require('onceler').TOTP;
var request = require('request');
var mysql = require('mysql');
var offers = new SteamTradeOffers();
var apik = 'xxx'; //The API Key of the bot.
var botsteamid = 'xxx'; //The SteamID of the bot.
var identitysecret = 'xxx'; //The identity secret of the bot.
var sharedsecret = 'xxx'; //The shared secret of the bot
var botusername = 'xxx';
var botpassword = 'xxx';
var admin = 'xxx'; //The steamid of the Admin.
var botid = 'xxx'; //The ID of the bot..
var pooling_interval = 10000; // 10 seconds by default, the bot checks for outgoing confirmations every X seconds, defined here
//Setting up device identity
var deviceid=SteamTotp.getDeviceID(botsteamid);
//Making the bot log in.
var details = {
"accountName" : botusername, // Bot username
"password" : botpassword, // Bot password
"twoFactorCode" : SteamTotp.generateAuthCode(sharedsecret)
};
var client = new SteamUser();
var manager = new TradeOfferManager({
"steam" : client,
"domain" : "localhost", //localhost
"language" : "en",
})
//Setting up the MySQL Connection - This is where I have errors.
var connection = mysql.createConnection({
host : 'xxx', // MYSQL , LEAVE IT AS LOCALHOST IF YOU RUN IT ON THE SAME SERVER AS THE WEBSITE AND DATABASE
user : 'xxx', // MYSQL USERNAME
password : 'xxx', // MYSQL PASSWORD
database : 'xxx', // MYSQL DATABASENAME
charset : 'utf8_general_ci'
});
connection.connect();
client.logOn(details);
//Checking mobile confirmations
function checkConfirmations(steamcommunityMobileConfirmations){
steamcommunityMobileConfirmations.FetchConfirmations((function (err, confirmations)
{
if (err)
{
console.log('Confirmations error: '+err);
if(err=='Error: 503') // This is an error you can most likely ignore, except if it's spammed a lot - To fix it simply restart the bot
{
}
if(err=='Error: Invalid protocol: steammobile:') // - To fix it simply restart the bot
{
// A fix should be coming soon!
}
return;
}
if(confirmations.length>0)
{
console.log('[SERVER] Received ' + confirmations.length + ' confirmations');
}
if ( ! confirmations.length)
{
return;
}
steamcommunityMobileConfirmations.AcceptConfirmation(confirmations[0], (function (err, result)
{
if (err)
{
console.log(err);
return;
}
console.log('[SERVER] Confirmation handling result: ' + result);
}).bind(this));
}).bind(this));
}
//Done with the functions, time to do commands.
//Logging the bot in
client.on('loggedOn', function(details)
{
client.on('webSession', function(sessionID, cookies){
manager.setCookies(cookies, function(err) {
if(err) {
console.log('setCookies error: '+err);
process.exit(1); // Fatal error since we couldn't get our API key
return;
}
var steamapi=manager.apiKey;
var SteamcommunityMobileConfirmations = require('steamcommunity-mobile-confirmations');
var steamcommunityMobileConfirmations = new SteamcommunityMobileConfirmations(
{
steamid: botsteamid,
identity_secret: identitysecret,
device_id: deviceid,
webCookie: cookies,
});
setInterval(function(){
checkConfirmations(steamcommunityMobileConfirmations)
}, pooling_interval);
console.log("[SERVER] The Bot has logged in!");
client.addFriend(admin);
client.setPersona(Steam.EPersonaState.LookingToTrade);
});
offers.setup({
sessionID: sessionID,
webCookie: cookies,
APIKey: apik
});
});
});
function checkWithdraw(){
connection.query("SELECT * FROM `withdraw` WHERE active=1 AND `botid`='"+botid+"' AND tradestatus='Queued' LIMIT 1", function(err, row, fields) {
if (!row.length) {
return;
}
var tradeid = row[0].id;
var sendItems = (row[0].assetids).split(',');
var item=[],num = 0;
for (i = 0; i < sendItems.length; i++) {
item[num] = {
appid: 730,
contextid: 2,
amount: 1,
assetid: sendItems[i]
}
num++;
}
offers.makeOffer ({
partnerSteamId: row[0].steamid,
accessToken: row[0].token,
itemsFromMe: item,
itemsFromThem: [],
message: 'Withdraw from '
},
function(err, response) {
if (err) {
console.log(err);
return;
}
console.log('Tradeoffer sent to ' + row[0].steamid);
tradeofferquery = response;
tradeofferid = (tradeofferquery['tradeofferid']);
connection.query('UPDATE `withdraw` SET `tradeid`=\''+tradeofferid+'\', `tradestatus`="Sent" WHERE `id`=\''+tradeid+'\'', function(err, row, fields) {});
})
});
}
function checkDeposit(){
connection.query("SELECT * FROM `deposits` WHERE `credited`=\'0\' AND `tradestatus`=\'Queued\' AND `botid`='"+botid+"' LIMIT 1", function(err, row, fields) {
if (!row.length) {
return
}
offers.getHoldDuration({partnerSteamId: row[0].steamid, accessToken: row[0].token}, function(err, response)
{
if (err)
{
return;
}
escrowduration = response;
thesd = (escrowduration['their']);
if(thesd === 0){
var tradeid = row[0].id;
var sendItems = (row[0].assetids).split(',');
var item=[],num = 0;
for (i = 0; i < sendItems.length; i++) {
item[num] = {
appid: 730,
contextid: 2,
amount: 1,
assetid: sendItems[i]
}
num++;
}
console.log(item);
offers.makeOffer ({
partnerSteamId: row[0].steamid,
accessToken: row[0].token,
itemsFromMe: [],
itemsFromThem: item,
message: 'Deposit to , code: ' + row[0].code
}, function(err, response) {
if (err) {
console.log(err);
return;
}
console.log('Tradeoffer sent to ' + row[0].steamid);
tradeofferquery = response;
tradeofferid = (tradeofferquery['tradeofferid']);
connection.query('UPDATE `deposits` SET `tradeid`=\''+tradeofferid+'\', `tradestatus`="Sent" WHERE `id`=\''+tradeid+'\'', function(err, row, fields) {});
})
} else {
connection.query('DELETE FROM `deposits` WHERE `steamid`=\''+ row[0].steamid +'\'', function(err, row, fields) {});
console.log('They are in escrow');
}
});
});
}
//Keeping track of sent offers.
manager.on('sentOfferChanged', function(offer, oldState) {
console.log("Offer #" + offer.id + " changed: " + TradeOfferManager.getStateName(oldState) + " -> " + TradeOfferManager.getStateName(offer.state));
connection.query('UPDATE `deposits` SET `tradestatus`=\''+TradeOfferManager.getStateName(offer.state)+'\' WHERE `tradeid`=\''+offer.id+'\'');
connection.query('UPDATE `withdraw` SET `tradestatus`=\''+TradeOfferManager.getStateName(offer.state)+'\' WHERE `tradeid`=\''+offer.id+'\'');
if(offer.state == TradeOfferManager.ETradeOfferState.Accepted) {
offer.getReceivedItems(function(err, items) {
if(err) {
console.log("Couldn't get received items: " + err);
} else {
items.forEach(function(item)
{
console.log('Recieved: ' + item.name);
connection.query('INSERT INTO `bank` (`botid`,`assetid`,`img`,`name`,`status`) VALUES (\''+botid+'\',\''+item.assetid+'\',\''+item.icon_url+'\',\''+item.market_name+'\',\'1\')', function(err, row, fields) {});
})
}
});
}
if(offer.state != (TradeOfferManager.ETradeOfferState.Accepted || TradeOfferManager.ETradeOfferState.Active)) {
connection.query('DELETE FROM `deposits` WHERE `tradeid`=\''+offer.id+'\'');
connection.query('DELETE FROM `withdraw` WHERE `tradeid`=\''+offer.id+'\'');
}
});
//Processing incomming offers
manager.on('newOffer', function(offer)
{
offer.decline(function(err)
{
console.log('[DEBUG] Declined Counter offer.');
if (err)
{
console.log('Decline error: '+err);
}
connection.query('DELETE FROM `deposits` WHERE `tradeid`=\''+offer.id+'\'');
connection.query('DELETE FROM `withdraw` WHERE `tradeid`=\''+offer.id+'\'');
});
});
setInterval(function() {
checkDeposit();
checkWithdraw();
}, 5000);
//Numeric and float
function is_float(mixed_var)
{
return +mixed_var === mixed_var && (!isFinite(mixed_var) || !! (mixed_var % 1));
}
function isNumeric(n){
return (typeof n == "number" && !isNaN(n));
}
//Setting up chat commands for the admin
client.on('friendMessage#'+admin+'', function(steamID, message)
{
console.log("[SERVER] Admin to Bot: " + message);
if((message.indexOf("ping") == 0) || (message.indexOf("/cmd") == 0))
{
checkDeposit();
client.chatMessage(admin, 'Pong!');
}
if((message.indexOf("pong") == 0) || (message.indexOf("/cmd") == 0))
{
checkWithdraw();
client.chatMessage(admin, 'Pong!');
}
if(message.indexOf("/code") == 0)
{
var code = SteamTotp.generateAuthCode(sharedsecret);
client.chatMessage(admin, '[SERVER] Your login code: '+code+'');
}
});
If i understod your question right this is where the error occur.
connection.query("SELECT * FROM `withdraw` WHERE active=1 AND `botid`='"+botid+"' AND tradestatus='Queued' LIMIT 1", function(err, row, fields) {
if (!row.length) {
return;
}
Im kind of new too the mysql area but i whould try to write the code something like this instead:
connection.query("SELECT * FROM `withdraw` WHERE active=1 AND `botid`= ? AND tradestatus='Queued' LIMIT 1",botid , function(err, rows, fields) {
if (!rows.length) {
return;
}
What ive done is changed the place where you input the "botid" to a "?" instead, that takes the value from the botid right before your function, and row is now rows just for code clearance.
As you said, i cant access your DB but i hope that this might work. Let me know!
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.
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");
});
});