Building FAQ lists with Keystone Lists - keystonejs

Here is the pseudo-code of what I want:
FAQ = {
name: 'Foobar FAQ',
items:[
//question/answer pairs here
]
}
How can I accomplish this in Keystone?
Here's what I've got so far:
var keystone = require('keystone');
var Types = keystone.Field.Types;
var FAQ = new keystone.List('FAQ',{
track: true
});
FAQ.add({
name: {type: String}
items: {} // ???
});
FAQ.register();
I'm unsure how to accomplish this. I'm brand new to React, Keystonejs and Mongodb.

This can be done through a Relationship field type.
items: { type: Types.Relationship, ref: 'Pair', many: true }
where Pair is the name of your Question/Answer pair list object.
For more info, see: http://keystonejs.com/docs/database/#relationships

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();

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

Mongoose relation not working both ways

I can't get a relationship running between my Rides and Comments controller in my app (built using the yeoman angular-fullstack generator).
Comment model:
var mongoose = require('mongoose'),
Schema = mongoose.Schema;
var CommentSchema = new Schema({
name: String,
comment: String,
active: Boolean,
ride: { type: mongoose.Schema.Types.ObjectId, ref: 'Ride' }
});
module.exports = mongoose.model('Comment', CommentSchema);
Ride model:
var mongoose = require('mongoose'),
Schema = mongoose.Schema;
var RideSchema = new Schema({
name: String,
distance: String,
climb: String,
rating: String,
active: Boolean,
comments: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Comment' }]
});
module.exports = mongoose.model('Ride', RideSchema);
Accessing /api/comments/ gives me a correct result, containing a related Ride:
{"_id":"54ce818f8c2889da58b01e19","name":"NAAM","comment":"COMMENT","ride":"54ce69647a78532057aa98e0","__v":0}]
Accessing /api/rides/ gives me the following result, without the corresponding Comments:
[{"_id":"54ce69647a78532057aa98e0","name":"Ride test ingevuld","distance":"4000","climb":"1200","rating":"1","__v":0,"comments":[]}]
Can anyone tell me what i am doing wrong?
Example from one of my projects:
exports.insertRoom = function(req, res) {
var id = req.body.id;
var r = req.body.room;
var room = new Room({name: r.name});
Floor.update(
{_id : id},
{
$push: { rooms: room}
},
{upsert:true},
function(floor, err)
{
res.sendStatus(200);
}
);
};
As far as I'am concerned it doesn't work like that. Your comments got it's ride, and your ride got it's comments. I think, you should remove
ride: { type: mongoose.Schema.Types.ObjectId, ref: 'Ride' }
and keep comments inside ride collection.
comments: ['Comment']
It is more objective solution as it supposed to be in MONGO DB which was designed for objective(hierarchial) data.

SDK: filtering a rallycardboard

How do you filter the cards in a rallycardboard using the SDK?
It seems simple enough, using the filter() function (though I'm not sure why there does not seem to be an option in the configs), but I can't seem to get it to work.
Here's my latest attempt:
var cardBoardConfig = {
xtype: 'rallycardboard',
types: ['PortfolioItem/Feature'],
columns: initiativeColumns,
attribute: 'Parent',
context: context
};
var cardBoard = this.add(cardBoardConfig);
var releaseFilter = Ext.create('Rally.data.QueryFilter',
{ property: 'Release.Name', operator: '!=', value: ''});
cardBoard.filter(releaseFilter);
I realize the filter it is looking for is actually a Ext.util.filter, but variants with that don't seem to work either. For example, I have tried:
var releaseFilter = Ext.create('Ext.util.Filter', { property: 'Release', operator: '=', value: null});
My goal here is to filter the cards based on whether they are assigned to a particular release or not.
If you look at the source for CardBoard's filter, you'll see that it's not implemented yet. That's pretty bad and we should fix that.
It looks like you can use cardboard.applyFilters([filter]) as an alternative. If that doesn't work, then this should work as a server side approach:
var cardBoardConfig = {
xtype: 'rallycardboard',
types: ['PortfolioItem/Feature'],
columns: initiativeColumns,
attribute: 'Parent',
context: context,
// have the server do the filtering
storeConfig: {
filters: [
{ property: 'Release.Name', operator: '!=', value: ''}
]
}
};
var cardBoard = this.add(cardBoardConfig);