Parse Cloud Code - How to query the User Class - parse-platform

I'm trying to query the Parse User Class but I'm not getting any results. The User class has a column labeled "phone", and I'm passing an array of dictionaries where each dictionary has a key "phone_numbers" that corresponds to an array of phone numbers. I'm trying to determine if a User in my table has one of those phone numbers. I'm not getting any errors running the code, but there does exist a user in my table with a matching phone number. What am I doing wrong?
Parse.Cloud.define("hasApp", function(request, response) {
var dict = request.params.contacts;
var num = 0;
var found = 0;
var error = 0;
var phoneNumbers = "";
for (var i = 0; i < dict.length; i++){
var result = dict[i].phone_numbers;
num += result.length;
for (var j = 0; j < result.length; j++){
phoneNumbers += result[j] + ", ";
var query = new Parse.Query(Parse.User);
query.equalTo("phone", result[j]);
query.find({
success: function(results) {
found = 1;
},
error: function() {
error = 1;
}
});
}
}
response.success("hasApp " + dict.length + " numbers " + num + " found " + found + " error " + error + " phoneNumbers " + phoneNumbers);
});
My response from calling this is
hasApp 337 numbers 352 found 0 error 0 phoneNumbers "list of phone numbers"
where some of those phone numbers appear in my User class. As far as I can tell I'm not getting any errors but I'm also not successfully querying the User table
UPDATE
After moving
response.success("hasApp " + dict.length + " numbers " + num + " found " + found + " error " + error + " phoneNumbers " + phoneNumbers);
to the body of the success block, I get the following error because I'm only allowed to call response.success once per cloud function.
Error Domain=Parse Code=141 "The operation couldn’t be completed. (Parse error 141.)"
UserInfo=0x14d035e0 {code=141, error=Error: Can't call success/error multiple times
at Object.success (<anonymous>:99:13)
at query.find.success (main.js:44:12)
at Parse.js:2:5786
at r (Parse.js:2:4981)
at Parse.js:2:4531
at Array.forEach (native)
at Object.w.each.w.forEach [as _arrayEach] (Parse.js:1:666)
at n.extend.resolve (Parse.js:2:4482)
at r (Parse.js:2:5117)
at Parse.js:2:4531}
Does this mean that I'm only able to verify one phone number at a time? So I can't pass an array of phone numbers and get the PFUser objects corresponding to those phone numbers (if they exist)?
I understand that my internal query to Parse.User is happening synchronously with my "hasApp" call, so is there a way to query Parse.User asynchronously? That way I can respond back to the client after checking all the phone numbers?

You can use Parse.Promise to solve logic where you need to iterate through O(n) number of database queries in one asynchronous Cloud Code definition:
var _ = require("underscore"),
isEmpty = function (o) { // standard function to check for empty objects
if (o == null) return true;
if (o.length > 0) return false;
if (o.length === 0) return true;
for (var p in o) {
if (hasOwnProperty.call(o, p)) return false;
}
return true;
};
Parse.Cloud.define("hasApp", function (request, response) {
var dict = request.params.contacts,
users = [];
var promise = Parse.Promise.as();
_.each(dict, function (obj) {
_.each(obj.phone_numbers, function (num) {
promise = promise.then(function () {
var promiseQuery = new Parse.Query(Parse.User);
promiseQuery.equalTo("phone", parseInt(num));
return promiseQuery.find({
success: function (result) {
if (isEmpty(result)) // don't save empty results e.g., "[]"
return;
users.push(result); // save results to a model to prevent losing it in scope
}
});
});
});
});
return promise.then(function () {
response.success("Found user(s): " + JSON.stringify(users));
});
});
Note a few things about this block:
You can append functionality iteratively to a Parse.Promise.
You can loose scope of database results in your iteration. Use a local array model to save your query results to. Although, I presume there is a better way to achieve the same functionality without the use of a user model, someone else can quote me on that.
Pay close attention to the way Parse handles data. For example, if you are storing your phone numbers as numbers, you have to make sure you use parseInt() when querying for it.
Be aware that you must attach your response.success() function to your promise to assure it is resolved after your iterations have run. From this block, your response from Parse will look similar to an array of User objects. You can decide on the many different ways you can save the data model depending on what you need it for.
As a final note, this block doesn't account for any validation or error handling that should be accounted for otherwise.

The problem seems to be that your response.success call is happening before the query can even happen. While you have response.success calls in your query success block, they are never called because you return success before the query is executed.
Try commenting out the line:
response.success("hasApp " + dict.length + " numbers " + num + " found " + found + " error " + error + " phoneNumbers " + phoneNumbers);
This should let the code go to your query, and maybe move it into the success block of your query.
Let me know if this works.

You're now calling response.success in a loop now. You should only call response.success/response.error once per cloud function.
It would help if you can show the original code with no commented out lines (to show your original intention) and the new code with no commented out lines as two separate code samples.

Querying a Parse.User is fairly easy and well explained in the documentation, here is an example taken from it
var query = new Parse.Query(Parse.User);
query.equalTo("gender", "female"); // find all the women
query.find({
success: function(women) {
// Do stuff
}
});
For your case it will be something like this:
Parse.Cloud.define("getAllFemales", function(request, response) {
var query = new Parse.Query(Parse.User);
query.equalTo("gender", "female"); // find all the women
query.find({
success: function(women) {
// Do stuff
}
});
});
Hope it helps, as always more info on the Documentation

Related

Is there a way to speed up/batch Google Calendar read/writes?

I am new to Google Apps Script and learning javascript as I go about this project. Over the course of the introductory codelabs I noted the best practice to read all the data into an array with one command, perform operations, and then write it with one command.
I understood how to do this working with Google Sheets but how do I achieve this working with Google Calendar? I have come across a few links discussing batching with Google Calendar API and Advanced Google Services but I didn't understand how to make use of the information.
I basically hope to batch edit events instead of accessing Google Calendar repeatedly in a for loop.
function deleteMonth() {
// Set Date range to delete
var today = new Date();
var firstDay = new Date(today.getFullYear(), today.getMonth(), 1);
var lastDay = new Date(today.getFullYear(), today.getMonth() + 1, 0);
// read spreadsheet data and get User Info from ss
var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
var idSheet = spreadsheet.getSheetByName('User Info');
//Get users from sheet in array of objects with properties from column header in
//'User Info' (name, email, year, calName, calID, early, late)
var userInfo = getSheetData(idSheet);
var deletedNames = "";
for (i = 0; i < userInfo.length; i++) {
var calID = userInfo[i].calID;
// if we have calID proceed to delete events
if (calID) {
console.time("get events");
var calendar = CalendarApp.getCalendarById(calID);
var events = calendar.getEvents(firstDay, lastDay);
console.timeEnd("get events");
// Delete events and add deleted name to string
// deletedNames
for (i = 0; i < events.length; i++) {
console.time("delete event");
deletedNames += events[i].getTitle() + ", ";
events[i].deleteEvent();
console.timeEnd("delete event");
}
}
}
spreadsheet.toast("Deleted events: \n" + deletedNames);
}
Time output from console.time():
Other related links which sounded relevant:
Using advanced google services (apps script resource)
Google Developer blog?
I believe your goal as follows
You want to delete all events in a month for several calendars using the batch process with Google Apps Script.
You want to reduce the process cost of above process.
For this, how about this answer?
Issue and workaround:
Calendar API can process with the batch requests. The batch requests can run 100 requests by one API call and can process with the asynchronous process. By this, I think that the process cost can bereduced. But, in the current stage, unfortunately, several calendar IDs cannot be used in one batch. When the requests including several calendar IDs, an error of Cannot perform operations on different calendars in the same batch request. occurs. So in your case, all events in a month in one calendar ID can be deleted in one batch requests. It is required to request the number of calendar IDs. I would like to propose this as the current workaround.
By the way, as the modification point of your scrit, in your script, the variable i is used in 1st for loop and 2nd for loop. By this, all values of userInfo are not used. Please be careful this.
Sample script:
Before you run the script, please enable Calendar API at Advanced Google services.
function deleteMonth() {
var today = new Date();
var firstDay = new Date(today.getFullYear(), today.getMonth(), 1);
var lastDay = new Date(today.getFullYear(), today.getMonth() + 1, 0);
var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
var idSheet = spreadsheet.getSheetByName('User Info');
var userInfo = getSheetData(idSheet);
var deletedNames = "";
var requests = []; // For batch requests.
for (i = 0; i < userInfo.length; i++) {
var req = [];
var calID = userInfo[i].calID;
if (calID) {
var calendar = CalendarApp.getCalendarById(calID);
var events = calendar.getEvents(firstDay, lastDay);
for (j = 0; j < events.length; j++) {
deletedNames += events[j].getTitle() + ", ";
var e = events[j];
req.push({
method: "DELETE",
endpoint: `https://www.googleapis.com/calendar/v3/calendars/${calID}/events/${e.getId().replace("#google.com", "")}`,
});
}
}
requests.push(req);
}
// Run batch requests.
requests.forEach(req => {
const limit = 100;
const split = Math.ceil(req.length / limit);
const boundary = "xxxxxxxxxx";
for (let i = 0; i < split; i++) {
const object = {batchPath: "batch/calendar/v3", requests: req.splice(0, limit)};
const payload = object.requests.reduce((s, e, i) => s += "Content-Type: application/http\r\nContent-ID: " + i + "\r\n\r\n" + e.method + " " + e.endpoint + "\r\nContent-Type: application/json; charset=utf-8\r\n\r\n" + JSON.stringify(e.requestBody) + "\r\n--" + boundary + "\r\n", "--" + boundary + "\r\n");
const params = {method: "post", contentType: "multipart/mixed; boundary=" + boundary, payload: payload, headers: {Authorization: "Bearer " + ScriptApp.getOAuthToken()}, muteHttpExceptions: true};
var res = UrlFetchApp.fetch("https://www.googleapis.com/" + object.batchPath, params);
console.log(res.getContentText())
}
})
spreadsheet.toast("Deleted events: \n" + deletedNames);
}
Note:
Please use this script with V8.
References:
Advanced Google services
Sending Batch Requests
Events: delete

2 items added to DynamoDB when I run putItem

I am working on a bookmark skill for Alexa to teach myself DynamoDB. I've got over various hurdles, and can now write to my table. The issue is that whenever I putItem it adds two items. I'm trying to store the userID (partition key in DynamoDB), the timestamp of the request (as a string, and the sort key in DynamoDB), the title of a book and the page the user is on. This issue has only started since I tried working with a composite key, but I think I will need both these fields to a) get a unique primary key and b) be able to find the last item saved by a user.
Here's my intent code in Lambda:
'addBookmark': function() {
//delegate to Alexa to collect all the required slot values
var filledSlots = delegateSlotCollection.call(this);
//Get slot values as variables
var userID = this.event.session.user.userId;
var pageNumber = this.event.request.intent.slots.pageNumber.value;
var bookTitle = this.event.request.intent.slots.bookTitle.value;
//DynamoDB expects the timestamp as a string, so we convert it
var timeStamp = Date.now().toString();
var params = {
TableName: 'bookmarkV6',
Item: {
'userID' : {S: userID},
'timeStamp': { S: timeStamp },
'bookTitle': { S: bookTitle },
'pageNumber': { N: pageNumber },
}
};
//Call DynamoDB to add the item to the table
ddb.putItem(params, function(err, data) {
if (err) {
console.log("Error", err);
}
else {
console.log("Success", data);
}
});
const speechOutput = "OK, I've made a note that you're on page " + pageNumber + " of " + bookTitle + ".";
this.response.cardRenderer("Bookmark", "Page " + pageNumber + " of " + sentenceCase(bookTitle) +"\n \n" + stringToDate(timeStamp));
this.response.speak(speechOutput);
this.emit(':responseReady');
},
The "duplicate" items have slightly different timestamp values.
I am also having the same issues. It is happening of delegate collections used, but not able to solve. I have delegate slot confirmation for 6 slots and when I give all 6 slots value, finally I end up with 7 records in the table.
In delegateSlotCollection() function, return "COMPLETED" in the else block and in your addbookmark intent , please check like below after your delegateSlotCollection.call method
var filledSlots = delegateSlotCollection.call(this);
if(filledSlots==='COMPLETED'){
place all your save dynamodb logic here.
}

Get a list of keys returned from PFObject Javascript

I'm working on some cloud code right now where I have an id I query against which returns all the data for that row.
I then need to iterate over all the fields (columns) of data and make some changes to the values then update that row.
Im able to get the data from parse but im not sure how to pull out the PFObject keys to iterate over the data in a for loop , make changes then save.
Here is some sample code where I hardcoded a field value in but I'm not sure how to get the fields, then iterate over them in a for loop..
Also excuse the JS code, I its been years since I wrote any JS.
<script type="text/javascript">
Parse.initialize("xxxx", "xxxx");
var LocationTag = Parse.Object.extend("LocationTags");
var query = new Parse.Query(LocationTag);
query.equalTo("SomeId", "302d87f2-0188-4cbe-bc2c-e6dcbf822539");
query.find({
success: function(results) {
for (var i = 0; i < results.length; i++) {
var object = results[i];
var data = object.get('T0fYiV9PeeU'); <--- hardcoded field key.. i need to iterate over all fields returned from the row..
count = data.length;
for (var c = 0; c < count; c++) {
var res = Number(data[c].split(":")[0]);
text += "Value: " + res + "<br>";
sum += parseInt(res);
}
document.getElementById("main").innerHTML = text + ' sum: ' + sum + ' average: ' + sum/100 + results
}
},
error: function(error) {
alert("Error: " + error.code + " " + error.message);
}
});
Any ideas.. sorry again if its just a simple JS issue.. but I need to iterate over all fields, returned in the PFObject
If I understand correctly, you want a list of properties from a Parse.Object. The easiest way to to this is to call .toJSON() on the Parse.Object and then extract the keys from the json.
Example:
for (var i = 0; i < results.length; i++) {
var pfObject = results[i];
var jsonObject = pfObject.toJSON();
var pfKeys = [];
for(var key in pfKeys){
if(jsonObject.hasOwnProperty(key)){
pfKeys.push(key);
}
}
//Now we have a list of the pfObject keys in pfKeys
}

is my if statement doing what i think its doing?

Here I have tis function that is querying data and returning it to me and im putting that data in to html elements to make a post.my if statement at the bottom is where im having a bit of problem i trying to only apply my comment window once to the new clones once they have been pushed over to the new div called story board, i believe im telling my if statement that if the class already exists in that new clone then do nothing else apply it there.. to seee what i am talking about...here is my test domain...http://subdomain.jason-c.com/
sign in is "kio" pass is the same and when you hit publish on the stories, everytime a nw one hits it will apply comment box to a post in the storyboard window that already has a comment text area. what am i doing wrong.
function publishWindowHandler(){
var query = new Parse.Query('Post');
console.log(currentUser);
query.equalTo("User", currentUser);
query.include("User");
query.descending("createdAt")
console.log(user.get('username'));
query.find({
success:function(results){
document.getElementById("publishCenter").textContent = "";
for(var i =0; i < results.length; i++){
var userPost = results[i];
//console.log(userPost.get("User") + " / " + userPost.get("Author") + " / " + userPost.get("Story") + " / " + userPost.get("objectId"));
var authorTitle = document.createElement("p");
var newPost = document.createElement("P");
var title = document.createElement("P");
var userLabel = document.createElement("p");
var postId = userPost.id;
var postBtn = document.createElement("INPUT");
postBtn.className ="publishBtn";
postBtn.id ="publishBtn";
postBtn.setAttribute("Type", "button");
postBtn.setAttribute("value", "Publish");
title.textContent = "Story: " + userPost.get("Title");
authorTitle.textContent = "Author: " + userPost.get("Author");
newPost.textContent = userPost.get("Story");
userLabel.textContent = "Published by: " +userPost.get("User").get ("username");
var postWrapper = document.createElement("DIV");
postWrapper.className = "postWrapper";
postWrapper.id = postId;
document.getElementById("publishCenter").appendChild(postWrapper);
postWrapper.appendChild(title);
postWrapper.appendChild(authorTitle);
postWrapper.appendChild(newPost);
postWrapper.appendChild(userLabel);
postWrapper.appendChild(postBtn);
postBtn.addEventListener("click", publicViewHandler);
function publicViewHandler(){
$(this).parent(".postWrapper").clone().appendTo(".storyBoard");
function testWindow(){
if($(publicBoard).children().hasClass(".commentWindow")){
}
else
{
$(".storyBoard").children().append(commentWindow);
}
}
testWindow();
}
}
}
})
}
According to the documentation, jquery hasClass doesn't need '.' prefixing the passed in class name.
https://api.jquery.com/hasclass/
Try removing that and see if that get's you anywhere.
Also, where is the variable commentWindow defined? Is it global?
var myClone = $(this).parent().clone(true);
myClone.appendTo(".storyBoard");
console.log(publicBoard);
console.log("hello",$(this));
console.log($(publicBoard).find('.postWrapper').find("commentWindow"));
myClone.append($(commentWindow).clone());
this is what i ended up doing to solve my issue took me a while and a little help from a friend.

What exactly am I sending through the parameters?

When doing a XMLHttpRequest and using POST as the form method, what exactly am I sending? I know it should be like send(parameters), parameters = "variable1=Hello", for example. But what if I want to do this:
parameters = "variable1=" + encodeURIComponent(document.getElementById("variable1").value);
variable1 being the id of the HTML form input.
Can I do it like this or do I need to assign the encodeURIComponent value to a javascript variable and send that variable:
var variable2;
parameters = "variable2=" + encodeURIComponent(document.getElementById("variable1").value);
You're suppose to send the object and it's value, but, is it an object from the HTML form, a javascript object or a php object? The problem is I already tried it and I still can't get the encoded input in my database, all I get is the raw input from the user.
BTW, I know it's a pretty dull question, but I feel the need to understand exactly what I'm doing if I want to come up with a solution.
g
function createObject()
{
var request_type;
var browser = navigator.appName;
if(browser == "Microsoft Internet Explorer")
{
request_type = new ActiveXObject("Microsoft.XMLHTTP");
}
else
{
request_type = new XMLHttpRequest();
}
return request_type;
}
var http = createObject();
//INSERT
function insert()
{
var Faculty2 = encodeURIComponent(document.getElementById("Faculty").value);
var Major2 = encodeURIComponent(document.getElementById("Major").value);
var Professor2 = encodeURIComponent(document.getElementById("Professor").value);
var Lastname2 = encodeURIComponent(document.getElementById("Lastname").value);
var Course2 = encodeURIComponent(document.getElementById("Course").value);
var Comments2 = encodeURIComponent(document.getElementById("Comments").value);
var Grade2 = encodeURIComponent(document.getElementById("Grade").value);
var Redflag2 = encodeURIComponent(document.getElementById("Redflag").value);
var Owner2 = encodeURIComponent(document.getElementById("Owner").value);
//Location and parameters of data about to be sent are defined
//Required: verify that all fields are not empty. Use encode URI() to solve some issues about character encoding.
var params = "Faculty=" + Faculty2 + "&Major=" + Major2 + "&Professor=" + Professor2 + "&Lastname=" + Lastname2 + "&Course=" + Course2 + "&Comments=" + Comments2 + "&Grade=" + Grade2 + "&Redflag=" + Redflag2 + "&Owner=" + Owner2;
var url = "prehp/insert.php";
http.open("POST", url, true);
//Technical information about the data
http.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
http.setRequestHeader("Content-length", params.length);
//Now, we send the data
http.onreadystatechange = function()
{
if(http.readyState == 4)
{ var answer = http.responseText;
document.getElementById('insert_response').innerHTML = "Ready!" + answer;
}
else
{document.getElementById('insert_response').innerHTML = "Error";
}}
http.send(params);
}
PHP code:
$insertAccounts_sql = "INSERT INTO Information (Faculty, Major, Professor, Lastname, Course, Comments, Grade, Redflag, Owner)
VALUES('$_POST[Faculty]','$_POST[Major]','$_POST[Professor]','$_POST[Lastname]','$_POST[Course]','$_POST[Comments]','$_POST[Grade]','$_POST[Redflag]','$_POST[Owner]')";
$dbq = mysql_query($insertAccounts_sql, $dbc);
if($dbq)
{
print "1 record added: Works very well!";
}
else
if(!$dbq)
{
die('Error: ' . mysql_error());
}
$dbk = mysql_close($dbc);
if($dbk)
{
print "Database closed!";
}
else
if(!$dbk)
{
print "Database not closed!";
}
I did that but the value that the database got was the raw input and not the encoded input. I'm running out of ideas, don't know what else to try. Could it be the settings of the database, can the database be decoding the input before storing it? That seems far-fetched to me, but I've been looking at this from all angles and still can't come up with a fresh answer.
PS: Sorry for posting my comments on the answer area, first timer here.
when creating query strings, it has really nothing to do with objects or anything like that. All you want to be sending is key/value pairs. how you construct that is up to you, but it often neater and more manageable to assign your values to variables first. i.e.
var myVar1Value = encodeURIComponent(document.getElementById('variable1').value);
var myVar2Value = encodeURIComponent(document.getElementById('variable2').value);
var url = "http://www.mydomain.com?" + "var1=" + myVar1Value + "&var2=" + myVar2Value;
It's called a query string, so it's just a string. what you do with it on the server side is what makes the 'magic' happen.
edit: If you're having problems with values, then you should print them to console to verify you are getting what you expect.

Resources