adding a createdBy field to the User model in keystone.js - node.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

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 },
},
);

Get data from mongodb with dynamic nested levels using nodejs and mongoose

For example
I have one table:
Menuid, menuname, parentid, levelid -- this is table
Consider the values:
Menuid:Pg, menuname:mainprogram, programid:'', levelid: 1
Menuid:sb, menuname:subprogram, programid:'Pg', levelid: 2
Menuid:cb, menuname:childprogram, programid:'sb', levelid: 3
Menuid:Pg2, menuname:mainprogram2, programid:'', levelid: 1
Output:
[ {
Menuid:'Pg',
Menuname:'mainprogram',
Children:[ {
Menuid:'sb',
Menuname:'subprogram',
Children:[ {
Menuid:'cb',
Menuname:'childprogram'
} ]
} ]
}, {
Menuid:'Pg2',
Menuname:'mainprogram2'
} ]
getting menu code
exports.getMenus = (req, res, next) => {
// need clarification
}
model for menu
const mongoose = require('mongoose');
const uniqueValidator = require('mongoose-unique-validator');
const menuSchema = mongoose.Schema({
menuId: {
type: String,
required: true,
unique: true
},
menuName: {
type: String,
required: true
},
icon: {
type: String,
required: false
},
type: {
type: String,
required: false
},
url: {
type: String,
required: false
},
levelId: {
type: Number,
required: true
},
programId: {
type: String,
required: false
},
order: {
type: Number,
required: true
},
createdBy: {
type: String,
require: false
},
createdDate: {
type: Date,
require: false,
},
updatedBy: {
type: String,
default: '',
require: false
},
updatedDate: {
type: Date,
default: '',
require: false,
},
});
menuSchema.plugin(uniqueValidator);
module.exports = mongoose.model('Menu', menuSchema);
router:
router.get('/getmenus/:id', MenuController.getMenus);
user request from client like
http://localhost:3000/api/menu/getmenus/pg
getmenus after slash word is a required menus under submenus
Note: if each data has order menans based on menu object need to render with that order. Please provide any solution.
Refer below link
your expected behaviour will be available in this graphlookup
https://docs.mongodb.com/manual/reference/operator/aggregation/graphLookup/

Display a list of friends with 3 states on sails.js

I just use sails.js, it is good
I have a problem, please help me.
I have 2 collections, it is create one to many and i want to show list friends with 3 state is defined.
User:
attributes: {
username: {
type: 'string',
required: true
},
firstname: {
type: 'string',
required: true
},
lastname: {
type: 'string',
required: true
},
email: {
type: 'string',
email: 'true',
required: true,
unique: true
},
buddy: {
collection: "Buddy",
via: "buddyOf"
}
}
Buddy:
attributes: {
user_id: {
type: 'string'
},
// 0: no friends
// 1: waiting accept
// 2: Cancel friend
statusBuddy: {
type: 'integer'
},
buddyOf: {
model: 'User'
}
}
I want to search user show 3 button status:
no friends, waiting accept, Cancel friend
Did not try this code, but maybe it could be possible without having 2 models?
identity: 'user',
isFriendedBy: {
collection: 'user',
via: 'friendsWith'
},
friendsWith: {
model: 'user'
},
friendshipStatus: 'integer'

Is there a tag field type in Keystone JS?

I'm looking for a tag field type which will autocomplete if the tag already exists, or simply add the tag if it doesn't. I think there are a lot of implementations of this in other CMS' and I wanted to shake the tree to see if someone had already done this before I roll up my sleeves. Assuming it existed, I imagine it would be implemented as follows:
var keystone = require('keystone'),
Types = keystone.Field.Types;
var Verbiage = new keystone.List('Verbiage', {
autokey: { path: 'slug', from: 'title', unique: true },
map: { name: 'title' },
defaultSort: '-createdAt',
label: "Verbiage",
plural : "Verbiage"
});
Verbiage.add({
title: { type: String, required: true },
author: { type: Types.Relationship, ref: 'User' },
tagged: { type: Types.Tag, required: false, many: true },
createdAt: { type: Date, default: Date.now },
publishedAt: Date
});
Verbiage.register();

How to do Character wise Search with mongoDB Collection inside model in sails.js

I am newbie to sails and node.js. I am creating a Forum and want to add search function in order to search threads that match with User Search input. My search function need to character wise match the user entered characters with database thread's title. Is there any possible way to do that in sails ? Is it possible to use String distance in sails ?
My models look like as follows,
Forum.js
attributes: {
id: {
type: 'string',
primaryKey: true,
unique: true
},
name: {
type: 'string',
required: true
},
threads: {
collection: 'thread',
via: 'threadOwner'
}
},
Thread.js
attributes: {
id: {
type: 'string',
primaryKey: true,
unique: true
},
creatorUid: {
type: 'string',
required: true
},
title: {
type: 'string',
required: true
},
description: {
type: 'string'
},
threadOwner: {
model: 'forum'
}
},
Try this,
Thread.find({
or : [
{title : { contains:searchKey }},
{description : { contains:searchKey }}
],
threadOwner:forum
}).exec(function(err,matched){
//Your code...
})

Resources