Field (Test.title) is required but not initial, and has no default or generated value - keystonejs

I was wondering if someone knows why I get that error, my model
var Test = new keystone.List('Test', {
autokey: { from: 'title', path: 'key', unique: true }
});
Test.add({
title: { type: String, required: true },
All I did was change the values from the post example below
var Test = new keystone.List('Test', {
autokey: { from: 'name', path: 'key', unique: true }
});
Test.add({
name: { type: String, required: true },
I can;t understand why it works with name and not with title

use map key inside option list
var Test = new keystone.List('Test', {
map: { name: 'title' },
autokey: { from: 'title', path: 'key', unique: true }
});
refs: http://keystonejs.com/docs/database/#lists-options
An object that maps fields to special list paths. Each path defaults
to its key if a field with that key is added. Mappable paths include
name - the field that contains the name of the item, for display in
the Admin UI

Related

Mongoose model schema referencing each other - how to simplify?

I am using mongoose and have two schemas: UserSchema and CommunitySchema:
const UserSchema = new Schema({
name: String,
communities: [{ type: Schema.Types.ObjectId, ref: CollectionModel }],
exCommunities: [{ type: Schema.Types.ObjectId, ref: CollectionModel }],
}, { timestamps: true });
const CommunitySchema = new Schema({
slug: { type: String, unique: true },
name: String,
description: String,
users: [
{ type: Schema.Types.ObjectId, ref: "User" }
]
}, { timestamps: true });
User can be a part of multiple communities and also leave any community and be in the exCommunities field.
When an user joins a community, I have to do a double work: Add the user to a user community and update the community user field with the reference ID.
Questions:
Is there a way to simplify it? For example, by managing the CommunitySchema users field automatically based on the UserSchema communities field?
Now I have to do this:
collection.users.push(userId);
user.communities.push(communityId);
Could the collection.users be automatically added when I push a community to user.communities? And how?)
Is it possible to add a date when the user is added to a community or leave a community? Something like: communities: [{ type: Schema.Types.ObjectId, ref: CollectionModel, createdAt: "<DATE>" }]
Thank you!
you no need to add communities and exCommunities in UserSchema
const UserSchema = new Schema({
name: String,
}, { timestamps: true });
const communityUserSchema = new Schema({
user_id:{type: Schema.Types.ObjectId, ref: "User"},
joined_at:{type:Date},
leaved_at:{type:Date},
is_active:{type:Boolean},
role:{type:String, enum:['user','admin'], default:'user'}
});
const CommunitySchema = new Schema({
slug: { type: String, unique: true },
name: String,
description: String,
users:{
type:[communityUserSchema],
default:[]
}
}, { timestamps: true });
you can find User's communities by :
let user_id = req.user._id;
all_communities = await Community.find({"users.user_id":user_id});
active_communities = await Community.find({"users.user_id":user_id, "is_active":true});
ex_communities = await Community.find({"users.user_id":user_id,"leaved_at":{"$ne":null}});
When User Create New Community (create as a Admin):
let current_user = {user_id:req.user.id,joined_at:new Date(),is_active:true,role:'admin'};
// if you select users from frontend while creating new community
let other_user = req.body.user_ids;
let other_users_mapped = other_user.map((item)=>{ return {user_id:item,joined_at:new Date(),role:'user',is_active:true}});
let all_users = [current_user];
all_users = all_users.concat(other_users_mapped);
let community = new Community();
community.name = req.body.name;
community.slug = req.body.slug;
community.description = req.body.description;
community.users = all_users ;
let created = await community.save();
When User Leave Community :
Community.updateOne({_id: community_id , 'users.user_id':user_id },{
$set:{
'users.$.is_active':false,
'users.$.leaved_at':new Date()
}
});
View Community with only active members :
let community_id = req.params.community_id;
let data = await Community.findOne({_id:community_id},{ users: { $elemMatch: { is_active: true } } });
AI solved it for me, here is the correct example:
import * as mongoose from 'mongoose';
const Schema = mongoose.Schema;
interface User {
name: string;
email: string;
communities: Community[];
}
interface Community {
name: string;
description: string;
users: User[];
}
const userSchema = new Schema({
name: String,
email: String,
}, {
timestamps: true,
});
const communitySchema = new Schema({
name: String,
description: String,
}, {
timestamps: true,
});
// Define the user_communities table using the communitySchema
const userCommunitySchema = new Schema({
user: { type: Schema.Types.ObjectId, ref: 'User' },
community: { type: Schema.Types.ObjectId, ref: 'Community' },
joined_on: Date,
}, {
timestamps: true,
});
// Use the userCommunitySchema to create the UserCommunity model
const UserCommunity = mongoose.model('UserCommunity', userCommunitySchema);
// Use the userSchema to create the User model, and define a virtual property
// for accessing the user's communities
userSchema.virtual('communities', {
ref: 'Community',
localField: '_id',
foreignField: 'user',
justOne: false,
});
const User = mongoose.model<User>('User', userSchema);
// Use the communitySchema to create the Community model, and define a virtual property
// for accessing the community's users
communitySchema.virtual('users', {
ref: 'User',
localField: '_id',
foreignField: 'community',
justOne: false,
});
const Community = mongoose.model<Community>('Community', communitySchema);
The userSchema and communitySchema are then used to create the User and Community models, respectively. For the User model, a virtual property called communities is defined using the virtual method. This virtual property is used to specify how to populate the user.communities property when querying the database. The communitySchema also defines a users virtual property, which is used to populate the community.users property when querying.

How to add pre-hook in keystonejs?

I want to add multiple select options field. But the docs state that doesn't allow for multiple select. But recommends pre-hook for that case.
Stores a String or Number in the model. Displayed as a select field in
the Admin UI. Does not allow for multiple items to be selected. If you
want to provide multiple values, you can use TextArray or NumberArray,
although neither will have the same constrained input. You can limit
the options using a pre-save hook.
I search for pre-hook but it seems came from mongoose. And in my case, I create the model using Keystone so that I can use it in admin page
var keystone = require('keystone');
var Types = keystone.Field.Types;
var MyModel = new keystone.List('MyModel');
MyModel.add({
aField: { type: Types.TextArray, required: false, initial: true },
});
so how do I create the pre-hook? for example, I want to limit the TextArray to be set of ('a','b','c')?
I have set up pre-save hooks like this (or something similar to this. Did not test this code).
var keystone = require('keystone');
var Types = keystone.Field.Types;
/**
* Musician Model
* ==========
*/
var Musician = new keystone.List('Musician', {
map: { name: 'title' },
autokey: { path: 'slug', from: 'title', unique: true },
});
Musician.add({
title: { type: String, required: true },
published: { type: Types.Boolean, default: false },
musicianId: { type: String, note: noteUpdateId },
});
Musician.schema.pre('save', function (next) {
console.log(this.title);
console.log(this.isNew);
if (this.isNew) {
// generates a random ID when the item is created
this.musicianId = Math.random().toString(36).slice(-8);
}
next();
});
Musician.defaultColumns = 'title, published, musicianId';
Musician.register();

mongoose populate nested subdocuments

I have following model
var fieldsSchema = new Schema({
name: String,
type: String,
value: String,
media: [{ type: Schema.Types.ObjectId, ref: 'Upload' }],
required: Boolean,
recepientvisible: Boolean
})
var orderSchema = new Schema({
number: String,
date: Number,
updated: Number,
type: { type: Schema.Types.ObjectId, ref: 'OrderTemplate' },
currentstatus: String,
comment: String,
assignedTo: { type: Schema.Types.ObjectId, ref: 'User' },
createdBy: { type: Schema.Types.ObjectId, ref: 'User' },
statuses: [{
name: String,
fields: [fieldsSchema]
}]
});
var Order = mongoose.model('Order', orderSchema);
module.exports = Order;
I do the following request
app.post('/order/', function (req, res) {
Order.find()
.populate({ path:'type', select: 'name -_id'})
.populate({ path:'assignedTo', select: 'name -_id'})
.populate({ path:'createdBy', select: 'name -_id'})
.populate({ path:'statuses', populate: { path: 'fields', populate: { path: 'media'} }})
.exec(function (err, orders) {
if (err) throw err;
res.send(orders)
});
})
What I need is to populate media fields. But in response I get only array of _id.
All other fields populates well.
How can I populate media field correctly ?

Virtual "name" field?

I need to have the name field of a model be virtual, created by concatenating two real fields together. This name is just for display only. I've tried the virtual examples in the doc, no luck. Keystone 4 beta5.
var keystone = require('keystone')
_ = require('underscore');
var Types = keystone.Field.Types;
/**
* Foo Model
* ==================
*/
var Foo = new keystone.List('Foo', {
map: {name: 'fooname'},
track: true
});
Foo.add({
bar: { type: Types.Relationship, required: true, initial: true, label: 'Barref', ref: 'Bar', many: false },
order: { type: Types.Select, required: true, initial: true, label: 'Order', options: _.range(1,100) },
price: { type: Types.Money, format: '$0,0.00', label: 'Price', required: true, initial: true },
});
Foo.schema.virtual('fooname').get(function() {
return this.bar+ ' ' + this.order;
});
Foo.defaultColumns = 'fooname, bar, order, price';
Foo.register();
When I use this model definition, I don't see the virtual name in the defaultcolumns list. I want to make a virtual name so lookups are easier when this model is used as a relationship.
You don't need a virtual to do this. Keystone allows you to track and recalculate a field every time the document is saved. You can enable those options in order to create a function which concatenates these two values for you (either synchronously or asynchronously, your choice.)
One other thing I noticed is that bar is a Relationship, which means you will need to populate that relationship prior to getting any useful information out of it. That also means your value function will have to be asynchronous, which is as simple as passing a callback function as an argument to that function. Keystone does the rest. If you don't need any information from this bar, and you only need the _id (which the model always has), you can do without the keystone.list('Bar') function that I included.
http://keystonejs.com/docs/database/#fields-watching
The map object also refers to an option on your model, so you'll need a fooname attribute on your model in any scenario, though it gets calculated dynamically.
var keystone = require('keystone'),
_ = require('underscore');
var Types = keystone.Field.Types;
/**
* Foo Model
* ==================
*/
var Foo = new keystone.List('Foo', {
map: {name: 'fooname'},
track: true
});
Foo.add({
fooname: { type: Types.Text, watch: true, value: function (cb) {
// Use this if the "bar" that this document refers to has some information that is relevant to the naming of this document.
keystone.list('Bar').model.findOne({_id: this.bar.toString()}).exec(function (err, result) {
if (!err && result) {
// Result now has all the information of the current "bar"
// If you just need the _id of the "bar", and don't need any information from it, uncomment the code underneath the closure of the "keystone.list('Bar')" function.
return cb(this.bar.name + " " + this.order);
}
});
// Use this if you don't need anything out of the "bar" that this document refers to, just its _id.
// return cb(this.bar.toString() + " " + this.order);
} },
bar: { type: Types.Relationship, required: true, initial: true, label: 'Barref', ref: 'Bar', many: false },
order: { type: Types.Select, required: true, initial: true, label: 'Order', options: _.range(1,100) },
price: { type: Types.Money, format: '$0,0.00', label: 'Price', required: true, initial: true },
});
Foo.defaultColumns = 'fooname, bar, order, price';
Foo.register();
try this:
Foo.schema.pre('save', function (next) {
this.name = this.bar+ ' '+ this.order;
next();
});
Could you provide more information? What is currently working? How should it work?
Sample Code?
EDIT:
After creating the model Foo, you can access the Mongoose schema using the attribute Foo.schema. (Keystone Concepts)
This schema provides a pre-hook for all methods, which registered hooks. (Mongoose API Schema#pre)
One of those methods is save, which can be used like this:
Foo.schema.pre('save', function(next){
console.log('pre-save');
next();
});

Unique Array objects in mongoose schema

I have a conversation schema which will allow users for private messaging, each two users can be in ONE conversation therefore, the recipients in the conversations should be unique
/** Users in this conversation**/
var usersSchema = new Schema({
id: {
type: Schema.Types.ObjectId,
index: true,
required: true,
ref: 'User'
},
name: {
type: String,
required: true
}
});
/** For each message we will have the below **/
var messagesSchema = new Schema({
from: {
type: Schema.Types.ObjectId,
required: true
},
content: {
type: String,
required: true
},
read: {
type: Boolean,
default: false
}
}, {
timestamps: true
});
/** Now all together inside the thread schema **/
var conversationsSchema = new Schema({
users: {
type: [usersSchema],
required: true,
index: true,
unique: true
},
messages: [messagesSchema],
}, {
timestamps: true
});
var Conversation = mongoose.model('Conversation', conversationsSchema);
module.exports.Conversation = Conversation;
The only way I can think of is to manually check by looking into the IDs inside the users array in the conversation schema. However, I think there is a way in mongoose to do that.
You can add unique : true in your userSchema to only allow unique users in a conversation.
/** Users in this conversation**/
var usersSchema = new Schema({
id: {
type: Schema.Types.ObjectId,
index: true,
required: true,
unique : true, //add unique behaviour here
ref: 'User'
},
name: {
type: String,
required: true
}
});