Dynamic type select in keystonejs model - node.js

I would like to use a combobox at the adminUI with fields that come from a webservice. I was thinking on get the data with a pre 'find' hook and then override the options atribute at the 'audience' property in the Schema.
Schema:
Compliance.add({
title: { type: Types.Text, required: true, initial: true, index: true },
url: { type: Types.Url, required: true, initial: true },
position: { type: Types.Number, initial: true },
audience: { type: Types.Select, options: [], many: true, initial: true},
});
Hook:
Compliance.schema.pre('find', async function(next) {
let audiences = await audienceService.getAudiences();
next();
})
But I didn't find the way to bind the data. Any ideas how this can be done?
Thanks

You can try making a function from the options:
function getAudiences() {
return ['a', 'b', 'c'];
}
Compliance.add({
title: { type: Types.Text, required: true, initial: true, index: true },
url: { type: Types.Url, required: true, initial: true },
position: { type: Types.Number, initial: true },
audience: { type: Types.Select, many: true, initial: true, options: getAudiences() }
});
Result as below:

Related

How to keep id on mongoose findByIdAndUpdate

I am trying to update a 'Board' model in mongoose using findByIdAndUpdate and the model has an array of 'items' (which are objects) on the model. I probably do not understand mongoose well enough but for some reason each item in the array gets an id generated, along with the Board. This is not a problem, it's quite handy actually, however, after doing a findByIdAndUpdate the id on each item has changed. This was quite surprising to me, I really thought they would stay the same. Could this be caused by updating all items in the array? Maybe mongoose is just throwing out the entire array and creating a new one when updating (maybe someone knows?). Anyways, my question is: Is there a way to update the model without changing these id's. I would really like them to stay consistent. The code I am using for update is
exports.updateBoard = asyncHandler(async (req, res, next) => {
let board = await Board.findById(req.params.id);
if (!board) {
return next(new CustomError(`Board not found with id of ${req.params.id}`, 404));
}
// Authorize user
if (board.user.toString() !== req.user.id) {
return next(new CustomError(`User ${req.user.id} is not authorized to update board ${board._id}`, 401));
}
req.body.lastUpdated = Date.now();
board = await Board.findByIdAndUpdate(req.params.id, req.body, { new: true, runValidators: true })
.select('-__v')
.populate({
path: 'user',
select: 'name avatar',
});
// 200 - success
res.status(200).json({ success: true, data: board });
});
and BoardSchema:
const BoardSchema = new Schema(
{
user: {
type: Schema.Types.ObjectId,
ref: 'User',
required: [true, 'Board must have a user'],
},
name: {
type: String,
required: true,
trim: true,
},
description: {
type: String,
required: false,
trim: true,
},
items: [
{
title: {
type: String,
required: true,
trim: true,
},
description: {
type: String,
required: false,
trim: true,
},
dateCreated: {
type: Date,
default: Date.now,
},
lastUpdated: {
type: Date,
default: Date.now,
},
},
],
columns: [
{
name: {
type: String,
required: true,
},
index: {
type: Number,
required: true,
},
show: {
type: Boolean,
required: true,
},
},
],
dateCreated: {
type: Date,
default: Date.now,
},
lastUpdated: {
type: Date,
default: Date.now,
},
},
{
toJSON: { virtuals: true },
toObject: { virtuals: true },
},
);

How to access inner object in keystoneJS

Say I have a model like
University.add({
university_id: { type: Types.Number, required: true, initial: true, index: true, unique: true },
name: { type: Types.Text, required: true, index: true },
address: { type: Types.Text, initial: true, required: false, index: false }
});
University.schema.add({
"inner_object": {
"name": String,
"phone": String,
"comment": String
}
});
I tried updating the object using getUpdateHandler()
University.model.findOne().where('university_id', universityData.university_id).exec(function (err, university) {
university.getUpdateHandler(req, res).process(req.body, {
fields: "name, address, 'inner_object.name', 'inner_object.phone', 'inner_object.comment'"
}, function(err) {
if(err) {
console.log(err);
res.json({ status: false, data: null, message: 'Error while creating university'});
} else {
res.json({ message: 'University updated successfully', status: true, data: university});
}
});
});
Iam getting an error UpdateHandler.process called with invalid field path: inner_object.name
Please update if anyone went through the same scenario
You're adding the inner_object outside of keystone, directly through mongoose, which means keystone won't have knowledge about its existence. Just add it to the first configuration object:
University.add({
university_id: { type: Types.Number, required: true, initial: true, index: true, unique: true },
name: { type: Types.Text, required: true, index: true },
address: { type: Types.Text, initial: true, required: false, index: false },
inner_object: {
name: {type:String},
phone: {type:String},
comment: {type:String}
}
});

Sails js one to one populate conditions not working?

I'm newbie of Sails and I've got a problem with one to one association.
First, I have model User:
module.exports = {
schema: true,
identity : "User",
tableName: "user",
attributes: {
email: {
type: 'email',
unique: true,
required: true
},
password: {
type: 'string'
},
salt: {
type: 'string'
},
merchant: {
model: 'merchant',
defaultsTo: null
},
removed: {
type: 'boolean',
required: true,
defaultsTo: false
}
}
}
And my Merchant model:
module.exports = {
schema: true,
identity : "Merchant",
tableName: "merchant",
attributes: {
name: {
type: 'string',
unique: true,
required: true
},
code: {
type: 'string',
unique: true,
required: true
},
security_key: {
type: 'string',
required: true
},
active: {
type: 'boolean',
defaultsTo: false,
required: true
},
user: {
model: 'user'
}
}
}
So when I need to find records where merchant.active = true, I write this query:
var findUser = User.find(query).populate('merchant', {active: true});
return findUser;
But it was not working at all.
Anyone any ideas to solve this properly?
P.S. my Sails version is: 0.11.1. My DB is MongoDB
First of all, remove defaultsTo from your association attributes. I don't like this :) I don't know if it makes the problem, but in documentation I never see this.
Also you need to execute your query, not just return it. If I take your models' declarations then I can write populate query like this.
var findUser = User.find(query).populate('merchant', {active: true});
findUser.then(function(user) {
console.log('Your user is ' + user);
}).catch(function(error) {
console.log('Your error is ' + error);
});

Query in Waterline using a model as a criteria

I just started to work with Waterline and I got question about search for records in a database Mongo using a model as criteria. After some hours of search I couldn't find any satisfactory solution.
First, I have basically 2 model related between themselves:
Post.js
var Post = Waterline.Collection.extend({
tableName: 'Post',
connection: 'default',
attributes: {
url : { type: 'string', required: true, unique: true, lowercase: true },
title : { type: 'string', required: true },
body : { type: 'string', required: true },
author : { type: 'string', required: true },
writeIn : { type: 'string', required: true },
tags: {
collection: 'Tag',
via: 'posts',
dominant: true
},
category: {
model: 'Category'
}
}});
Category.js
var Category = Waterline.Collection.extend({
tableName: 'Category',
connection: 'default',
attributes: {
url: { type: 'string', required: true, unique: true, lowercase: true },
name: { type: 'string', required: true },
posts: {
collection: 'Post',
via: 'category'
}
}});
They are related using Many-to-Many association. The point is that I would like to query a list of posts through a category name.
Something like this:
Post.find().where({category: {url: 'java'}})
Does any of you know how to do this?
Yes you can do this by different way...
Category.find()
.where({url: 'java'})
.populate('posts')

adding a createdBy field to the User model in keystone.js

I got the 'createdBy' field added to the model but it allows the admin to select all users as the 'createdBy' user. I want this field to be auto populated with the admin that is currently logged in and can't seem to get it to work.
Ideally this wouldn't appear in the UI at all but just be stored when the user is saved.
User.add({
name: { type: Types.Name, required: true, index: true },
email: { type: Types.Email, initial: true, required: true, index: true },
company: { type: String, required: true, index: true, initial: true },
phone: { type: String, required: true, index: true, initial: true },
password: { type: Types.Password, initial: true, required: true },
createdBy: { type: Types.Relationship, initial:true, required:true, ref: 'User' },
createdAt: { type: Date, default: Date.now }
}, 'Permissions', {
level : { type: Types.Select, numeric: true, options: [{ value: 1, label: 'User' }, { value: 2, label: 'Group Administrator' }, { value: 3, label: 'System Administrator' }] }
},
'Screening', {
rooms : { type: Types.Select, numeric: true, options: [{ value: 1, label: 'Screening room 1' }, { value: 2, label: 'Screening room 2' }, { value: 3, label: 'Screening room 3' }] }
});
While your implementation is functional, a number of Keystone developers (myself included) have raised concerns regarding the security risk of sending the user.id via a POST. You are also correct when you say that currently there is no good way of doing this in Keystone.
My solution was to implement the feature on Keystone itself. I added an optional meta pattern, which I called audit meta. This adds two fields to the List (createdBy and updatedBy) which I populate in the UpdateHandler() using an existing cached copy of req.user. This way there's no need to send user._id via POST.
To use it you just add List.addPattern('audit meta'); after defining your list, just like you would if you were using the standard meta. My implementation of audit meta also adds the standard meta fields, so there's no need to use both.
To implement this I made the following changes to Keystone
First, in lib\list.js I added the following code (prefixed with +) to the addPatern() method:
List.prototype.addPattern = function(pattern) {
switch (pattern) {
...
+ case 'audit meta':
+ var userModel = keystone.get('user model');
+
+ if(!this.schema.path('createdOn') && !this.schema.path('updatedOn')) {
+ this.addPattern('standard meta');
+ }
+
+ if (userModel) {
+ this.add({
+ createdBy: { type: Field.Types.Relationship, ref: userModel, hidden: true, index: true },
+ updatedBy: { type: Field.Types.Relationship, ref: userModel, hidden: true, index: true }
+ });
+ this.map('createdBy', 'createdBy');
+ this.map('modifiedBy', 'updatedBy');
+ }
+ break;
+
}
return this;
Then in lib/updateHandler.js I added the following code to UpdateHandler.prototype.process(), just before progress() is called at then end of the method.
+ // check for audit meta fields (mapped to createdBy/modifiedBy)
+ if (this.list.mappings.createdBy && this.item.isNew) {
+ this.item.set(this.list.mappings.createdBy, this.user._id);
+ }
+ if (this.list.mappings.modifiedBy) {
+ this.item.set(this.list.mappings.modifiedBy, this.user._id);
+ }
Earlier I submitted a pull request (https://github.com/JedWatson/keystone/pull/490) to Keystone, which includes a detailed explanation of my implementation. So, if you need this urgently, you can always fork a copy of Keystone and merge my PR.
Apparently there is no good way to do this but I did come up with a work around using someone else's idea by creating a custom hidden input field. It's not ideal but will work for this project. The default value on createdBy is just so I can make it a required field but it is populated in the form jade template not he initial jade template for that input type.
User.add({
name: { type: Types.Name, required: true, index: true },
email: { type: Types.Email, initial: true, required: true, index: true },
company: { type: String, required: true, index: true, initial: true },
phone: { type: String, required: true, index: true, initial: true },
password: { type: Types.Password, initial: true, required: true },
createdBy: { type: Types.Admin, required: true, initial: true, default: 'createdBy' },
createdAt: { type: Types.Hidden, default: Date.now }
}, 'Permissions', {
level : { type: Types.Select, numeric: true, options: [{ value: 1, label: 'User' }, { value: 2, label: 'Group Administrator' }, { value: 3, label: 'System Administrator' }] }
},Screening', {
rooms : { type: Types.Select, numeric: true, options: [{ value: 1, label: 'Screening room 1' }, { value: 2, label: 'Screening room 2' }, { value: 3, label: 'Screening room 3' }] }
});
then the custom fieldtype just something like this, just create one for form and initial. Also create the fieldTypes/admin.js and update the fieldTypes.index.js
Input admin/form.jade
.field(class='type-' + field.type, data-field-type=field.type, data-field-path=field.path, data-field-collapse=field.collapse ? 'true' : false, data-field-depends-on=field.dependsOn, data-field-noedit=field.noedit ? 'true' : 'false')
- var value = field.format(item)
.field-ui(class='width-' + field.width)
if field.noedit
.field-value= user._id
else
input(type='hidden', name=field.path, value=user._id, autocomplete='off').form-control
if field.note
.field-note!= field.note

Resources