Unique Array objects in mongoose schema - express

I have a conversation schema which will allow users for private messaging, each two users can be in ONE conversation therefore, the recipients in the conversations should be unique
/** Users in this conversation**/
var usersSchema = new Schema({
id: {
type: Schema.Types.ObjectId,
index: true,
required: true,
ref: 'User'
},
name: {
type: String,
required: true
}
});
/** For each message we will have the below **/
var messagesSchema = new Schema({
from: {
type: Schema.Types.ObjectId,
required: true
},
content: {
type: String,
required: true
},
read: {
type: Boolean,
default: false
}
}, {
timestamps: true
});
/** Now all together inside the thread schema **/
var conversationsSchema = new Schema({
users: {
type: [usersSchema],
required: true,
index: true,
unique: true
},
messages: [messagesSchema],
}, {
timestamps: true
});
var Conversation = mongoose.model('Conversation', conversationsSchema);
module.exports.Conversation = Conversation;
The only way I can think of is to manually check by looking into the IDs inside the users array in the conversation schema. However, I think there is a way in mongoose to do that.

You can add unique : true in your userSchema to only allow unique users in a conversation.
/** Users in this conversation**/
var usersSchema = new Schema({
id: {
type: Schema.Types.ObjectId,
index: true,
required: true,
unique : true, //add unique behaviour here
ref: 'User'
},
name: {
type: String,
required: true
}
});

Related

Mongoose model schema referencing each other - how to simplify?

I am using mongoose and have two schemas: UserSchema and CommunitySchema:
const UserSchema = new Schema({
name: String,
communities: [{ type: Schema.Types.ObjectId, ref: CollectionModel }],
exCommunities: [{ type: Schema.Types.ObjectId, ref: CollectionModel }],
}, { timestamps: true });
const CommunitySchema = new Schema({
slug: { type: String, unique: true },
name: String,
description: String,
users: [
{ type: Schema.Types.ObjectId, ref: "User" }
]
}, { timestamps: true });
User can be a part of multiple communities and also leave any community and be in the exCommunities field.
When an user joins a community, I have to do a double work: Add the user to a user community and update the community user field with the reference ID.
Questions:
Is there a way to simplify it? For example, by managing the CommunitySchema users field automatically based on the UserSchema communities field?
Now I have to do this:
collection.users.push(userId);
user.communities.push(communityId);
Could the collection.users be automatically added when I push a community to user.communities? And how?)
Is it possible to add a date when the user is added to a community or leave a community? Something like: communities: [{ type: Schema.Types.ObjectId, ref: CollectionModel, createdAt: "<DATE>" }]
Thank you!
you no need to add communities and exCommunities in UserSchema
const UserSchema = new Schema({
name: String,
}, { timestamps: true });
const communityUserSchema = new Schema({
user_id:{type: Schema.Types.ObjectId, ref: "User"},
joined_at:{type:Date},
leaved_at:{type:Date},
is_active:{type:Boolean},
role:{type:String, enum:['user','admin'], default:'user'}
});
const CommunitySchema = new Schema({
slug: { type: String, unique: true },
name: String,
description: String,
users:{
type:[communityUserSchema],
default:[]
}
}, { timestamps: true });
you can find User's communities by :
let user_id = req.user._id;
all_communities = await Community.find({"users.user_id":user_id});
active_communities = await Community.find({"users.user_id":user_id, "is_active":true});
ex_communities = await Community.find({"users.user_id":user_id,"leaved_at":{"$ne":null}});
When User Create New Community (create as a Admin):
let current_user = {user_id:req.user.id,joined_at:new Date(),is_active:true,role:'admin'};
// if you select users from frontend while creating new community
let other_user = req.body.user_ids;
let other_users_mapped = other_user.map((item)=>{ return {user_id:item,joined_at:new Date(),role:'user',is_active:true}});
let all_users = [current_user];
all_users = all_users.concat(other_users_mapped);
let community = new Community();
community.name = req.body.name;
community.slug = req.body.slug;
community.description = req.body.description;
community.users = all_users ;
let created = await community.save();
When User Leave Community :
Community.updateOne({_id: community_id , 'users.user_id':user_id },{
$set:{
'users.$.is_active':false,
'users.$.leaved_at':new Date()
}
});
View Community with only active members :
let community_id = req.params.community_id;
let data = await Community.findOne({_id:community_id},{ users: { $elemMatch: { is_active: true } } });
AI solved it for me, here is the correct example:
import * as mongoose from 'mongoose';
const Schema = mongoose.Schema;
interface User {
name: string;
email: string;
communities: Community[];
}
interface Community {
name: string;
description: string;
users: User[];
}
const userSchema = new Schema({
name: String,
email: String,
}, {
timestamps: true,
});
const communitySchema = new Schema({
name: String,
description: String,
}, {
timestamps: true,
});
// Define the user_communities table using the communitySchema
const userCommunitySchema = new Schema({
user: { type: Schema.Types.ObjectId, ref: 'User' },
community: { type: Schema.Types.ObjectId, ref: 'Community' },
joined_on: Date,
}, {
timestamps: true,
});
// Use the userCommunitySchema to create the UserCommunity model
const UserCommunity = mongoose.model('UserCommunity', userCommunitySchema);
// Use the userSchema to create the User model, and define a virtual property
// for accessing the user's communities
userSchema.virtual('communities', {
ref: 'Community',
localField: '_id',
foreignField: 'user',
justOne: false,
});
const User = mongoose.model<User>('User', userSchema);
// Use the communitySchema to create the Community model, and define a virtual property
// for accessing the community's users
communitySchema.virtual('users', {
ref: 'User',
localField: '_id',
foreignField: 'community',
justOne: false,
});
const Community = mongoose.model<Community>('Community', communitySchema);
The userSchema and communitySchema are then used to create the User and Community models, respectively. For the User model, a virtual property called communities is defined using the virtual method. This virtual property is used to specify how to populate the user.communities property when querying the database. The communitySchema also defines a users virtual property, which is used to populate the community.users property when querying.

Sequelize Many to Many Relationship using Through does not insert additional attributes

I have a many to many relationship between: Step and Control Through ControlsConfig.
When creating a Control object and call addStep function and specify the additional attributes (which exist in the relation table), Sequelize creates the records in the relational table ControlsConfig but the additional attributes are NULLs.
PS: The tables are creating correctly in the database.
Table 1: Step
Table 2: Control
Relation table: ControlsConfig
Step
var Step = sequelize.define('Step', {
title: { type: DataTypes.STRING, allowNull: false },
description: DataTypes.STRING,
type: { type: DataTypes.ENUM('task', 'approval'), allowNull: false, defaultValue: 'task' },
order: DataTypes.INTEGER
});
Step.associate = function(models) {
models.Step.belongsTo(models.User);
models.Step.belongsTo(models.Template);
models.Step.hasMany(models.Action);
};
Control
var Control = sequelize.define('Control', {
label: { type: DataTypes.STRING, allowNull: false },
order: { type: DataTypes.INTEGER },
type: { type: DataTypes.ENUM('text', 'yes/no') },
config: { type: DataTypes.TEXT },
controlUiId: { type: DataTypes.STRING }
});
Control.associate = function(models) {
models.Control.belongsTo(models.Section);
};
ControlsConfigs
module.exports = (sequelize, DataTypes) => {
var ControlsConfig = sequelize.define('ControlsConfig', {
visibility: { type: DataTypes.ENUM('hidden', 'readonly', 'editable', 'required') },
config: { type: DataTypes.TEXT }
});
ControlsConfig.associate = function(models) {
models.Control.belongsToMany(models.Step, { through: models.ControlsConfig });
models.Step.belongsToMany(models.Control, { through: models.ControlsConfig });
models.ControlsConfig.belongsTo(models.Template);
};
return ControlsConfig;
};
Insertion:
try {
var step1 = await Step.create({ /*bla bla*/ });
var control1 = await Control.create({ /*bla bla*/ });
var OK = await control1.addStep(step1, {through: { config: 'THIS FIELD ALWAYS APPEARS NULL' }});
} catch (error) { /* No errors*/ }
I am following the same strategy stated at the documentation
//If you want additional attributes in your join table, you can define a model for the join table in sequelize, before you define the association, and then tell sequelize that it should use that model for joining, instead of creating a new one:
const User = sequelize.define('user', {})
const Project = sequelize.define('project', {})
const UserProjects = sequelize.define('userProjects', {
status: DataTypes.STRING
})
User.belongsToMany(Project, { through: UserProjects })
Project.belongsToMany(User, { through: UserProjects })
//To add a new project to a user and set its status, you pass extra options.through to the setter, which contains the attributes for the join table
user.addProject(project, { through: { status: 'started' }})
You have to pass edit: true to the addProject and addStep method.
See this answer it has a similar issue
Sequelize belongsToMany additional attributes in join table

mongoose populate nested subdocuments

I have following model
var fieldsSchema = new Schema({
name: String,
type: String,
value: String,
media: [{ type: Schema.Types.ObjectId, ref: 'Upload' }],
required: Boolean,
recepientvisible: Boolean
})
var orderSchema = new Schema({
number: String,
date: Number,
updated: Number,
type: { type: Schema.Types.ObjectId, ref: 'OrderTemplate' },
currentstatus: String,
comment: String,
assignedTo: { type: Schema.Types.ObjectId, ref: 'User' },
createdBy: { type: Schema.Types.ObjectId, ref: 'User' },
statuses: [{
name: String,
fields: [fieldsSchema]
}]
});
var Order = mongoose.model('Order', orderSchema);
module.exports = Order;
I do the following request
app.post('/order/', function (req, res) {
Order.find()
.populate({ path:'type', select: 'name -_id'})
.populate({ path:'assignedTo', select: 'name -_id'})
.populate({ path:'createdBy', select: 'name -_id'})
.populate({ path:'statuses', populate: { path: 'fields', populate: { path: 'media'} }})
.exec(function (err, orders) {
if (err) throw err;
res.send(orders)
});
})
What I need is to populate media fields. But in response I get only array of _id.
All other fields populates well.
How can I populate media field correctly ?

Keystone.js filtering on related fields within own model

I am working on filtering my subsection selection to display only subSections that are related to the current mainNavigationSection. Each of these subsections also has a mainNavigation section. For some reason the current implementation is not returning any results.
Here is my Page Model:
Page.add({
name: { type: String, required: true },
mainNavigationSection: { type: Types.Relationship, ref: 'NavItem', refPath: 'key', many: true, index: true },
subSection: { type: Types.Relationship, ref: 'SubSection', filters: { mainNavigationSection:':mainNavigationSection' }, many: true, index: true, note: 'lorem ipsum' },
state: { type: Types.Select, options: 'draft, published, archived', default: 'draft', index: true },
author: { type: Types.Relationship, ref: 'User', index: true }
}
Here is my subSectionModel:
SubSection.add({
name: { type: String, required: true, index: true },
mainNavigationSection: { type: Types.Relationship, ref: 'NavItem', many: true, required: true, initial: true},
showInFooterNav: { type: Boolean, default: false },
defaultPage: { type: Types.Relationship, ref: 'Page' },
description: { type: Types.Html, wysiwyg: true, height: 150, hint: 'optional description' }
});
From what it seems, you have the possibility of many mainNavigationSections on your model. You'd have to iterate over each of them on the current Page, and find the related SubSections. You'll need to use the async Node module to run all the queries and get the results from each.
var async = require('async');
var pID = req.params.pid; // Or however you are identifying the current page
keystone.list('Page').model.findOne({page: pID}).exec(function (err, page) {
if (page && !err) {
async.each(page.mainNavigationSection, function (curMainNavigationSection, cb) {
keystone.list('SubSection').model
.find({mainNavigationSection: curMainNavigationSection._id.toString()})
.exec(function (err2, curSubSections) {
if (curSubSections.length !== 0 && !err2) {
// Do what you need to do with the navigation subSections here
// I recommend using a local variable, which will persist through
// every iteration of this loop and into the callback function in order
// to persist data
return cb(null)
}
else {
return cb(err || "An unexpected error occurred.");
}
});
}, function (err) {
if (!err) {
return next(); // Or do whatever
}
else {
// Handle error
}
});
}
else {
// There were no pages or you have an error loading them
}
});

Field (Test.title) is required but not initial, and has no default or generated value

I was wondering if someone knows why I get that error, my model
var Test = new keystone.List('Test', {
autokey: { from: 'title', path: 'key', unique: true }
});
Test.add({
title: { type: String, required: true },
All I did was change the values from the post example below
var Test = new keystone.List('Test', {
autokey: { from: 'name', path: 'key', unique: true }
});
Test.add({
name: { type: String, required: true },
I can;t understand why it works with name and not with title
use map key inside option list
var Test = new keystone.List('Test', {
map: { name: 'title' },
autokey: { from: 'title', path: 'key', unique: true }
});
refs: http://keystonejs.com/docs/database/#lists-options
An object that maps fields to special list paths. Each path defaults
to its key if a field with that key is added. Mappable paths include
name - the field that contains the name of the item, for display in
the Admin UI