Strongloop: how to fetch a related model using code (not REST API) - node.js

Having trouble getting a related model on a User object. Users have a to-many relation with Customers.
Can I not just say User.customers to grab the customers associated with a User?
I have tried
User.find({include:'customers'}, function(err, user) {
//now what?
//user.customers does not work; there is no Customer array returned.
});
Happy to look in the docs for this but I can't find where this is written.
Thank you

In the loopback examples they often create a "user" model as an extension of loopbacks "User" model.
Note the lower case u.
I had trouble accessing the model when using "User" not "user"
user.json
{
"name": "user",
"base": "User",
"idInjection": true,
"emailVerificationRequired": false,
"properties": {
"createdAt": {
"type": "date"
},
"updatedAt": {
"type": "date"
},
.......
user.js
module.exports = function(user) {
user.observe('before save', function(ctx, next){
if (ctx.instance) {
//If created at is defined
if(ctx.instance.createdAt){
ctx.instance.updatedAt = new Date();
}
else{
ctx.instance.createdAt = ctx.instance.updatedAt = new Date();
}
} else {
ctx.data.updatedAt = new Date();
}
next();
})`

Related

Check if user id is in mongoose document - if yes, change property value to true?

I have an expressjs router that looks in MongoDB collection using the mongoose findById method. It returns an object where inside there exist an userHasSelected array with users id. I dont want to return users id, but just check if current users (the one who made the request) exist in the array. If he is then return true instead of returning the user id.
The verifytoken middleware in the router adds a user id property to the request.That user id is available in the get router message - can i somehow pass that to the Mongoose schema ???
//My router
router.get('/challenge/:challengeId', verifyToken ,function (req, res){
//+ Return one challenge and its options
//- Check if userId is set to options and winner properties
let userId = req.userId;
console.log(userId);
let challengeId = req.params.challengeId;
Challenge.findById(challengeId, (err, suc)=>{
if(err){
res.status(304).send(err);
}
Challenge.
res.status(200).send(suc);
});
})
// And the mongoose Schema
const mongoose = require('mongoose');
var Schema = mongoose.Schema;
//Optionsschema is use in the ChallengeSchema
var OptionSchema = new Schema({
name: { type: String},
isCorrect : { type: Boolean },
description: { type: String },
image : { type : String },
userHasSelected : { type : Object, get : returnUserChallengeStatus}
})
OptionSchema.set('toObject', { getters: true });
OptionSchema.set('toJSON', { getters: true });
var ChallengeSchema = new Schema({
shortEventId : String,
organization: String,
name: String,
winner: String,
options : [OptionSchema]
});
ChallengeSchema.set('toObject', { getters: true });
ChallengeSchema.set('toJSON', { getters: true });
ChallengeSchema.virtual('born').get(function(value) {
return this.name + "plus andet"
});
module.exports = mongoose.model('challenge', ChallengeSchema);
So again - I dont want to return the user id from the userHasSelected array - just check if he is there and if yes, use a getter or a method to set value to true.
Updated explanation
The findById returns this object / document
{
"_id":"5b86dc5bfb6fc03893e55001",
"shortEventId": "d2d3",
"organization": "Braedstrup",
"name": "1. december",
"winner": "5b864cbaa9ce291b148ddd6d",
"options": [
{
"name": "Matas",
"isCorrect": "true",
"description": "Matas er byens førende indenfor pleje og Matas er byens førende indenfor pleje og omsorg Matas er byens førende indenfor pleje og omsorg",
"image": "https://cityxpstorage.blob.core.windows.net/images/Matas.png",
"userHasSelected": [
{
"userId": "5b864cbaa9ce291b148ddd6d"
}
]
},
{
"name": "Føtex",
"isCorrect": "false",
"description": "Føtex er en dejlig butik",
"image": "https://cityxpstorage.blob.core.windows.net/images/Føtex.png"
},
{
"name": "Kvickly",
"isCorrect": "false",
"description": "Kvickly er en dejlig butik",
"image": "https://cityxpstorage.blob.core.windows.net/images/Matas.png"
},
{
"name": "MC Jørgensen",
"isCorrect": "false",
"description": "MC Jørgensen er en dejlig butik",
"image": "https://cityxpstorage.blob.core.windows.net/images/Matas.png"
}
],
"startDate": "2018-10-06T00:00:00.000Z",
"endDate": "2018-10-06T23:59:00.000Z"
}
So the nested array 'userHasSelected' contains information about the users id. I do not want to send that - instead I would like to a {userId : true}.
I have read that getters a able to handle outgoing data.
Posible Solution
I could make the check inside the router get method before returning the object to the client like this
// If user is in array set user to true. I would like to move this responsibility to the schema / document.
suc.options.forEach(option => {
if(Array.isArray(option.userHasSelected))
option.userHasSelected = {userId : true}
});
But I would really like schema to be responsible for that - Is that possible?
I had similar issue and found a workaround. Simply create an optional field on your responsible modal schema, let call it "status". On your controller, check if your array includes requested user's id and write to that field. For example;
on your schema;
caseStatus: {
type: Boolean,
required: false
},
voters: [{
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
}],
then on your controller;
let theCase = await Case.find({speciality: res.locals.user_speciality }).exec();
let status = theCase.voters.includes(res.locals.user_id);
caseItem.caseStatus = status;

Loopback + mongoDB: Can't find extended user

I am using Loopback 3.0 with MongoDB connector.
In a REST method exposed somewhere, I need to access the currently logged user and make some updates on it.
I have extended the base User model, calling it appUser, the login works, and I can get the token (after I changed the token model to point to the appUser) of a logged user. The model is the following one:
{
"name": "appUser",
"plural": "appUsers",
"base": "User",
"idInjection": true,
"options": {
"validateUpsert": true
},
"properties": {
"gender": {
"type": "string",
"enum": [
"M",
"F"
]
},
"birthDate": {
"type": "Date"
}
},
"validations": [],
"relations": {},
"acls": []
}
I need to access the user profile, in order to update it. But when I query it, I get null as result.
const User = app.models.appUser;
User.findOne({
where: {
_id: ObjectId("5aae7ecd2ed1b11b1c09cf25")
}
}, (err, user) => {
if (err || res == null) {
console.log("Error updating the user ");
const error = {
"name": "Database error",
"status": 500,
"message": "Can't access the database."
}
callback(error, null, null);
} else {
//Whatever
}
});
But if I run the same query from Robo3T on MongoDB, it works.
db.getCollection('appUser').find({"_id": ObjectId("5aae7ecd2ed1b11b1c09cf25")})
What am I doing wrong?
Thank you,
Massimo
You didn't call the user so that should be the case, also in your callback you are passing null and not your user result. However I don't get where the res variable came from.
const User = app.models.appUser;
User.findOne({
where: {
_id: ObjectId("5aae7ecd2ed1b11b1c09cf25"),
}
}, (err, user) => {
if (err) {
console.log("Error updating the user", err); // logs the app error
const error = {
"name": "Database error",
"status": 500,
"message": "Can't access the database."
}
callback(error, null); // passes your custom error
}
console.log("APP USER", user);
callback(null, user);
});
I don't how you calling your callback but i think you can manage this.
If you still have no result try changing _id to id

How to add an other property to a mongoDB found documents using mongoose?

I want to add an extra property state: 'found' to each found document in mongoose. I have the following code:
router.get('/all', function(req, res, next) {
var allPets = [];
FoundPet.find({}, function(err, pets) {
pets = pets.map((obj) => {
obj.state = 'found';
return obj;
})
res.send(pets)
})
});
I am expecting to have something like this returned:
[
{
"_id": "59c7be569a01ca347006350d",
"finderId": "59c79570c5362d19e4e64a64",
"type": "bird",
"color": "brown",
"__v": 0,
"timestamp": 1506291998948,
"gallery": [],
"state": "found" // the added property
},
{
"_id": "59c7c1b55b25781b1c9b3fae",
"finderId": "59c79a579685a91498bddee5",
"type": "rodent",
"color": "brown",
"__v": 0,
"timestamp": 1506291998951,
"gallery": [],
"state": "found" // the added property
}
]
but I can't get the new property added successfully using the above code, is there any solution for that ?
The reason why it is not working, is because Mongoose by default returns a model for each document returned from the database.
Try the same but using lean(), which returns a plain javascript object instead.
FoundPet
.find({})
.lean()
.exec(function(err, pets) {
pets.forEach((obj) => {
obj.state = 'found';
});
res.send(pets);
});
One approach would be to use the aggregation framework in which you can add the extra field using the $addFields pipeline. This allows you to add new fields to documents and the pipeline outputs documents that contain all existing fields from the input documents and newly added fields.
Hence you can run the aggregate operation as:
router.get('/all', function(req, res, next) {
FoundPet.aggregate([
{
"$addFields": {
"state": { "$literal": "found" }
}
}
]).exec((err, pets) => {
if (err) throw err;
res.send(pets);
});
});

create a new object Id in mongoDB using node js

I am using the below code to insert data to mongodb
router.post('/NewStory', function (req, res) {
var currentObject = { user: userId , story : story , _id:new ObjectID().toHexString() };
req.db.get('clnTemple').findAndModify({
query: { _id: req.body.postId },
update: { $addToSet: { Stories: currentObject } },
upsert: true
});
});
This code is working fine if i remove the _id:new ObjectID().toHexString()
What i want to achieve here is that for every new story i want a unique _id object to be attached to it
What am i doing wrong?
{
"_id": {
"$oid": "55ae24016fb73f6ac7c2d640"
},
"Name": "some name",
...... some other details
"Stories": [
{
"userId": "105304831528398207103",
"story": "some story"
},
{
"userId": "105304831528398207103",
"story": "some story"
}
]
}
This is the document model, the _id that i am trying to create is for the stories
You should not be calling .toHexString() on this as you would be getting a "string" and not an ObjectID. A string takes more space than the bytes of an ObjectId.
var async = require('async'),
mongo = require('mongodb'),
db = require('monk')('localhost/test'),
ObjectID = mongo.ObjectID;
var coll = db.get('junk');
var obj = { "_id": new ObjectID(), "name": "Bill" };
coll.findAndModify(
{ "_id": new ObjectID() },
{ "$addToSet": { "stories": obj } },
{
"upsert": true,
"new": true
},
function(err,doc) {
if (err) throw err;
console.log(doc);
}
)
So that works perfectly for me. Noting the "new" option there as well so the modified document is returned, rather than the original form of the document which is the default.
{ _id: 55c04b5b52d0ec940694f819,
stories: [ { _id: 55c04b5b52d0ec940694f818, name: 'Bill' } ] }
There is however a catch here, and that is that if you are using $addToSet and generating a new ObjectId for every item, then that new ObjectId makes everything "unique". So you would keep adding things into the "set". This may as well be $push if that is what you want to do.
So if userId and story in combination already make this "unique", then do this way instead:
coll.findAndModify(
{
"_id": docId,
"stories": {
"$not": { "$elemMatch": { "userId": userId, "story": story } }
}
},
{ "$push": {
"stories": {
"userId": userId, "story": story, "_id": new ObjectID()
}
}},
{
"new": true
},
function(err,doc) {
if (err) throw err;
console.log(doc);
}
)
So test for the presence of the unique elements in the array, and where they do not exist then append them to the array. Also noting there that you cannot do an "inequality match" on the array element while mixing with "upserts". Your test to "upsert" the document should be on the primary "_id" value only. Managing array entries and document "upserts" need to be in separate update operations. Do not try an mix the two, otherwise you will end up creating new documents when you did not intend to.
By the way, you can generate an ObjectID just using monk.
var db = monk(credentials.database);
var ObjectID = db.helper.id.ObjectID
console.log(ObjectID()) // generates an ObjectID

nodejs/loopback: defining relationships doesn't seem to be reflected on database?

I am creating an API with strongloop loopback.
I have defined my models, and basically all is good there.
But I have a problem understanding how loopback deals with relationships.
Not all of my relationships I defined seem to really be reflected in the database and the interface.
For example, I have a model song, it
hasAndBelongsToMany albums
hasAndBelongsToMany playlists
hasAndBelongsToMany userplaylists
belongsTo artist
Here is /common/models/song.json
{
"name": "song",
"plural": "song",
"base": "PersistedModel",
"idInjection": true,
"properties": {
//some more properties of song
},
"validations": [],
"relations": {
"albums": {
"type": "hasAndBelongsToMany",
"model": "album",
"foreignKey": ""
},
"artist": {
"type": "belongsTo",
"model": "artist",
"foreignKey": ""
},
"playlists": {
"type": "hasAndBelongsToMany",
"model": "playlist",
"foreignKey": ""
},
"userplaylists": {
"type": "hasAndBelongsToMany",
"model": "userplaylist",
"foreignKey": ""
}
},
"acls": [],
"methods": []
}
But when I look at the postgresql table generated, I see:
title | character varying(1024) | not null
id | integer | not null default nextval('song_id_seq'::regclass)
#some other properties of song
artistid | integer |
Accordingly, the interface in loopbacks explorer at localhost:3000/explorer says:
post /song
Response Class
Model
Model Schema
{
"title": "",
//some other properties of song
"id": 0,
"artistId": 0
}
The question: Shouldn't there also be a songs, a playlists and a userplaylists variable??? Or have I been working too much in the NoSql world and now I forgot how to handle relationships?
BTW. I have a migrate script which I executed when adding the relationships to the models:
var path = require('path');
var app = require(path.resolve(__dirname, '../server'));
var dataSource = app.dataSources.cantoalegre_ps_DS;
dataSource.automigrate(function(err) {
if (err) {
console.log("Error migrating models: " + err);
}
else {
console.log("Successfully migrated models");
}
process.exit();
});
usually related data has the foreign key:
customer,
order belongs to customer so order has a column that contains the customer id.
Take care that at every change models have to be synchronized to the db by an autoupdate script.
module.exports = function(app) {
var ds = app.dataSources.pg;
ds.isActual('mymodel', function(err, actual) {
if (!actual) {
ds.autoupdate('mymodel', function(err, result) {
console.log("AUTOUPDATE mymodel",err,result);
});
}
});
};

Resources