Fetching Images through Cloud Code - parse-platform

I've been scratching my head over this for a while. What am I doing wrong? Your help is much appreciated :)
I've tried many different image codes, but I think it's a promise issue I'm seeing. With the code below I only see the "Start of loop" log message.
If I move the results push outside the promise structure to underneath then I see the Stage log messages, albeit after all the Start of loops have printed (hence why I put the push in the then function).
Parse.Cloud.job("fetchjson", function(request, status) {
var url = 'some json url';
Parse.Cloud.httpRequest({url: url}).then(function(httpResponse){
//var Image = require("parse-image");
var Seeds = Parse.Object.extend("Seeds");
var jsonobj = JSON.parse(httpResponse.text);
var results = [];
// do NOT iterate arrays with `for... in loops`
for(var i = 0; i < jsonobj.seeds.length; i++){
var seed = new Seed();
var a = new Seed(jsonobj.seeds[i]);
console.log("Start of loop");
Parse.Cloud.httpRequest({url: a.get("image") }).then(function(response) {
console.log("Stage 1");
//var file = new Parse.File('thumb.jpg', { base64: response.buffer.toString('base64', 0, response.buffer.length) });
//return file.save();
return "hi"
}).then(function(thumb) {
console.log("Stage 2");
//a.set("imageFile", thumb);
//a.set("viewsInt", parseInt(a.get("views")));
}, function(error) {
console.log("Error occurred :(");
}).then(function(){
results.push(seed.save(a)); // add to aggregate
});
}
// .when waits for all promises
Parse.Promise.when(results).then(function(data){
status.success("All saved");
});
}, function(error) {
console.error('Request failed with response code ' + httpResponse.status);
status.error("Failed");
});
});

Related

Parse Cloud Code Save Issue

I wrote some backend code for a Parse.com mobile app a couple of years ago, and have just been asked to add a feature. However, I found that after a small tweak the code wouldn't succeed. So, I rolled back to the working copy, downloaded, then deployed that back and it wouldn't work either! I wonder if this is a change in the Parse software?
The code is failing at the save method as all the logs are fine until then. The log for the error case shows 'No message provided'. If I don't use the message attribute it just shows '{}', so I presume it's empty. I have put the promise resolution in the error case to stop the job timing out while I debug. One thing I have never understood is why I have to make two Seed objects and piggy-back off one to save correctly. If I did a.save(null,...) it wouldn't work.
Any help would be fantastic. Thanks!
PS: Apologies for the indenting below - it is correct in my file.
function flush() {
//Clear the previous records from the class.
var Seed = Parse.Object.extend("Seeds");
var _ = require("underscore");
var arr = [];
var query = new Parse.Query(Seed);
return query.find().then(function(oldSeeds) {
_.each(oldSeeds, function(oldSeed) {
arr.push(oldSeed.destroy());
});
return Parse.Promise.when(arr);
});
}
Parse.Cloud.job("fetchjson", function(request, status) {
var url = 'someurl';
flush().then(function() { Parse.Cloud.httpRequest({url: url}).then(function(httpResponse){
var Seed = Parse.Object.extend("Seeds");
var jsonobj = JSON.parse(httpResponse.text);
var _ = require("underscore");
var results = [];
// do NOT iterate arrays with `for... in loops`
_.each(jsonobj.seeds, function(s) {
var p = new Parse.Promise();
results.push(p); // Needs to be done here or when() will execute immediately with no promises.
var seed = new Seed();
var a = new Seed(s);
var image_url = a.get("image")
//Get the JSON.
Parse.Cloud.httpRequest({url: image_url}).then(function(response) {
console.log("Fetching image at URL: " + image_url);
//Create a new image object and save, passing ref through promise.
var file = new Parse.File('thumb.jpg', { base64: response.buffer.toString('base64', 0, response.buffer.length) });
return file.save();
}).then(function(thumb) {
console.log("Attaching thumb to object");
//Set image ref as object attribute.
a.set("imageFile", thumb);
console.log("Parsing views into viewsint");
//Save decimal string as int into another attribute.
a.set("viewsInt", parseInt(a.get("views")));
console.log("Parsing description into descriptionarray");
//Save string as array into another attribute.
var dar = new Array(1);
//dar[0] = a.get("description")
a.set("descriptionarray", [a.get("description")]);
}, function(error) {
console.log("Error occurred :(");
}).then(function(){
console.log("Saving object");
//Save the object and resolve the promise so we can stop.
seed.save(a,{
success: function(successData){
console.log(successData);
p.resolve(successData);
},
error: function(error){
console.log(error.message);
p.resolve(error);
}
});
});
});
// .when waits for all promises to be resolved. This is async baby!
Parse.Promise.when(results).then(function(data){
console.log("All objects saved");
status.success("Updated Succesfully");
});
}, function(error) {
//Oh noes :'(
console.error('Request failed with response code ' + httpResponse.status);
status.error("Update Failed");
});
});
});
I changed your code a bit and put some comments to explain:
// DEFINE THESE ON THE TOP. NO NEED TO REPEAT.
var _ = require("underscore");
var Seed = Parse.Object.extend("Seeds");
function flush() {
//Clear the previous records from the class.
var arr = [];
var query = new Parse.Query(Seed);
return query.find().then(function(oldSeeds) {
_.each(oldSeeds, function(oldSeed) {
arr.push(oldSeed.destroy());
});
return Parse.Promise.when(arr);
});
}
Parse.Cloud.job("fetchjson", function(request, status) {
var url = 'someurl';
flush().then(function() {
Parse.Cloud.httpRequest({url: url}).then(function(httpResponse){
var jsonobj = JSON.parse(httpResponse.text);
var results = [];
_.each(jsonobj.seeds, function(s) {
// ONE SEED OBJECT WITH INITIAL SET OF DATA FROM JSON
var seed = new Seed(s);
var image_url = seed.get("image")
// A SERIAL PROMISE FOR EACH SEED
var promise = Parse.Cloud.httpRequest({url: image_url}).then(function(response) {
console.log("Fetching image at URL: " + image_url);
//Create a new image object and save, passing ref through promise.
var file = new Parse.File('thumb.jpg', { base64: response.buffer.toString('base64', 0, response.buffer.length) });
return file.save();
}).then(function(thumb) {
// SETTING MORE PROPERTIES
//Set image ref as object attribute.
console.log("Attaching thumb to object");
seed.set("imageFile", thumb);
//Save decimal string as int into another attribute.
console.log("Parsing views into viewsint");
seed.set("viewsInt", parseInt(seed.get("views")));
//Save string as array into another attribute.
console.log("Parsing description into descriptionarray");
seed.set("descriptionarray", [seed.get("description")]);
// SAVING THE OBJECT
console.log("Saving object");
return seed.save();
});
// PUSH THIS PROMISE TO THE ARRAY TO PERFORM IN PARALLEL
results.push(promise);
});
Parse.Promise.when(results).then(function(data){
console.log("All objects saved");
status.success("Updated Succesfully");
});
}, function(error) {
console.error('Request failed with response code ' + httpResponse.status);
status.error("Update Failed");
});
});
});
Thanks knshn. I had refactored the code a lot since that version (including several of the changes you made), but I had posted the version that was identical to that which was working fine before. Your changes let me see the right error. For some reason doing the simple single object implementation didn't work for me originally, hence the nasty workaround. It works now though.
I have now found the culprit - the Seed class had an attribute called 'id'. With the old version this worked fine, but when I deployed that code now it gave an error 101: 'object not found for update'. This must be because the new Parse code is mixing that up with the internal objectId and getting confused that the id is different to what it expects. I wonder how that could still work with the rollback though. Perhaps the at version was tagged to use the older Parse code.
My fix was to use a different name for the id - 'seed_id'.

Recursive Query to get more than 1000 results outside Parse.cloud impossible?

I'm in need to fetch over 1000 elements from a Class.
So I tried following advices on this topic, but something is not working correctly, and already spent the whole day to find a solution.
Parse.Cloud.define("getFollow", function(request, response) {
var following = [];
var user = request.params.user;
user.fetch().then(function(result) {
if (!result.get('following')) {
following = getFollowing({
'user': user
});
}
}).then(function() {
response.success(following);
}, function(error) {
response.error(error);
});
});
function getFollowing(request) {
var count = 0;
var skip = request.skip || 0;
var limit = 1000;
var following = request.following || [];
var Follow = Parse.Object.extend('follow');
var query = new Parse.Query(Follow);
query.limit(limit);
query.ascending('objectId');
query.skip(skip * limit);
query.equalTo('followers', request.user);
query.find().then(function(results) {
skip+= 1;
count = results.length;
/* I can't see any DEBUG, seems nothing is queried */
console.log('[DEBUG] Check <count>: ' + count);
return Parse.Promise.when(results.map(function(result) {
following.push(result.get('followed'));
}));
}).then(function() {
if(count >= limit) {
following = getFollowingUsers({
'user': request.user,
'skip': skip,
'following': following
});
}
}).then(function() {
return following;
}, function(error) {
return error;
});
}
I tried many variant of this code, trying to return the very first result of the query, rather than a collection. I also tried to remove all contraints, but even so, my query seems not to be run.
I also tried to use a Cloud.code function to make this recursively using only Parse.Cloud, but if I do that, I'm having a message Too many recursive calls into Cloud Code
What did I do wrong with this logic ?

Parse Cloud: Query not running in exported function from save() callback

I'm using Parse to represent the state of a beer keg (among other things). I'd like to check the user's notifications, stored in a "Notifications" table, to see if they'd like to receive a notification when the keg is filled.
I have all of the logic for setting the user's notification settings as well as sending notifications in cloud/notifications.js. All of the logic for updating the keg is in cloud/beer.js. I created an exported function called "sendKegRefillNotification" which performs a query.find() on the Notifications table and gets called from beer.js.
The problem is that it doesn't seem to be executing query.find() when I call the function from beer.js, however when I call the same function from a job within notifications.js, it works just fine.
main.js:
require("cloud/beer.js");
require("cloud/notifications.js");
beer.js:
var notify = require("cloud/notifications.js");
var Keg = Parse.Object.extend("Keg");
var fillKeg = function(beerName) {
var promise = new Parse.Promise();
var keg = new Keg();
keg.set("beerName", beerName)
keg.set("kickedReports", []);
keg.save(null, { useMasterKey: true }).then(function(keg) {
console.log("Keg updated to " + beerName + ".");
promise.resolve(keg);
notify.sendKegRefillNotification(keg);
},
function(keg, error) {
promise.reject(error);
});
return promise;
}
Parse.Cloud.define("beerFillKeg", function(request, response) {
var beerName = request.params.name;
if (!beerName) {
response.error("No beer was specified.");
return;
}
if (!util.isUserAdmin(request.user)) {
response.error("User does not have permission to update the keg.");
return;
}
fillKeg(beerName).then(function(keg) {
kegResponse(keg).then(function(result) {
response.success(result);
});
},
function(error) {
response.error(error);
});
});
function kegResponse(keg) {
var promise = new Parse.Promise();
var result = {
id: keg.id,
beer: {
name: keg.get("beerName")
},
filled: keg.createdAt,
kickedReports: []
};
var kickedReports = keg.get("kickedReports");
if (!kickedReports || kickedReports.length == 0) {
promise.resolve(result);
} else {
util.findUsers(kickedReports).then(function(users) {
result.kickedReports = util.infoForUsers(users);
promise.resolve(result);
}, function(users, error) {
console.log(error);
promise.resolve(result);
});
}
return promise;
}
notifications.js:
var Keg = Parse.Object.extend("Keg");
var Notification = Parse.Object.extend("Notifications");
exports.sendKegRefillNotification = function(keg) {
var beerName = keg.get("beerName");
console.log("Sending notifications that keg is refilled to '" + beerName + "'.");
var promise = new Parse.Promise();
var query = new Parse.Query(Notification);
query.include("user");
query.equalTo("keg_filled", true);
query.find({ useMasterKey: true }).then(function(notifications) {
console.log("Found notifications!");
promise.resolve("Found notifications!");
},
function(notifications, error) {
console.error("No notifications");
console.error(error);
promise.reject(error);
});
return promise;
}
Parse.Cloud.job("beerSendRefillNotification", function(request, status) {
var query = new Parse.Query(Keg);
query.descending("createdAt");
query.first().then(function(keg) {
if (!keg) {
status.error("No keg");
return;
}
exports.sendKegRefillNotification(keg);
},
function(keg, error) {
response.error(error);
});
});
When I run the job "beerSendRefillNotification" from the Parse dashboard, I can tell that query.find() is getting called because it prints "Found notifications!":
E2015-02-23T06:59:49.006Z]v1564 Ran job beerSendRefillNotification with:
Input: {}
Result: success/error was not called
I2015-02-23T06:59:49.055Z]false
I2015-02-23T06:59:49.190Z]Sending notifications that keg is refilled to 'test'.
I2015-02-23T06:59:49.243Z]Found notifications!
However, when I call the cloud function "beerFillKeg", it isn't because it's not printing "Found notifications!" or "No notifications":
I2015-02-23T07:00:17.414Z]v1564 Ran cloud function beerFillKeg for user HKePOEWZvC with:
Input: {"name":"Duff"}
Result: {"beer":{"name":"Duff"},"filled":{"__type":"Date","iso":"2015-02-23T07:00:17.485Z"},"id":"olLXh0F54E","kickedReports":[]}
I2015-02-23T07:00:17.438Z]false
I2015-02-23T07:00:17.523Z]Keg updated to Duff.
I2015-02-23T07:00:17.525Z]Sending notifications that keg is refilled to 'Duff'.
I finally understand it. In sendKegRefillNotification, you're calling query.find({...}), then returning an object. That find is asynchronous, and you're doing nothing to wait for the result. I think you need to return the find function call, rather than an object you set within that method.
In other words, you're running along, leaving some async running code behind you.
Edit: I understand what you tried to do. It sort of makes sense. You defined a promise, and thought the caller would wait for the promise. The problem is, the promise is defined in an asynchronous block. It doesn't yet have any meaning at the moment the caller gets it.
It looks like Parse doesn't allow you to run a query from inside a callback from save(). When I moved "notify.sendKegRefillNotification(keg);" to outside of the callback, it worked.
var fillKeg = function(beerName) {
var promise = new Parse.Promise();
var keg = new Keg();
keg.set("beerName", beerName)
keg.set("kickedReports", []);
keg.save(null, { useMasterKey: true }).then(function(keg) {
console.log("Keg updated to " + beerName + ".");
console.log("Send notifications.");
promise.resolve(keg);
},
function(keg, error) {
promise.reject(error);
});
notify.sendKegRefillNotification(keg); // Now this works
return promise;
}
Can anyone shed some more light on why this worked?

How many API Request is considered by Parse.com?

I have a cloud code written
Parse.Cloud.define("getApartmentVendorProduct", function(request, response) {
var isApartmentCallComplete = false;
var isVendorCallComplete = false;
var isProductCallComplete = false;
var result = {};
var apartmentQuery = new Parse.Query("Apartment");
apartmentQuery.find({
success: function(results) {
isApartmentCallComplete = true;
results.apartments = results;
}
});
var vendorQuery = new Parse.Query("Vendor");
vendorQuery.find({
success: function(results) {
isVendorCallComplete = true;
results.vendors = results;
}
});
var productQuery = new Parse.Query("Product");
productQuery.find({
success: function(results) {
isProductCallComplete = true;
results.products = results;
}
});
setInterval(function () {
if (isApartmentCallComplete && isVendorCallComplete && isProductCallComplete) {
response.success(results);
}
}, 50);
});
PS: I'm well aware that setInterval wont work on Parse.. This code is just for understanding.
In this cloud function i'm making 3 Query operation.
From my Android application i'm calling this cloud code.
Here is my question.
How many API request is this considered?
1) 3 API Request made by cloud code and 1 API Request made by Android - Total 4
2) Just 1 API Request made by Android. - Total 1
The option is 1 it makes 4 requests.
I tried with a sample code to test Burst Limit
Parse.Cloud.define("testBurstLimit", function(request, response) {
var globalI = 0;
for(var i = 0; i < 500; i++) {
var productQuery = new Parse.Query("Product");
productQuery.find({
success: function(results) {
console.log("success " + i + " " + globalI);
globalI++;
if (globalI == 250) {
response.success("success");
}
},
error: function(error) {
isApartmentCallComplete = true;
if (isApartmentCallComplete && isVendorCallComplete && isProductCallComplete) {
console.log(error.message + " " + error.code);
}
}
});
}
});
One thing strange i noticed is that. Parse doesn't calculate requests/second, instead it calculates in Requests per/min. Check the response from Parse when i perform the BurstLimit cloud code again and again
{"code":155,"error":"This application performed 1814 requests over the last 28s, and exceeded its request limit. Please retry in 32s"}

Parse.com - Promises - Return the parent object with other one-to-one object as a property

I have 2 tables in parse
Memories and Locations with a relation One-To-One, but the pointer is in Location table ( Pointer )
What i need is to read all memories ten by ten with skip and limit and for each memory to atach a property with location object
By now i have this:
var _ = require('underscore.js');
Parse.Cloud.define("feed", function(request, response) {
var memories = Parse.Object.extend("Memories");
var memoriesQuery = new Parse.Query(memories);
memoriesQuery.skip(0);//request.params.skip);
memoriesQuery.limit(10);//request.params.limit);
memoriesQuery.descending("createdAt");
memoriesQuery.include("group");
var parsedResults = [];
memoriesQuery.find().then(function(memories) {
var promise = Parse.Promise.as();
_.each(memories, function(memory) {
promise = promise.then(function() {
var locations = Parse.Object.extend("Locations");
var locationsQuery = new Parse.Query(locations);
locationsQuery.equalTo("memory", memory);
var subPromise = Parse.Promise();
locationsQuery.first().then(function(location) {
memory["location"] = location;
console.log(JSON.stringify(memory) + " ........ " + memory["location"]);
console.log("=============");
parsedResults.push(memory);
subPromise.resolve(memory);
});
return subPromise ;
});
console.log("-----------");
console.log("Promise:" +promise.toString());
});
return promise;
}).then(function(){
response.success(parsedResults);
});
});
I have no clue what to do.. More than 10 hours of tries.
I appreciate any help!
Finally solved!
Maybe it will help somebody.
Solution:
var _ = require('underscore.js');
var memoriesResult = [];
Parse.Cloud.define("feed", function(request, response) {
var promises = [];
var promise = findMemories();
promise.then(function(memories){
console.log("success promise!!");
_.each(memories, function (memory) {
console.log("each");
promises.push(findLocation(memory));
});
return Parse.Promise.when(promises);
}).then(function(){
console.log("Finish");
response.success(memoriesResult);
}, function(error){
console.error("Promise Error: " + error.message);
response.error(error);
});
});
function findMemories(){
console.log("Enter findMemories");
var memories = Parse.Object.extend("Memories");
var memoriesQuery = new Parse.Query(memories);
memoriesQuery.skip(0);//request.params.skip);
memoriesQuery.limit(10);//request.params.limit);
memoriesQuery.descending("createdAt");
memoriesQuery.include("group");
var promise = new Parse.Promise();
memoriesQuery.find().then(function(memories) {
console.log("Memories found!");
promise.resolve(memories);
}, function(error) {
promise.reject(error);
});
return promise;
}
function findLocation(memory) {
console.log("Enter findLocation");
var locations = Parse.Object.extend("Locations");
var locationsQuery = new Parse.Query(locations);
locationsQuery.equalTo("memory", memory);
var promise = new Parse.Promise();
locationsQuery.first().then(function(location) {
console.log("Location found");
memoriesResult.push({"memory": memory, "location" : location});
promise.resolve(memory);
}, function(error) {
promise.reject(error);
});
return promise;
}
After doing some experiments, I've come up with the following requirements and solution:
You want to page through Memories items a page at a time, sorted by their createdAt
You want the Locations related to each memory
You are limited by the fact that the relationship is Locations->Memories
Your first step is to define the query for memories:
var memoryQuery = new Parse.Query('Memories');
memoryQuery.skip(request.params.skip);
memoryQuery.skip(request.params.limit);
memoryQuery.descending('createdAt');
You can now use this query to limit the Locations objects returned. If your links are all setup correctly you will get back 10 records:
var locationQuery = new Parse.Query('Locations');
// limit to only the page of Memory items requested
locationQuery.matchesQuery('memory', memoryQuery);
// include the Memory pointer's data
locationQuery.include('memory');
var memoriesResult = [];
locationQuery.find().then(function(locations) {
var result = _.map(locations, function(location) {
memory: location.get('memory'),
location: location
});
response.success(result);
}, function(error) {
response.error(error);
});
The only issue with the above is that I'm not sure on the order of the returned records, so you might want to re-sort them before returning them. That is extremely simple though with the underscore library.
This will result in 2 queries no matter the page size.

Resources