Joining collections in mongodb with node js - node.js

I have two collections in mongodb "components" and "airframes" which I am trying to join together (with a one to many relationship). I have the following code which gets the airframe and component data separately from the database, however after days of effort, I cannot figure out how to join the two together. I assume I need to use $lookup to achieve the desired result but any assistance in constructing the code would be greatly appreciated.
my models are as follows and I am trying to join all the component records under the associated Airframe. the airframe field on the Component holds the related Airframes' id.
const airframeSchema = mongoose.Schema({
name: { type: String, required: true },
sNumber: { type: String, required: true },
aStatus: { type: String, required: true },
components: [ {
type: mongoose.Schema.Types.ObjectId,
ref: 'Component' } ]
});
module.exports = mongoose.model('Airframe', airframeSchema);
const componentSchema = mongoose.Schema({
name: { type: String, required: true },
serial: { type: String, required: true },
type: { type: String, required: true },
airFrame: {
type: mongoose.Schema.Types.ObjectId,
required: true,
ref: 'Airframe'},
});
module.exports = mongoose.model('Component', componentSchema);
the AirFramesService is as follow. I would like to join the component data under a array called "component".
getAirframes() {
this.http
.get<{ message: string; airframes: any }>("http://3.135.49.46:8080/api/airframes")
.pipe(
map(airframeData => {
return airframeData.airframes.map(airframe => {
return {
name: airframe.name,
sNumber: airframe.sNumber,
aStatus: airframe.aStatus,
id: airframe._id,
};
});
})
)
.subscribe(transformedAirframes => {
this.airframes = transformedAirframes;
this.airframesUpdated.next([...this.airframes]);
});
}
getAirframeUpdateListener() {
return this.airframesUpdated.asObservable();
}
getAirframe(id: string) {
return this.http.get<{ _id: string; name: string; sNumber: string ; aStatus: string}>(
"http://3.135.49.46:8080/api/airframes/" + id
);
}
The airframes route code is as follows:
router.get("", (req, res, next) => {
Airframe.find().then(documents => {
res.status(200).json({
message: "Airframes fetched successfully!",
airframes: documents
});
});
});
and here is the code within the ts component file that gets the airframe data is as follows.
constructor( public airframesService: AirframesService) {
this.airframesService.getAirframes();
this.airframesSub = this.airframesService.getAirframeUpdateListener()
.subscribe((airframes: Airframe[]) => {
this.isLoading = false;
this.airframes = airframes;
}, 0);
});
}
the desired outcome would be the following (at the moment I only get the airframe data):
{
_id: "123"
name: "Airframe01"
sNumber: "757"
aStatus: "Active"
id: "5e8052ad1fa18f1c73524664"
components: [
{
name: "Left Tank",
serial: "3456789",
type: "Landing Gear",
airFrame: "5e8052ad1fa18f1c73524664"
},
{
name: "Right Tank",
serial: "45678",
type: "Landing Gear",
airFrame: "5e8052ad1fa18f1c73524664"
}
]
}

Your document structure already established a one-to-many kinda relationship between the two models, you can use Mongoose population to get the join you described in the question. The populate code should be somewhere in the airframes route like this:
router.get("", (req, res, next) => {
// Notice the populate() method chain below
Airframe.find().populate('components').then(documents => {
// The documents output should have their "components" property populated
// with an array of components instead of just Object IDs.
res.status(200).json({
message: "Airframes fetched successfully!",
airframes: documents
});
});
});
You can read more about Mongoose population here.

Related

How to populate data in dynamoose

I'm building an API where a user can make a publication to be displayed on a thread. I'm trying to make the author data to be seen with the publication. This way the author data could be get like
console.log( publication.author.completeName )
When saving publication, I save the author field with the value of the user id posting the publication.
Then I'm trying to populate the data like shown here
This is my User model
const dynamoose = require("dynamoose");
const { v4: uuidv4 } = require('uuid');
const userSchema = new dynamoose.Schema(
{
id: {
type: String,
hashKey: true,
default: () => uuidv4(),
},
email: {
type: String,
required: true
},
completeName: {
type: String,
},
pseudo: {
type: String, // Should make check on create and edit to ensure unicity of this column
},
gender: {
type: String,
enum: ['male', 'female', 'other']
},
speciality: {
type: String
},
address: {
type: String,
},
phoneNumber: {
type: String,
}
},
{ timestamps: true }
);
module.exports = dynamoose.model("User", userSchema);
and this is my publication model:
const dynamoose = require("dynamoose");
const { v4: uuidv4 } = require('uuid');
const publicationSchema = new dynamoose.Schema(
{
id: {
type: String,
hashKey: true,
default: () => uuidv4(),
},
photo: {
type: Array,
schema: [String],
default: []
},
description: {
type: String,
required: true
},
anatomies: {
type: Array,
schema: [String],
required: true,
},
specialities: {
type: Array,
schema: [String],
required: true,
},
groupId: {
type: String,
},
author: {
type: String
}
},
{ timestamps: true }
);
module.exports = dynamoose.model("Publication", publicationSchema);
I'm trying to populate the author field when getting all the data like this:
exports.listPublication = async (req, res, next) => {
try {
Publication
.scan()
.exec()
.then( async function (data) {
return Promise.all( data.map(function(pub){
return pub.populate({
path: 'author',
model: 'User'
});
}))
})
.then((data) => {
success(res, { data: data });
})
.catch((err) => {
throw new HttpException(err.message);
});
} catch (err) {
error(next, res, err);
}
}
but the author field is not populated, it only display the value of the author field, which is the string value of the author id.
Help please, I can't figure what I'm doing wrong

Is there a way i could keep track of the Time and the entity that was changed in a model

Basically I'm trying to get the time and the entity changed in a particular model when ever the update method is called.
This is my model I want to keep track of:
const mongoose = require("mongoose");
const modelSchema = mongoose.Schema({
user: {
type: mongoose.Schema.Types.ObjectId,
ref: "User",
},
name: {
type: String,
required: true,
},
note1: String,
note2: String,
note3: String,
images: {
type: Array,
required: true
},
status: {
enum: ['draft', 'pending_quote', 'pendong_payment', 'in_production', 'in_repair', 'pemding_my_review', 'fulfilled'],
type: String,
default: "draft"
},
price: {
type: mongoose.Schema.Types.ObjectId,
ref: "Price",
}
}, {
timestamps: true,
})
module.exports = mongoose.model("Model", modelSchema)
And this is the method I call to update the status:
exports.updateModel = async (req, res) => {
try {
let id = req.params.id;
let response = await Model.findByIdAndUpdate(id, req.body, {
new: true
})
res.status(200).json({
status: "Success",
data: response
})
} catch (err) {
res.status(500).json({
error: err,
msg: "Something Went Wrong"
})
}
}
you can add a new field in your schema like:
logs:[{
entity: String,
timeStamp: Date
}]
Then updating it basing on your current code:
let id = req.params.id;
// I don't know whats in the req.body but assuming that it
// has the correct structure when passed from the front end
let response = await Model.findByIdAndUpdate(id,
{
$set:req.body,
$push:{logs:{entity:'your entity name here',timeStamp:new Date()}}
}, {
new: true
})

how to save document and update another which are dependent on each other with mongoose

Here is my bid model.
const BidSchema = new Schema({
auctionKey: {
type: mongoose.Types.ObjectId,
ref: "Auction",
required: true
},
amount: { type: String, required: true },
userName: { type: String, required: true },
});
And, here is my Auction Model (Notice the relationships between these two models).
const AuctionSchema = new Schema({
title: { type: String, required: true },
startDate: { type: Date, required: true },
closeDate: { type: Date, required: true },
initialBidAmount: { type: Number, required: true },
bidIncrementAmount: { type: Number, required: true },
bids: [
{
type: mongoose.Types.ObjectId,
ref: 'Bid'
}
]
});
When user bids for any auction, I'm saving bid in bids collection and updating auctions collection using mongoose findOneAndUpdate.
const postBid = async (req, res, next) => {
const { auctionKey } = req.body;
const bid = new BidModel(req.body);
bid.save(error => {
if (error) {
res.status(500).json({ message: "Could not post bid." });
}
});
const aucById = await AuctionModel.findOneAndUpdate(
{ _id: auctionKey },
{ $push: { bids: bid } }
).exec((error: any, auction: IAuction) => {
if (error) {
res.status(500).json({ message: "Could not post bid." });
} else {
res.status(201).json({ bid });
}
});
};
For any reason if any of these two (save bid and findOneAndUpdate) throws any error I want nothing to be saved into database. I mean to say either they should save and update or nothing should be done on database.
I have tried using mongoose session and transaction but got this error.
MongoError: This MongoDB deployment does not support retryable writes. Please add retryWrites=false to your connection string.
Is there any way to work out in this scenario?
If I understand your problem right, you can just delete created document in:
.exec((error: any, auction: IAuction) => {
if (error) {
// here, by using .deleteOne()
res.status(500).json({ message: "Could not post bid." });
}
Or just change structure of your code, so only when two are successfully created, they will be saved and response will be sent.

node mongoose : aggregate not giving expected result

withe the following Group Schema,
group.model.js
const Role = new mongoose.Schema({
name: { type: String, required: true }, // ensure uniqueness withn group instance using addToSet
description: { type: String, required: false }
});
const GroupSchema = new mongoose.Schema({
name: { type: String, index: { unique: true, required: true, dropDups: true } },
description: { type: String, required: false },
roles: [Role],
createdAt: {
type: Date,
default: Date.now
}
});
I am trying to list all roles ( subdocument) got a specific group
group.controller.js
function listRoles(req, res) {
const group = req.group;
console.log('GROUP: %j', group);
const limit = parseInt(req.query.limit, 10) || 50;
const skip = parseInt(req.query.skip, 10) || 0;
Group.aggregate([
{ $match: { _id: req.params.groupId } },
{ $unwind: '$roles' },
{ $skip: skip },
{ $limit: limit }
], (err, result) => {
if (err) {
res.status(500);
res.json({ message: 'Error. Cannot list roles', errror: err });
}
res.status(200);
console.log('RESULT: %j', result);
res.json(result);
});
}
I should get an array with one role, but I get an empty array
what's wrong with my aggregate code ? thanks for feedback
note: I tried to aggregate only with the $match in the pipe and I also get an empty array... so I guess. the issue comes from the req.params.groupId should be an ObjectId .. how can I cast it ?
console.log
GROUP: {"_id":"5923e2e83afd4149bdf16c61","name":"Admin","description":"Administration group","__v":1,"createdAt":"2017-05-23T07:21:12.470Z","roles":[{"name":"Role1","description":"description role1","_id":"5923e2e83afd4149bdf16c62"}]}
RESULT: []
To better diagnose this, I'd recommend removing steps from your aggregation pipeline and seeing what the result is. However, I suspect your problem is because you have no match at the first stage because you're comparing a string to an ObjectId. Try this:
const mongoose = require('mongoose')
// and in the aggregation:
{ $match: { _id: mongoose.Types.ObjectId(req.params.groupId) } }

SailsJS Perform sorting on populate data

Here is my current event model:
module.exports = {
attributes: {
name: {
type: 'string',
required: true,
unique: true
},
client: {
model: 'client',
required: true
},
location: {
model: 'location',
required: true
}
}
};
The client Model:
module.exports = {
attributes: {
name: {
type: 'string',
required: true,
unique: true
},
address: {
type: 'string'
},
clientContact: {
model: 'user',
required: true
}
}
};
So how do I implement sorting based on the client name and also have the skip and limit property(for pagination) to work along with it.
I tried using the following query:
Event.find({ id: ['583eb530902db3d926345215', '583eb6dd91b972ee26dd97b1'] },
{ select: ['name', 'client'] })
.populate('client', { sort: 'name DESC' })
.exec((err, resp) => resp.map(r => console.log(r.name, r.client)));
But this does not seem to do it.
Waterline doesn't support sorting a result by child records like this. If client was a collection of child records attached to an event, then your code would sort each of the returned Event record's populated client array by name, but I'm assuming in this case client is a singular association (i.e. model: 'client').
If what you want is an array of Event records sorted by the name of their client, you can do it relatively easily using Lodash after you retrieve the records:
var _ = require('lodash');
Event.find(queryCriteria).populate('client').exec((err, resp) => {
if (err) { /* handle error */ }
var sortedRecords = _.sortBy(eventRecords, (record) => { return record.client.name; });
});

Resources