How to update immutable values in mongodb? - javascript

I have collection like this.
const Device: Schema = new Schema(
{
location: {
type: String,
required: true,
},
macAddress: {
type: String,
required: true,
immutable: true,
},
ip: {
type: String,
required: true,
immutable: true,
},
},
{
timestamps: true,
versionKey: false,
collection: 'Device',
}
);
I want to update immutable fields with my endpoints and i use this function and doesnt work.
Device.findOneAndUpdate(
{ _id: req.params.mdeviceId },
{ $set: { macAddress: req.body.macAddress, ip: req.body.ip},
{ new: true, upsert: true }
);
How can i update this specific fields?

I would guess req.params.mdeviceId is a string and needs to be casted to ObjectId, this is not related to the immutable property as you are providing the new flag which allows to bypass the schema protection, try using this:
Device.findOneAndUpdate(
{ _id: new mongoose.Types.ObjectId(req.params.mdeviceId) },
{ $set: { macAddress: req.body.macAddress, ip: req.body.id},
{ new: true, upsert: true }
);

Related

MongoDB Add More users to a field

I have a MongoDB collection for company users. Inside of this, the company is able to add in their team members to a field called: "teamMemberDetails"
For example, see a demo of what it currently looks like when I put a user into the DB through my API.
{
"teamMemberDetails": [
{
"memberEmail": "example2#hotmail.com"
}
],
"teamMembers": "0",
"_id": "62fc49b53bcb32ca823466dc",
"companyTitle": "Working Acc!v2",
}
It's also worth mentioning the schema is:
const CompanyProfileSchema = new mongoose.Schema(
{
companyTitle:{type:String, required: true, unique: true, default: ""},
companyPassword:{type:String, required: true, default: ""},
centralEmail:{type:String, required: true, unique: true, default: ""},
description:{type:String, required: true, default: ""},
inviteToken:{type:String, required: true, default:""},
industry:{type:String, required: true},
employees:{type: Number},
companyImage: {type:String, required: false, unique: false, default: ""},
locationCity:{type:String, required: false, unique: false, default: ""},
industry:{type:String, required: false, unique: false, default: ""},
country:{type:String, required: false, unique: false, default: ""},
teamMembers: {
type: String, required: true, default: 0
},
teamMemberDetails: {
memberName: String, default: "",
memberEmail: String, default: "",
memberRole: String, default: ""
},
highlights: {type: Array},
isAdmin:{
type: Boolean,
default: false,
},
isVerified:{
type: Boolean,
default: false,
},
accountLevel: {type: Array},
emailAuthorised: { type: Boolean, default: false},
invitedUsers: {type: Array}
},
);
This user was placed in with an API request like below:
//UPDATE Company - PROFILE
router.put("/updateCompany/:id", async (req, res) => {
try {
const updatedUser = await CompanyProfile.findByIdAndUpdate(
req.params.id,
{
$set: req.body,
},
{ new: true }
);
res.status(200).json(updatedUser);
} catch (err) {
res.status(500).json(err);
}
});
However, i'd like this team members detail to build, for example i'd like something like this:
"teamMemberDetails": [
{
"memberEmail": "example2#hotmail.com", "example3#hotmail.com", "g2g#hotmail.com", "192#hotmail.com"
}
],
Basically, I want to concatenate onto this field with several ongoing email addresses as they add new users.
Any ideas?
Change your schema for memberEmail so that it accepts an array. ie. [String]:
teamMemberDetails: {
memberName: String, default: "",
memberEmail: [String], default: [],
memberRole: String, default: ""
},
and to update the document:
const teamMemberDetailsObject =
{
"memberEmail": [
"example2#hotmail.com",
"example3#hotmail.com",
"g2g#hotmail.com",
"192#hotmail.com"
]
}
if you'd like to initialize the array of emails, you can do that like this:
const updatedUser = await CompanyProfile.findByIdAndUpdate(
req.params.id,
{
$set: {teamMemberDetails: teamMemberDetailsObject}
},
{ new: true }
);
If you'd like to add new emails to the memberEmail array in the same document in the collection, you can use the following:
const updatedUser = await CompanyProfile.findByIdAndUpdate(req.params.id, {
$addToSet: {
"teamMemberDetails.memberEmail":"newemailexample#hotmail.com"
}
},
{ new: true }
);
Using $set and $push together, you can update other values on the document
as well as push new items to the memberEmail array.
By using $push, you can add new items to the array.
// model
teamMemberDetails: {
memberName: String, default: "",
memberEmail: [String], default: [],
memberRole: String, default: ""
},
// dummy payload
const memberEmail = [
"example2#hotmail.com",
"example3#hotmail.com",
"g2g#hotmail.com",
"192#hotmail.com"
]
// Update call
const updatedUser = await CompanyProfile.updateOne(
{_id: req.params.id},
{
$set: req.body,
$push: {
teamMemberDetails.memberEmail: memberEmail
}
},
{ new: true }
);
Here's a working example.

How to use mongoose transactions with updateMany?

I am using the mongoose updateMany() method and I also want to keep it a part of transaction. The documentation shows the example of save() where I can do something like Model.save({session: mySession}) but don't really know how to use it with for example Model.updateMany()
UPDATE:
For example I have two models called SubDomain and Service and they look like this respectively:
SUB-DOMAIN
{
name: {
type: String,
required: true,
},
url: {
type: String,
required: true,
unique: true,
},
services: [
{
type: mongoose.Schema.Types.ObjectId,
ref: "Service",
},
],
user: {
type: mongoose.Schema.Types.ObjectId,
ref: "User",
},
}
SERVICE:
{
name: {
type: String,
required: true,
},
description: {
type: String,
required: true,
},
price: { type: Number },
tags: { type: Array },
packages: [
{
name: { type: String, required: true },
description: { type: String, required: true },
price: { type: Number, required: true },
},
],
map: { type: String },
isHidden: {
type: Boolean,
required: true,
default: false,
},
sortingOrder: { type: Number },
isForDomain: { type: Boolean, required: false, default: false },
isForSubDomain: { type: Boolean, required: false, default: false },
subDomains: [
{
type: mongoose.Schema.Types.ObjectId,
ref: "SubDomain",
},
],
}
Now the main field here is the services field in SubDomain and subDomains field in Service.
The complicated part😅:
Whenever the user wants to create new service, I want to $push that service's _id into the array of services of all the subDomains inside that new service
And for that, I am using the updateMany() like this:
const sess = await mongoose.startSession();
sess.startTransaction();
const newService = new Service({
_id: mongoose.Types.ObjectId(),
subDomains: req.body.subDomains
...foo
})
await SubDomain.updateMany(
{ _id: { $in: req.body.subDomains } },
{ $push: { services: newService._id } }
);
The problem starts here, of course I can do:
newService.save({session: sess})
but how do I keep my SubDomain's updateMany in the same transaction (i.e sess)
I know my example is difficult to wrap your head around but I have tried to pick a simplest example rather than copying the exact same code which would have been a lot more difficult

Sequelize - Build dynamic where clause with 'Op.or'

I had this code block working with Sequelize v5. But since switching to v6, it seems to be erroring out. I am getting the error: Error: Invalid value { customer_id: 'dg5j5435r4gfd' }.
And here is the code that creates the where condition block:
let whereBlock = {
deleted_at: null,
};
if (args.includeCore) {
if (customerID !== 'all') {
// whereBlock[Op.or] = [
// { customer_id: customerID },
// { customer_id: coreCustomerID },
// ];
whereBlock[Op.or] = [];
whereBlock[Op.or].push({
customer_id: customerID,
});
whereBlock[Op.or].push({ customer_id: coreCustomerID });
}
} else {
whereBlock.customer_id = customerID;
}
I was using the commented code. And then I tried the code below that. Both are producing the same error. But when I remove all that code from the if block and just put in whereBlock.customer_id = customerID;, then it works fine. So I know the issue is how I am constructing the where condition.
Update: As requested, here is my Sheets model where the where clause is being run on.
'use strict';
export default (sequelize, DataTypes) => {
return sequelize.define(
'Sheet',
{
id: {
type: DataTypes.UUID,
primaryKey: true,
defaultValue: DataTypes.UUIDV4,
},
sheet_name: {
type: DataTypes.STRING,
isAlphaNumeric: true,
required: true,
allowNull: true,
len: [3, 80],
},
sheet_file_name: {
type: DataTypes.STRING,
unique: true,
isAlphaNumeric: true,
required: false,
allowNull: true,
},
brand_name: {
type: DataTypes.STRING,
unique: false,
isAlphaNumeric: true,
required: false,
allowNull: true,
},
customer_id: {
// fk in customers table
type: DataTypes.TINYINT(2).UNSIGNED,
required: true,
allowNull: false,
},
chemical_id: {
// fk in loads table
type: DataTypes.SMALLINT.UNSIGNED,
required: true,
allowNull: false,
},
load_id: {
// fk in loads table
type: DataTypes.SMALLINT.UNSIGNED,
required: true,
allowNull: false,
},
active: {
type: DataTypes.BOOLEAN,
required: true,
allowNull: false,
defaultValue: true,
},
created_at: {
type: DataTypes.DATE,
},
updated_at: {
type: DataTypes.DATE,
},
deleted_at: {
type: DataTypes.DATE,
},
},
{
underscored: true,
paranoid: false,
}
);
};
And in my index I have this to associate sheets with customers: db.Sheet.belongsTo(db.Customer);
Also here is the full code where the whereBlock is used, if that helps:
const files = await db.Sheet.findAll({
raw: true,
attributes: [
'sheet_name',
'sheet_file_name',
['brand_name', 'brand'],
'updated_at',
'active',
[Sequelize.col('Chemical.name'), 'chemical'],
[Sequelize.col('Load.value'), 'load'],
],
include: [
{
model: db.Load.scope(null),
required: true,
as: 'Load',
attributes: ['value'],
},
{
model: db.Chemical.scope(null),
required: true,
as: 'Chemical',
attributes: ['name'],
},
],
// model: model,
where: whereBlock,
order: [['active', 'DESC']],
});
TLDR: So here is what it comes down to:
whereBlock = {
deleted_at: null,
customer_id: customerID,
// [Op.or]: [
// { customer_id: customerID },
// { customer_id: coreCustomerID },
// ],
};
That code above works, but the commented code errors out with: Error: Invalid value { customer_id: '123456' }
OK, this is very weird. But I finally figured out the issue!! Was not something I would have thought of, just found it by chance. It was the way I was importing Op from sequelize.
import Op from 'sequelize';
So apparently, that Op object has another object inside it called Op. So when I call my [Op.or], I instead need to do this: [Op.Op.or].
I did try switching my import to import Op.Op from 'sequelize'; and that caused errors. Anyone know how I can properly import the inner object?
Update
OK, so apparently in my other DB files, I was doing the import differently.
export default (db) => {
const Op = db.Sequelize.Op;
That method works to pull in the correct Op object. So there you go. Hopefully this nightmare issue helps someone else in the future.

Sequelize: Modify output of date timestamp using Getters

I need to convert my signup_at timestamp to a certain format each time it's selected from the database.
I want to use a getter for this, but it doesn't appear to be returning the modified data. It continues to return the same date object stored in the database.
var moment = require("moment");
var Referral = sequelize.define("referral", {
id: {
allowNull: false,
type: DataTypes.CHAR(24),
unique: true,
primaryKey: true
},
active: {
allowNull: false,
type: DataTypes.BOOLEAN,
defaultValue: true
},
name: {
allowNull: true,
type: DataTypes.STRING
},
method: {
allowNull: true,
type: DataTypes.STRING
},
signup_at: {
allowNull: false,
type: DataTypes.DATE,
get: function() {
return moment(this.getDataValue("signup_at")).format("MM/DD/YYYY");
}
}
});
Referral.findAll({
where: {
active: true
},
raw: true
}).then(function(referrals) {
console.log(referrals);
});
Try the following code
Referral.findAll({
where: {
active: true
},
raw: false
}).then(function (referrals) {
referrals.forEach(function (referral, index, array) {
var value = referral.get();
console.log(value);
});
});
It seems raw = true won't use the getter function.
You may make raw false and call the get explicitly.

many-to-many relationship - populating related data in query - Keystonejs

I am working on a keystonejs project here and am running into a problem with relationships between two models.
I have the below two models:
User model:
User.add({
name: { type: Types.Name, required: true, index: true },
email: { type: Types.Email, initial: true, required: true, index: true },
number: { type: Types.Number, index: true },
password: { type: Types.Password, initial: true, required: true }
}, 'Permissions', {
isAdmin: { type: Boolean, label: 'Can access Keystone', index: true },
}, 'Groups', {
groups: {type: Types.Relationship, ref: 'Group', many: true }
});
And I have a Group model
Group.add({
name: { type: String, required: true, index: true },
description: { type: String, initial: true, required: true, index: true}
});
I have a aggregate function that basically pulls all of the groups that have users in it
User.model.aggregate({$group:{'_id':'$groups'}}).exec(function(err,data){
console.log(data);
});
My problem is that the above aggregate only shows me my groups by their _id values and not their names.
How could I populate and show the names in the front end? Obviously the id's are necessary on the back end to reference but to the front end users that won't work.
Thanks
You can create what you want pretty easily so don't be discouraged. You just need a heads up on the '.populate()' function in mongoose. See example below.
User Model (slightly tidied - I removed the (strange?) nesting)
User.add({
name: { type: Types.Name, required: true, index: true },
email: { type: Types.Email, initial: true, required: true, index: true},
number: { type: Types.Number, index: true },
password: { type: Types.Password, initial: true, required: true },
isAdmin: { type: Boolean, label: 'Can access Keystone', index: true },
groups: {type: Types.Relationship, ref: 'Group', many: true }
});
Group Model -- note the Group.relationship({}) option I've added near the bottom for admin convenience (shows users which reference that group on the backend)
Group.add({
name: { type: String, required: true, index: true },
description: { type: String, initial: true, required: true, index: true}
});
Group.relationship({ ref: 'User', path: 'users', refPath:'Groups'});
Controller getting a list of users with all their corresponding group information
view.on('init', function(next) {
keystone.list('User').model.find()
.populate('groups')
.exec(function(err, users) {
if(err) {
console.log(err);
return next(err);
} else {
locals.data.users = users;
next(err);
}
});
});
Controller getting Users within a specific group to display on the frontend (you need the group ID first)
view.on('init', function(next) {
keystone.list('User').model.find()
.where('Groups').in([array, of, group, ids])
.populate('groups')
.exec(function(err, users) {
if(err) {
console.log(err);
return next(err);
} else {
locals.data.users = users;
next(err);
}
});
});
locals.data.users would return like this in each case:
[
{
_id: '',
name: '',
...
groups: [
{
_id: '',
name: ''
...
}
]
}
]

Categories