CloudCode multiple query in query doesn't work - javascript

I'm trying to find users I didn't connect to before with a cloud function. There are few tables I'm using:
Users table - The standard table with an added "hasLight" boolean column
Connected table:
'user' - pointer to the user (first side)
'userId' - objectId of the user
'user2' - pointer to the user we connect with
'userId2' - objectId of the user we connected to
Available table:
'userObjectId' - objectId of the user
'counter' - number
The code:
Parse.Cloud.define("findFreshUser", function(request, response) {
Parse.Cloud.useMasterKey(); // For reading all the table //
var user = Parse.User.current();
// Get all possible users we already connected to //
var connectQuery = new Parse.Query("Connected");
connectQuery.include('userId2');
connectQuery.equalTo("userId", Parse.User.current().id);
// Get all users with availability of '0' //
var availableQuery = new Parse.Query("Available");
availableQuery.notEqualTo("counter", 0);
var freshUserQuery = new Parse.Query(Parse.User);
freshUserQuery.doesNotMatchKeyInQuery("objectId", "userId2", connectQuery); // We haven't connected before - THIS DOEN'T WORK !!! //
freshUserQuery.doesNotMatchKeyInQuery("objectId", "userObjectId", availableQuery); // We won't use '0' availability users - THIS WORKS //
freshUserQuery.equalTo("hasLight", false); // user must not have a light //
freshUserQuery.descending("updatedAt");
freshUserQuery.limit(1); // We need only 1 user //
freshUserQuery.find({
success: function(results) {
if (results.length>0){
console.log("Found the user "+ results[0].id);
response.success(results[0].id);
}else{
response.error("No user found");
}
},
error: function() {
response.error("No user found");
}
});
});
For some reason CloudCode completely ignoring connectQuery (all other statements are fine) when using 2 doesNotMatchKeyInQuery statements:
When using only
freshUserQuery.doesNotMatchKeyInQuery("objectId", "userId2", connectQuery);
and comment out
freshUserQuery.doesNotMatchKeyInQuery("objectId", "userObjectId", availableQuery);
it does work. So I think it is related to using both at same time, they are probably conflicting each other. What do I need to do to make both apply?
It feels like it's some parse issue but I'm really new to CloudCode so it's probably something I'm not doing right.
Note: Pay attention that I even don't compare the user object itself but the id of it (this is as part of isolating the issue). Means, I know I could make the code and DB lots nicer.

You have to use Promise to achieve such query : blog.parse.com/learn/engineering/whats-so-great-about-javascript-promises/

Related

Cloud Code Not able to find users installations on Parse database

I have a Friends table that contains userIds for a user list of friends. The user column is a pointer to the User table and the friendId does the same.
I'm attempting to find all of the users that equal the "friendId" for a particular user and send notifications to those users.
I've added a "user" column to my Installations table which is a pointer to "User" so that I can find my installations for specific users and send a push notification.
The issue I am having is I an unable to link those queries together to get send my push notifications to my list of friends.
Any suggestions are helpful.
My current cloud code
Parse.Cloud.define("pushCheckIn", function(request, response) {
// Find users near with pending messages
var friendList = Parse.Object.extend("Friends");
var username = request.params.username;
var location = request.params.location;
var userQuery = new Parse.Query(friendList);
userQuery.equalTo("friendId", Parse.User);
// Find devices associated with these users
var pushQuery = new Parse.Query(Parse.Installation);
pushQuery.matchesQuery("user", userQuery);
var alertMsg = username + " checked-in at " + location;
Parse.Push.send({
where: pushQuery,
data: {
alert: alertMsg,
sound: "beep-shinymetal.caf",
title: alertMsg
}
}, {
success: function() {
response.success(alertMsg);
// Push was successful
},
error: function(error) {
response.error("push failed");
}
});
});
Your description of your tables is complicated and I don't fully understand, however I think that new Parse.Query(friendList); should rather be new Parse.Query("Friends"); and userQuery.equalTo("friendId", Parse.User); doesn't make sense at all, if it is string you should use userQuery.equalTo("friendId", yourFriendIdString);
If you try to explain your tables better, I can try to give you better advice :)
The Installations table can not be queried directly without using the master key. To query the installations table, use
Parse.Cloud.useMasterKey();
before executing the query

After updating object (_User) and later retrieving it, field is not updated (Parse)

ISSUE
I have been trying to update a field (gamesArray) from the current User (Parse.User.current()), so later in another view I can see it. The problem is that when I do it, the field gets updated (I console.log() it right after the object is saved and check it on the Database UI from Parse) but when I go to the other view, in which the User is retrieved and I show the field on screen, it is not updated.
CONTEXT
My _User has an array of Games called gamesArray so I update that array just like this:
addGameToUser = function(game, userId) {
var userQuery = new Parse.Query(Parse.User);
userQuery.equalTo("objectId", userId);
userQuery.first({
success: function(user) {
// User found
},
error: function(error) {
console.log(error.message);
}
}).then(function(user) {
// If user is current user update it
if(user.id == Parse.User.current().id) {
var games = user.get('gamesArray');
games.push(game);
user.set('gamesArray', games);
user.save(null, {
success: function(user) {
console.log(user.get('gamesArray'));
},
error: function(user, error) {
console.log(error);
}
});
}
else {
// Call cloud function...
}
});
}
In this case, console.log(user.get('gamesArray')) returns the updated field. I use this function when the user creates a new game.
The problem is that in a different view, when I retrieve the user and get that field, it is not updated:
getUserGames: function() {
var games = Parse.User.current().get('gamesArray');
console.log(games);
}
Here, console.log(games) is printing the old field value. So if I had 4 games in the array, the previous function printed 5 (4 plus the created one) and this still prints 4.
I thought that maybe I was not saving the Game properly, but this is the output of the Parse's Database UI gamesArray column of the current User:
[{"__type":"Pointer","className":"Game","objectId":"..."}]
The only way that I can get the updated field is logging out and logging in with the same user.
QUESTIONS
Why is that? What am I doing wrong? How could I "update" the user so I don't have to log out?
It sounds like your user object that you are reading from is not the same specific object that you are updating. So you are updating the object in your database, but the user object you're reading from doesn't know that the object in the database has been updated. You should try fetching the user first. iOS has a fetchIfNeeded method.

Parse.com security: can I save an object and claim it's another user's?

I'm looking at this example of modeling a blog system using javascript, code snippet copied as below:
var user = Parse.User.current();
// Make a new post
var Post = Parse.Object.extend("Post");
var post = new Post();
post.set("title", "My New Post");
post.set("body", "This is some great content.");
post.set("user", user);
post.save(null, {
success: function(post) {
// Find all posts by the current user
var query = new Parse.Query(Post);
query.equalTo("user", user);
query.find({
success: function(usersPosts) {
// userPosts contains all of the posts by the current user.
}
});
}
});
It basically creates a post object and sets the current user object to its user field. To show all blog posts by the current user, it queries all blog posts with the user field set to the current user.
But since the User table by default is read only to all users, wouldn't this be problematic that a malicious user (X) can create random posts and "claim" that they are create by another user (Y), by setting the user field of those posts to Y as he queries from the User table? So the consequence would be that when the system shows posts for user Y, he would see all his true posts in addition to the post that was "forged" by X.
Is the mitigation that the User table needs to be ACL'd somehow? But if it is the solution, then why is the default behavior that an arbitrary user can see the entire User table?
Cloud Code is your friend here.
In this case you want a beforeSave handler that locks the user field to the currently authenticated user on new objects, and rejects the save if they're updating a post and trying to change the user field (or just using ACLs to prevent everyone except the post owner from modifying Post rows).
Something like this:
Parse.Cloud.beforeSave('Post', function(request, response) {
var post = request.object;
var user = request.user;
if (post.isNew()) {
post.set('user', user);
response.success();
} else {
// any special handling you want for updates, e.g.:
if (post.dirty('user')) {
response.error('Cannot change the owner of a Post!');
} else {
response.success();
}
}
});
My recommended approach to handling updates for something like a "Post" would be to prevent all updates. In the "Set permissions" for the class in the Data Browser I would change the following:
Update : Disabled
Delete : Disabled
To disable something just untick the "Any user can perform this action". Optionally you might want to assign a Role like "Administrator" or "Moderator" to allow those people to directly update/delete items.
These functions would then only be possible from Cloud Code when useMasterKey() is used, e.g.:
Parse.Cloud.define('deletePost', function(request, response) {
var postID = request.params.postID;
var query = new Parse.Query('post');
query.get(postID).then(function (post) {
if (post) {
// post found
var postOwner = post.get('user');
if (postOwner.id == request.user.id) {
// we let the owner delete their own posts
// NOTE: need to use the master key to modify/delete posts
Parse.Cloud.useMasterKey();
post.destroy().then(function () {
// TODO: delete all replies too?
response.success();
}, function (error) {
response.error(error);
});
} else {
response.error('Only the owner of a post can delete it!');
}
} else {
// post not found, might as well respond with success
response.success();
}
}, function (error) {
response.error(error);
}
});
But since the User table by default is read only to all users,
wouldn't this be problematic that a malicious user can create random
posts and "claim" that they are create by another user, by setting the
user field to the other user?
You can play around with curl to explore this.
IMO - you are right about world read on the _User class. So what. That is read.
When it comes to POST action, you are going to need an authenticated session as the user in question. You cant just spuuf things by claiming that u are a user that you read on the table.
try curl posts without an established session as the user. You will get a 403 or some 'illegal access' response.

Error using parse cloud code that allows users to essentially like each others profile.

So I am building an app, and in this app users will be able to go to each others' profile, and essentially like their profile. I am calling these likes compliments. This requires the current user to be able to access the selected users information to update the compliment count. So in order to implement this I am using Parse.com's cloud code.
My code looks like:
Parse.Cloud.define("complimentCounter", function(request, response) {
Parse.Cloud.useMasterKey();
var User = Parse.Object.extend("User");
var user = new User();
var user.id = request.params.userId;
var increment = request.params.increment;
user.increment = ("complimentsValue", increment);
user.save(null, {
success: function(user) {
response.success(true);
},
error: function(user, error) {
response.error("Could not compliment.");
}
});
});
However when I run it I get the error "Unexpected token . in main.js:8", and when I take that "." out it only returns the error of the function and not the success. Can someone please guide me in the right direction, and let me know what the issue is here? Thanks!
The increment() function is documented here.
The correct way to call it is:
// increment by 1
user.increment("keyName");
// increment by a number in a variable (yes you can use negative numbers too)
var increaseAmount = -5;
user.increment("keyName", increaseAmount);
You're reading a value, which seems to mean you want to increase the count by more than one, so the 2nd syntax is what you want.
Unfortunately you're calling it incorrectly, just take the = out of your line so it reads as follows:
user.increment("complimentsValue", increment);

Parse.com: Find all objects belonging to a user with objectId

I have a Class in parse, say Pictures. Each of these belongs to a user. Reference to this user is stored in the Pictures table/class as a Pointer to the user.
In my cloud code I am trying to get all Pictures belonging to a user, using master key. Following is my code:
Parse.Cloud.define("getPictures", function(request, response) {
Parse.Cloud.useMasterKey();
var query = new Parse.Query("Pictures");
query.equalTo("user", request.params.user);
query.find({
success: function(results) {
var status = "Found " + results.length + " pictures for userId " + request.params.user;
response.success(status);
},
error: function() {
status = "No pictures exist for userId " + request.params.user;
response.error(status);
}
});
});
This code outputs that there are 0 pictures for a certain user with id 'xyz' for example. However, I can see that the user has a lot of pictures stored.
I have also verified that the problem is not with using master key, as I see in the console log that the code is being executed as master. Moreover, if I query for a picture by objectId, it does come out in the results, which means ACL is not the problem here.
I think I have to use relations/joining here, but I am not sure how to do that.
Pointers are stored as objects in Parse database, so if you try to compare a string to an object with query.equalTo() function, nothing will be found. This is how pointers are stored:
{
__type: 'Pointer',
className: '_User',
objectId: user-object-id
}
If you are querying a class with pointers and want your result comes with the whole object nested, you should set this in your query:
var query = new Parse.Query('Pictures');
query.include('user');
In my queries when I want to search by a pointer column, I compare my user object with the nested user object.
var user = new Parse.User();
// Set your id to desired user object id
user.id = your-user-id;
var query = new Parse.Query('Pictures');
// This include will make your query resut comes with the full object
// instead of just a pointer
query.include('user');
// Now you'll compare your local object to database objects
query.equalTo('user', user);
query.find({
success: function(userPicture) {
response.success(userPicture);
}
});
Anyway, seems that if you have many pictures related to an user, you probably are searching for parse relations instead of pointers: https://www.parse.com/docs/relations_guide
If you write a query to retrieve a parent object and a child object to which you have pointer, but no read access as per ACL, then the query may return only parent object and child will be null because the ACL wont let you read it.
There may be a problem with your params. If "user" is a pointer, then 'request.params.user' is incorrect, because PFObjects may not be sent as params. If "user" is a pointer, use 'request.user'. If request.params.user is a string of the userId, you could use the Id to reconstruct a PFObject shell before the query as was suggested by Murilo already, but deleting the "user" param and using request.user would shorten your code and not duplicate any values. Murilo's solution is also beneficial because you could pass a userId other than the current user's Id.

Categories