How do I populate a nested field with array of ID to another collection using Mongoose? - mongodb

I have two collections. User and Event. In my Event model, I have an object "attendees" with ("hosting", "pending", "accepted", "declined") that have an array of ObjectId's referencing the User. I am trying to grab a single event and have those populated with User information.
My Event Schema
const EventSchema = new mongoose.Schema(
{
title: {
type: String,
required: true,
},
description: {
type: String,
required: true,
},
eventDate: {
type: Date,
required: true,
},
dateCreated: {
type: Date,
default: Date.now,
},
dateUpdated: {
type: Date,
default: Date.now,
},
attendees: {
hosting: [
{
type: Schema.ObjectId,
ref: "User",
},
],
pending: [
{
type: Schema.ObjectId,
ref: "User",
},
],
accepted: [
{
type: Schema.ObjectId,
ref: "User",
},
],
declined: [
{
type: Schema.ObjectId,
ref: "User",
},
],
},
My User Schema
const UserSchema = new mongoose.Schema(
{
name: {
first: {
type: String,
required: true,
},
last: {
type: String,
required: true,
},
},
phone: {
type: String,
required: true,
},
email: {
type: String,
match: [/^\w+([\.-]?\w+)*#\w+([\.-]?\w+)*(\.\w{2,3})+$/, "Please add a valid email"],
unique: true,
required: true,
},
password: {
type: String,
required: true,
},
pictureUrl: String,
allergies: [String],
preferences: [String],
favoriteRecipes: [String],
events: {
hosting: [
{
type: Schema.ObjectId,
ref: "Event",
},
],
pending: [
{
type: Schema.ObjectId,
ref: "Event",
},
],
accepted: [
{
type: Schema.ObjectId,
ref: "Event",
},
],
declined: [
{
type: Schema.ObjectId,
ref: "Event",
},
],
},
I've looked through a ton of stack overflow and can't find something to help. Maybe I need to change my models? Here is my query:
db.Event.find({})
.populate({
path: "attendees.pending",
model: "User",
})
.exec((err, docs) => {
console.log(docs);
process.exit(0);
});

Related

How to create this tsvector generated always as column with sequelize?

I see that sequelize has DataTypes.TSVECTOR for postgres dialect.
I have a column whose definition in raw SQL is as follows
tsvector GENERATED ALWAYS AS (((
setweight(to_tsvector('english'::regconfig, (COALESCE(title, ''::character varying))::text), 'A'::"char") ||
setweight(to_tsvector('english'::regconfig, COALESCE(summary, ''::text)), 'B'::"char")) ||
setweight(to_tsvector('english'::regconfig, (COALESCE(content, ''::character varying))::text), 'C'::"char")))
STORED
How can I define this in my sequelize model
const FeedItem = sequelize.define(
'FeedItem', {
feedItemId: {
type: DataTypes.UUID,
primaryKey: true,
allowNull: false,
defaultValue: DataTypes.UUIDV4,
},
pubdate: {
type: DataTypes.DATE,
allowNull: false,
defaultValue: sequelize.literal('CURRENT_TIMESTAMP'),
validate: {
isDate: true,
},
},
link: {
type: DataTypes.STRING,
allowNull: false,
validate: {
len: [0, 2047],
},
},
guid: {
type: DataTypes.STRING,
validate: {
len: [0, 2047],
},
},
title: {
type: DataTypes.TEXT,
allowNull: false,
validate: {
len: [0, 65535],
},
},
summary: {
type: DataTypes.TEXT,
validate: {
len: [0, 65535],
},
},
content: {
type: DataTypes.TEXT,
validate: {
len: [0, 1048575],
},
},
author: {
type: DataTypes.STRING,
validate: {
len: [0, 63],
},
},
tags: {
type: DataTypes.ARRAY(DataTypes.STRING),
defaultValue: [],
},
// How to do that generated always part here???
searchable: {
type: DataTypes.TSVECTOR
},
}, {
timestamps: false,
underscored: true,
indexes: [
{
name: 'idx_feed_items_searchable',
fields: ['searchable'],
using: 'gin',
},
],
}
);
The model needs to be modified as follows to get this working
const FeedItem = sequelize.define(
'FeedItem',
{
feedItemId: {
type: DataTypes.UUID,
primaryKey: true,
allowNull: false,
defaultValue: DataTypes.UUIDV4,
},
pubdate: {
type: DataTypes.DATE,
allowNull: false,
defaultValue: sequelize.literal('CURRENT_TIMESTAMP'),
validate: {
isDate: true,
},
},
link: {
type: DataTypes.STRING,
allowNull: false,
validate: {
len: [0, 2047],
},
},
guid: {
type: DataTypes.STRING,
validate: {
len: [0, 2047],
},
},
title: {
type: DataTypes.TEXT,
allowNull: false,
validate: {
len: [0, 65535],
},
},
summary: {
type: DataTypes.TEXT,
validate: {
len: [0, 65535],
},
},
content: {
type: DataTypes.TEXT,
validate: {
len: [0, 1048575],
},
},
author: {
type: DataTypes.STRING,
validate: {
len: [0, 63],
},
},
tags: {
type: DataTypes.ARRAY(DataTypes.STRING),
defaultValue: [],
},
// https://stackoverflow.com/questions/67051281/use-postgres-generated-columns-in-sequelize-model
searchable: {
type: `tsvector GENERATED ALWAYS AS (((setweight(to_tsvector('english'::regconfig, (COALESCE(title, ''::character varying))::text), 'A'::"char") || setweight(to_tsvector('english'::regconfig, COALESCE(summary, ''::text)), 'B'::"char")) || setweight(to_tsvector('english'::regconfig, (COALESCE(content, ''::character varying))::text), 'C'::"char"))) STORED`,
set() {
throw new Error('generatedValue is read-only');
},
},
},
{
timestamps: false,
underscored: true,
indexes: [
{
name: 'idx_feed_items_pubdate_feed_item_id_desc',
fields: [
{ attribute: 'pubdate', order: 'DESC' },
{ attribute: 'feed_item_id', order: 'DESC' },
],
},
{
name: 'idx_feed_items_tags',
fields: ['tags'],
using: 'gin',
},
{
name: 'idx_feed_items_searchable',
fields: ['searchable'],
using: 'gin',
},
],
}
);
Does not work with sequelize.sync({alter: true}) you have to force:true or sequelize migrations

How to query to get all documents referenced to another collection based on one field as username

In my Node API and MongoDB, I'm trying to make an endpoint to get all the posts associated with one username of a profile. In my Profile schema, I have a reference to the Post Schema and in my Post schema, I have a reference to the Username from Profile schema.
My issue I don't know how to get all the posts for that username. I did similarly but with embedded for the Experience schema but I'm not sure how to do that for the referenced collections.
Post model:
const { Connect } = require("../db");
const reactionSchema = {
likedBy: {
type: String,
unique: true,
sparse: true
}
};
const postSchema = {
text: {
type: String,
required: true,
unique: true,
sparse: false
},
username: {
type: Connect.Schema.Types.String,
ref: "Profile"
},
image: {
type: String,
default: "https://via.placeholder.com/150",
required: false
},
createdAt: {
type: Date,
default: Date.now,
required: false
},
updatedAt: {
type: Date,
default: Date.now,
required: false
},
reactions: [reactionSchema],
comments: {
type: Connect.Schema.Types.ObjectId,
ref: "Comment",
required: false
}
};
const collectionName = "post";
const postSchemaModel = Connect.Schema(postSchema);
const Post = Connect.model(collectionName, postSchemaModel);
module.exports = Post;
Profile model:
// Here defining profile model
// Embedded we have the Experience as []
const { Connect } = require("../db");
const { isEmail } = require("validator");
const postSchema = {
type: Connect.Schema.Types.ObjectId,
ref: "Post"
};
const experienceSchema = {
role: {
type: String,
required: true
},
company: {
type: String,
required: true
},
startDate: {
type: Date,
required: true
},
endDate: {
type: Date,
required: false
},
description: {
type: String,
required: false
},
area: {
type: String,
required: true
},
createdAt: {
type: Date,
default: Date.now,
required: false
},
updatedAt: {
type: Date,
default: Date.now,
required: false
},
username: {
type: String,
required: false
},
image: {
type: String,
required: false,
default: "https://via.placeholder.com/150"
}
};
const profileSchema = {
firstname: {
type: String,
required: true
},
surname: {
type: String,
required: true
},
email: {
type: String,
trim: true,
lowercase: true,
unique: true,
required: [true, "Email is required"],
validate: {
validator: string => isEmail(string),
message: "Provided email is invalid"
}
},
bio: {
type: String,
required: true
},
title: {
type: String,
required: true
},
area: {
type: String,
required: true
},
imageUrl: {
type: String,
required: false,
default: "https://via.placeholder.com/150"
},
username: {
type: String,
required: true,
unique: true
},
experience: [experienceSchema],
posts: [postSchema],
createdAt: {
type: Date,
default: Date.now,
required: false
},
updatedAt: {
type: Date,
default: Date.now,
required: false
}
};
const collectionName = "profile";
const profileSchemaModel = Connect.Schema(profileSchema);
const Profile = Connect.model(collectionName, profileSchemaModel);
module.exports = Profile;
I was able to make this for the embedded experience but not sure that correct for referenced collection:
const profileWithExperiences = await Student.aggregate([
{ $match: { username: res.username.username } },
{
$unwind: "$experience"
},
{
$match: {
"experience._id": new ObjectID(req.params.experienceId)
}
},
{
$project: {
username: 1,
experience: 1,
_id: 0
}
}
]);
I would like to see an example for referenced collections as it is confusing me how should I do that
[EDIT]
JSON for Profiles and Posts
{
"_id": ObjectId("5e2c98fc3d785252ce5b5693"),
"imageUrl": "https://i.pravatar.cc/300",
"firstname": "Jakos",
"surname": "Lemi",
"email": "lemi#email.com",
"bio": "My bio bio",
"title": "Senior IT developer",
"area": "Copenhagen",
"username": "Jakos",
"experience": [
{
"image": "https://via.placeholder.com/150",
"_id": ObjectId("5e3975f95fbeec9095ff3d2f"),
"role": "Developer",
"company": "Google",
"startDate": ISODate("2018-11-09T23:00:00.000Z"),
"endDate": ISODate("2019-01-05T23:00:00.000Z"),
"area": "Copenhagen",
"createdAt": ISODate("2020-02-04T13:47:37.167Z"),
"updatedAt": ISODate("2020-02-04T13:47:37.167Z")
},
{
"image": "https://via.placeholder.com/150",
"_id": ObjectId("5e3978bf5e399698e20c56d4"),
"role": "Developer",
"company": "IBM",
"startDate": ISODate("2018-11-09T23:00:00.000Z"),
"endDate": ISODate("2019-01-05T23:00:00.000Z"),
"area": "Copenhagen",
"createdAt": ISODate("2020-02-04T13:59:27.412Z"),
"updatedAt": ISODate("2020-02-04T13:59:27.412Z")
}
],
"createdAt": ISODate("2020-01-25T19:37:32.727Z"),
"updatedAt": ISODate("2020-02-04T23:14:37.122Z"),
"__v": NumberInt("0")
}
Post
{
"_id": ObjectId("5e3beb639e072afedd19dcef"),
"username": ObjectId("5e2c98fc3d785252ce5b5693"),
"image": "https://via.placeholder.com/150",
"text": "My awesome post",
"createdAt": ISODate("2020-02-06T10:33:07.22Z"),
"updatedAt": ISODate("2020-02-06T10:33:07.22Z"),
"reactions": [ ],
"__v": NumberInt("0")
}
Output expected:
{
"username": "Jakos",
"postsCount": [1],
"posts": [
{
"_id": ObjectId("5e3beb639e072afedd19dcef"),
"image": "https://via.placeholder.com/150",
"text": "My awesome post",
"createdAt": ISODate("2020-02-06T10:33:07.22Z"),
"updatedAt": ISODate("2020-02-06T10:33:07.22Z"),
"reactions": [ ],
"__v": NumberInt("0")
}
]
}
I want to see all the posts related to that username
You can use the $lookup aggregation to join collections.
db.profiles.aggregate([
{
$match: {
username: "Jakos"
}
},
{
$lookup: {
from: "posts", //the name of the posts collection, change this if it is different
localField: "_id",
foreignField: "username",
as: "posts"
}
},
{
$project: {
username: 1,
posts: 1,
postsCount: {
$size: "$posts"
},
_id: 0
}
}
])
Playground
For mongoose it should be like this in your app:
const profileWithExperiences = await Student.aggregate([
{ $match: { username: res.username.username } },
{
$unwind: "$experience"
},
{
$lookup: {
from: "posts", //the name of the posts collection, change this if it is different
localField: "_id",
foreignField: "username",
as: "posts"
}
},
{
$project: {
username: 1,
posts: 1,
postsCount: {
$size: "$posts"
},
_id: 0
}
}
]);
Try to get like this i am getting data from two table with the use of node and mongodb.
var param = {};
param['user_id']=id;
var search={ user_id:param.user_id,status: [ 'Pending', 'Accepted' ] };
var output={};
output = await JobRequest.find(search);
if(output !=0)
{
var JSon=await JobRequest.aggregate([
{$match: { user_id: {$gte:id}} },
{
"$project": {
"employee_id": {
"$toObjectId": "$employee_id"
},
"service_id": {
"$toObjectId": "$service_id"
},
"createdDate": {
"$toString": "$createdDate"
},
"chat_status": {
"$toString": "$chat_status"
},
"status": {
"$toString": "$status"
},
"delivery_address": {
"$toString": "$delivery_address"
},
"delivery_lat": {
"$toString": "$delivery_lat"
},
"delivery_lang": {
"$toString": "$delivery_lang"
},
}
},
{
$lookup:
{
from: "employees",
localField: "employee_id",
foreignField: "_id",
as: "employee_details"
}
},
{
$lookup:
{
from: "services",
localField: "service_id",
foreignField: "_id",
as: "service_details"
}
}
]);

mongoose populate query doesn't show the desired results

I'm new to MongoDB and Mongoose and I'm having some problems with it, to be more specific I've problems with populate, problem displayed below.
My Schema:
'use strict';
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
/* ------------------------------------- Field config ---------------------------------------- */
const UsersSchema = new Schema({
name: {
type: String,
required: true,
min: 3,
set: str => str.replace(/\b\w/g, l => l.toUpperCase())
},
lastname: {
type: String,
required: true,
min: 3,
set: str => str.replace(/\b\w/g, l => l.toUpperCase())
},
email: {
type: String,
required: true,
unique: true,
validade: {
validator: str => regExp.test(str)
}
},
password: {
type: String,
required: true,
min: 8
},
fcm:{
type: String,
required: true
},
birthDate: {
type: Date,
required: true,
get: convertToBR
},
gender: {
type: String,
enum: ['M', 'F'],
required: true
},
profileImage: {
type: String,
required: false,
get: alterUri
},
aboutMe: {
type: String,
max: 500
},
enabled: {
type: Boolean,
required: true,
default: true
},
createdAt: {
type: Date,
required: true,
default: new Date()
},
token: {
type: String,
required: true,
index: true
},
locations: {
type: [mongoose.Schema.Types.ObjectId],
require: false,
ref: 'Location'
},
lastVisitants: {
type: [{
user: mongoose.Schema.Types.ObjectId,
createAt: {
type: Date,
required: true,
default: new Date()
}
}],
required: false,
ref: 'Users'
}
}, {
toObject: {
virtuals: true,
getters: true,
setters: true
},
toJSON: {
virtuals: true,
getters: true,
setters: true
}
});
//Code bellow removed for make to easy know
Generated doc inside MongoDB:
{
"_id": "589288de533c9555163cf263",
"email": "luiz#sene.com",
"password": "CQziXoB6XrBFBTWe5s/kFsIGYqbtDPBBKQgADUZF9co=",
"name": "luiz Fernando",
"lastname": "Sene",
"birthDate": "1988-08-11T00:00:00.000Z",
"gender": "M",
"fcm": "34567890987654345678",
"token": "58979c08b048917ed8b434ac",
"createdAt": "2017-02-02T01:17:38.168Z",
"enabled": true,
"__v": 0,
"aboutMe": "algo sobre mim aqui mudar",
"locations": [
"5893e2e0c8a01b4ed21c8c39",
"5893e305c8a01b4ed21c8c3a",
"5893e32ac8a01b4ed21c8c3b",
"5893e34dc8a01b4ed21c8c3c",
"5893e92838bd205ba42c2c8a",
"5893ea888628c45d2bc6683a"
],
"profileImage": "images/589288de533c9555163cf263/1486330852976.png",
"lastVisitants": [
{
"createAt": "2017-02-05T19:23:17.697Z",
"_id": "58977ba99fc2b7485dbdbf26",
"user": "589778e22a9dd5449e4f92df"
}
]
}
My problem occurs when I try populate locations and lastVisitants.
My consult is below:
UsersSchema.findOne({_id: data.data.id}, {lastVisitants: true, locations: true})
.populate({
path: 'locations',
model: 'Location',
select: 'description',
options: {
limit: 1,
sort: {
createdAt: -1
}
}
})
.populate({
path: 'lastVisitants.user',
model: 'Users',
select: '_id lastVisitants',
options: {
limit: 5
//TODO: Add sort by createdAt
}
})
.exec((err, result) => {
if (err) {
Utils.callback(err, null, res);
return;
}
Utils.callback(null, result, res);
});
Result of my consult:
{
"_id": "589288de533c9555163cf263",
"lastVisitants": [
{
"_id": "58977c1c9fc2b7485dbdbf27",
"user": {
"_id": "589778e22a9dd5449e4f92df",
"lastVisitants": [
{
"_id": "58977c1c9fc2b7485dbdbf27",
"user": "589288de533c9555163cf263",
"createAt": "2017-02-05T19:23:17.697Z"
}
],
"profileImage": "http://192.168.0.19:3000/undefined",
"birthDate": {
"en": "NaN-NaN-NaN",
"br": "NaN/NaN/NaN"
},
"fullname": "undefined undefined",
"age": null,
"id": "589778e22a9dd5449e4f92df"
},
"createAt": "2017-02-05T19:23:17.697Z"
}
],
"profileImage": "http://192.168.0.19:3000/undefined",
"birthDate": {
"en": "NaN-NaN-NaN",
"br": "NaN/NaN/NaN"
},
"fullname": "undefined undefined",
"age": null,
"id": "589288de533c9555163cf263"
}
Result expected:
{
"_id": "589288de533c9555163cf263",
"locations":[
{
"_id": "5893ea888628c45d2bc6683a",
"description": "Cruzeiro - SP, Brasil"
}
]
"lastVisitants": [
{
_id: ""
user: {
"id": "58977ba99fc2b7485dbdbf26",
"fullname": "Fábio Pereira",
"profileImage": "http://192.168.0.19:3000/images/589288de533c9555163cf263/1486319528451.jpeg",
"gender": "M",
// more fields ...
},
createdAt: ""
}
]
}
My question is how can I make this query bring what I really desire?

referencing documents only by the mongo objectID?

is it possible to reference another value other than the mongo generated _id?
User Model
uid: {type: String, required: true},
channel_pub: {type: String},
channel_groups: [{type: String}],
auth_key: {type: String},
channels: [{
name: {
type: String,
ref: 'channel'
}
}]
Channel Model
name: {type: String, required: true},
uid: [{
type: String,
ref: 'user',
required: true
}]
I am trying to reference the actual channel name in the user document.
You can do this with Populate Virtuals since mongoose 4.5.0 :
var UserSchema = new mongoose.Schema({
uid: { type: String, required: true }
}, {
toJSON: {
virtuals: true
}
});
var ChannelSchema = new mongoose.Schema({
name: { type: String, required: true },
uid: [{
type: String,
ref: 'User',
required: true
}]
});
UserSchema.virtual('channels.data', {
ref: 'Channel',
localField: 'channels.name',
foreignField: 'name'
});
Here the local field is channels.name, the Channel object will be populated in channels.data.
For instance a find with channels.data populated :
User.find({}).populate('channels.data').exec(function(error, res) {
console.log(JSON.stringify(res, null, 4));
});
will give :
[{
"_id": "588a82ff7fe89686fd2210b0",
"uid": "user1",
"channels": [{
"data": {
"_id": "588a80fd7fe89686fd2210a8",
"name": "channel1",
"uid": []
},
"name": "channel1"
}, {
"data": {
"_id": "588a80fd7fe89686fd2210a9",
"name": "channel2",
"uid": []
},
"name": "channel2"
}],
"id": "588a82ff7fe89686fd2210b0"
}
...
]

Mongoose update push, delete in array

I have a mongoose model:
var schema = new Schema({
loginName: {
type: String,
unique: true,
required: true
},
hashedPassword: {
type: String,
required: true
},
salt: {
type: String,
required: true
},
created: {
type: Date,
default: Date.now
},
rooms: [{ _id: Schema.Types.ObjectId, loginName: [{ type: String }] }]
});
Example result:
{
_id: "56c0a986eeb118741109a45f",
loginName: "MegaDaddgy",
hashedPassword: "*****",
salt: "******",
__v: 10,
rooms: [
{
_id: "56c0a986eeb118741109a461",
loginName: [
"MegaDaddgy"
]
},
{
_id: "56c0d9e332f6ddc80ec7271c",
loginName: [
"MegaDaddgy"
]
}
],
created: "2016-02-14T16:21:26.272Z"
}
What I need:
search on field : rooms._id in every user document,
push new loginName in array loginName in every found user document
delete selected loginName in array
Example params:
rooms._id : 56c0a986eeb118741109a461
loginName: "John"
Result:
{
_id: "56c0a986eeb118741109a45f",
loginName: "MegaDaddgy",
hashedPassword: "*****",
salt: "******",
__v: 10,
rooms: [
{
_id: "56c0a986eeb118741109a461",
loginName: [
"MegaDaddgy", "John"
]
},
{
_id: "56c0d9e332f6ddc80ec7271c",
loginName: [
"MegaDaddgy"
]
}
],
created: "2016-02-14T16:21:26.272Z"
}
Example params:
rooms._id : 56c0a986eeb118741109a461
loginName: "John"
Result:
{
_id: "56c0a986eeb118741109a45f",
loginName: "MegaDaddgy",
hashedPassword: "*****",
salt: "******",
__v: 10,
rooms: [
{
_id: "56c0a986eeb118741109a461",
loginName: [
"MegaDaddgy"
]
},
{
_id: "56c0d9e332f6ddc80ec7271c",
loginName: [
"MegaDaddgy"
]
}
],
created: "2016-02-14T16:21:26.272Z"
}
How can I do this?
You could push John into loginName array through $push
Model.update({'rooms._id': ObjectId('56c0a986eeb118741109a461')},
{$push: {'rooms.$.loginName': 'John'}}, function(...));
delete John from loginName array through $pull
Model.update({'rooms._id': ObjectId('56c0a986eeb118741109a461')},
{$pull: {'rooms.$.loginName': 'John'}}, function(...));