KeystoneJS: site-wide configuration variables in database - node.js

When creating a site with KeystoneJS, how might I add some site-wide configuration variables that are stored in the database - that can preferably be manipulated via the admin - in the vein of Craft CMS's 'globals'?
I can't find anything in the Keystone database documentation about this, and would prefer not to use a singleton with a Keystone list (e.g. by implementing a list that has only one item) if at all possible.

I've just had a chat with one of the Keystone developers about this. It's been widely discussed on ProductPains, and as it turns out, having a singleton with a list is currently (as of 0.3.x) the only way to do this:
Define a new model in e.g. models/Configuration.js:
const keystone = require('keystone');
const Types = keystone.Field.Types;
const Configuration = new keystone.List('Configuration', {
nocreate: true,
nodelete: true,
label: 'Configuration',
path: 'configuration',
});
Configuration.add({
siteName: { type: String },
siteDescription: { type: Types.Textarea },
});
Configuration.defaultColumns = 'siteName, siteDescription';
Configuration.register();
Add an update e.g. updates/0.0.2-configuration.js:
exports.create = {
Configuration: [
{ 'siteName': 'My site', 'siteDescription': 'TODO' }
]
};

Related

Cloud Functions Query Database [duplicate]

Given the data structure below in firebase, i want to run a query to retrieve the blog 'efg'. I don't know the user id at this point.
{Users :
"1234567": {
name: 'Bob',
blogs: {
'abc':{..},
'zyx':{..}
}
},
"7654321": {
name: 'Frank',
blogs: {
'efg':{..},
'hij':{..}
}
}
}
The Firebase API only allows you to filter children one level deep (or with a known path) with its orderByChild and equalTo methods.
So without modifying/expanding your current data structure that just leaves the option to retrieve all data and filter it client-side:
var ref = firebase.database().ref('Users');
ref.once('value', function(snapshot) {
snapshot.forEach(function(userSnapshot) {
var blogs = userSnapshot.val().blogs;
var daBlog = blogs['efg'];
});
});
This is of course highly inefficient and won't scale when you have a non-trivial number of users/blogs.
So the common solution to that is to a so-called index to your tree that maps the key that you are looking for to the path where it resides:
{Blogs:
"abc": "1234567",
"zyx": "1234567",
"efg": "7654321",
"hij": "7654321"
}
Then you can quickly access the blog using:
var ref = firebase.database().ref();
ref.child('Blogs/efg').once('value', function(snapshot) {
var user = snapshot.val();
ref.child('Blogs/'+user+'/blogs').once('value', function(blogSnapshot) {
var daBlog = blogSnapshot.val();
});
});
You might also want to reconsider if you can restructure your data to better fit your use-case and Firebase's limitations. They have some good documentation on structuring your data, but the most important one for people new to NoSQL/hierarchical databases seems to be "avoid building nests".
Also see my answer on Firebase query if child of child contains a value for a good example. I'd also recommend reading about many-to-many relationships in Firebase, and this article on general NoSQL data modeling.
Given your current data structure you can retrieve the User that contains the blog post you are looking for.
const db = firebase.database()
const usersRef = db.ref('users')
const query = usersRef.orderByChild('blogs/efg').limitToLast(1)
query.once('value').then((ss) => {
console.log(ss.val()) //=> { '7654321': { blogs: {...}}}
})
You need to use limitToLast since Objects are sorted last when using orderByChild docs.
It's actually super easy - just use foreslash:
db.ref('Users').child("userid/name")
db.ref('Users').child("userid/blogs")
db.ref('Users').child("userid/blogs/abc")
No need of loops or anything more.

KeystoneJS plural option not working in AdminUI

Even if i set the plural option in the list when defining the model,
in the Admin UI it does not show up, it keeps showing the default trailing 's'.
my model:
var keystone = require('keystone');
var Types = keystone.Field.Types;
var Pollo = new keystone.List('Pollo', {
map:{ name: 'nome',},
autokey:{path:'slug', from: 'nome', unique: true},
plural: 'polli'
});
so in the Admin UI i see "pollos" instead of "polli"
In your keystone.List('Pollo', { ... }) options, you need to also add label and singular options in addition to plural.

How to show uploaded image in Keystonejs back-end

Very similar to problem here but I'm not using S3 files and the info in that link is somewhat dated (hasn't been updated since github issues linked from question above were closed).
My question is about how to get a preview of an uploaded image in Keystonejs's admin back-end. Although it seems like it's a hacky fix (editing keystone files as suggested in link above) I'm wondering if there's other options.
Although they've added support for S3 files and Types.CloudinaryImage is supported I can't get a preview of the uploaded image when it's just an uploaded image since Keystone treats it as an arbitrary file (not an image).
Screenshot: as you can see Keystone just shows the filename (highlighted in red).
Model is set up as follows:
var keystone = require('keystone');
var Types = keystone.Field.Types;
/**
* Image Upload Model
* ==================
* A database model for uploading images to the local file system
*/
var ImageUpload = new keystone.List('ImageUpload');
var myStorage = new keystone.Storage({
adapter: keystone.Storage.Adapters.FS,
fs: {
path: keystone.expandPath('./public/uploads/images'),
publicPath: '/public/uploads/images',
}
});
ImageUpload.add({
name: { type: Types.Key, index: true },
image: {
type: Types.File,
storage: myStorage
},
createdTimeStamp: { type: String },
url: { type: String }
});
ImageUpload.defaultColumns = 'url, createdTimeStamp, image';
ImageUpload.register();
As far as I get it the only way - is to implement this by yourself.
It's not so scary as it my look but you should spend a lot of time to do that.
You need to investigate how for now different filed types are showed in admin page - for that you should take a look at template for admin page which is provided with KeystoneJS already (path: node_modules\keystone\admin\server\templates)
After that you might want to look for (path: node_modules\keystone\fields)
You might be interested in TYPES subfolder - cause there different field types rules
So your goal is to find corresponding field description (for your ImageUpload FileSystem model) or create a new one with img tag to show picture from url
I think File type is that what you are looking for - \node_modules\keystone\fields\types\file
Image previews are now possible in the latest master branch of keystone (see https://github.com/keystonejs/keystone/pull/4509). At the moment you need to depend on the git version of keystone, so put this in your package.json and run npm install:
"keystone": "https://github.com/keystonejs/keystone.git"
In your model, specify thumb: true on the image field in question. You also need the url property in the schema. For example:
const storage = new keystone.Storage({
adapter: keystone.Storage.Adapters.FS,
fs: {
path: keystone.expandPath('./uploads/images'),
publicPath: '/images/'
},
schema: {
url: true,
}
})
ImageUpload.add({
name: { type: Types.Key, index: true },
image: {
type: Types.File,
storage: myStorage,
thumb: true
},
createdTimeStamp: { type: String }
});
The admin UI should now show a small preview of the image and a link to it.

Can I add a mongoose plugin to a model after it has been created?

I have a situation where I need to add a plugin to a mongoose model but change the options passed to that plugin potentially every time it is used.
See example below:
const PersonnelSchema = new Schema({
_id: { type: Schema.ObjectId },
GivenName: { type: String },
FamilyName: { type: String }
});
module.exports = mongoose.model('Personnel', PersonnelSchema, 'Personnel');
What I'd like to be able to do is add the plugin at the time of using the model so that I can pass parameters to it.
I've tried adding the plugin to the schema object on the model when using it for example:
objModel.schema.plugin(mongoosastic, {
index: strIndexName,
transform: (data) => {
data.TenantDB = strTenantDB;
return data;
}
});
but this only adds the plugin methods to statics on the schema object, and does not initialize the plugin properly on the model.
Is there some way of achieving this?
Shortly after posting I found that I can achieve this by calling compile on my model after attaching the plugin to the schema, for example:
objModel.schema.plugin(mongoosastic, objOptions);
return objModel.compile(objModel.modelName, objModel.schema, objModel.collection.name, objModel.db, mongoose);

override sails GET blueprint with many-to-one

caveat: new to web programming. I have the following in a sails project:
// in models/User.js
// user has many videos
module.exports = {
attributes: {
videos: {
collection: 'video',
via: 'owner'
}
}
}
// in models/Video.js
// video has one user
module.exports = {
attributes: {
owner: {
model: 'user'
}
}
}
I'm using the REST blueprints with a /v1 prefix. I'm trying to either override GET /v1/user/:id/video, or make a custom route to GET /v1/user/:id/myGetVideo
So I've tried the following:
find and findOne methods in VideoController.js and UserController.js. These don't trigger for GET /v1/user/:id/video - I guess because the many-to-one association is treated differently
creating a custom route
// in routes.js
module.exports.routes = {
'get /user/:id/myGetVideo': 'UserController.myGetVideo'
}
This doesn't trigger anything because of the namespace, but it does trigger /user/:id/myGetVideo as expected. I don't want to add /v1 namespace here b/c that will proliferate and become an issue to change versions.
So, how do I either override the method I want, or put it in the namespace? Overriding the first method seems cleaner, but idk.
Thanks for any help!
Edit: Any comments on what is better practice for a REST API?
Got it with wildcards:
// in routes.js
module.exports.routes = { 'get /v*/user/:id/myGetVideo':'UserController.myGetVideo' }
not bullet-proof if the prefix scheme changes, but good enough

Resources