mongodb: Deleting a user and all data references associated with it - javascript

Currently working on a personal website for myself and ran in to some difficulties with mongodb & mongoose when trying to delete a user.
Right now I have two Schema's show below.
UserSchema:
const userSchema = new mongoose.Schema({
username: {
type:String,
required: true,
unique: true,
},
password: String,
firstName: String,
lastName: String,
eMail: String,
status: {type:Boolean, default: true},
hobbies: [
{
type: mongoose.Schema.Types.ObjectId,
ref: "Hobby"
}
]
HobbySchema:
const hobbySchema = new mongoose.Schema({
title: String,
user: {
id: {
type: mongoose.Schema.Types.ObjectId,
ref: "User"
},
username: String
},
desc: String,
iconURL: String
});
My issue right now is that when I delete the user using the following route with nodeJS:
router.delete('/delete/:id', async (req,res) => {
await User.findOneAndDelete(req.params.id, (err, foundUser) => {
if(err){
console.log(err);
}else{
res.redirect("/logout");
}
})
});
I run into the issue that the Hobby collection still contains all hobbies that the user created. I'm looking to have them all deleted when the user wants to delete their account.I understand that Mongodb/Mongoose is a non-relation database and is unlike SQL as I'm coming from SQL. Is there any way to delete all hobbies a user created? (Just creating hobbies as a very basic example).
What is the best alternative? Just going with SQL lite and changing the database entirely?
Thanks

Related

Friend Request System - Express, MongoDB, EJS

I want to create a social network thus allowing users to send and interact with frind requests. As of now I have created the register, log-in and "search for other users function".
When I find and select another user, I display their user-info and have created a "Add friend" button.
Can anyone help me in a direction of the creation of the "Add friend" option? I have looked around for some time now, and not been able to find the correct solution. Below I have attached my UserSchema and route for finding users:
//User Schema
const UserSchema = new mongoose.Schema({
firstName: {
type: String,
required: true
},
lastName: {
type: String,
required: true
},
password: {
type: String,
required: true
},
},{ collection: 'Users' });
//Get single user based on ID
router.get('/user/get:id', ensureAuthenticated, function (req, res) {
MongoClient.connect(DBUri,{useUnifiedTopology: true }, function (err, db) {
let dbo = db.db(DBName);
const query = {_id: objectId(req.params.id)}
dbo.collection("Users").find(query).toArray(function(err, resultTasks) {
if (err) throw err;
res.render('../View/findFriend', {
resultTasks: resultTasks
});
db.close();
});
});
});
You can add something like this in your user schema:
friends: [{ type : ObjectId, ref: 'User' }],
OR
friends: userSchema
Take the one which suits you.
What that will do is add an array to the user, Then you can store IDs of friends.(Who are other users, hence the ref: 'User')
Then, When you have to fetch users you can do:
User.find(<ID or whatever you have to find Users>).populate('friends')
Also, To push a new friend simply use: user.friends.push(newFriend._id)

Mongoose deleteMany subdocuments and related subdocuments

I have a document Project with an array of subdocuments, with a schema Tasks. Tasks has an array of subdocuments with a schema Comments.
const projectSchema = new Schema({
_id: Schema.Types.ObjectId,
name: { type: String, required: true, unique: true },
description: { type: String, default: '' },
tasks: [{ type: Schema.Types.ObjectId, ref: 'Threads' }]
});
module.exports = mongoose.model('Project', projectSchema);
const tasksSchema = new Schema({
projectId: { type: Schema.Types.ObjectId },
_id: Schema.Types.ObjectId,
title: { type: String, required: true },
text: { type: String, required: true },
comments: [{ type: Schema.Types.ObjectId, ref: 'Replies' }]
})
module.exports = mongoose.model('Tasks', tasksSchema);
const commentSchema = new Schema({
taskId: { type: Schema.Types.ObjectId },
_id: Schema.Types.ObjectId,
text: { type: String, required: true }
})
module.exports = mongoose.model('Comment', commentSchema);
When I delete the Project document I want to delete every Task and every Comment relate to that project.
To delete the Project I use findOneAndDelete so I set up a post middleware to delete all the Tasks
projectSchema.post('findOneAndDelete', function(doc, next) {
mongoose.model('Tasks').deleteMany({ projectId: doc._id }).exec();
next();
})
But now I don’t know how to delete every comment, because deletemany returns an object with the result of the operation.
Should I map the array of Tasks and call findOneAndDelete every time and then delete every single comment? It looks very inefficient for a lot of tasks.
How about embedding comments in post? since its one to many(not huge) relation. So in your code where you delete a project, you first delete all posts, which contain all the comments, only after it succeeds you delete the project. It will also benefit your read performance significantly because you just have to return a single post document instead of multiple(1post + many comment) documents.
Embedding post to project could also be possible, but depending on the size and number of possible posts, its probably better to keep it as a separate document.
In this case you need some logic to ensure consistency.
Here you could use mongodb's new feature, transaction. But I think for this case a transaction is not necessary.(Also I find it quite unstable for now) You could go with the "eventual consistency" method.
Basically you just delete all the posts related to a project and then delete a project. And then you run batches to check for any inconsistency.(check if there are any posts where its project doesnt exist. If it doestnt then delete the posts)

How to Populate nested queries

I have a typical Project with Node.js - Express - Mongoose - MongoDB
My goal is to generate a News feed by calling all the recent articles of all the users that the main User follows.
User Model
var UserSchema = new Schema({
username: { type: String, required: true, unique: true },
password: { type: String, required: true },
email: String,
articles : [{ type: Schema.Types.ObjectId, ref: 'Article' }],
follows: [{ type: Schema.Types.ObjectId, ref: 'User' }],
followers: [{ type: Schema.Types.ObjectId, ref: 'User' }]
});
Article Model
var articleSchema = new Schema({
_userID: { type: Schema.Types.ObjectId, ref: 'user' },
url: String,
summary: String,
title: String,
content: String,
image_url: String,
});
The Problem:
I am trying to write a mongoose query that does the following
Search by userID and get a list of follows
populate those followers recent articles
I can figure out how to populate the follows but I do not know how to then also populate the follows's respected articles and return them in chronologically order (recent > oldest)
exports.getFeed = function(req, res) {
var id = mongoose.Types.ObjectId(req.params.id);
// mongoose query goes here
};
Any help would be greatly appreciated.
You'd need two queries.
The first query is the one you already figured out and need anyway (get the list of follows).
You basically need an array of the follows IDs and then you ask for all articles from this user array:
.where('_userID').in(arrayOfFollowIDs)
Hope that helps

mongoose document filtering properties

I have defined a schema like
var UserSchema = new Schema({
firstName: { type: String, required: true },
lastName: { type: String, required: true },
email: { type: String, required: true },
location: { type: String, required: true },
picture: { type: String, required: true },
passwordHash: { type: String, required: true },
resetPasswordToken: String,
resetPasswordExpired: Boolean
});
I have a REST Endpoint which return list of all users. In that list I want to hide some properties i.e, passwordHash, resetPasswordToken, resetPasswordExpired
I defined a custom filter function like below
var doFilterUser = function(user) {
_.omit(user, ['passwordHash', 'resetPasswordToken', 'resetPasswordExpired']);
user.id = user._id;
delete user._id;
delete user.__v;
return user;
};
_ is lodash
When I check my API is responding with all user properties
This filter function is defined in common helper module and I am calling it like
User.findOne({_id: id}, function(err, user) {
var filtered = helper.doFilterUser(user);
});
How to resolve this issue?
Try this:
You are allowed to access certain values through mongoose.
User.findOne({_id: id}, 'firstName lastName email location picture', function(err, user){
console.log(user);
});
You just mention the fields needed, after the query.
Hope it helps....
The problem here is that you still have a mongoose document that conforms to s strict schema. If you want to change that document, then you need to make it a "raw" object without all the additional controls:
User.findOne({_id: id}, function(err, user) {
var filtered = helper.doFilterUser(user.toObject());
});
So the .toObject() method here will return an object in it's raw form. That allows you to manipulate the keys how you wish.
You can also explicitly direct it not to serve back certain properties. Useful if you don't want to render a hashed password over the wire. The find method would look like this:
User.find({}, '-id -__v',function(err,users){
})
or
User.findOne({_id: id}, '-id -__v',function(err,user){
})

Relational database design to mongoDB/mongoose design

I have recently started using mongoDB and mongoose for my new node.js application. Having only used relational databases before I am struggling to adapt to the mongoDB/noSQL way of thinking such as denormalization and lack of foreign key relationships. I have this relational database design:
**Users Table**
user_id
username
email
password
**Games Table**
game_id
game_name
**Lobbies Table**
lobby_id
game_id
lobby_name
**Scores Table**
user_id
game_id
score
So, each lobby belongs to a game, and multiple lobbies can belong to the same game. Users also have different scores for different games. So far for my user schema I have the following:
var UserSchema = new mongoose.Schema({
username: {
type: String,
index: true,
required: true,
unique: true
},
email: {
type: String,
required: true
},
password: {
type: String,
required: true
}
});
So my question is, how would I go about structing the relational design into mongoDB/mongoose schemas? Thanks!
EDIT 1
I have now tried to create all the schemas but I have no idea if this is the right approach or not.
var UserSchema = new mongoose.Schema({
_id: Number,
username: {
type: String,
index: true,
required: true,
unique: true
},
email: {
type: String,
required: true
},
password: {
type: String,
required: true
},
scores: [{ type: Schema.Types.ObjectId, ref: 'Score' }]
});
var GameSchema = new mongoose.Schema({
_id: Number,
name: String
});
var LobbySchema = new mongoose.Schema({
_id: Number,
_game: { type: Number, ref: 'Game' },
name: String
});
var ScoreSchema = new mongoose.Schema({
_user : { type: Number, ref: 'User' },
_game : { type: Number, ref: 'Game' },
score: Number
});
Mongoose is designed in such a way that you can model your tables relationally with relative ease and populate relational data based on the ref you defined in the schema. The gotcha is that you need to be careful with populating. If you populate too much or nest your populations you will run into performance bottle necks.
Your approach in Edit 1 is largely correct however you usually don't want to populate a remote ref based on a Number or set the _id of a model to a Number since mongo uses it's own hashing mechanism for managing the _id, this would usually be an ObjectId with _id implied. Example as shown below:
var ScoreSchema = new mongoose.Schema({
user : { type: Schema.Types.ObjectId, ref: 'User' },
game : { type: Schema.Types.ObjectId, ref: 'Game' },
score: Number
});
If for some reason you need to maintain a number id for your records consider calling it uid or something that won't conflict with mongo / mongoose internals. Good luck!
First of all, you are hitting on some good points here. The beauty of Mongoose is that you can easily connect and bind schemas to a single collection and reference them in other collections, thus getting the best of both relational and non-relational DBs.
Also, you wouldn't have _id as one of you properties, Mongo will add it for you.
I've made some changes to your schemas using the mongoose.Schema.Types.ObjectId type.
var UserSchema = new mongoose.Schema({
username: {
type: String,
index: true,
required: true,
unique: true
},
email: {
type: String,
required: true
},
password: {
type: String,
required: true
},
scores: [{ type: Schema.Types.ObjectId, ref: 'Score' }]
});
var GameSchema = new mongoose.Schema({
name: String
});
var LobbySchema = new mongoose.Schema({
_game: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Game'
},
name: String
});
var ScoreSchema = new mongoose.Schema({
_user : {
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
},
_game : {
type: mongoose.Schema.Types.ObjectId,
ref: 'Game'
},
score: Number
});
This will allow you to query your database and populate any referenced collections and objects.
For example:
ScoreSchema.find({_id:##userIdHere##})
.populate('_user')
.populate('_game')
.exec(function(err, foundScore){
if(err){
res.send(err)
} else {
res.send(foundScore)
}
}
This will populate the related user and game properties.
As you edited the post, I think it would be good. At least not bad :)
Check Mongoose Query Population. It's very useful to get related data.
var mongoose = require('mongoose'),
ObjectId = mongoose.Schema.Types.ObjectId
// code, code, code
function something(req, res) {
var id = req.params.id
// test id
return Lobby.findOne({_id: new ObjectId(id)})
.populate('_game')
.exec(function(error, lobby) {
console.log(lobby._game.name);
});
}
Two ways (that I know of). You store an id (that is indexed) and once you query the first table, you then query the second table to grab info from that, as there are no joins. This means that if you grab say, user id's from one table, you will then need to make multiple queries to the user table to get the user's data.
The other way is to store it all in one table, even if it's repetitive. If all you need to store is for example, a user's screen name with something else, then just store it with the other data, even if it's already in the user table. I'm sure others will know of better/different ways.

Categories