Parse: remove user and its related records - javascript

I have Parse table with entities.
User - default class
Commets - class with pointer to _User entity.
I need to delete user from entity User together with all its comments, located in Comments entity:
Right now I have JS Cloud code:
Parse.Cloud.define("deleteUser", function(request, response) {
var User = Parse.Object.extend("User");
var query = new Parse.Query(User);
var userID = request.params.userID;
query.get(userID,{
success: function(User) {
var message = 'success';
User.destroy({
useMasterKey: true ,
success:function() {
response.success(message);
return;
},
error:function(error) {
response.error('Could not delete object '+ User.id);
return;
}
});
},
error: function(object, error) {
var message = 'User could not found';
response.error(message);
}
});
});
It's removing user only.
How I can combine to remove also records from other entity by user?
Thanks for advance

As eth3lbert pointed out in the comments, you should use an afterDelete hook that gets called after the User object has been deleted. You can kick off any other delete operations you want.
However, there is a little gotcha, the before* and after* methods get killed after 3 seconds of wall clock time which might lead to unwanted results depending on the amount of data that you need to delete.
The best solution for you is to setup a background job (they can run for up to 15 minutes), schedule it to run, lets say once every day, and do any cleanup work in that job.
You could create a simple table for that, that contains the objectIds of deleted users, whenever your afterDelete method gets called, you add the deleted users id into that table, your background job then queries that table on run and deletes the content that was associated with it.

You can delete user easily from user request as below.
Parse.Cloud.define('deleteUser', async (req) => {
const user = req.user;
return user.destroy({ useMasterKey: true});
})
and add additional deleting logic related to User.

Related

delete incoming write event after calculations in firebase functions

I have an app that uses firebase, the whole stack pretty much, functions, database, storage, auth, messaging, the whole 9. I want to keep the client end very lightweight. So if a user comments on a post and "tags" another user, let's say using the typical "#username" style tagging, I moved all of the heavy lifting to the firebase functions. That way the client doesn't have to figure out the user ID based on the username, and do everything else. It is setup using triggers, so when the above scenario happens I write to a "table" called "create_notifications" with some data like
{
type: "comment",
post_id: postID,
from: user.getUid(),
comment_id: newCommentKey,
to: taggedUser
}
Where the taggedUser is the username, the postID is the active post, the newCommentKey is retrieved from .push() on the comments db reference, and the user.getUid() is from the firebase auth class.
Now in my firebase functions I have a "onWrite" trigger for that specific table that gets all of the relevant information and sends out a notification to the poster of the post with all the relevant details. All of that is complete, what I am trying to figure out is... how do I delete the incoming event, that way I don't need any sort of cron jobs to clear out this table. I can just grab the event, do my needed calculations and data gathering, send the message, then delete the incoming event so it never even really exists in the database except for the small amount of time it took to gather the data.
A simplified sample of the firebase functions trigger is...
exports.createNotification = functions.database.ref("/create_notifications/{notification_id}").onWrite(event => {
const from = event.data.val().from;
const toName = event.data.val().to;
const notificationType = event.data.val().type;
const post_id = event.data.val().post_id;
var comment_id, commentReference;
if(notificationType == "comment") {
comment_id = event.data.val().comment_id;
}
const toUser = admin.database().ref(`users`).orderByChild("username").equalTo(toName).once('value');
const fromUser = admin.database().ref(`/users/${from}`).once('value');
const referencePost = admin.database().ref(`posts/${post_id}`).once('value');
return Promise.all([toUser, fromUser, referencePost]).then(results => {
const toUserRef = results[0];
const fromUserRef = results[1];
const postRef = results[2];
var newNotification = {
type: notificationType,
post_id: post_id,
from: from,
sent: false,
create_on: Date.now()
}
if(notificationType == "comment") {
newNotification.comment_id = comment_id;
}
return admin.database().ref(`/user_notifications/${toUserRef.key}`).push().set(newNotification).then(() => {
//NEED TO DELETE THE INCOMING "event" HERE TO KEEP DB CLEAN
});
})
}
So in that function in the final "return" of it, after it writes the finalized data to the "/user_notifications" table, I need to delete the event that started the whole thing. Does anyone know how to do that? Thank you.
First off, use .onCreate instead of .onWrite. You only need to read each child when they are first written, so this will avoid undesirable side effects. See the documentation here for more information on the available triggers.
event.data.ref() holds the reference where the event occurred. You can call remove() on the reference to delete it:
return event.data.ref().remove()
The simplest way to achieve this is through calling the remove() function offered by the admin sdk,
you could get the reference to the notification_id through the event, i.e event.params.notification_id then remove it when need be with admin.database().ref('pass in the path').remove(); and you are good to go.
For newer versions of Firebase, use:
return change.after.ref.remove()

Append item to array of fetched item Parse Cloud Code

Javascript
Parse Cloud Code
In my app a User can request to join another Users account. The pseudo code for this is as follows:
send up the username of the account we want to join
search to see if the username exist in the database
if no return
if yes create a new AccountRequest object
add the newly created AccountRequest object to the user we were searching for.
I'm able to do steps 1-4 however I'm having trouble accomplishing #5.
Here is my code that I'm working with.
Parse.Cloud.define("sendAccountAdditionRequest", function(request, response) {
console.log("-sendAccountAdditionRequest");
// Create the query on the User class
var query = new Parse.Query(Parse.User);
// Set our parameters to search on
query.equalTo("username", request.params.adminUsername);
// Perform search
query.find({
// We found a matching user
success: function(results) {
Parse.Cloud.useMasterKey();
var fetchedUser = results[0]
console.log("--found user");
console.log("--creating new AccountRequest");
// Create a new instance of AccountRequest
var AccountRequestClass = Parse.Object.extend("AccountRequest");
var accountRequest = new AccountRequestClass();
// Set the User it is related to. Our User class has a 1..n relationship to AccountRequest
accountRequest.set("user", fetchedUser);
// Set out other values for the AccountRequest
accountRequest.set("phone", request.params.adminUsername);
accountRequest.set("dateSent", Date.now());
// Save the new AccountRequest
accountRequest.save(null,{
// Once the new AccountRequest has been saved we need to add it to our fetched User
success:function(savedRequest) {
console.log("---adding AccountRequest to fetched User");
//
// === This is where stuff breaks
//
var requestRelation = fetchedUser.relation("accountRequest");
// Now we need to add the new AccountRequest to the fetched User. The accountRequest property for a User is array...I'm not sure how I'm suppose to append a new item to that. I think I need to somehow cast results[0] to a User object? Maybe?
requestRelation.add(savedRequest);
// We perform a save on the User now that the accountRequest has been added.
fetchedUser.save(null, {
success:function(response) {
console.log("----AccountRequest complete");
response.success("A request has been sent!");
},
error:function(error) {
// This is printing out: ParseUser { _objCount: 2, className: '_User', id: 'QjhQxWCFWs' }
console.log(error);
response.error(error);
}
});
//
// ================================
//
//response.success("A request has been sent!");
},
// There was an error saving the new AccountRequest
error:function(error) {
response.error(error);
}
});
},
// We were not able to find an account with the supplied username
error: function() {
response.error("An account with that number does not exist. Please tell your administrator to sign up before you are added.");
}
});
});
I believe my problem is fetching the accountRequest relation from the returned search results from the initial query. Another thing to note is that I have not created the accountRequest property of my PFUser, my understanding is that this will automatically be done by Parse when I perform the save function.
Whether the accountRequest will get created for you depends on whether class level permissions are set to allow the client application to add fields. This is probably set to NO for your Parse.User.
But the relation on Parse.User isn't needed anyway, since you've already established it on the AccountRequest class. Given a user, you can get it's accountRequests with:
PFQuery *query = [PFQuery queryWithClassName:#"AccountRequest"];
[query whereKey:#"user" equalTo:aUser];
query.find()...
This is equivalent to getting the relation on User, getting its query and running it.
A couple notes about your code: (a) findOne will save you a line when you know there's just one result, (b) using Parse.Promise would really tidy things up.

Update 2 different collections simultaneously

I have two models defined like this:
var OrganizationSchema = new mongoose.Schema({
users: [mongoose.Schema.Types.ObjectId]
});
and
var UserSchema = new mongoose.Schema({
organizations: [mongoose.Schema.types.ObjectId]
});
When a user want to join an organization I need to update both the organization collection and the user collection. I want to know what is the best way to achieve this ? Is it worth considering the case when one update request fail ? I'm currently doing something like this (where organization is a collection instance of the model Organization):
User.findByIdAndUpdate(req.userSession.userId, { $push: { organizations: organization.id } }, function (err){
if (err)
{
// server error
console.log(err);
}
else
{
organization.users.push(req.userSession.userId);
organization.save(function (err){
if (err)
{
// server error we need to cancel
User.findByIdAndUpdate(req.userSession.userId, { $pull: { organizations: organization.id } }, function (err){
if (err)
{
// we got a problem one collection updated and not the other one !!
console.log(err);
}
});
}
else
{
// success
}
});
}
});
The problem is: if my second update method fail I will end up with one collection updated and not the other ? Is there a way to make sure they are both updated ?
Well firstly, I would stay clear of that design. I would either reference or embed user in organisations and the other way around, not both of them at same time, so I wouldn't have problems like this(which happens every-time you duplicate data).
MongoDB doesn't have support for simultaneous updates, or transactions. So you are left to manage this in your code.
So yes, if the second update fails, then as you wrote your code you have to rollback, and if the rollback fails, you have to retry till it succeeds(though with exponential backoff probably). Keep in mind that might intefer with other requests(another user tries to save the same thing simultaneously). To handle that you have to give a unique to each entry in the array.

Add a new field to a document mongodb

I am very new to mongodb and have a basic question that I am having trouble with. How do I get the ID field of a document that has already been created? I need the ID so i can update/add a new field to the document.
//newProfile is an object, one string it holds is called school
if(Schools.find({name: newProfile.school}).fetch().length != 1){
var school = {
name: newProfile.school
}
Meteor.call('newSchool', school);
//Method 1 (doesn't work)
var schoolDoc = Schools.findOne({name: newProfile.school});
Schools.update({_id: schoolDoc._id}, {$set: {enrolledStudents: Meteor.user()}});
//Method 2?
//Schools.update(_id: <what goes here?>, {$push: {enrolledStudents: Meteor.user()}});
}
else {
//Schools.update... <add users to an existing school>
}
I create a new school document if the listed school does not already exist. Schools need to hold an array/list of students (this is where i am having trouble). How do I add students to a NEW field (called enrolledStudents)?
Thanks!
I'm having some trouble understanding exactly what you're trying to do. Here's my analysis and understanding so far with a couple pointers thrown in:
if(Schools.find({name: newProfile.school}).fetch().length != 1){
this would be more efficient
if(Schools.find({name: new Profile.school}).count() != 1) {
Meteor.call('newSchool', school);
Not sure what you're doing here, unless you this will run asynchronously, meaning by the time the rest of this block of code has executed, chances are this Meteor.call() function has not completed on the server side.
//Method 1 (doesn't work)
var schoolDoc = Schools.findOne({name: newProfile.school});
Schools.update({_id: schoolDoc._id}, {$set: {enrolledStudents: Meteor.user()}});
Judging by the if statement at the top of your code, there is more than one school with this name in the database. So I'm unsure if the schoolDoc variable is the record you're after.
I believe you are having trouble because of the asynchronous nature of Meteor.call on the client.
Try doing something like this:
// include on both server and client
Meteor.methods({
newSchool: function (school) {
var newSchoolId,
currentUser = Meteor.user();
if (!currentUser) throw new Meteor.Error(403, 'Access denied');
// add some check here using the Meteor check/match function to ensure 'school'
// contains proper data
try {
school.enrolledStudents = [currentUser._id];
newSchoolId = Schools.insert(school);
return newSchoolId;
} catch (ex) {
// handle appropriately
}
}
});
// on client
var schoolExists = false;
if (Schools.findOne({name: newProfile.school})) {
schoolExists = true;
}
if (schoolExists) {
var school = {
name: newProfile.school
};
Meteor.call('newSchool', school, function (err, result) {
if (err) {
alert('An error occurred...');
} else {
// result is now the _id of the newly inserted record
}
})
} else {
}
Including the method on both the client and the server allows Meteor to do latency compensation and 'simulate' the insert immediately on the client without waiting for the server round-trip. But you could also just keep the method on the server-side.
You should do the enrolledStudents part on the server to prevent malicious users from messing with your data. Also, you probably don't want to actually be storing the entire user object in the enrolledStudents array, just the user _id.
For what you're trying to do, there is no need to get the _id. When you use update, just switch out the {_id: schoolDoc._id} with your query. Looks like using {name: newProfile.school} will work, assuming that the rest of your code does what you want it to do.
While that would work with the normal Mongo driver, I see that Meteor does not allow your update query to be anything but _id: Meteor throws throwIfSelectorIsNotId exception
First, make sure that you're pulling the right document, and you can try something like this:
var school_id = Schools.findOne({name: newProfile.school})._id;
Schools.update({_id: school_id}, { $push: { enrolledStudents: Meteor.user()}});
If that doesn't work, you'll have to do a little debugging to see what in particular about it isn't working.

CouchDB, Node.js, Cradle - How to get data based on returned data

I am working on a messaging system using node.js + cradle and couchdb.
When a user pulls a list of their messages, I need to pull the online status of the user that sent them the message. The online status is stored in the user document for each registered user, and the message info is stored in a separate document.
Here is the only way I can manage to do what I need, but its hugely inefficient
privatemessages/all key = username of the message recipient
db.view('privatemessages/all', {"key":username}, function (err, res) {
res.forEach(function (rowA) {
db.view('users/all', {"key":rowA.username}, function (err, res) {
res.forEach(function (row) {
result.push({onlinestatus:row.onlinestatus, messagedata: rowA});
});
});
});
response.end(JSON.stringify(result));
});
Can someone tell me the correct way of doing this?
Thank you
Your code could return empty result because you are calling response at the time when user statuses may not yet be fetched from DB. Other problem is that if I received multiple messages from the same user, then call for his status may be duplicit. Below is a function which first fetch messages from DB avoiding duplicity of users and then get their statuses.
function getMessages(username, callback) {
// this would be "buffer" for senders of the messages
var users = {};
// variable for a number of total users I have - it would be used to determine
// the callback call because this function is doing async jobs
var usersCount = 0;
// helpers vars
var i = 0, user, item;
// get all the messages which recipient is "username"
db.view('privatemessages/all', {"key":username}, function (errA, resA) {
// for each of the message
resA.forEach(function (rowA) {
user = users[rowA.username];
// if user doesn't exists - add him to users list with current message
// else - add current message to existing user
if(!user) {
users[rowA.username] = {
// I guess this is the name of the sender
name: rowA.username,
// here will come his current status later
status: "",
// in this case I may only need content, so there is probably
// no need to insert whole message to array
messages: [rowA]
};
usersCount++;
} else {
user.messages.push(rowA);
}
});
// I should have all the senders with their messages
// and now I need to get their statuses
for(item in users) {
// assuming that user documents have keys based on their names
db.get(item, function(err, doc) {
i++;
// assign user status
users[item].status = doc.onlineStatus;
// when I finally fetched status of the last user, it's time to
// execute callback and rerutn my results
if(i === usersCount) {
callback(users);
}
});
}
});
}
...
getMessages(username, function(result) {
response.end(JSON.stringify(result));
});
Although CouchDB is a great document database you should be careful with frequent updates of existing documents because it creates entirely new document version after each update (this is because of it's MVCC model which is used to achieve high availability and data durability). Consequence of this behavior is higher disk space consumption (more data/updates, more disk space needed - example), so you should watch it and run database consumption accordingly.
I think your system could use an in memory hashmap like memcached. Each user status entry would expire after a time limit.
Mapping would be
[user -> lasttimeseen]
If the hashmap contains the user, then the user is online.
On some certain actions, refresh the lasttimeseen.
Then instead of pinging the whole world each time, just query the map itself and return the result.
I'm reminded of this presentation:
Databases Suck for Messaging
And its quote from Tim O'Reilly:
"On monday friendfeed polled flickr nearly 3 million times for 45000 users, only 6K of whom were logged in. Architectural mismatch."
As pointed out in the other answers, updates in CouchDB are expensive and should be avoided if possible, and there's probably no need for this data to be persistent. A cache or messaging system may solve your problem more elegantly and more efficiently.

Categories

Resources