Retweet schema in MongoDB - node.js

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!

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 aggregate and append

I have a Mongo DB (latest version) that I am accessing with Mongoose (v6.5.4)
The project is using a discriminator pattern to keep all documents in the same collection.
There are many instances where i need to join documents.
Set up:
// Models:
const UserSchema = new Schema<IUser>(
{
firstName: {
type: String,
required: true,
},
lastName: {
type: String,
required: true,
},
email: {
type: String,
required: true,
unique: true,
},
});
// There are other similar models to <Team>
const TeamSchema = new Schema<ITeam>(
{
name: {
type: String,
required: true,
},
userIds: {
type: [Schema.Types.ObjectId],
required: true,
ref: "User",
default: [],
},
});
Problem:
I can use populate to return collections of Teams and the userIds be an array of user objects.
Where I am stuck is querying getting an array of users with an added field of teams[].
I've been trying aggregate to no success, I can loop over the users collection and return a list of Teams but this feels wrong and expensive in terms of read units (production data base is on a pay as you go service)
As data models go there is not much going for it - but it is an existing solution
Can anyone advise?
I was being stupid. The from field in my look up was wrong.
Should have been 'teams' not 'Team' which is the model name.

How to perform a cascading deleting in MongoDB or Mongoose

I'm working on an e-commerce project in Express and MongoDB. I'm confused with architecture on how to make relationship between two models such that if I delete one element from a table, all of it's associations should be deleted. CASCADE delete, if I'm using the correct term.
I'm not a database designer, just learning Express and MongoDB. So apologies if my schema is not that good.
I have two tables (or documents in MongoDB). One is Order with schema below:
const orderSchema = new mongoose.Schema({
shippingInfo : {
type: mongoose.Types.ObjectId,
ref: 'Address'
},
user : {
type: mongoose.Types.ObjectId,
ref: 'User',
required: true
},
orderItems: [
{
type: mongoose.Types.ObjectId,
ref:'OrderItem'
}
],
totalPrice: {
type: Number,
required: true,
default: 0.0
},
status: {
type: String,
enum: ['processing', 'shipped', 'delivered','cancelled'],
default: 'processing'
},
deliveredAt: {
type: Date,
}
})
and OrderItems
const orderItemSchema = new mongoose.Schema({
product: {
type: mongoose.Types.ObjectId,
ref: 'Product'
},
name: {
type: String,
required: true
},
quantity: {
type: Number,
required: true
},
price: {
type: Number,
required: true
},
image: {
type: String,
required: true
},
})
I want if I delete an Order, all of its OrderItems should be deleted right away (using remove middleware in Order).
I know that Django has something called on_delete=model.CASCADE when we create relationships, but I'm unaware of such thing in Mongoose.
I don't want to explicitly make another API request to search for and delete all OrderItems that are referenced in orderItems array in an Order, once it is deleted. There has to be a better approach for this.
Another post on Stack Overflow suggested that in remove middleware of Order I should do something like
OrderItem.find({ order_id: this._id }).remove().exec();
That would require me to refer order_id in OrderItem right?
And this would create circular dependency since OrderItem would require Order to be created first and vice versa.
What should I do here? Should I change the schema for both tables i.e. remove orderItems entry from Order and instead add order_id in OrderItem? Or is there a Mongoose way to overcome this situation?

Setting up a complex comment model in NodeJs and mongoose

I am setting up a comment model where users can post comments reference and can also reply. the complication comes with the reply part. I want users to be able to reply to comments or others' replies, and I am lost on how to set up my model for that.
How should I set up my model to be able to capture that data in my reply?
also, any other suggestion would be appreciated
Here is the model I am currently setting up
const mongoose = require('mongoose')
const commentSchema = new mongoose.Schema({
owner: {
type: mongoose.Schema.Types.ObjectId,
required: true,
ref: 'User'
},
reference: {
type: mongoose.Schema.Types.ObjectId,
required: false,
ref: 'Project' || null,
default: false
},
body: {
type: String,
required: true,
trim: true
},
reply: {
owner: {
type: mongoose.Schema.Types.ObjectId,
required: false,
ref: 'User'
},
body: {
type: String,
required: true
}
}
}, {
timestamps: true
})
const Comment = mongoose.model('Comment', commentSchema)
module.exports = Comment
If you are thinking about a model where we have
some post
>commentA
>replyA-a
>replyA-a-a
>replyA-a-a-a
>replyA-b
>commentB
>commentC
I would aggregate everything for the corresponding entity
Comment {
user,
body,
replies: [Comment] // pattern composite
}
EntityComment { // only persist this one
reference: { id, type: post|topic|whatever },
comment: [Comment]
}
Props are:
an entityComment can grow big (is this problematic?)
no need for multiple fetch, everything's there
easy to "hide" some comments and just show its count (array length)
If record entityComment becomes too big (the max record length seems to be 16MB so likely not be the limit, but maybe the payload is slow to load), then
we can think of saving each comment (using replies: [{ ref: Comment, type: ObjectId)}])
but maybe a better idea is to use a reference for body (body: [ref: CommentBody, type: ObjectId])
The reason is body is likely the culprit (datasize wise), and this would allow to
keep everything nested in entityComment
delay the fetch of the bodies we are interested in (not the whole hierarchy)
There are tradeoffs:
is fine for read
is simpler for writes (just update/delete a singular comment)

How to check access to document based on related ones?

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

Resources