Mongoose/Mongodb basic trello like scheme problem with rendering in vue - javascript

I'm creating a very basic functionality kanban board.
My board has 4 models so far:
User model
var userSchema = new Schema({
name: {
type: String,
required: true
}
})
module.exports = mongoose.model('User', userSchema)
Board model
var boardSchema = new Schema({
title: {
type: String,
required: true
},
lists: [ listSchema ]
members: [
{
type: Schema.Types.ObjectId,
ref: 'user'
}
]
});
module.exports = mongoose.model('Board', boardSchema)
List schema
let listSchema = new Schema({
title: {
type: String,
required: true
},
userCreated: {
type: Schema.Types.ObjectId,
required: true,
ref: 'user'
},
boardId: {
type: Schema.Types.ObjectId,
required: true,
ref: 'board'
},
sort: {
type: Number,
decimal: true,
required: true
}
})
module.exports = mongoose.model('List', listSchema)
Card schema
var cardSchema = new Schema({
title: {
type: String,
required: true
},
description: {
type: String
},
boardId: {
type: Schema.Types.ObjectId,
required: true,
ref: 'board'
},
listId: {
type: Schema.Types.ObjectId,
required: true,
ref: 'list'
},
members: [
{
type: Schema.Types.ObjectId,
ref: 'user'
}
],
sort: {
type: Number,
decimal: true,
required: true
}
})
module.exports = mongoose.model('Card', cardSchema)
What am I looking for?
My front-end is made with Vue.js and sortable.js drag and drop lib.
I want to find the best way to render board with lists (columns) and cards in them.
From what I understand, I should get my board first, by the users id in members array.
Then I have my board which has lists embedded already.
On second api request, I get all the cards by boardId.
My question is - how do I correctly put/render all the cards into each owns lists?
So in the end I want to have something like:
{
title: 'My board',
lists: [
{
title: 'My list',
_id: '35jj3j532jj'
cards: [
{
title: 'my card',
listId: '35jj3j532jj'
}
]
},
{
title: 'My list 2',
_id: '4gfdg5454dfg'
cards: [
{
title: 'my card 22',
listId: '4gfdg5454dfg'
},
{
title: 'my card 22',
listId: '4gfdg5454dfg'
}
]
}
]
members: [
'df76g7gh7gf86889gf989fdg'
]
}
What I've tried?
I've came up with only one thing so far, that is:
Two api calls in mounted hook - one to get the board with lists, second to get all cards.
Then I loop trough lists and loop trough cards and push each card into the list by id?
But this way it seems that my lists would need to have an empty array called cards: [] just for the front-end card-to-list sorting by id, seems somehow wrong.
Is this a good way? Or should I redesign my models and schemas and go with some other way? Any help would be appreciated!

The schema you've defined is pretty good, just one modifications though.
No need to have 'lists' in Board model, since it's already available in lists and also if you keep it in boards, then everytime a new list is added, you'll need to edit the board as well.
Here's how the flow would be.
Initially, when a user signs in, you'll need to show them the list of boards. This should be easy since you'll just do a find query with the user_id on the board collection.
Board.find({members: user_id}) // where user_id is the ID of the user
Now when a user clicks on a particular board, you can get the lists with the board_id, similar to the above query.
List.find({boardId: board_id}) // where board_id is the ID of the board
Similarly, you can get cards with the help of list_id and board_id.
Card.find({boardId: board_id, listId: list_id}) // where board_id is the ID of the board and listId is the Id of the list
Now, let's look at cases wherein you might need data from 2 or more collection at the same time. For example, when a user clicks on board, you not only need the lists in the board but also the cards in that board. In that case, you'll need to write an aggregation as such,
Board.aggregate([
// get boards which match a particular user_id
{
$match: {members: user_id}
},
// get lists that match the board_id
{
$lookup:
{
from: 'list',
localField: '_id',
foreignField: 'boardId',
as: 'lists'
}
}
])
This will return the boards, and in each board, there'll be an array of lists associated with that board. If a particular board doesn't have a list, then it'll have an empty array.
Similarly, if you want to add cards to the list and board, the aggregation query will be a bot more complex, as such,
Board.aggregate([
// get boards which match a particular user_id
{
$match: {members: user_id}
},
// get lists that match the board_id
{
$lookup:
{
from: 'list',
localField: '_id',
foreignField: 'boardId',
as: 'lists'
}
},
// get cards that match the board_id
{
$lookup:
{
from: 'card',
localField: '_id',
foreignField: 'boardId',
as: 'cards'
}
}
])
This will add an array of cards as well to the mix. Similarly, you can get cards of the lists as well.

A bit late to the answer but I think it'll help someone nevertheless. The problem you have could be solved using aggregation framework. While the other answer mentions a pretty good way, it still doesn't have the cards data embedded into it.
MongoDB docs show a way for nested aggregation queries. Nested Lookup
A similar approach could be used for your question.
Board.aggregate([
{
$match: { _id: mongoose.Types.ObjectId(boardId) },
},
{
$lookup: {
from: 'lists',
let: { boardId: '$_id' },
pipeline: [
{ $match: { $expr: { $eq: ['$boardId', '$$boardId'] } } },
{
$lookup: {
from: 'cards',
let: { listId: '$_id' },
pipeline: [{ $match: { $expr: { $eq: ['$listId', '$$listId'] } } }],
as: 'cards',
},
},
],
as: 'lists',
},
},
]);
This will include the cards as an array inside of every list.

Related

Many population layer mongose

I have a problem finding a solution to populate a filed populated(the number of populate unknowen)
export const FilesSchema = new mongoose.Schema(
{
name: {
type: String,
trim: true,
required: true,
unique: true,
},
children: [
{
type: mongoose.Schema.Types.ObjectId,
ref: 'Files',
},
],
},
{ timestamps: true },
);
it represent a structure of file that can be a file or a folder.
a folder can contient another folder and another folder with files
the number of population are unknowen . any help with this ?
i found a solution
Say you have a user schema which keeps track of the user's friends.
const userSchema = new Schema({
name: String,
friends: [{ type: ObjectId, ref: 'User' }]
});
Populate lets you get a list of a user's friends, but what if you also wanted a user's friends of friends? Specify the populate option to tell mongoose to populate the friends array of all the user's friends:
User.
findOne({ name: 'Val' }).
populate({
path: 'friends',
// Get friends of friends - populate the 'friends' array for every friend
populate: { path: 'friends' }
});
https://mongoosejs.com/docs/populate.html#deep-populate

How to update multiple objects in array in a single mongodb document using mongoose

I know that this can be a repeat question but none of the existing answers solved my problem.
I have a friendsSchema like below:
const friendsSchema = new mongoose.Schema({
owner: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Player'
},
friends: [{
friendId: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Player'
},
isFromFacebook: {
type: Boolean,
default: false
},
thisFriendReceivedGiftTimeStamp: {
type: Date,
default: null
},
thisFriendSentGiftTimeStamp: {
type: Date,
default: null
}
}]
}, {
timestamps: true
});
The client is sending me an array of friendsIds.
So, I want to find all objects in friends array which matches all friendsIds and update their thisFriendReceivedGiftTimeStamp to Date.now().
Consider the client is sending 100s of ids in friendsIds array, what will be the most efficient way to achieve the result.
Thanks in advance.
db.collection.update({IF YOU WANT TO ADD QUERY ACCORDING TO OWNER FIELD PLEASE ADD HERE},
{
"$set": {
"friends.$[x].thisFriendReceivedGiftTimeStamp": new Date()
}
},
{
"arrayFilters": [
{
"x.friendId": {
$in: [
1,
3,
5
]
}
}
],
"multi": true
})
arrayFilters should work great here. But I am not 100% sure about efficiency
Here is a working mongoplayground link

Impossible to query nested mongoose array?

I want to Query and array with regex inside and mongoose (mongoDB) model.
I want to search inside the nested array of the Productmodel :
const productSchema = new schema(
{
name: requiredString,
sku: requiredUniqueNumber,
ean: requiredUniqueNumber,
suppliers: [{ type: mongoose.Schema.Types.ObjectId, ref: SupplierModel }],
categories: [{ type: mongoose.Schema.Types.ObjectId, ref: CategoryModel }],
mainImage: requiredString,
images: [{ type: String }],
description: requiredString,
stock: requiredNumber,
price: requiredNumber,
totalOrders: requiredNumber,
reviews: [review],
},
{
timestamps: true,
count: true,
}
);
The model inside the "suppliers" array is:
const supplierSchema = new schema(
{
supplierName: requiredUniqueString,
invoiceAddress: address,
visitAddress: address,
status: supplierStatusEnum,
contacts: address,
accountType: accountTypeEnum,
logo: requiredString,
user: { type: schema.Types.ObjectId, ref: "products" },
},
{
timestamps: true,
}
);
Now here's the problem, if i query and and populate() i get all the results. But for some reason I cannot search inside the Array containing several suppliers. Here's of what i have:
foundProducts = await ProductModel.find({
$or: [
{
name: {
$regex: regex,
},
},
{
"suppliers.supplierName": {
$regex: regex,
},
},
{
description: {
$regex: regex,
},
},
],
});
The object in JSON:
If he finds that the suppliers model contains the regex he should give back the whole porductmodel containing that supplier.
What is the best way to search in all the items inside of an nested array.
ps. I'm a junior developer comming from PostgreSQL, so bare with me while I'm trying this noSQL "thing" :)
I was doing the wrong query. I need to do
{
"suppliers._id": {
$regex: regex,
},
},
I can only search on _id, since this is the way that i "modeled" it.

How to push an element into an array inside an object inside another array in Mongoose?

I am developing a server using Expressjs, Mongodb and Mongoose. I need to push an element (a string) into the "tweets" array which is inside an object (a friend) which is in turn inside the "friends" array which is inside a "user" Object which document in the "users" collection. Here is an example of how my documents in the Mongodb collection looks like:
{
"loggedIn": true,
"_id": "5f91ef0ce75d3b1d40539da0",
"username": "username",
"email": "a#h.com",
"password": "$2a$10$9krWS9Kq5024lRTexqaweePrn8aughepqTkaj3oA48x0fJ2ajd79u",
"dateOfBirth": "2002-12-07",
"gender": "male",
"friends": [
{
"tweets": [],
"_id": "5f91effae75d3b1d40539da7",
"username": "Jonas"
},
],
"__v": 0
}
I need to pick the specified username from the "Users" arrary first and then access "friends" array within this user and then pick the right friend object and finally push the tweet on $position: 0 in this array. I I tried to achieve that as shown in this code and I could access the friend object with the given friendUsername
await Users.updateOne(
{ username: req.params.username },
{
$push: {
friends: {
$elemMatch: {
username: req.params.friendUsername,
},
},
},
}
);
And now the question is how to access the "tweets" array inside $elemMatch and push the req.body.tweet at $position: 0 into it?
Here is how I would solve your issue, I first would re-define the way I am defining schemas.
My User schema would look something like the following
User.js
const mongoose = require('mongoose')
const UserSchema = mongoose.Schema({
...
friends: {
type: [{
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
}],
required: true,
default: []
},
tweets: {
type: [{
type: mongoose.Schema.Types.ObjectId,
ref: 'Tweet'
}],
required: true,
default: []
},
...
}, {timestamps: true})
module.exports = mongoose.model('User', UserSchema)
User.js
const mongoose = require('mongoose')
const TweetSchema = mongoose.Schema({
...
text: {
type: String,
required: true
},
tweeter: {
type: mongoose.Schema.Types.ObjectId,
required: true,
ref: 'User'
},
likes: {
type: [{
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
}],
required: true,
default: []
},
...
}, {timestamps: true})
module.exports = mongoose.model('Tweet', TweetSchema)
This assumes that every user can have tweets and that a User can be friend with another User
And now if someone tweets something you can do something like
const Tweet = require('./Tweet.js')
const User = require('./User.js')
let tweet = new Tweet({
text: "My first tweet!",
tweeter: "ID Of user who is posting the tweet"
})
tweet.save()
// Now update the user who tweeted
User.findOneAndUpdate()
User.updateOne({ _id: "ID Of user who is posting the tweet" }, { $push: { tweets: tweet._id } })
and now whenever you request a user all of his friends will be referenced and all of their tweets will also be referenced! if you want to see the actual tweets then use something like .populate() here are the docs for .populate() https://mongoosejs.com/docs/populate.html
Keep in mind is really a good practice to only return the actual ids and your frontend takes care of requesting the appropriate objects from their perspective endpoints. And if you wish to reduce network calls then the frontend would cache the data.
If the above doesn't help and you still would like to achieve your goal with your schemas then something like this should work (assuming your schema is called User)
let tweetObj = {}
User.updateOne({_id: 'your userid'}, {$push: {"friends.$.tweets": tweetObj}})
NOTE: I have omitted callbacks as they are irrelevant to the question

Mongoose populate referencing object id, not the object itself

Background
Here's part of my User model:
const Group = require("./Group")
...
groups: {
type: [{ type: Schema.ObjectId, ref: Group }],
default: [],
},
And here's my Group model:
module.exports = mongoose.model(
"Group",
new Schema(
{
name: {
type: String,
required: true,
unique: true,
},
/**
* Array of User ObjectIDs that have owner rights on this group
*/
owners: {
type: [{ type: Schema.ObjectId, ref: User }],
default: [],
},
},
{
timestamps: true,
}
)
)
The Code
Here's the code I'm running to try and populate:
const user = await (await User.findOne({ _id: ... })).execPopulate("Group")
console.log(user.groups)
My console.log is outputting an array of object IDs, when I'd like it to output an actual Group document.
Attempted solutions
I've tried changing my ref to be using the string ("Group"), I've tried arranging my query differently, etc. I'm not sure how I'd go about doing this.
Apologies in advance if this is a duplicate, I've done my best to search but can't really find a solution that works for me.
Specifically, what do I need help with?
I'm trying to create a 'link' between a user model and a group model. In my console.log, I expect it to output a Group document; but it outputs an object ID (which is how it's stored raw in the database, meaning that Mongoose isn't transforming it correctly)
When you change execPopulate to populate like:
async function findUserAndPopulate(userId){
const response = await User.findOne({
_id: userId,
}).populate('groups')
console.log("response",response)
}
You got:
{
groups: [
{
owners: [Array],
_id: 5ecc637916a2223f15581ec7,
name: 'Crazy',
createdAt: 2020-05-26T00:31:53.379Z,
updatedAt: 2020-05-26T00:31:53.379Z,
__v: 0
}
],
_id: 5ecc6206820d583b99b6b595,
fullname: 'James R',
createdAt: 2020-05-26T00:25:42.948Z,
updatedAt: 2020-05-26T00:36:12.186Z,
__v: 1
}
So you can access the user.groups
See the doc: https://mongoosejs.com/docs/populate.html

Categories