Mongoose: Array with 2 different nested schemas as key-value - node.js

I'm trying to insert data from Google Reply to Reviews API into MongoDb using mongoose but some of the objects inserted have empty fields and some of them are nested key-value objects inside an Array which I don't know how to catch.
This is the JSON response from the API:
{
"reviews": [
{
"reviewId": "19940672-2a0f-470b-8760-05ce91add518",
"authorName": "el piculin 357",
"comments": [
{
"userComment": {
"text": "\tEs bueno y los logos son faciles de adivinar",
"lastModified": {
"seconds": "1675041753",
"nanos": 386000000
},
"starRating": 5,
"reviewerLanguage": "es",
"device": "grandpplte",
"androidOsVersion": 23,
"appVersionCode": 99,
"appVersionName": "2.5.0",
"deviceMetadata": {
"productName": "grandpplte (Galaxy J2 Prime)",
"manufacturer": "Samsung",
"deviceClass": "FORM_FACTOR_PHONE",
"screenWidthPx": 540,
"screenHeightPx": 960,
"nativePlatform": "ABI_ARM_V7,ABI_ARM",
"screenDensityDpi": 240,
"glEsVersion": 196609,
"ramMb": 1407
}
}
}
]
}
]
}
This is my Schema (review.model.js):
const mongoose = require("mongoose");
const ReviewSchema = new mongoose.Schema({
reviewId: { type: String, default: '', unique: true },
authorName: { type: String, default: '' },
userComment: {
text: { type: String, default: '' },
lastModified: {
seconds: { type: String, default: '' },
nanos: { type: Number, default: 0 },
},
starRating: { type: Number, default: 0 },
reviewerLanguage: { type: String, default: '' },
device: { type: String, default: '' },
androidOsVersion: { type: Number, default: 0 },
appVersionCode: { type: Number, default: 0 },
appVersionNumber: { type: String, default: '' },
thumbsUpCount: { type: Number, default: 0 },
thumbsDownCount: { type: Number, default: 0 },
},
developerComment: {
text: { type: String, default: '' },
lastModified: {
seconds: { type: String, default: '' },
nanos: { type: Number, default: 0 },
}
},
appInformation: {
packageIdentifier: { type: String, default: '' }
}
});
const Review = mongoose.model("Review", ReviewSchema);
module.exports = Review;
Another thing that I tried:
const mongoose = require("mongoose");
const AppInformationSchema = new mongoose.Schema({
packageIdentifier: { type: String, default: '' }
});
const DeveloperCommentSchema = new mongoose.Schema({
text: { type: String, default: '' },
lastModified: { LastModifiedSchema }
});
const LastModifiedSchema = new mongoose.Schema({
seconds: { type: String, default: '' },
nanos: { type: Number, default: 0 },
});
const UserCommentScehma = new mongoose.Schema({
text: { type: String, default: '' },
lastModified: { type: LastModifiedSchema },
starRating: { type: Number, default: 0 },
reviewerLanguage: { type: String, default: '' },
device: { type: String, default: '' },
androidOsVersion: { type: Number, default: 0 },
appVersionCode: { type: Number, default: 0 },
appVersionNumber: { type: String, default: '' },
thumbsUpCount: { type: Number, default: 0 },
thumbsDownCount: { type: Number, default: 0 }
});
const ReviewSchema = new mongoose.Schema({
reviewId: { type: String, default: '', unique: true },
authorName: { type: String, default: '' },
comments: [UserCommentScehma, DeveloperCommentSchema],
appInformation: { AppInformationSchema }
});
const Review = mongoose.model("Review", ReviewSchema);
module.exports = Review;
But I get an error when trying to implement it like that:
ReferenceError: Cannot access 'LastModifiedSchema' before initialization
What is the right way of implementing it?

As UserCommentScehma is under userComment itself, so you need to consider this structure in the schema as well.
const ReviewSchema = new mongoose.Schema({
reviewId: { type: String, default: '', unique: true },
authorName: { type: String, default: '' },
comments: [{ userComment: UserCommentScehma }, DeveloperCommentSchema],
appInformation: { AppInformationSchema }
});

Related

One to many with MongoDB (Mongoose) with .populate()

What I am doing
I am trying to return a player by playerId with all ref items from each object in it's schema. In this example, I am specifically talking about the players inventory.
How can I return all reference items and their properties?
In my service file, I am getting my player with:
/**
* Get a player by playerId
* #param playerId
* #returns {Promise<*>}
*/
module.exports.getPlayer = async (playerId) => {
return await Player.findOne({ playerId: playerId }).populate('inventory');
};
And this is my returned JSON
{
"success": true,
"data": {
"_id": "63bb1f3ec17d33f4d2a87194",
"playerId": 4183489039,
"name": "AlanGreyjoy",
"currency": {
"copper": 500,
"silver": 10,
"gold": 0
},
"online": false,
"inventory": [
{
"currentDurability": 0,
"item": "63ba0c54407456969ba38615",
"amount": 1,
"_id": "63bc4fa070eaa247288e3573",
"createdAt": "2023-01-09T17:32:16.643Z",
"updatedAt": "2023-01-09T17:32:16.643Z"
}
],
"bank": [],
"questTracker": [],
"friends": [],
"knownRecipes": [],
"jobs": [],
"createdAt": "2023-01-08T19:53:34.903Z",
"updatedAt": "2023-01-09T17:32:16.643Z",
"__v": 1
}
}
As you can see, the item in the inventory array is not populating with the ref item from the items collection.
I have googled and googled and tried so many different things, but nothing is working.\
The item does exist in the db
My Models
My Player.model.js
const mongoose = require('mongoose');
const toJSON = require('../plugins/mongoToJson');
const Schema = mongoose.Schema;
const questTracker = new Schema(
{
quest: { type: mongoose.Schema.Types.ObjectId, ref: 'Quest' },
complete: { type: Boolean, default: false },
},
{ timestamps: true }
);
const friends = new Schema(
{
player: { type: mongoose.Schema.Types.ObjectId, ref: 'Player' },
isFavorite: { type: Boolean, default: false },
},
{ timestamps: true }
);
const knownRecipes = new Schema(
{
recipe: { type: mongoose.Schema.Types.ObjectId, ref: 'Recipe' },
},
{ timestamps: true }
);
const jobs = new Schema(
{
job: { type: mongoose.Schema.Types.ObjectId, ref: 'Job' },
},
{ timestamps: true }
);
const inventoryItems = new Schema(
{
item: { type: mongoose.Schema.Types.ObjectId, ref: 'Item' },
amount: { type: Number, default: 0 },
currentDurability: { type: Number, default: 0 },
},
{ timestamps: true }
);
const bank = new Schema(
{
item: { type: mongoose.Schema.Types.ObjectId, ref: 'Item' },
amount: { type: Number, default: 0 },
},
{ timestamps: true }
);
const playerSchema = new Schema(
{
playerId: {
type: Number,
unique: true,
required: true,
},
name: {
type: String,
required: true,
unique: true,
},
currency: {
type: Object,
default: {
copper: 500,
silver: 10,
gold: 0,
},
},
inventory: [inventoryItems],
bank: [bank],
questTracker: [questTracker],
friends: [friends],
knownRecipes: [knownRecipes],
jobs: [jobs],
online: {
type: Boolean,
default: false,
},
},
{ timestamps: true }
);
const PlayerModel = mongoose.model('player', playerSchema);
module.exports = PlayerModel;
My Item.model.js
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const toJSON = require('../plugins/mongoToJson');
const vendorPriceSchema = mongoose.Schema({
copper: {
type: Number,
default: 0,
},
silver: {
type: Number,
default: 0,
},
gold: {
type: Number,
default: 0,
},
});
const itemSchema = new Schema(
{
title: {
type: String,
required: true,
unique: true,
},
assetId: {
type: Number,
required: true,
},
description: {
type: String,
},
equipDescription: {
type: String,
},
additionalDescription: {
type: String,
},
consumableUseDescription: {
type: String,
},
itemLevel: {
type: Number,
},
requiredLevel: {
type: Number,
},
type: {
type: String,
required: true,
},
subtype: {
type: String,
required: true,
},
stamina: {
type: Number,
},
intellect: {
type: Number,
},
criticalStrike: {
type: Number,
},
agility: {
type: Number,
},
mastery: {
type: Number,
},
maxDurability: {
type: Number,
},
vendorPrice: { vendorPriceSchema },
minDamage: {
type: Number,
default: 0,
},
maxDamage: {
type: Number,
default: 0,
},
speed: {
type: Number,
},
maxStack: {
type: Number,
},
},
{ timestamps: true }
);
const ItemModel = mongoose.model('Item', itemSchema);
module.exports = ItemModel;
You can use something like this:
module.exports.getPlayer = async (playerId) => {
return await Player.findOne({ playerId: playerId }).populate({
path: "inventory",
populate: {
path: "item",
},
});
};

Problems using $in in MongoDB with NodeJS

I am facing a strange problem in trying to use $in operator with mongoose and node. Following is the snippet from the code.
queryObj = {imei: {$in: queryItems.imei}}
console.log(JSON.stringify(queryObj))
//{"imei":{"$in":[866551038558099,866551038558099, 866551038549072, 866551038536939, 866551038557844]}}
The query returns an empty array, whereas if I copy-paste the log in MongoDB Compass, I can see the result set.
const mongoose = require('mongoose');
const GeofenceReportSchema = new mongoose.Schema({
createdOn: {
type: Date,
default: new Date(),
required: true
},
targetDate: {
type: Date,
required: true
},
vehicleNo: {
type: String,
default: '',
required: true
},
imei: {
type: Number,
required: true
},
numberPlate: {
type: String,
default: '',
required: false
},
driverName: {
type: String,
default: '',
required: false
},
driverContact: {
type: Number,
default: 0,
required: false
},
startTimeIgnOn: {
type: String,
default: '',
required: false
},
endTimeIgnOff: {
type: String,
default: '',
required: false
},
idlingTime: {
type: Number,
default: 0,
required: false
},
distance: {
type: Number,
default: 0,
required: true
},
averageSpeed: {
type: Number,
default: 0,
required: true
},
engineHours: {
type: Number,
default: 0,
required: true
},
startOdometer: {
type: Number,
default: 0,
required: true
},
endOdometer: {
type: Number,
default: 0,
required: true
},
totalFuelConsumption: {
type: Number,
default: 0,
required: true
},
totalStops: {
type: Number,
default: 0,
required: true
},
geofenceInTime: {
type: Number,
default: 0,
required: false
},
geofenceOutTime: {
type: Number,
default: 0,
required: false
},
distanceInsideGeofence: {
type: Number,
default: 0,
required: false
},
distanceOutsideGeofence: {
type: Number,
default: 0,
required: false
},
performance: {
type: Number,
default: 0,
required: false
}
});
let model;
try {
model = mongoose.model('GeofenceReport');
} catch (error) {
model = mongoose.model('GeofenceReport', GeofenceReportSchema);
}
module.exports = model;
function getGeofenceReport(queryItems){
let queryObj = {$and: [ {targetDate: { $gte: new Date(queryItems.fromTime), $lte: new Date(queryItems.toTime)}},{imei: {$in: queryItems.imei}}]}
console.log(JSON.stringify(queryObj))
return new Promise((resolve, reject) => {
connectToDatabase().then(() => {
GeofenceReportSchema.find(queryObj)
.sort({"targetDate" : 1})
.then((resp) => {
resolve(resp);
})
.catch((err) => {
reject(err);
})
}).catch((err) => {
reject(err);
});
});
}
Sample data
{
"_id": {
"$oid": "606d82c585ac11242012cb37"
},
"createdOn": {
"$date": "2021-04-07T10:00:37.269Z"
},
"targetDate": {
"$date": "2021-04-06T14:30:00.000Z"
},
"vehicleNo": "ABCDE",
"imei": {
"$numberLong": "866551038558000"
},
"numberPlate": "",
"driverName": "John Doe",
"driverContact": 99999999,
"startTimeIgnOn": "2021-04-06T01:59:44.000Z",
"endTimeIgnOff": "2021-04-06T11:15:31.000Z",
"idlingTime": 8344000,
"distance": 27277.310000000056,
"averageSpeed": 3.59232320529802,
"engineHours": 23325000,
"startOdometer": 658548.82,
"endOdometer": 685826.13,
"totalFuelConsumption": 27.277310000000057,
"totalStops": 8,
"geofenceInTime": 18043000,
"geofenceOutTime": 5282000,
"distanceInsideGeofence": 4421.061251513145,
"distanceOutsideGeofence": 22856.248748486913,
"performance": 59.52
}
Invocation
{
fromTime : '2021-03-03T18:30:00.000Z',
toTime: '2021-03-31T18:29:59.000Z',
imei: 866551038558000
}
Note that I have already checked DB connections, have tried with the empty find method and I am able to get data. I have also checked data types. Isn't an issue there. What can be the issue?

Mongoose, express- create schema with array of objects

I try to create mongoose schema containing array of objects. Schema looks like:
const mapSchema = new Schema({
name: {
type: String
},
description: {
type: String
},
rate: {
type: Number,
default: 0
},
sumOfRates: {
type: Number,
default: 0
},
places: [
{
name: String,
description: String,
difficulty: { type: String, default: 0 },
sumOfRates: { type: String, default: 0 },
lat: Number,
lng: Number
}
]
}, {
timestamps: true
})
Places is array of objects. I tried to create mongoose schema and use it as element of places array(places: [ref: 'placeSchema']).
const placeSchema = new Schema({
name: {
type: String
},
description: {
type: String
},
difficulty: {
type: Number,
default: 0
},
lat: {
type: Number
},
lng: {
type: Number
}
})
But I think it would create another, separeted document in mongo(places document). Does it? I try to avoid such situation To keep it in one document.Now(code pasted above), gives me an error, when i try to insert json in postman:
{
"name": "ergergergg",
"description": "rgergrg",
"places": [
{
"name":"cvcv",
"description":"rgergrg",
"lat":233,
"lng":232
}]
}
Error:
ValidationError: places: Cast to Array failed for value "[object Object]" at path "places"
Why such error? How to fix this?
The appropriate way to go would be to use of a sub schema.
const subSchema = new Schema({
name: {
type: String,
},
description: {
type: String,
},
difficulty: {
type: Number,
default: 0,
},
lat: {
type: Number,
},
lng: {
type: Number,
},
});
const mapSchema = new Schema({
name: {
type: String,
},
description: {
type: String,
},
rate: {
type: Number,
default: 0,
},
sumOfRates: {
type: Number,
default: 0,
},
places: [subSchema],
}, {
timestamps: true
});
To insert a new data
mapSchema.insert({
name: "ergergergg",
description: "rgergrg",
places: [
{
name: "cvcv",
description: "rgergrg",
lat: 233,
lng: 232,
},
{
name: "22",
description: "erwer2",
lat: 123,
lng: 321,
},
],
});
To make the subSchema optional, you can maybe do something like this #DanMossa :
pages: {
type: [subSchema],
required: false
},

NodeJS - Mongoose populate trouble

I am running my app on Node 8.4.0 MongoDB 3.4 and I can't get Mongoose population to work.
I have two models:
var mongoose = require('mongoose');
var Carrier = require('./Carrier.js');
var ValuationFormSchema = new mongoose.Schema({
assignmentnumber: {
type: Number,
default: Number()
},
assignmentpartnumber: {
type: Number,
default: Number()
},
currency: {
type: String,
default: String()
},
email: {
type: String,
default: String()
},
iagree: {
type: Boolean,
default: Boolean()
},
totalValue: {
type: Number,
default: Number()
},
carrier: [{
type: mongoose.Schema.Types.ObjectId,
ref: 'Carrier'
}],
createdAt: {
type: Date,
default: Date.now
},
uuid: {
type: String,
default: String()
},
submitted: {
type: Boolean,
default: false
},
send: {
type: Boolean,
default: false
},
template: String,
surveyvolume: Object,
agreement: Object
});
module.exports = mongoose.model('ValForm', ValuationFormSchema);
var mongoose = require('mongoose');
var Item = require('./Item.js');
var CarrierSchema = new mongoose.Schema({
name: {
type: String,
required: true
},
items: [{
type: mongoose.Schema.Types.ObjectId,
ref: 'Item'
}],
createdAt: {
type: Date,
default: Date.now
}
});
module.exports = mongoose.model('Carrier', CarrierSchema);
and this is my API
app.post('/api/valuationform/populate', function(req, res) {
var email = req.body.email;
ValForm.find({'email' : email},{'submitted' : false})
.populate('Carrier')
.exec(function(error, docs) {
if (!error) {
console.log(JSON.stringify(docs));
res.json(docs);
} else {
res.send('{ error: ' + error + ' }');
}
});
})
Instead of getting the carrier objects i get the id's of the objects.
There are multiple carriers per valforms.
The result looks like this:
{
"_id": "58d24ff9c195a72784108d85",
"assignmentid": 123441,
"__v": 0,
"send": false,
"uuid": "044da37c-8976-4f80-b103-d07c43a97af2",
"createdAt": "2017-03-22T10:20:41.260Z",
"carrier": [
"58d24ff9c195a72784108d86",
"58d24ff9c195a72784108d8d"
],
"totalValue": 40000,
"iagree": false,
"email": "a#b.com",
"currency": "SEK",
"assignmentpartnumber": 0,
"assignmentnumber": 0
}
I want the carriers to be returned as objects not objectid's.
What am i doing wrong here ?
Maybe try in lowercase carrier as defined in your schema.
ValForm.find({ email: email }, { submitted: false }).populate('carrier').exec(...);

Mongoose get value from embedded document

i have a scheme like this
var WFWorkItemDocument = new Schema({
id: { type: String, required: true, unique: true, default: uuid.v1 },
description: { type: String },
period: [{
id: { type: String, default: uuid.v1 },
start: { type: Date, default: Date.now }
due: { type: Number, integer: true },
new: { type: Number, integer: true },
}],
i want to get the period's due value for that i used a method like
WorkItem.findOne({ id: idUpdate }, function(err, WorkItem) {
if (err) {
console.log("invlaid id");
//return res.send(404, { error: 'invalid id' });
}
if (WorkItem) {
console.log("id");
console.log(WorkItem.period.due);
} else {
//res.send(404, new Error('Workitem not found'));
}
});
but it doesn't work how can i get the due value??
This is the result for console.log(WorkItem)
Change the schema to embed one object. Unless you need embedded array.
var WFWorkItemDocument = new Schema({
id: { type: String, required: true, unique: true, default: uuid.v1 },
description: { type: String },
period: {
id: { type: String, default: uuid.v1 },
start: { type: Date, default: Date.now }
due: { type: Number, integer: true },
new: { type: Number, integer: true },
},
And if you define it as an embedded array, you can access like :
WorkItem.period[index].due

Resources