How to check access to document based on related ones? - node.js

I have the following document schemas:
var CalendarModel = mongoose.model('Calendar', mongoose.Schema({
author: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
required: true
},
rooms: [{
_id: {type: mongoose.Schema.Types.ObjectId},
title: {type: String}
}]
}));
var EventModel = mongoose.model('Event', mongoose.Schema({
room: {
type: mongoose.Schema.Types.ObjectId,
required: true
},
startTime: {
type: Date
},
endTime: {
type: Date
}
}));
Each event related to specific room by their id. I have the following cases:
User could create/update/delete/edit event
User could edit room (only title at now)
When user issues query to perform on of the cases I need to check access to perform operation.
Let's say user can delete some event by _id and he could if he is owner of calendar where event relates to specific room.
What is the best practice to check writes when user tries to delete event?
EDIT: basically I'm looking for best practices for storing permissions for each user's calendar and make fast checking for access. I know that I may query mongo every time that user makes operation (read/edit and etc) but it's overhead. So I need better solution to store application state

Related

MongoDB pipeline to get Posts that User has liked, sorted by most recent

I currently have a table of posts which has the post details and an ID, and a likes table which contains entries of a user ID, a post ID, and a timestamp.
const likeSchema = Schema({
user: {
type: mongoose.Types.ObjectId,
ref: "User",
required: true
},
listing: {
type: mongoose.Types.ObjectId,
ref: "Listing",
required: true
},
timestamp: {
type: Date,
required: true
}
});
I am trying to return an list of posts that a user has liked, ordered by the like timestamp.
I understand that this can probably be done in some way with a aggregation/pipeline although I'm struggling to figure it out.
My alternative would be to fetch all likes of a user, then fetch all listing ID's associated with them in a separate request, although this seems inefficient and doesn't keep them in order of the like timestamp.

Mongoose TTL for user saved tokens

I have this User schema.
const userSchema = new Schema({
name: {
type: String,
required: true,
},
email: {
type: String,
required: true,
unique: true
},
password: {
type: String,
required: true
},
tokens: [{
token: {
type: String,
required: true,
}
}], {timestamps: true});
I would like the token provided will expiresAt given time.
Any suggestions?
That is not possible with mongodb.
You can check this document: MongoDb Index TTL
TTL indexes are special single-field indexes that MongoDB can use to
automatically remove documents from a collection after a certain
amount of time or at a specific clock time. Data expiration is useful
for certain types of information like machine generated event data,
logs, and session information that only need to persist in a database
for a finite amount of time.
You can create a new collection for user tokens and relate them on model. I think this could be a better practice. Then you can use ttl index on token collection

Retweet schema in MongoDB

What is the best way to model retweet schema in MongoDB? It is important that I have createdAt times of both original message and the time when retweet occurred because of pagination, I use createdAt as cursor for GraphQL query.
I also need a flag weather the message itself is retweet or original, and id references to original message and original user and reposter user.
I came up with 2 solutions, first one is that I keep ids of reposters and createdAt in array in Message model. The downside is that I have to generate timeline every time and for subscription its not clear what message to push to client.
The second is that I treat retweet as message on its own, I have createdAt and reposterId in place but I have a lot of replication, if I were to add like to message i have to push in array of every single retweet.
I could use help with this what is the most efficient way to do it in MongoDB?
First way:
import mongoose from 'mongoose';
const messageSchema = new mongoose.Schema(
{
text: {
type: mongoose.Schema.Types.String,
required: true,
},
userId: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
required: true,
},
likesIds: [{ type: mongoose.Schema.Types.ObjectId, ref: 'User' }],
reposts: [
{
reposterId: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
},
createdAt: { type: Date, default: Date.now },
},
],
},
{
timestamps: true,
},
);
const Message = mongoose.model('Message', messageSchema);
Second way:
import mongoose from 'mongoose';
const messageSchema = new mongoose.Schema(
{
text: {
type: mongoose.Schema.Types.String,
required: true,
},
userId: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
required: true,
},
likesIds: [{ type: mongoose.Schema.Types.ObjectId, ref: 'User' }],
isReposted: {
type: mongoose.Schema.Types.Boolean,
default: false,
},
repost: {
reposterId: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
},
originalMessageId: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Message',
},
},
},
{
timestamps: true,
},
);
const Message = mongoose.model('Message', messageSchema);
export default Message;
Option 2 is the better choice here. I'm operating with the assumption that this is a Twitter re-tweet or Facebook share like functionality. You refer to this functionality as both retweet and repost so I'll stick to "repost" here.
Option 1 creates an efficiency problem where, to find reposts for a user, the db needs to iterate over all of the repost arrays of all the messageSchema collections to ensure it found all of the reposterIds. Storing ids in mongo arrays in collection X referencing collection Y is great if you want to traverse from X to Y. It's not as nice if you want to traverse from Y to X.
With option 2, you can specify a more classic one-to-many relationship between messages and reposts that will be simpler and more efficient to query. Reposts and non-repost messages alike will ultimately be placed into messageSchema in the order the user made them, making organization easier. Option 2 also makes it easy to allow reposting users to add text of their own to the repost, where it can be displayed alongside the repost in the view this feeds into. This is popular on facebook where people add context to the things they share.
My one question is, why are three fields being used to track reposts in Option 2?
isReposted, repost.reposterId and repost.originalMessageId provide redundant data. All that you should need is an originalMessageId field that, if not null, contains a messageSchema key and, if null, signifies that the message is not itself a repost. If you really need it, the userId of the original message's creator can be found in that message when you query for it.
Hope this helps!

Friend Request System with Express & MongoDB

I am trying to let users send friend requests to other users similar to that of Facebook and other social media platforms. I have started creating this functionality, but quickly got stuck since I am new to mongoDB and the whole Schema thing.
Here are the models I currently have:
// User Schema
var UserSchema = new Schema({
_id: {
type: Number
},
name: {
type: String
},
username: {
type: String,
index: true
},
password: {
type: String,
required: true
},
email: {
type: String
},
friends: [{
friendName: {
type: Schema.ObjectId,
required: true,
ref: 'User'
},
duration: {
type: Number
}
}]
});
// Friendship Schema
var FriendshipSchema = new Schema({
participants: [{
type: Schema.Types.ObjectId,
ref: 'User'
}],
requestTo: {
type: Schema.Types.ObjectId,
ref: 'User'
},
accepted: {
type: Boolean,
default: false
},
user: {
type: Schema.ObjectId,
ref: 'User'
}
});
var Friendship = module.exports = mongoose.model('Friendship', FriendshipSchema);
var User = module.exports = mongoose.model('User', UserSchema);
This is as far as I have gotten. From here, I do not know how to use these schemas to establish friendships between 2 users. My ultimate goal is to have a button on a webpage that sends a friend request to the intended recipient, where they can then accept or deny the request.
Any help with this would be awesome, since I do not know what to do from here with these 2 schemas. Thanks!
We would need to take one schema only which is userSchema(as is Israel said above, you only need an array/object to list your friendship on the userSchema). But we will need to add another schema(said it friendRequestSchema).
FriendRequest schema would be:
- ID user request (int)
- ID user recipient (int)
- status (int) //let say 1= requested, 2=accepted, 3=rejected
And the controller it should be from the user A click "Friend Request" button on your user B page.
Friend Request Button will call a function let saying it "sendFriendRequest()"
If function running it would be recorded on friendRequest DB, which is will record ID of user A(as requester), ID of user B and request status.
If request status = 1 then user B will be notified and give him two option which is accepted and rejected.
User B accept or reject it
If user press button accept, then the status updated in friendRequest DB to be=> 2 (Accepted). Then, you have to call another function to add user ID A to friendship list of User B. Conversely. Then if you want to make a notification you can call it as well.
Else user B will press reject (status will be => 3) then notif it.
UserSchema
var UserSchema = new Schema({
name: String,
username: {
type: String,
index: true
},
password: {
type: String,
required: true
},
email: String,
friendship: [String] });
Then FriendRequestschema
var FriendRequestSchema = new Schema({
requester: {
type: int,
required: true
},
recipient: {
type: int,
required: true
},
status:
type: int,
required: true });
This just to let you know, how its work. More complex method about (sendrequest,block .etc) you can check this link, It's flow process for PHP, but you can easily move it to your js. Hope it help you man.
Your model can be improved, and your code can be cleaned:
First, you don't need the brackets if you only give type for the field:
var UserSchema = new Schema({
name: String,
username: {
type: String,
index: true
},
password: {
type: String,
required: true
},
email: String,
friends: [String]
});
This should be a simplified version of your schema. The _id field doesn't need to be specified because mongoose creates it automatically. If you wanna put a customized value there, just do it when you insert.
Second:
If you wanna reference other users, why not to use only a simple array that contains ids from other users. For example, if you have user A, the "friendship" of this user are user ids contained in his "friends" field.
{id:12345, username:"A", password:***, email:"a#fakemail.com", friends:[B_id,C_id,D_id]}
In that case, whenever you wanna make a list of friends of A, you can just perform a $lookup operation in mongodb and it will fill the other users information for you.
I don't think I covered all of your questions, but I hope my answer was helpful.

MONGODB MULTI PARAMETER SEARCH QUERY

I have the following schema:
var ListingSchema = new Schema({
creatorId : [{ type: Schema.Types.ObjectId, ref: 'User' }],//LISTING CREATOR i.e. specific user
roommatePreference: { //preferred things in roommate
age: {//age preferences if any
early20s: { type: Boolean, default: true },
late20s: { type: Boolean, default: true },
thirtys: { type: Boolean, default: true },
fortysAndOld: { type: Boolean, default: true }
},
gender: {type:String,default:"Male"}
},
roomInfo: {//your own location of which place to rent
address: {type:String,default:"Default"},
city: {type:String,default:"Default"},
state: {type:String,default:"Default"},
zipcode: {type:Number,default:0},
},
location: {//ROOM LOCATION
type: [Number], // [<longitude>, <latitude>]
index: '2d' // create the geospatial index
},
pricing: {//room pricing information
monthlyRent: {type:Number,default:0},
deposit: {type:Number,default:0},
},
availability:{//room availability information
durationOfLease: {
minDuration: {type:Number,default:0},
maxDuration: {type:Number,default:0},
},
moveInDate: { type: Date, default: Date.now }
},
amneties : [{ type: Schema.Types.ObjectId, ref: 'Amnety' }],
rules : [{ type: Schema.Types.ObjectId, ref: 'Rule' }],
photos : [{ type: Schema.Types.ObjectId, ref: 'Media' }],//Array of photos having photo's ids, photos belong to Media class
description: String,//description of room for roomi
status:{type:Boolean,default:true}//STATUS OF ENTRY, BY DEFAULT ACTIVE=TRUE
},
{
timestamps:true
}
);
The application background is like Airbnb/Roomi app, where users can give their rooms/places on rent. Now i want to implement a filter for a user finding the appropriae listing of room.
Here creatorId, rules, amneties are refIds of other schemas. I want to write a query which will give me listings based on several parameters,
e.g. user can pass rules, pricing info, some amneties, gender etc in req queries.
The query parameters depends upon user's will.
Is there any way to do nested query like thing for this?, like the way we did in SQL.
Well, mongodb is not made to be used as relational DB.
instead, i would suggest transforming amenities array into an array of objects with the amenities embeded inside the Listings schema.
so you can query as follows:
// Schema
ListSchema = mongoose.Schema({
....
amneties: [{aType: 'shower'}]
// or you can make it a simple array of strings:
// amneties: ['shower']
....
})
// query
Listings.find({'amneties.aType' : <some amenity>})
there are no joins in mongodb, you can still make "joins" as mongoose calls them populate, but they are happening on your server, and every populations requires a round trip to the server.
if you still wish to use references to the amneties collection, you should query it first and populate the Listing object on them.

Resources