Coverage

32% line coverage
25% statement coverage
12% block coverage
4601 SLOC

index.js

16% block coverage
184 SLOC
LineHitsStatementsSourceAction
11100%var fs = require('fs'),
21100% path = require('path'),
31100% _ = require('underscore'),
41100% express = require('express'),
51100% async = require('async'),
61100% jade = require('jade'),
71100% moment = require('moment'),
81100% numeral = require('numeral'),
91100% cloudinary = require('cloudinary'),
101100% utils = require('keystone-utils');
11 not covered
121100%var templateCache = {};
13 not covered
14 not covered /**
15 not covered * Don't use process.cwd() as it breaks module encapsulation
16 not covered * Instead, let's use module.parent if it's present, or the module itself if there is no parent (probably testing keystone directly if that's the case)
17 not covered * This way, the consuming app/module can be an embedded node_module and path resolutions will still work
18 not covered * (process.cwd() breaks module encapsulation if the consuming app/module is itself a node_module)
19 not covered */
201100%var moduleRoot = (function(_rootPath) {
211100% var parts = _rootPath.split(path.sep);
221100% parts.pop(); //get rid of /node_modules from the end of the path
231100% return parts.join(path.sep);
24150%})(module.parent ? module.parent.paths[0] : not covered module.paths[0];
25 not covered
26 not covered
27 not covered /**
28 not covered * Keystone Class
29 not covered *
30 not covered * @api public
31 not covered */
32 not covered
331100%var Keystone = function() {
341100% this.lists = {};
351100% this.paths = {};
361100% this._options = { 'name': 'Keystone', 'brand': 'Keystone', 'compress': true, 'headless': false, 'logger': 'dev', 'auto update': false, 'model prefix': null };
37 not covered 'name': 'Keystone',
381100% this._pre = { routes: [], render: [] };
391100% this._redirects = {};
40 not covered
411100% this.express = express;
42 not covered
431100% this.set('env', process.env.NODE_ENV || 'development');
44 not covered
451100% this.set('port', process.env.PORT || process.env.OPENSHIFT_NODEJS_PORT);
461100% this.set('host', process.env.HOST || process.env.IP || process.env.OPENSHIFT_NODEJS_IP);
471100% this.set('listen', process.env.LISTEN);
48 not covered
491100% this.set('ssl', process.env.SSL);
501100% this.set('ssl port', process.env.SSL_PORT);
511100% this.set('ssl host', process.env.SSL_HOST || process.env.SSL_IP);
521100% this.set('ssl key', process.env.SSL_KEY);
531100% this.set('ssl cert', process.env.SSL_CERT);
54 not covered
551100% this.set('cookie secret', process.env.COOKIE_SECRET);
56183% this.set('cookie signin', (this.get('env') === 'development') ? true : not covered false;
57 not covered
581100% this.set('embedly api key', process.env.EMBEDLY_API_KEY || process.env.EMBEDLY_APIKEY);
591100% this.set('mandrill api key', process.env.MANDRILL_API_KEY || process.env.MANDRILL_APIKEY);
601100% this.set('mandrill username', process.env.MANDRILL_USERNAME);
611100% this.set('google api key', process.env.GOOGLE_BROWSER_KEY);
621100% this.set('google server api key', process.env.GOOGLE_SERVER_KEY);
631100% this.set('ga property', process.env.GA_PROPERTY);
641100% this.set('ga domain', process.env.GA_DOMAIN);
651100% this.set('chartbeat property', process.env.CHARTBEAT_PROPERTY);
661100% this.set('chartbeat domain', process.env.CHARTBEAT_DOMAIN);
671100% this.set('allowed ip ranges', process.env.ALLOWED_IP_RANGES);
68 not covered
691100% if (process.env.S3_BUCKET && process.env.S3_KEY && process.env.S3_SECRET) {
700 not covered this.set('s3 config', { bucket: process.env.S3_BUCKET, key: process.env.S3_KEY, secret: process.env.S3_SECRET, region: process.env.S3_REGION });
71 not covered }
721100% if (process.env.AZURE_STORAGE_ACCOUNT && process.env.AZURE_STORAGE_ACCESS_KEY) {
730 not covered this.set('azurefile config', { account: process.env.AZURE_STORAGE_ACCOUNT, key: process.env.AZURE_STORAGE_ACCESS_KEY });
74 not covered }
750 not covered this.set('cloudinary config', true);
76 not covered }
771100% this.initAPI = require('./lib/middleware/initAPI')(this);
78 not covered
79 not covered
801100%_.extend(Keystone.prototype, require('./lib/core/options')(moduleRoot));
81 not covered
82 not covered
83 not covered /**
84 not covered * Registers a pre-event handler.
85 not covered *
86 not covered * Valid events include:
87 not covered * - `routes` - calls the function before any routes are matched, after all other middleware
88 not covered *
89 not covered * @param {String} event
90 not covered * @param {Function} function to call
91 not covered * @api public
92 not covered */
93 not covered
941100%Keystone.prototype.pre = function(event, fn) {
950 not covered if (!this._pre[event]) {
960 not covered throw new Error('keystone.pre() Error: event ' + event + ' does not exist.');
97 not covered }
980 not covered this._pre[event].push(fn);
990 not covered return this;
100 not covered };
101 not covered
102 not covered
1031100%Keystone.prototype.prefixModel = function (key) {
10415100% var modelPrefix = this.get('model prefix');
105 not covered
10615100% if (modelPrefix)
1070 not covered key = modelPrefix + '_' + key;
108 not covered
10915100% return require('mongoose/lib/utils').toCollectionName(key);
110 not covered };
111 not covered
112 not covered /* Attach core functionality to Keystone.prototype */
1131100%Keystone.prototype.init = require('./lib/core/init');
1141100%Keystone.prototype.initNav = require('./lib/core/initNav');
1151100%Keystone.prototype.connect = require('./lib/core/connect');
1161100%Keystone.prototype.start = require('./lib/core/start');
1171100%Keystone.prototype.mount = require('./lib/core/mount');
1181100%Keystone.prototype.routes = require('./lib/core/routes');
1191100%Keystone.prototype.static = require('./lib/core/static');
1201100%Keystone.prototype.importer = require('./lib/core/importer');
1211100%Keystone.prototype.createItems = require('./lib/core/createItems');
1221100%Keystone.prototype.redirect = require('./lib/core/redirect');
1231100%Keystone.prototype.list = require('./lib/core/list');
1241100%Keystone.prototype.getOrphanedLists = require('./lib/core/getOrphanedLists');
1251100%Keystone.prototype.bindEmailTestRoutes = require('./lib/core/bindEmailTestRoutes');
1261100%Keystone.prototype.wrapHTMLError = require('./lib/core/wrapHTMLError');
127 not covered
128 not covered
129 not covered /**
130 not covered * The exports object is an instance of Keystone.
131 not covered *
132 not covered * @api public
133 not covered */
134 not covered
1351100%var keystone = module.exports = exports = new Keystone();
136 not covered
137 not covered // Expose modules and Classes
1381100%keystone.utils = utils;
1391100%keystone.content = require('./lib/content');
1401100%keystone.List = require('./lib/list');
1411100%keystone.Field = require('./lib/field');
1421100%keystone.Field.Types = require('./lib/fieldTypes');
1431100%keystone.View = require('./lib/view');
1441100%keystone.Email = require('./lib/email');
145 not covered
1461100%keystone.security = {
1471100% csrf: require('./lib/security/csrf')
148 not covered };
149 not covered
150 not covered
151 not covered /**
152 not covered * returns all .js modules (recursively) in the path specified, relative
153 not covered * to the module root (where the keystone project is being consumed from).
154 not covered *
155 not covered * ####Example:
156 not covered *
157 not covered * var models = keystone.import('models');
158 not covered *
159 not covered * @param {String} dirname
160 not covered * @api public
161 not covered */
162 not covered
1631100%Keystone.prototype.import = function(dirname) {
1640 not covered var initialPath = path.join(moduleRoot, dirname);
165 not covered
1660 not covered var doImport = function(fromPath) {
1670 not covered var imported = {};
168 not covered
1690 not covered fs.readdirSync(fromPath).forEach(function(name) {
1700 not covered var fsPath = path.join(fromPath, name),
171 not covered
172 not covered // recur
1730 not covered if (info.isDirectory()) {
1740 not covered imported[name] = doImport(fsPath);
175 not covered } else {
1760 not covered var parts = name.split('.');
1770 not covered var ext = parts.pop();
1780 not covered if (ext === 'js' || ext === 'coffee') {
1790 not covered imported[parts.join('-')] = require(fsPath);
180 not covered }
181 not covered
1820 not covered return imported;
183 not covered };
184 not covered
1850 not covered return doImport(initialPath);
186 not covered };
187 not covered
188 not covered
189 not covered /**
190 not covered * Applies Application updates
191 not covered */
192 not covered
1931100%Keystone.prototype.applyUpdates = function(callback) {
1940 not covered require('./lib/updates').apply(callback);
195 not covered };
196 not covered
197 not covered
198 not covered /**
199 not covered * Renders a Keystone View
200 not covered *
201 not covered * @api private
202 not covered */
203 not covered
2041100%Keystone.prototype.render = function(req, res, view, ext) {
2050 not covered var keystone = this;
206 not covered
2070 not covered var templatePath = __dirname + '/templates/views/' + view + '.jade';
208 not covered
2090 not covered var jadeOptions = {
2100 not covered pretty: keystone.get('env') !== 'production'
211 not covered };
212 not covered
2130 not covered var compileTemplate = function() {
2140 not covered return jade.compile(fs.readFileSync(templatePath, 'utf8'), jadeOptions);
215 not covered };
216 not covered
2170 not covered var template = this.get('viewCache')
2180 not covered ? templateCache[view] || (templateCache[view] = compileTemplate())
2190 not covered : compileTemplate();
220 not covered
2210 not covered var flashMessages = {
2220 not covered info: res.req.flash('info'),
2230 not covered success: res.req.flash('success'),
2240 not covered warning: res.req.flash('warning'),
2250 not covered error: res.req.flash('error'),
2260 not covered hilight: res.req.flash('hilight')
227 not covered };
228 not covered
2290 not covered var locals = {
2300 not covered env: this.get('env'),
2310 not covered brand: keystone.get('brand'),
232 not covered nav: keystone.nav,
2330 not covered messages: _.any(flashMessages, function(msgs) { return msgs.length; }) ? flashMessages : false,
234 not covered lists: keystone.lists,
2350 not covered signout: this.get('signout url'),
2360 not covered backUrl: this.get('back url') || '/',
237 not covered section: {},
2380 not covered csrf_token_value: keystone.security.csrf.getToken(req, res),
2390 not covered csrf_query: '&' + keystone.security.csrf.TOKEN_KEY + '=' + keystone.security.csrf.getToken(req, res),
240 not covered ga: {
2410 not covered property: this.get('ga property'),
2420 not covered domain: this.get('ga domain')
243 not covered },
2440 not covered enableImages: keystone.get('wysiwyg images') ? true : false,
2450 not covered enableCloudinaryUploads: keystone.get('wysiwyg cloudinary images') ? true : false,
2460 not covered additionalButtons: keystone.get('wysiwyg additional buttons') || '',
2470 not covered additionalPlugins: keystone.get('wysiwyg additional plugins') || '',
2480 not covered additionalOptions: keystone.get('wysiwyg additional options') || {},
2490 not covered overrideToolbar: keystone.get('wysiwyg override toolbar'),
2500 not covered skin: keystone.get('wysiwyg skin') || 'keystone',
2510 not covered menubar: keystone.get('wysiwyg menubar')
252 not covered }
253 not covered
2540 not covered _.extend(locals, ext);
255 not covered
2560 not covered if (keystone.get('cloudinary config')) {
257 not covered try {
2580 not covered var cloudinaryUpload = cloudinary.uploader.direct_upload();
2590 not covered locals.cloudinary = {
2600 not covered cloud_name: keystone.get('cloudinary config').cloud_name,
2610 not covered api_key: keystone.get('cloudinary config').api_key,
262 not covered timestamp: cloudinaryUpload.hidden_fields.timestamp,
263 not covered signature: cloudinaryUpload.hidden_fields.signature,
2640 not covered prefix: keystone.get('cloudinary prefix') || '',
265 not covered uploader: cloudinary.uploader
2660 not covered locals.cloudinary_js_config = cloudinary.cloudinary_js_config();
267 not covered } catch(e) {
2680 not covered if (e === 'Must supply api_key') {
2690 not covered throw new Error('Invalid Cloudinary Config Provided\n\n' +
270 not covered } else {
2710 not covered throw e;
272 not covered }
2730 not covered locals.fieldLocals = _.pick(locals, '_', 'moment', 'numeral', 'env', 'js', 'utils', 'user', 'cloudinary');
274 not covered
2750 not covered var html = template(_.extend(locals, ext));
276 not covered
2770 not covered res.send(html);
278 not covered };
279 not covered
280 not covered /**
281 not covered * Populates relationships on a document or array of documents
282 not covered *
283 not covered * WARNING: This is currently highly inefficient and should only be used in development, or for
284 not covered * small data sets. There are lots of things that can be done to improve performance... later.
285 not covered *
286 not covered * @api public
287 not covered */
288 not covered
2891100%Keystone.prototype.populateRelated = function(docs, relationships, callback) {
2900 not covered if (Array.isArray(docs)) {
2910 not covered async.each(docs, function(doc, done) {
2920 not covered doc.populateRelated(relationships, done);
293 not covered }, callback);
2940 not covered } else if (docs && docs.populateRelated) {
2950 not covered docs.populateRelated(relationships, callback);
296 not covered } else {
2970 not covered callback();
298 not covered }
299 not covered
300 not covered /**
301 not covered * Logs a configuration error to the console
302 not covered *
303 not covered * @api public
304 not covered */
305 not covered
3061100%Keystone.prototype.console = {};
3071100%Keystone.prototype.console.err = function(type, msg) {
3082100% if (keystone.get('logger')) {
3092100% var dashes = '\n------------------------------------------------\n';
3102100% console.log(dashes + 'KeystoneJS: ' + type + ':\n\n' + msg + dashes);
311 not covered }
312 not covered
313 not covered /**
314 not covered * Keystone version
315 not covered *
316 not covered * @api public
317 not covered */
318 not covered
3191100%keystone.version = require('./package.json').version;
320 not covered
321 not covered
322 not covered // Expose Modules
3231100%keystone.session = require('./lib/session');

lib/core/options.js

22% block coverage
57 SLOC
LineHitsStatementsSourceAction
11100%var path = require('path'),
21100% _ = require('underscore'),
31100% cloudinary = require('cloudinary'),
41100% mandrillapi = require('mandrill-api'),
51100% utils = require('keystone-utils');
6 not covered
7 not covered function options(moduleRoot) {
8 not covered
91100% var exports = {};
10 not covered
11 not covered /**
12 not covered * This file contains methods specific to dealing with Keystone's options.
13 not covered * All exports are added to the Keystone.prototype
14 not covered */
15 not covered
16 not covered // Deprecated options that have been mapped to new keys
171100% var remappedOptions = { 'signin success': 'signin redirect', 'signout': 'signout url' };
18 not covered
19 not covered /**
20 not covered * Sets keystone options
21 not covered *
22 not covered * ####Example:
23 not covered *
24 not covered * keystone.set('user model', 'User') // sets the 'user model' option to `User`
25 not covered *
26 not covered * @param {String} key
27 not covered * @param {String} value
28 not covered * @api public
29 not covered */
301100% exports.set = function(key, value) {
3154100% if (arguments.length === 1) {
3232100% return this._options[key];
33 not covered }
340 not covered if (this.get('logger')) {
350 not covered console.log('\nWarning: the `' + key + '` option has been deprecated. Please use `' + remappedOptions[key] + '` instead.\n\n' +
36 not covered }
370 not covered key = remappedOptions[key];
38 not covered }
3922100% switch (key) {
40 not covered case 'cloudinary config':
410 not covered if (_.isObject(value)) {
420 not covered cloudinary.config(value);
43 not covered }
440 not covered value = cloudinary.config();
45 not covered break;
461100% if (value) {
470 not covered this.mandrillAPI = new mandrillapi.Mandrill(value);
48 not covered }
490 not covered if (value === true && !this.get('session')) {
500 not covered this.set('session', true);
51 not covered }
520 not covered this.nav = this.initNav(value);
53 not covered break;
540 not covered if ('string' !== typeof value) {
550 not covered if (Array.isArray(value) && (value.length === 2 || value.length === 3)) {
560 not covered console.log('\nWarning: using an array for the `mongo` option has been deprecated.\nPlease use a mongodb connection string, e.g. mongodb://localhost/db_name instead.\n\n' +
570 not covered value = (value.length === 2) ? 'mongodb://' + value[0] + '/' + value[1] : 'mongodb://' + value[0] + ':' + value[2] + '/' + value[1];
58 not covered } else {
590 not covered console.error('\nInvalid Configuration:\nThe `mongo` option must be a mongodb connection string, e.g. mongodb://localhost/db_name\n');
600 not covered process.exit(1);
61 not covered }
6222100% this._options[key] = value;
6322100% return this;
64 not covered };
65 not covered
66 not covered
67 not covered /**
68 not covered * Sets multiple keystone options.
69 not covered *
70 not covered * ####Example:
71 not covered *
72 not covered * keystone.set({test: value}) // sets the 'test' option to `value`
73 not covered *
74 not covered * @param {Object} options
75 not covered * @api public
76 not covered */
77 not covered
781100% exports.options = function(options) {
793100% if (!arguments.length)
800 not covered return this._options;
813100% if (utils.isObject(options)) {
820 not covered var keys = Object.keys(options),
83 not covered i = keys.length,
840 not covered while (i--) {
850 not covered k = keys[i];
860 not covered this.set(k, options[k]);
87 not covered }
883100% return this._options;
89 not covered };
90 not covered
91 not covered
92 not covered /**
93 not covered * Gets keystone options
94 not covered *
95 not covered * ####Example:
96 not covered *
97 not covered * keystone.get('test') // returns the 'test' value
98 not covered *
99 not covered * @param {String} key
100 not covered * @api public
101 not covered */
102 not covered
1031100% exports.get = exports.set;
104 not covered
105 not covered /**
106 not covered * Gets an expanded path option, expanded to include moduleRoot if it is relative
107 not covered *
108 not covered * ####Example:
109 not covered *
110 not covered * keystone.get('pathOption', 'defaultValue')
111 not covered *
112 not covered * @param {String} key
113 not covered * @param {String} defaultValue
114 not covered * @api public
115 not covered */
116 not covered
1171100% exports.getPath = function(key, defaultValue) {
1180 not covered return this.expandPath(this.get(key) || defaultValue);
119 not covered };
120 not covered
121 not covered /**
122 not covered * Expands a path to include moduleRoot if it is relative
123 not covered *
124 not covered * @param {String} pathValue
125 not covered * @api public
126 not covered */
127 not covered
1281100% exports.expandPath = function(pathValue) {
1290 not covered pathValue = ('string' === typeof pathValue && pathValue.substr(0,1) !== path.sep && pathValue.substr(1,2) !== ':\\')
1300 not covered ? path.join(moduleRoot, pathValue)
1310 not covered : pathValue;
1320 not covered return pathValue;
133 not covered };
134 not covered
1351100% return exports;
136 not covered
137 not covered }
138 not covered
1391100%module.exports = options;

lib/core/init.js

100% line coverage
100% statement coverage
100% block coverage
8 SLOC
LineHitsStatementsSourceAction
1 not covered /**
2 not covered * Initialises Keystone in encapsulated mode.
3 not covered *
4 not covered * Creates an Express app and configures it if none has been connected.
5 not covered *
6 not covered * Also connects to the default mongoose instance if none has been connected.
7 not covered *
8 not covered * Accepts an options argument.
9 not covered *
10 not covered * Returns `this` to allow chaining.
11 not covered *
12 not covered * @param {Object} options
13 not covered * @api public
14 not covered */
15 not covered
161100%var express = require('express');
17 not covered
18 not covered function init(options) {
19 not covered
203100% this.options(options);
21 not covered
223100% if (!this.app) {
231100% this.app = express();
24 not covered }
25 not covered
263100% if (!this.mongoose) {
271100% this.connect(require('mongoose'));
28 not covered }
29 not covered
303100% return this;
31 not covered
32 not covered }
33 not covered
341100%module.exports = init;

lib/core/initNav.js

0% block coverage
35 SLOC
LineHitsStatementsSourceAction
1 not covered /**
2 not covered * Initialises Keystone's internal nav config
3 not covered *
4 not covered * @param {Object} nav
5 not covered * @api private
6 not covered */
7 not covered
81100%var _ = require('underscore'), utils = require('keystone-utils');
9 not covered
10 not covered function initNav(sections) {
11 not covered
120 not covered var keystone = this;
13 not covered
140 not covered var nav = {
15 not covered
160 not covered if (!sections) {
170 not covered sections = {};
180 not covered nav.flat = true;
190 not covered _.each(this.lists, function(list) {
200 not covered if (list.get('hidden')) return;
210 not covered sections[list.path] = [list.path];
22 not covered });
23 not covered }
24 not covered
250 not covered _.each(sections, function(section, key) {
260 not covered if ('string' === typeof section) {
270 not covered section = [section];
28 not covered }
290 not covered section = {
30 not covered lists: section,
310 not covered label: nav.flat ? keystone.list(section[0]).label : utils.keyToLabel(key)
32 not covered };
330 not covered section.key = key;
340 not covered section.lists = _.map(section.lists, function(i) {
350 not covered var msg, list = keystone.list(i);
360 not covered if (!list) {
370 not covered msg = 'Invalid Keystone Option (nav): list ' + i + ' has not been defined.\n';
380 not covered throw new Error(msg);
39 not covered }
400 not covered if (list.get('hidden')) {
410 not covered msg = 'Invalid Keystone Option (nav): list ' + i + ' is hidden.\n';
420 not covered throw new Error(msg);
43 not covered }
440 not covered nav.by.list[list.key] = section;
450 not covered return list;
46 not covered });
47 not covered if (section.lists.length) {
480 not covered nav.sections.push(section);
490 not covered nav.by.section[section.key] = section;
50 not covered }
51 not covered
520 not covered return nav;
53 not covered }
54 not covered
551100%module.exports = initNav;

lib/core/connect.js

75% block coverage
7 SLOC
LineHitsStatementsSourceAction
1 not covered /**
2 not covered * Connects keystone to the application's mongoose instance.
3 not covered *
4 not covered * ####Example:
5 not covered *
6 not covered * var mongoose = require('mongoose');
7 not covered *
8 not covered * keystone.connect(mongoose);
9 not covered *
10 not covered * @param {Object} connections
11 not covered * @api public
12 not covered */
13 not covered
14 not covered function connect() {
15 not covered // detect type of each argument
166100% for (var i = 0; i < arguments.length; i++) {
173100% if (arguments[i].constructor.name === 'Mongoose') {
18 not covered // detected Mongoose
193100% this.mongoose = arguments[i];
200 not covered } else if (arguments[i].name === 'app') {
21 not covered // detected Express app
220 not covered this.app = arguments[i];
23 not covered }
24 not covered }
253100% return this;
26 not covered }
27 not covered
281100%module.exports = connect;

lib/core/start.js

0% block coverage
96 SLOC
LineHitsStatementsSourceAction
1 not covered /**
2 not covered * Configures and starts a Keystone app in encapsulated mode.
3 not covered *
4 not covered * Connects to the database, runs updates and listens for incoming requests.
5 not covered *
6 not covered * Events are fired during initialisation to allow customisation, including:
7 not covered *
8 not covered * - onMount
9 not covered * - onStart
10 not covered * - onHttpServerCreated
11 not covered * - onHttpsServerCreated
12 not covered *
13 not covered * If the events argument is a function, it is assumed to be the started event.
14 not covered *
15 not covered *
16 not covered * ####Options:
17 not covered *
18 not covered * Keystone supports the following options specifically for running in encapsulated mode:
19 not covered *
20 not covered * - name
21 not covered * - port
22 not covered * - views
23 not covered * - view engine
24 not covered * - compress
25 not covered * - favico
26 not covered * - less
27 not covered * - static
28 not covered * - headless
29 not covered * - logger
30 not covered * - cookie secret
31 not covered * - session
32 not covered * - 404
33 not covered * - 500
34 not covered * - routes
35 not covered * - locals
36 not covered * - auto update
37 not covered * - ssl
38 not covered * - sslport
39 not covered * - sslkey
40 not covered * - sslcert
41 not covered *
42 not covered *
43 not covered * @api public
44 not covered */
45 not covered
461100%var fs = require('fs'),
471100% http = require('http'),
481100% https = require('https');
49 not covered
501100%var dashes = '\n------------------------------------------------\n';
51 not covered
52 not covered function start(events) {
53 not covered
54 not covered // Validate arguments
55 not covered
560 not covered if ('function' === typeof events) {
570 not covered events = { onStart: events };
58 not covered }
59 not covered
600 not covered if (!events) events = {};
61 not covered
62 not covered // Ensure Keystone has been initialised
63 not covered
640 not covered if (!this.app) {
650 not covered throw new Error('KeystoneJS Initialisaton Error:\n\napp must be initialised. Call keystone.init() or keystone.connect(new Express()) first.\n\n');
66 not covered }
67 not covered
68 not covered // Localise references to this for closures
69 not covered
700 not covered var keystone = this,
71 not covered
72 not covered // Maintain passed in onMount binding but override to start http servers
73 not covered // (call user-defined onMount first if present)
74 not covered
750 not covered var onMount = events.onMount;
76 not covered
770 not covered events.onMount = function() {
780 not covered onMount && onMount();
79 not covered
800 not covered var startupMessages = ['KeystoneJS Started:'],
81 not covered
820 not covered var serverStarted = function() {
830 not covered waitForServers--;
840 not covered if (waitForServers) return;
850 not covered if (keystone.get('logger')) {
860 not covered console.log(dashes + startupMessages.join('\n') + dashes);
87 not covered }
880 not covered events.onStart && events.onStart();
89 not covered };
90 not covered
910 not covered keystone.httpServer = http.createServer(app);
920 not covered events.onHttpServerCreated && events.onHttpServerCreated();
93 not covered
940 not covered var host = keystone.get('host'),
950 not covered port = keystone.get('port'),
960 not covered listen = keystone.get('listen'),
970 not covered ssl = keystone.get('ssl');
98 not covered
990 not covered if (ssl !== 'only') {
100 not covered
1010 not covered var httpStarted = function(msg) {
1020 not covered return function() {
1030 not covered startupMessages.push(msg);
1040 not covered serverStarted();
105 not covered };
106 not covered };
107 not covered
1080 not covered if (port || port === 0) {
109 not covered
1100 not covered app.set('port', port);
111 not covered
1120 not covered var httpReadyMsg = keystone.get('name') + ' is ready';
113 not covered
1140 not covered if (host) {
1150 not covered httpReadyMsg += ' on http://' + host;
1160 not covered if (port) {
1170 not covered httpReadyMsg += ':' + port;
118 not covered }
1190 not covered keystone.httpServer.listen(port, host, httpStarted(httpReadyMsg));
120 not covered } else {
1210 not covered if (port) {
1220 not covered httpReadyMsg += ' on port ' + port;
123 not covered }
1240 not covered keystone.httpServer.listen(port, httpStarted(httpReadyMsg));
125 not covered }
1260 not covered } else if (host) {
127 not covered // start listening on a specific host address and default port 3000
1280 not covered app.set('port', 3000);
1290 not covered keystone.httpServer.listen(3000, host, httpStarted(keystone.get('name') + ' is ready on ' + host + ':3000'));
1300 not covered } else if (listen) {
131 not covered // start listening to a unix socket
1320 not covered keystone.httpServer.listen(listen, httpStarted(keystone.get('name') + ' is ready' + (('string' === typeof listen) ? ' on ' + listen : '')));
133 not covered } else {
1340 not covered app.set('port', 3000);
1350 not covered keystone.httpServer.listen(3000, httpStarted(keystone.get('name') + ' is ready on default port 3000'));
136 not covered }
1370 not covered waitForServers--;
138 not covered }
1390 not covered if (ssl) {
140 not covered
1410 not covered var sslOpts = {};
142 not covered
1430 not covered if (keystone.get('ssl cert') && fs.existsSync(keystone.getPath('ssl cert'))) {
1440 not covered sslOpts.cert = fs.readFileSync(keystone.getPath('ssl cert'));
145 not covered }
1460 not covered if (keystone.get('ssl key') && fs.existsSync(keystone.getPath('ssl key'))) {
1470 not covered sslOpts.key = fs.readFileSync(keystone.getPath('ssl key'));
148 not covered }
1490 not covered if (!sslOpts.key || !sslOpts.cert) {
150 not covered
1510 not covered if (ssl === 'only') {
1520 not covered console.log(keystone.get('name') + ' failed to start: invalid ssl configuration');
1530 not covered process.exit();
154 not covered } else {
1550 not covered startupMessages.push('Warning: Invalid SSL Configuration');
1560 not covered serverStarted();
157 not covered }
1580 not covered var httpsStarted = function(msg) {
1590 not covered return function() {
1600 not covered startupMessages.push(msg);
1610 not covered serverStarted();
162 not covered };
163 not covered };
164 not covered
1650 not covered keystone.httpsServer = https.createServer(sslOpts, app);
1660 not covered events.onHttpsServerCreated && events.onHttpsServerCreated();
167 not covered
1680 not covered var sslHost = keystone.get('ssl host') || host,
169 not covered
1700 not covered var httpsReadyMsg = (ssl === 'only') ? keystone.get('name') + ' (SSL) is ready on ' : 'SSL Server is ready on ';
171 not covered
1720 not covered if (sslHost) {
1730 not covered keystone.httpsServer.listen(sslPort, sslHost, httpsStarted(httpsReadyMsg + 'https://' + sslHost + ':' + sslPort));
174 not covered } else {
1750 not covered var httpsPortMsg = (keystone.get('ssl port')) ? 'port: ' + keystone.get('ssl port') : 'default port 3001';
1760 not covered keystone.httpsServer.listen(sslPort, httpsStarted(httpsReadyMsg + httpsPortMsg));
177 not covered }
1780 not covered waitForServers--;
179 not covered }
1800 not covered process.on('uncaughtException', function(e) {
1810 not covered if (e.code === 'EADDRINUSE') {
182 not covered console.log(dashes +
1830 not covered keystone.get('name') + ' failed to start: address already in use\n' +
1840 not covered 'Please check you are not already running a server on the specified port.\n');
1850 not covered process.exit();
186 not covered }/* else if (e.code === 'ECONNRESET') {
187 not covered // Connection reset by peer, ignore it instead of exiting server with a throw.
1880 not covered console.log(e.stack || e);
1890 not covered process.exit(1);
190 not covered }
191 not covered
192 not covered
193 not covered //mount the express app
1940 not covered this.mount(events);
195 not covered
1960 not covered return this;
197 not covered
198 not covered }
199 not covered
2001100%module.exports = start;

lib/core/mount.js

0% block coverage
244 SLOC
LineHitsStatementsSourceAction
1 not covered /**
2 not covered * Configures a Keystone app in encapsulated mode, but does not start it.
3 not covered *
4 not covered * Connects to the database and runs updates and then calls back.
5 not covered *
6 not covered * This is the code-path to use if you'd like to mount the keystone app as a sub-app in another express application.
7 not covered *
8 not covered * var app = express();
9 not covered *
10 not covered * //...do your normal express setup stuff, add middleware and routes (but not static content or error handling middleware yet)
11 not covered *
12 not covered * keystone.mount('/content', app, function() {
13 not covered * //put your app's static content and error handling middleware here and start your server
14 not covered * });
15 not covered *
16 not covered * Events are fired during initialisation to allow customisation, including:
17 not covered *
18 not covered * - onMount
19 not covered *
20 not covered * If the events argument is a function, it is assumed to be the mounted event.
21 not covered *
22 not covered *
23 not covered * ####Options:
24 not covered *
25 not covered * Keystone supports the following options specifically for running in encapsulated mode (with no embedded server):
26 not covered *
27 not covered * - name
28 not covered * - port
29 not covered * - views
30 not covered * - view engine
31 not covered * - compress
32 not covered * - favico
33 not covered * - less
34 not covered * - static
35 not covered * - headless
36 not covered * - logger
37 not covered * - cookie secret
38 not covered * - session
39 not covered * - 404
40 not covered * - 500
41 not covered * - routes
42 not covered * - locals
43 not covered * - auto update
44 not covered *
45 not covered *
46 not covered * @api public
47 not covered */
48 not covered
491100%var _ = require('underscore'),
501100% express = require('express'),
511100% path = require('path'),
521100% utils = require('keystone-utils');
53 not covered
541100%var dashes = '\n------------------------------------------------\n';
55 not covered
56 not covered function mount(mountPath, parentApp, events) {
57 not covered
58 not covered // Validate the express app instance
59 not covered
600 not covered if (!this.app) {
610 not covered console.error('\nKeystoneJS Initialisaton Error:\n\napp must be initialised. Call keystone.init() or keystone.connect(new Express()) first.\n');
620 not covered process.exit(1);
63 not covered }
64 not covered
65 not covered // Localise references to this for closures
66 not covered
670 not covered var keystone = this,
68 not covered
69 not covered // this.nativeApp indicates keystone has been mounted natively
70 not covered // (not as part of a custom middleware stack)
71 not covered //
720 not covered this.nativeApp = true;
73 not covered
74 not covered // Initialise the mongo connection url
75 not covered
760 not covered if (!this.get('mongo')) {
770 not covered var dbName = this.get('db name') || utils.slug(this.get('name'));
780 not covered var dbUrl = process.env.MONGO_URI || process.env.MONGO_URL || process.env.MONGOLAB_URI || process.env.MONGOLAB_URL || (process.env.OPENSHIFT_MONGODB_DB_URL || 'mongodb://localhost/') + dbName;
790 not covered this.set('mongo', dbUrl);
80 not covered }
81 not covered
82 not covered // Initialise and validate session options
83 not covered
840 not covered if (!this.get('cookie secret')) {
850 not covered console.error('\nKeystoneJS Configuration Error:\n\nPlease provide a `cookie secret` value for session encryption.\n');
860 not covered process.exit(1);
87 not covered }
88 not covered
890 not covered var sessionOptions = this.get('session options');
90 not covered
910 not covered if (!_.isObject(sessionOptions)) {
920 not covered sessionOptions = {};
93 not covered }
94 not covered
950 not covered if (!sessionOptions.key) {
960 not covered sessionOptions.key = 'keystone.sid';
97 not covered }
98 not covered
990 not covered sessionOptions.cookieParser = express.cookieParser(this.get('cookie secret'));
100 not covered
1010 not covered var sessionStore = this.get('session store');
102 not covered
1030 not covered if (sessionStore) {
104 not covered
1050 not covered var sessionStoreOptions = this.get('session store options') || {};
106 not covered
107 not covered // Perform any session store specific configuration or exit on an unsupported session store
108 not covered
1090 not covered switch (sessionStore) {
110 not covered
111 not covered case 'mongo':
112 not covered // default session store for using MongoDB
1130 not covered sessionStore = 'connect-mongo';
114 not covered case 'connect-mongo':
1150 not covered _.defaults(sessionStoreOptions, {
1160 not covered url: this.get('mongo')
117 not covered });
118 not covered break;
119 not covered
120 not covered case 'connect-mongostore':
1210 not covered _.defaults(sessionStoreOptions, {
1220 not covered if (!sessionStoreOptions.db) {
1230 not covered console.error(
124 not covered '\nERROR: ' + sessionStore + ' requires `session store options` to be set.' +
1250 not covered '\n' +
1260 not covered '\nSee http://localhost:8080/docs/configuration#options-database for details.' +
1270 not covered '\n');
1280 not covered process.exit(1);
129 not covered }
130 not covered break;
131 not covered
132 not covered case 'redis':
133 not covered // default session store for using Redis
1340 not covered sessionStore = 'connect-redis';
135 not covered case 'connect-redis':
136 not covered break;
137 not covered
138 not covered default:
1390 not covered console.error(
140 not covered '\nERROR: unsupported session store ' + sessionStore + '.' +
1410 not covered '\n' +
1420 not covered '\nSee http://localhost:8080/docs/configuration#options-database for details.' +
1430 not covered '\n');
1440 not covered process.exit(1);
145 not covered break;
146 not covered }
147 not covered
148 not covered // Initialize the session store
149 not covered try {
150 not covered
1510 not covered var _SessionStore = require(sessionStore)(express);
1520 not covered sessionOptions.store = new _SessionStore(sessionStoreOptions);
153 not covered
154 not covered } catch(e) {
155 not covered
1560 not covered if (e.code === 'MODULE_NOT_FOUND') {
157 not covered
158 not covered // connect-redis must be explicitly installed @1.4.7, so we special-case it here
1590 not covered var installName = (sessionStore === 'connect-redis') ? sessionStore + '@1.4.7' : sessionStore;
160 not covered
1610 not covered console.error(
162 not covered '\nERROR: ' + sessionStore + ' not found.\n' +
1630 not covered '\nPlease install ' + sessionStore + ' from npm to use it as a `session store` option.' +
1640 not covered '\nYou can do this by running "npm install ' + installName + ' --save".' +
1650 not covered '\n');
1660 not covered process.exit(1);
167 not covered
168 not covered } else {
1690 not covered throw e;
170 not covered }
171 not covered }
172 not covered }
173 not covered
174 not covered // expose initialised session options
175 not covered
1760 not covered this.set('session options', sessionOptions);
177 not covered
178 not covered // wrangle arguments
179 not covered
1800 not covered if (arguments.length === 1) {
1810 not covered events = arguments[0];
1820 not covered mountPath = null;
183 not covered }
184 not covered
1850 not covered if ('function' === typeof events) {
1860 not covered events = { onMount: events };
187 not covered }
188 not covered
1890 not covered if (!events) events = {};
190 not covered
191 not covered /* Express sub-app mounting to external app at a mount point (if specified) */
192 not covered
1930 not covered if (mountPath) {
194 not covered //fix root-relative keystone urls for assets (gets around having to re-write all the keystone templates)
1950 not covered parentApp.all(/^\/keystone($|\/*)/, function(req, res, next) {
1960 not covered req.url = mountPath + req.url;
1970 not covered next();
198 not covered });
199 not covered
2000 not covered parentApp.use(mountPath, app);
201 not covered }
202 not covered
203 not covered /* Keystone's encapsulated Express App Setup */
204 not covered
205 not covered // Allow usage of custom view engines
206 not covered
2070 not covered if (this.get('custom engine')) {
2080 not covered app.engine(this.get('view engine'), this.get('custom engine'));
209 not covered }
210 not covered
211 not covered // Set location of view templates and view engine
212 not covered
2130 not covered app.set('views', this.getPath('views') || path.sep + 'views');
2140 not covered app.set('view engine', this.get('view engine'));
215 not covered
216 not covered // Apply locals
217 not covered
2180 not covered if (utils.isObject(this.get('locals'))) {
2190 not covered _.extend(app.locals, this.get('locals'));
220 not covered }
221 not covered
222 not covered // Indent HTML everywhere, except production
223 not covered
2240 not covered if (this.get('env') !== 'production') {
2250 not covered app.locals.pretty = true;
226 not covered }
227 not covered
228 not covered // Default view caching logic
229 not covered
2300 not covered app.set('view cache', this.get('env') === 'production' ? true : false);
231 not covered
232 not covered // Setup view caching from app settings
233 not covered
2340 not covered if (this.get('view cache') !== undefined) {
2350 not covered app.set('view cache', this.get('view cache'));
236 not covered }
237 not covered
238 not covered // Serve static assets
239 not covered
2400 not covered if (this.get('compress')) {
2410 not covered app.use(express.compress());
242 not covered }
243 not covered
2440 not covered if (this.get('favico')) {
2450 not covered app.use(express.favicon(this.getPath('favico')));
246 not covered }
247 not covered
2480 not covered if (this.get('less')) {
2490 not covered app.use(require('less-middleware')(this.getPath('less')));
250 not covered }
251 not covered
2520 not covered if (this.get('sass')) {
2530 not covered var sass;
254 not covered try {
2550 not covered sass = require('node-sass');
256 not covered } catch(e) {
2570 not covered if (e.code === 'MODULE_NOT_FOUND') {
2580 not covered console.error(
259 not covered '\nERROR: node-sass not found.\n' +
2600 not covered '\nPlease install the node-sass from npm to use the `sass` option.' +
2610 not covered '\nYou can do this by running "npm install node-sass --save".\n'
262 not covered );
2630 not covered process.exit(1);
264 not covered } else {
2650 not covered throw e;
266 not covered }
267 not covered }
2680 not covered app.use(sass.middleware({
2690 not covered src: this.getPath('sass'),
2700 not covered dest: this.getPath('sass'),
2710 not covered outputStyle: this.get('env') === 'production' ? 'compressed' : 'nested'
272 not covered }));
273 not covered }
274 not covered
275 not covered // the static option can be a single path, or array of paths
276 not covered
2770 not covered var staticPaths = this.get('static');
278 not covered
2790 not covered if (_.isString(staticPaths)) {
2800 not covered staticPaths = [staticPaths];
281 not covered }
282 not covered
2830 not covered if (_.isArray(staticPaths)) {
2840 not covered _.each(staticPaths, function(value) {
2850 not covered app.use(express.static(this.expandPath(value)));
286 not covered }, this);
287 not covered }
288 not covered
289 not covered // unless the headless option is set (which disables the Admin UI),
290 not covered // bind the static handler for the Admin UI public resources
2910 not covered if (!this.get('headless')) {
2920 not covered this.static(app);
293 not covered }
294 not covered
295 not covered // Handle dynamic requests
296 not covered
2970 not covered if (this.get('logger')) {
2980 not covered app.use(express.logger(this.get('logger')));
299 not covered }
300 not covered
3010 not covered if (this.get('file limit')) {
3020 not covered app.use(express.limit(this.get('file limit')));
303 not covered }
304 not covered
3050 not covered app.use(express.bodyParser());
3060 not covered app.use(express.methodOverride());
3070 not covered app.use(sessionOptions.cookieParser);
3080 not covered app.use(express.session(sessionOptions));
3090 not covered app.use(require('connect-flash')());
310 not covered
3110 not covered if (this.get('session') === true) {
3120 not covered app.use(this.session.persist);
3130 not covered } else if ('function' === typeof this.get('session')) {
3140 not covered app.use(this.get('session'));
315 not covered }
316 not covered
317 not covered // Process 'X-Forwarded-For' request header
318 not covered
3190 not covered if (this.get('trust proxy') === true) {
3200 not covered app.enable('trust proxy');
321 not covered } else {
3220 not covered app.disable('trust proxy');
323 not covered }
324 not covered
325 not covered // Check for IP range restrictions
326 not covered
3270 not covered if (this.get('allowed ip ranges')) {
3280 not covered if (!app.get('trust proxy')) {
3290 not covered console.log(
3300 not covered 'KeystoneJS Initialisaton Error:\n\n' +
331 not covered );
3320 not covered process.exit(1);
333 not covered }
3340 not covered var ipRangeMiddleware = require('./lib/security/ipRangeRestrict')(
3350 not covered this.get('allowed ip ranges'),
336 not covered this.wrapHTMLError
3370 not covered this.pre('routes', ipRangeMiddleware);
338 not covered }
339 not covered
340 not covered // Pre-route middleware
341 not covered
3420 not covered this._pre.routes.forEach(function(fn) {
3430 not covered app.use(fn);
344 not covered }
345 not covered catch(e) {
3460 not covered if (keystone.get('logger')) {
3470 not covered console.log('Invalid pre-route middleware provided');
348 not covered }
3490 not covered throw e;
350 not covered }
351 not covered
352 not covered // Route requests
353 not covered
3540 not covered app.use(app.router);
355 not covered
356 not covered // Headless mode means don't bind the Keystone routes
357 not covered
3580 not covered if (!this.get('headless')) {
3590 not covered this.routes(app);
360 not covered }
361 not covered
362 not covered
363 not covered // Configure application routes
3640 not covered if ('function' === typeof this.get('routes')) {
3650 not covered this.get('routes')(app);
366 not covered }
367 not covered
368 not covered //prepare the error handlers; they should be called last
3690 not covered var setHandlers = function () {
3700 not covered if (Object.keys(keystone._redirects).length) {
3710 not covered app.use(function(req, res, next) {
372 not covered if (keystone._redirects[req.path]) {
3730 not covered res.redirect(keystone._redirects[req.path]);
374 not covered } else {
3750 not covered next();
376 not covered }
377 not covered });
3780 not covered var default404Handler = function(req, res, next) {
3790 not covered res.status(404).send(keystone.wrapHTMLError('Sorry, no page could be found at this address (404)'));
380 not covered };
381 not covered
3820 not covered app.use(function(req, res, next) {
3830 not covered var err404 = keystone.get('404');
384 not covered
3850 not covered if (err404) {
386 not covered try {
3870 not covered if ('function' === typeof err404) {
3880 not covered err404(req, res, next);
3890 not covered } else if ('string' === typeof err404) {
3900 not covered res.status(404).render(err404);
391 not covered } else {
3920 not covered if (keystone.get('logger')) {
3930 not covered console.log(dashes + 'Error handling 404 (not found): Invalid type (' + (typeof err404) + ') for 404 setting.' + dashes);
394 not covered }
3950 not covered default404Handler(req, res, next);
396 not covered }
397 not covered } catch(e) {
3980 not covered if (keystone.get('logger')) {
3990 not covered console.log(dashes + 'Error handling 404 (not found):');
4000 not covered console.log(e);
4010 not covered console.log(dashes);
402 not covered }
4030 not covered default404Handler(req, res, next);
404 not covered }
4050 not covered default404Handler(req, res, next);
406 not covered }
407 not covered
4080 not covered var default500Handler = function(err, req, res, next) {
4090 not covered if (keystone.get('logger')) {
4100 not covered if (err instanceof Error) {
4110 not covered console.log((err.type ? err.type + ' ' : '') + 'Error thrown for request: ' + req.url);
412 not covered } else {
4130 not covered console.log('Error thrown for request: ' + req.url);
414 not covered }
4150 not covered console.log(err.stack || err);
416 not covered }
4170 not covered var msg = '';
418 not covered
4190 not covered if (keystone.get('env') === 'development') {
420 not covered
4210 not covered if (err instanceof Error) {
422 not covered if (err.type) {
4230 not covered msg += '<h2>' + err.type + '</h2>';
424 not covered }
4250 not covered msg += utils.textToHTML(err.message);
4260 not covered } else if ('object' === typeof err) {
4270 not covered msg += '<code>' + JSON.stringify(err) + '</code>';
4280 not covered } else if (err) {
4290 not covered msg += err;
430 not covered }
4310 not covered res.status(500).send(keystone.wrapHTMLError('Sorry, an error occurred loading the page (500)', msg));
432 not covered };
433 not covered
4340 not covered app.use(function(err, req, res, next) {
4350 not covered var err500 = keystone.get('500');
436 not covered
4370 not covered if (err500) {
438 not covered try {
4390 not covered if ('function' === typeof err500) {
4400 not covered err500(err, req, res, next);
4410 not covered } else if ('string' === typeof err500) {
4420 not covered res.locals.err = err;
4430 not covered res.status(500).render(err500);
444 not covered } else {
4450 not covered if (keystone.get('logger')) {
4460 not covered console.log(dashes + 'Error handling 500 (error): Invalid type (' + (typeof err500) + ') for 500 setting.' + dashes);
447 not covered }
4480 not covered default500Handler(err, req, res, next);
449 not covered }
450 not covered } catch(e) {
4510 not covered if (keystone.get('logger')) {
4520 not covered console.log(dashes + 'Error handling 500 (error):');
4530 not covered console.log(e);
4540 not covered console.log(dashes);
455 not covered }
4560 not covered default500Handler(err, req, res, next);
457 not covered }
4580 not covered default500Handler(err, req, res, next);
459 not covered }
460 not covered }
4610 not covered var mongoConnectionOpen = false;
462 not covered
463 not covered // support replica sets for mongoose
4640 not covered if (this.get('mongo replica set')){
465 not covered
4660 not covered var replicaData = this.get('mongo replica set');
4670 not covered var replica = "";
468 not covered
4690 not covered var credentials = (replicaData.username && replicaData.password) ? replicaData.username +":"+replicaData.password+"@" : '';
470 not covered
4710 not covered replicaData.db.servers.forEach(function (server) {
4720 not covered replica += "mongodb://"+credentials+server["host"]+":"+server["port"]+"/"+replicaData.db.name+",";
473 not covered });
474 not covered
4750 not covered var options = {
476 not covered
4770 not covered this.mongoose.connect(replica, options);
478 not covered
479 not covered } else {
480 not covered
4810 not covered this.mongoose.connect(this.get('mongo'));
482 not covered
483 not covered }
484 not covered
4850 not covered this.mongoose.connection.on('error', function(err) {
4860 not covered if (keystone.get('logger')) {
4870 not covered console.log('------------------------------------------------');
4880 not covered console.log('Mongo Error:\n');
4890 not covered console.log(err);
490 not covered }
491 not covered
4920 not covered if (mongoConnectionOpen) {
4930 not covered throw new Error('Mongo Error');
494 not covered } else {
4950 not covered throw new Error('KeystoneJS (' + keystone.get('name') + ') failed to start');
496 not covered }
497 not covered
498 not covered
4990 not covered mongoConnectionOpen = true;
500 not covered
5010 not covered if (keystone.get('auto update')) {
5020 not covered var mounted = function () {
5030 not covered events.onMount();
5040 not covered setHandlers();
505 not covered }
5060 not covered keystone.applyUpdates(mounted);
5070 not covered events.onMount && events.onMount();
5080 not covered setHandlers();
509 not covered }
510 not covered }
511 not covered
5121100%module.exports = mount;

lib/core/routes.js

0% block coverage
42 SLOC
LineHitsStatementsSourceAction
1 not covered /**
2 not covered * Adds bindings for the keystone routes
3 not covered *
4 not covered * ####Example:
5 not covered *
6 not covered * var app = express();
7 not covered * app.configure(...); // configuration settings
8 not covered * app.use(...); // middleware, routes, etc. should come before keystone is initialised
9 not covered * keystone.routes(app);
10 not covered *
11 not covered * @param {Express()} app
12 not covered * @api public
13 not covered */
14 not covered
15 not covered function routes(app) {
16 not covered
170 not covered this.app = app;
180 not covered var keystone = this;
19 not covered
20 not covered // ensure keystone nav has been initialised
210 not covered if (!this.nav) {
220 not covered this.nav = this.initNav();
23 not covered }
24 not covered
25 not covered // Cache compiled view templates if we are in Production mode
260 not covered this.set('view cache', this.get('env') === 'production');
27 not covered
28 not covered // Bind auth middleware (generic or custom) to /keystone* routes, allowing
29 not covered // access to the generic signin page if generic auth is used
30 not covered
310 not covered if (this.get('auth') === true) {
32 not covered
330 not covered if (!this.get('signout url')) {
340 not covered this.set('signout url', '/keystone/signout');
35 not covered }
360 not covered if (!this.get('signin url')) {
370 not covered this.set('signin url', '/keystone/signin');
38 not covered }
39 not covered
400 not covered if (!this.nativeApp || !this.get('session')) {
410 not covered app.all('/keystone*', this.session.persist);
42 not covered }
43 not covered
440 not covered app.all('/keystone/signin', require('../../routes/views/signin'));
450 not covered app.all('/keystone/signout', require('../../routes/views/signout'));
460 not covered app.all('/keystone*', this.session.keystoneAuth);
47 not covered
480 not covered } else if ('function' === typeof this.get('auth')) {
490 not covered app.all('/keystone*', this.get('auth'));
50 not covered }
51 not covered
520 not covered var initList = function(protect) {
530 not covered return function(req, res, next) {
540 not covered req.list = keystone.list(req.params.list);
550 not covered if (!req.list || (protect && req.list.get('hidden'))) {
560 not covered req.flash('error', 'List ' + req.params.list + ' could not be found.');
570 not covered return res.redirect('/keystone');
58 not covered }
590 not covered next();
60 not covered };
61 not covered };
62 not covered
63 not covered // Keystone Admin Route
640 not covered app.all('/keystone', require('../../routes/views/home'));
65 not covered
66 not covered // Email test routes
670 not covered if (this.get('email tests')) {
680 not covered this.bindEmailTestRoutes(app, this.get('email tests'));
69 not covered }
70 not covered
71 not covered // Cloudinary API for image uploading (only if Cloudinary is configured)
720 not covered if (keystone.get('wysiwyg cloudinary images')) {
730 not covered if (!keystone.get('cloudinary config')) {
740 not covered throw new Error('KeystoneJS Initialisaton Error:\n\nTo use wysiwyg cloudinary images, the \'cloudinary config\' setting must be configured.\n\n');
75 not covered }
760 not covered app.post('/keystone/api/cloudinary/upload', require('../../routes/api/cloudinary').upload);
77 not covered }
78 not covered
79 not covered // Cloudinary API for selecting an existing image from the cloud
800 not covered if (keystone.get('cloudinary config')) {
810 not covered app.get('/keystone/api/cloudinary/get', require('../../routes/api/cloudinary').get);
820 not covered app.get('/keystone/api/cloudinary/autocomplete', require('../../routes/api/cloudinary').autocomplete);
83 not covered }
84 not covered
85 not covered // Generic Lists API
860 not covered app.all('/keystone/api/:list/:action', initList(), require('../../routes/api/list'));
87 not covered
88 not covered // Generic Lists Download Route
890 not covered app.all('/keystone/download/:list', initList(), require('../../routes/download/list'));
90 not covered
91 not covered // List and Item Details Admin Routes
920 not covered app.all('/keystone/:list/:page([0-9]{1,5})?', initList(true), require('../../routes/views/list'));
930 not covered app.all('/keystone/:list/:item', initList(true), require('../../routes/views/item'));
94 not covered
950 not covered return this;
96 not covered
97 not covered }
98 not covered
991100%module.exports = routes;

lib/core/static.js

0% block coverage
6 SLOC
LineHitsStatementsSourceAction
11100%var path = require('path'), express = require('express');
2 not covered
3 not covered /**
4 not covered * Adds bindings for keystone static resources
5 not covered * Can be included before other middleware (e.g. session management, logging, etc) for
6 not covered * reduced overhead
7 not covered *
8 not covered * @param {Express()} app
9 not covered * @api public
10 not covered */
11 not covered
12 not covered function static(app) {
13 not covered
140 not covered app.use('/keystone', require('less-middleware')(__dirname + path.sep + '..' + path.sep + '..' + path.sep + 'public'));
150 not covered app.use('/keystone', express.static(__dirname + path.sep + '..' + path.sep + '..' + path.sep + 'public'));
16 not covered
170 not covered return this;
18 not covered
19 not covered }
20 not covered
211100%module.exports = static;

lib/core/importer.js

0% block coverage
20 SLOC
LineHitsStatementsSourceAction
11100%var fs = require('fs'), path = require('path');
2 not covered
3 not covered /**
4 not covered * Returns a function that looks in a specified path relative to the current
5 not covered * directory, and returns all .js modules it (recursively).
6 not covered *
7 not covered * ####Example:
8 not covered *
9 not covered * var importRoutes = keystone.importer(__dirname);
10 not covered *
11 not covered * var routes = {
12 not covered * site: importRoutes('./site'),
13 not covered * api: importRoutes('./api')
14 not covered * };
15 not covered *
16 not covered * @param {String} rel__dirname
17 not covered * @api public
18 not covered */
19 not covered
20 not covered function dispatchImporter(rel__dirname) {
21 not covered
22 not covered function importer(from) {
230 not covered var imported = {};
240 not covered var joinPath = function() {
250 not covered return '.' + path.sep + path.join.apply(path, arguments);
26 not covered };
270 not covered var fsPath = joinPath(path.relative(process.cwd(), rel__dirname), from);
280 not covered fs.readdirSync(fsPath).forEach(function(name) {
290 not covered var info = fs.statSync(path.join(fsPath, name));
30 not covered // recur
310 not covered if (info.isDirectory()) {
320 not covered imported[name] = importer(joinPath(from, name));
33 not covered } else {
34 not covered // only import .js files
350 not covered var parts = name.split('.');
360 not covered var ext = parts.pop();
370 not covered if (ext === 'js' || ext === 'coffee') {
380 not covered imported[parts.join('-')] = require(path.join(rel__dirname, from, name));
39 not covered }
400 not covered return imported;
41 not covered });
420 not covered return imported;
43 not covered }
44 not covered
450 not covered return importer;
46 not covered
47 not covered }
48 not covered
491100%module.exports = dispatchImporter;

lib/core/createItems.js

0% block coverage
150 SLOC
LineHitsStatementsSourceAction
1 not covered /**
2 not covered * Creates multiple items in one or more Lists
3 not covered */
4 not covered
51100%var _ = require('underscore'),
61100% async = require('async'),
71100% utils = require('keystone-utils');
8 not covered
9 not covered function createItems(data, ops, callback) {
10 not covered
110 not covered var keystone = this;
12 not covered
130 not covered var options = {
14 not covered
150 not covered var dashes = '------------------------------------------------';
16 not covered
170 not covered if (!_.isObject(data)) {
180 not covered throw new Error('keystone.createItems() requires a data object as the first argument.');
19 not covered }
20 not covered
210 not covered if (_.isObject(ops)) {
220 not covered _.extend(options, ops);
23 not covered }
24 not covered
250 not covered if (_.isFunction(ops)) {
260 not covered callback = ops;
27 not covered }
28 not covered
290 not covered var lists = _.keys(data),
30 not covered
31 not covered // logger function
32 not covered function writeLog(data) {
330 not covered console.log(keystone.get('name') + ": " + data);
34 not covered }
35 not covered
360 not covered async.waterfall([
370 not covered async.eachSeries(lists, function(key, doneList) {
380 not covered var list = keystone.list(key),
39 not covered
400 not covered if (!list) {
41 not covered if (options.strict) {
420 not covered return doneList({
43 not covered type: 'invalid list',
440 not covered message: 'List key ' + key + ' is invalid.'
45 not covered });
46 not covered }
470 not covered writeLog('Skipping invalid list: ' + key);
48 not covered }
490 not covered return doneList();
50 not covered }
510 not covered refs[list.key] = {};
520 not covered stats[list.key] = {
53 not covered singular: list.singular,
540 not covered var itemsProcessed = 0,
55 not covered
560 not covered writeLog(dashes);
570 not covered writeLog('Processing list: ' + key);
580 not covered writeLog('Items to create: ' + totalItems);
590 not covered writeLog(dashes);
60 not covered }
610 not covered async.eachSeries(data[key], function(data, doneItem) {
620 not covered itemsProcessed++;
63 not covered
64 not covered // Evaluate function properties to allow generated values (excluding relationships)
650 not covered _.keys(data).forEach(function(i) {
660 not covered if (_.isFunction(data[i]) && relationshipPaths.indexOf(i) === -1) {
670 not covered data[i] = data[i]();
68 not covered if (options.verbose) {
690 not covered writeLog('Generated dynamic value for [' + i + ']: ' + data[i]);
70 not covered }
71 not covered }
72 not covered
730 not covered var doc = data.__doc = new list.model();
74 not covered
750 not covered refs[list.key][data.__ref] = doc;
76 not covered }
770 not covered _.each(list.fields, function(field) {
780 not covered if (field.type !== 'relationship') {
790 not covered field.updateItem(doc, data);
80 not covered }
81 not covered
820 not covered var documentName = list.getDocumentName(doc);
830 not covered writeLog('Creating item [' + itemsProcessed + ' of ' + totalItems + '] - ' + documentName);
84 not covered }
850 not covered doc.save(doneItem);
860 not covered stats[list.key].created++;
87 not covered
88 not covered
89 not covered },
90 not covered
910 not covered async.each(lists, function(key, doneList) {
920 not covered var list = keystone.list(key),
93 not covered
940 not covered if (!list || !relationships.length) {
950 not covered return doneList();
96 not covered }
97 not covered
980 not covered var itemsProcessed = 0,
99 not covered
1000 not covered writeLog(dashes);
1010 not covered writeLog('Processing relationships for: ' + key);
1020 not covered writeLog('Items to process: ' + totalItems);
1030 not covered writeLog(dashes);
104 not covered }
1050 not covered async.each(data[key], function(srcData, doneItem) {
1060 not covered var doc = srcData.__doc,
107 not covered
1080 not covered itemsProcessed++;
109 not covered
110 not covered if (options.verbose) {
1110 not covered var documentName = list.getDocumentName(doc);
1120 not covered writeLog('Processing item [' + itemsProcessed + ' of ' + totalItems + '] - ' + documentName);
113 not covered }
1140 not covered async.each(relationships, function(field, doneField) {
1150 not covered var fieldValue = null,
116 not covered
1170 not covered if ( !field.path ) {
1180 not covered writeLog("WARNING: Invalid relationship (undefined list path) [List: "+key+"]");
1190 not covered stats[list.key].warnings++;
1200 not covered return doneField();
121 not covered }
122 not covered else
1230 not covered fieldValue = srcData[field.path];
124 not covered
1250 not covered if ( !field.refList ) {
1260 not covered if ( fieldValue )
127 not covered {
1280 not covered writeLog("WARNING: Invalid relationship (undefined reference list) [list: "+key+"] [path: "+fieldValue+"]");
1290 not covered stats[list.key].warnings++;
130 not covered }
1310 not covered return doneField();
132 not covered }
1330 not covered if ( !field.refList.key ) {
1340 not covered writeLog("WARNING: Invalid relationship (undefined ref list key) [list: "+key+"] [field.refList: "+field.refList+"] [fieldValue: "+fieldValue+"]");
1350 not covered stats[list.key].warnings++;
1360 not covered return doneField();
137 not covered }
1380 not covered refsLookup = refs[field.refList.key];
139 not covered
1400 not covered if (!fieldValue) {
1410 not covered return doneField();
142 not covered }
1430 not covered if (_.isFunction(fieldValue)) {
144 not covered
1450 not covered relationshipsUpdated++;
146 not covered
1470 not covered var fn = fieldValue,
1480 not covered lists = fn.toString().match(argsRegExp)[1].split(',').map(function(i) { return i.trim(); }),
1490 not covered args = lists.map(function(i) {
1500 not covered return keystone.list(i);
151 not covered }),
1520 not covered query = fn.apply(keystone, args);
153 not covered
1540 not covered query.exec(function(err, results) {
1550 not covered doc.set(field.path, results || []);
156 not covered } else {
1570 not covered doc.set(field.path, (results && results.length) ? results[0] : undefined);
158 not covered }
1590 not covered doneField(err);
160 not covered });
161 not covered
1620 not covered } else if (_.isArray(fieldValue)) {
163 not covered
1640 not covered var refsArr = _.compact(fieldValue.map(function(ref) {
1650 not covered return refsLookup[ref] ? refsLookup[ref].id : undefined;
166 not covered }));
167 not covered
1680 not covered if (options.strict && refsArr.length !== fieldValue.length) {
1690 not covered return doneField({
170 not covered type: 'invalid ref',
1710 not covered message: 'Relationship ' + list.key + '.' + field.path + ' contains an invalid reference.'
172 not covered });
1730 not covered relationshipsUpdated++;
1740 not covered doc.set(field.path, refsArr);
1750 not covered doneField();
176 not covered
1770 not covered return doneField({
1780 not covered message: 'Single-value relationship ' + list.key + '.' + field.path + ' provided as an array.'
179 not covered });
180 not covered }
1810 not covered } else if (_.isString(fieldValue)) {
182 not covered
1830 not covered var refItem = refsLookup[fieldValue];
184 not covered
1850 not covered if (!refItem) {
1860 not covered return options.strict ? doneField({
187 not covered type: 'invalid ref',
1880 not covered message: 'Relationship ' + list.key + '.' + field.path + ' contains an invalid reference: "' + fieldValue + '".'
1890 not covered }) : doneField();
190 not covered }
1910 not covered relationshipsUpdated++;
192 not covered
1930 not covered doc.set(field.path, field.many ? [refItem.id] : refItem.id);
194 not covered
1950 not covered doneField();
196 not covered
1970 not covered return doneField({
1980 not covered message: 'Relationship ' + list.key + '.' + field.path + ' contains an invalid data type.'
199 not covered });
200 not covered }
2010 not covered if (err) {
2020 not covered return doneItem(err);
203 not covered }
2040 not covered writeLog('Populated ' + utils.plural(relationshipsUpdated, '* relationship', '* relationships') + '.');
205 not covered }
2060 not covered if (relationshipsUpdated) {
2070 not covered doc.save(doneItem);
208 not covered } else {
2090 not covered doneItem();
210 not covered }
211 not covered
212 not covered
213 not covered }
2140 not covered if (err) {
2150 not covered return callback && callback(err);
216 not covered }
2170 not covered var msg = '\nSuccessfully created:\n';
2180 not covered _.each(stats, function(list) {
2190 not covered msg += '\n* ' + utils.plural(list.created, '* ' + list.singular, '* ' + list.plural);
220 not covered if (list.warnings) {
2210 not covered msg += '\n ' + utils.plural(list.warnings, '* warning', '* warnings');
222 not covered }
223 not covered });
2240 not covered stats.message = msg + '\n';
225 not covered
2260 not covered callback(null, stats);
227 not covered
228 not covered
229 not covered }
230 not covered
2311100%module.exports = createItems;

lib/core/redirect.js

0% block coverage
8 SLOC
LineHitsStatementsSourceAction
11100%var _ = require('underscore'), utils = require('keystone-utils');
2 not covered
3 not covered /**
4 not covered * Adds one or more redirections (urls that are redirected when no matching
5 not covered * routes are found, before treating the request as a 404)
6 not covered *
7 not covered * #### Example:
8 not covered * keystone.redirect('/old-route', 'new-route');
9 not covered *
10 not covered * // or
11 not covered *
12 not covered * keystone.redirect({
13 not covered * 'old-route': 'new-route'
14 not covered * });
15 not covered */
16 not covered
17 not covered function redirect() {
18 not covered
190 not covered if (arguments.length === 1 && utils.isObject(arguments[0])) {
200 not covered _.extend(this._redirects, arguments[0]);
210 not covered } else if (arguments.length === 2 && 'string' === typeof arguments[0] && 'string' === typeof arguments[1]) {
220 not covered this._redirects[arguments[0]] = arguments[1];
23 not covered }
24 not covered
250 not covered return this;
26 not covered
27 not covered }
28 not covered
291100%module.exports = redirect;

lib/core/list.js

100% block coverage
6 SLOC
LineHitsStatementsSourceAction
1 not covered /**
2 not covered * Registers or retrieves a list
3 not covered */
4 not covered
5 not covered function list(arg) {
612100% if (arg && arg.constructor === this.List) {
712100% this.lists[arg.key] = arg;
812100% this.paths[arg.path] = arg.key;
912100% return arg;
10 not covered }
110 not covered return this.lists[arg] || this.lists[this.paths[arg]];
12 not covered }
13 not covered
141100%module.exports = list;

lib/core/getOrphanedLists.js

0% block coverage
8 SLOC
LineHitsStatementsSourceAction
11100%var _ = require('underscore');
2 not covered
3 not covered /**
4 not covered * Retrieves orphaned lists (those not in a nav section)
5 not covered */
6 not covered
7 not covered function getOrphanedLists() {
80 not covered if (!this.nav) {
90 not covered return [];
10 not covered }
110 not covered return _.filter(this.lists, function(list, key) {
120 not covered if (list.get('hidden')) return false;
130 not covered return (!this.nav.by.list[key]) ? list : false;
14 not covered }.bind(this));
15 not covered }
16 not covered
171100%module.exports = getOrphanedLists;

lib/core/bindEmailTestRoutes.js

0% block coverage
24 SLOC
LineHitsStatementsSourceAction
11100%var _ = require('underscore');
2 not covered
3 not covered function bindEmailTestRoutes(app, emails) {
4 not covered
50 not covered var keystone = this;
6 not covered
70 not covered var handleError = function(req, res, err) {
80 not covered res.err(err);
9 not covered } else {
100 not covered res.status(500).send(JSON.stringify(err));
11 not covered }
12 not covered
13 not covered // TODO: Index of email tests, and custom email test 404's (currently bounces to list 404)
14 not covered
150 not covered _.each(emails, function(vars, key) {
160 not covered var render = function(err, req, res, locals) {
170 not covered new keystone.Email(key).render(locals, function(err, email) {
180 not covered if (err) {
190 not covered handleError(req, res, err);
20 not covered } else {
210 not covered res.send(email.html);
22 not covered }
23 not covered });
24 not covered };
25 not covered
260 not covered app.get('/keystone/test-email/' + key, function(req, res) {
270 not covered if ('function' === typeof vars) {
280 not covered vars(req, res, function(err, locals) {
290 not covered render(err, req, res, locals);
30 not covered });
31 not covered } else {
320 not covered render(null, req, res, vars);
33 not covered }
34 not covered });
35 not covered
36 not covered
370 not covered return this;
38 not covered
39 not covered }
40 not covered
411100%module.exports = bindEmailTestRoutes;

lib/core/wrapHTMLError.js

0% block coverage
5 SLOC
LineHitsStatementsSourceAction
1 not covered /**
2 not covered * Wraps an error in simple HTML to be sent as a response to the browser
3 not covered *
4 not covered * @api public
5 not covered */
6 not covered
7 not covered function wrapHTMLError(title, err) {
8 not covered return '<html><head><meta charset=\'utf-8\'><title>Error</title>' +
90 not covered '<link rel=\'stylesheet\' href=\'/keystone/styles/error.css\'>' +
100 not covered '</head><body><div class=\'error\'><h1 class=\'error-title\'>' + title + '</h1>' +
110 not covered '<div class="error-message">' + (err || '') + '</div></div></body></html>';
12 not covered }
13 not covered
141100%module.exports = wrapHTMLError;

lib/middleware/initAPI.js

12% block coverage
20 SLOC
LineHitsStatementsSourceAction
1 not covered /**
2 not covered * Middleware to initialise a standard API response.
3 not covered *
4 not covered * Adds `res.apiResonse` and `res.apiError` methods.
5 not covered *
6 not covered * ####Example:
7 not covered *
8 not covered * app.all('/api*', initAPI);
9 not covered *
10 not covered * @param {app.request} req
11 not covered * @param {app.response} res
12 not covered * @param {function} next
13 not covered * @api public
14 not covered */
15 not covered
16 not covered // The exported function returns a closure that retains
17 not covered // a reference to the keystone instance, so it can be
18 not covered // passed as middeware to the express app.
19 not covered
201100%exports = module.exports = function(keystone) {
211100% return function initAPI(req, res, next) {
220 not covered res.apiResponse = function(status) {
230 not covered res.jsonp(status);
24 not covered else
250 not covered res.json(status);
26 not covered };
27 not covered
280 not covered res.apiError = function(key, err, msg, code) {
290 not covered msg = msg || 'Error';
300 not covered key = key || 'unknown error';
310 not covered msg += ' (' + key + ')';
320 not covered if (keystone.get('logger')) {
330 not covered console.log(msg + (err ? ':' : ''));
340 not covered if (err) {
350 not covered console.log(err);
36 not covered }
370 not covered res.status(code || 500);
380 not covered res.apiResponse({ error: key || 'error', detail: err });
39 not covered };
40 not covered
410 not covered next();
42 not covered };
43 not covered };

lib/content/index.js

2% block coverage
81 SLOC
LineHitsStatementsSourceAction
11100%var _ = require('underscore'),
21100% keystone = require('../../'),
3 not covered utils = keystone.utils;
4 not covered
5 not covered /**
6 not covered * Content Class
7 not covered *
8 not covered * Accessed via `Keystone.content`
9 not covered *
10 not covered * @api public
11 not covered */
12 not covered
131100%var Content = function() {};
14 not covered
15 not covered /**
16 not covered * Loads page content by page key (optional).
17 not covered *
18 not covered * If page key is not provided, returns a hash of all page contents in the database.
19 not covered *
20 not covered * ####Example:
21 not covered *
22 not covered * keystone.content.fetch('home', function(err, content) { ... });
23 not covered *
24 not covered * @param {String} key (optional)
25 not covered * @param {Function} callback
26 not covered * @api public
27 not covered */
28 not covered
291100%Content.prototype.fetch = function(page, callback) {
300 not covered if (utils.isFunction(page)) {
310 not covered callback = page;
320 not covered page = null;
33 not covered }
340 not covered var content = this;
35 not covered
360 not covered if (!this.AppContent) {
370 not covered return callback({ error: 'invalid page', message: 'No pages have been registered.' });
38 not covered }
390 not covered if (page) {
40 not covered
410 not covered if (!this.pages[page]) {
420 not covered return callback({ error: 'invalid page', message: 'The page ' + page + ' does not exist.' });
43 not covered }
440 not covered this.AppContent.findOne({ key: page }, function(err, result) {
450 not covered if (err) return callback(err);
46 not covered
470 not covered return callback(null, content.pages[page].populate(result ? result.content.data : {}));
48 not covered
49 not covered });
50 not covered
51 not covered } else {
52 not covered
530 not covered this.AppContent.find(function(err, results) {
540 not covered if (err) return callback(err);
55 not covered
560 not covered var data = {};
57 not covered
580 not covered results.forEach(function(i) {
59 not covered if (content.pages[i.key]) {
600 not covered data[i.key] = content.pages[i.key].populate(i.content.data);
61 not covered }
62 not covered });
63 not covered
640 not covered _.each(content.pages, function(i) {
650 not covered if (!data[i.key]) {
660 not covered data[i.key] = i.populate();
67 not covered }
68 not covered
690 not covered return data;
70 not covered
71 not covered
72 not covered
73 not covered /**
74 not covered * Sets page content by page key.
75 not covered *
76 not covered * Merges content with existing content.
77 not covered *
78 not covered * ####Example:
79 not covered *
80 not covered * keystone.content.store('home', { title: 'Welcome' }, function(err) { ... });
81 not covered *
82 not covered * @param {String} key
83 not covered * @param {Object} content
84 not covered * @param {Function} callback
85 not covered * @api public
86 not covered */
87 not covered
881100%Content.prototype.store = function(page, content, callback) {
890 not covered if (!this.pages[page]) {
900 not covered return callback({ error: 'invalid page', message: 'The page ' + page + ' does not exist.' });
91 not covered }
920 not covered content = this.pages[page].validate(content);
93 not covered
94 not covered // TODO: Handle validation errors
95 not covered
960 not covered this.AppContent.findOne({ key: page }, function(err, doc) {
970 not covered if (err) return callback(err);
98 not covered
990 not covered if (doc) {
100 not covered if (doc.content) {
1010 not covered doc.history.push(doc.content);
102 not covered }
1030 not covered _.defaults(content, doc.content);
104 not covered } else {
1050 not covered doc = new content.AppContent({ key: page });
106 not covered }
107 not covered
1080 not covered doc.content = { data: this.pages[page].clean(content) };
1090 not covered doc.lastChangeDate = Date.now();
110 not covered
1110 not covered doc.save(callback);
112 not covered
113 not covered
114 not covered
115 not covered /**
116 not covered * Registers a page. Should not be called directly, use Page.register() instead.
117 not covered *
118 not covered * @param {Page} page
119 not covered * @api private
120 not covered */
121 not covered
1221100%Content.prototype.page = function(key, page) {
1230 not covered if (!this.pages) {
1240 not covered this.pages = {};
125 not covered }
1260 not covered if (arguments.length === 1) {
127 not covered
1280 not covered if (!this.pages[key]) {
1290 not covered throw new Error('keystone.content.page() Error: page ' + key + ' cannot be registered more than once.');
130 not covered }
1310 not covered return this.pages[key];
132 not covered
133 not covered }
134 not covered
1350 not covered this.initModel();
136 not covered
137 not covered if (this.pages[key]) {
1380 not covered throw new Error('keystone.content.page() Error: page ' + key + ' cannot be registered more than once.');
139 not covered }
1400 not covered this.pages[key] = page;
141 not covered
1420 not covered return page;
143 not covered
144 not covered
145 not covered /**
146 not covered * Ensures the Mongoose model for storing content is initialised.
147 not covered *
148 not covered * Called automatically when pages are added.
149 not covered *
150 not covered * @api private
151 not covered */
152 not covered
1531100%Content.prototype.initModel = function() {
1540 not covered if (this.AppContent) return;
155 not covered
1560 not covered var contentSchemaDef = {
157 not covered
1580 not covered var ContentSchema = new keystone.mongoose.Schema(contentSchemaDef);
159 not covered
1600 not covered var PageSchema = new keystone.mongoose.Schema({
161 not covered
1620 not covered this.AppContent = keystone.mongoose.model('App_Content', PageSchema);
163 not covered
164 not covered
165 not covered /**
166 not covered * Outputs client-side editable data for content management
167 not covered *
168 not covered * Called automatically when pages are added.
169 not covered *
170 not covered * @api private
171 not covered */
172 not covered
1731100%Content.prototype.editable = function(user, options) {
1740 not covered if (!user || !user.canAccessKeystone) {
1750 not covered return undefined;
176 not covered }
1770 not covered var list = keystone.list(options.list);
178 not covered
1790 not covered if (!list) {
1800 not covered return JSON.stringify({ type: 'error', err: 'list not found' });
181 not covered }
1820 not covered var data = {
183 not covered
184 not covered if (options.id) {
1850 not covered data.id = options.id;
186 not covered }
1870 not covered return JSON.stringify(data);
188 not covered
189 not covered
190 not covered
191 not covered /**
192 not covered * The exports object is an instance of Content.
193 not covered *
194 not covered * @api public
195 not covered */
196 not covered
1971100%module.exports = exports = new Content();
198 not covered
199 not covered // Expose Classes
2001100%exports.Page = require('./page');
2011100%exports.Types = require('./types');

lib/content/page.js

0% block coverage
55 SLOC
LineHitsStatementsSourceAction
11100%var _ = require('underscore'),
21100% keystone = require('../../'),
3 not covered utils = keystone.utils,
41100% Type = require('./type');
5 not covered
6 not covered /**
7 not covered * Page Class
8 not covered *
9 not covered * @param {String} key
10 not covered * @param {Object} options
11 not covered * @api public
12 not covered */
13 not covered
14 not covered function Page(key, options) {
15 not covered
160 not covered if (!(this instanceof Page))
170 not covered return new Page(key, options);
18 not covered
190 not covered var page = this;
20 not covered
210 not covered this.options = utils.options({
22 not covered
230 not covered this.key = key;
240 not covered this.fields = {};
25 not covered
26 not covered }
27 not covered
281100%Object.defineProperty(Page.prototype, 'name', { get: function() {
290 not covered return this.get('name') || this.set('name', utils.keyToLabel(this.key));
30 not covered }});
31 not covered
32 not covered /**
33 not covered * Sets page options
34 not covered *
35 not covered * ####Example:
36 not covered *
37 not covered * page.set('test', value) // sets the 'test' option to `value`
38 not covered *
39 not covered * @param {String} key
40 not covered * @param {String} value
41 not covered * @api public
42 not covered */
43 not covered
441100%Page.prototype.set = function(key, value) {
450 not covered if (arguments.length === 1) {
460 not covered return this.options[key];
47 not covered }
480 not covered this.options[key] = value;
490 not covered return value;
50 not covered
51 not covered
52 not covered
53 not covered /**
54 not covered * Gets page options
55 not covered *
56 not covered * ####Example:
57 not covered *
58 not covered * page.get('test') // returns the 'test' value
59 not covered *
60 not covered * @param {String} key
61 not covered * @method get
62 not covered * @api public
63 not covered */
64 not covered
651100%Page.prototype.get = Page.prototype.set;
66 not covered
67 not covered /**
68 not covered * Adds one or more fields to the page
69 not covered *
70 not covered * @api public
71 not covered */
72 not covered
731100%Page.prototype.add = function(fields) { // TODO: nested paths
740 not covered if (!utils.isObject(fields)) {
750 not covered throw new Error('keystone.content.Page.add() Error: fields must be an object.');
76 not covered }
770 not covered _.each(fields, function(options, path) {
780 not covered if ('function' === typeof options) {
790 not covered options = { type: options };
80 not covered }
81 not covered
820 not covered if ('function' !== typeof options.type) {
830 not covered throw new Error('Page fields must be specified with a type function');
84 not covered }
850 not covered if (options.type.prototype.__proto__ !== Type.prototype) {
86 not covered
870 not covered if (options.type === String)
880 not covered options.type = keystone.content.Types.Text;
89 not covered
900 not covered throw new Error('Unrecognised field constructor: ' + options.type);
91 not covered
920 not covered this.fields[path] = new options.type(path, options);
93 not covered
94 not covered
950 not covered return this;
96 not covered
97 not covered
98 not covered /**
99 not covered * Registers the page with Keystone.
100 not covered *
101 not covered * ####Example:
102 not covered *
103 not covered * var homePage = new keystone.content.Page('home');
104 not covered * // ...
105 not covered * homePage.register();
106 not covered *
107 not covered * // later...
108 not covered * var homePage = keystone.content.page('home');
109 not covered *
110 not covered * @api public
111 not covered */
112 not covered
1131100%Page.prototype.register = function() {
1140 not covered return keystone.content.page(this.key, this);
115 not covered };
116 not covered
117 not covered /**
118 not covered * Populates a data structure based on defined fields
119 not covered *
120 not covered * @api public
121 not covered */
122 not covered
1231100%Page.prototype.populate = function(data) {
1240 not covered if (!utils.isObject(data)) {
1250 not covered data = {};
126 not covered }
1270 not covered return data;
128 not covered
129 not covered
130 not covered /**
131 not covered * Validates a data structure based on defined fields
132 not covered *
133 not covered * @api public
134 not covered */
135 not covered
1361100%Page.prototype.validate = function(data) {
1370 not covered if (!_.isObject(data)) {
1380 not covered data = {};
139 not covered }
1400 not covered return data;
141 not covered
142 not covered
143 not covered /**
144 not covered * Cleans a data structure so only the defined fields are present
145 not covered *
146 not covered * @api public
147 not covered */
148 not covered
1491100%Page.prototype.clean = function(data) {
1500 not covered if (!_.isObject(data)) {
1510 not covered data = {};
152 not covered }
1530 not covered return data;
154 not covered
155 not covered
156 not covered
157 not covered /*!
158 not covered * Export class
159 not covered */
160 not covered
1611100%exports = module.exports = Page;

lib/content/type.js

100% line coverage
100% statement coverage
0% block coverage
6 SLOC
LineHitsStatementsSourceAction
1 not covered /*!
2 not covered * Module dependencies.
3 not covered */
4 not covered
51100%var _ = require('underscore'),
61100% keystone = require('../../'),
7 not covered utils = keystone.utils;
8 not covered
9 not covered /**
10 not covered * Type Class
11 not covered * @api private
12 not covered */
13 not covered
141100%var Type = function(path, options) { // TODO };
15 not covered
16 not covered
17 not covered /*!
18 not covered * Export class
19 not covered */
20 not covered
211100%module.exports = exports = Type;

lib/content/types/index.js

100% line coverage
100% statement coverage
0% block coverage
2 SLOC
LineHitsStatementsSourceAction
11100%exports.Text = require('./text');
21100%exports.Html = require('./html');

lib/content/types/text.js

0% block coverage
5 SLOC
LineHitsStatementsSourceAction
1 not covered /*!
2 not covered * Module dependencies.
3 not covered */
4 not covered
51100%var util = require('util'), super_ = require('../type');
6 not covered
7 not covered /**
8 not covered * Text ContentType Constructor
9 not covered * @extends Field
10 not covered * @api public
11 not covered */
12 not covered
13 not covered function text(path, options) {
140 not covered text.super_.call(path, options);
15 not covered }
16 not covered
17 not covered /*!
18 not covered * Inherit from Type
19 not covered */
20 not covered
211100%util.inherits(text, super_);
22 not covered
23 not covered
24 not covered /*!
25 not covered * Export class
26 not covered */
27 not covered
281100%module.exports = exports = text;

lib/content/types/html.js

0% block coverage
5 SLOC
LineHitsStatementsSourceAction
1 not covered /*!
2 not covered * Module dependencies.
3 not covered */
4 not covered
51100%var util = require('util'), super_ = require('../type');
6 not covered
7 not covered /**
8 not covered * HTML ContentType Constructor
9 not covered * @extends Field
10 not covered * @api public
11 not covered */
12 not covered
13 not covered function html(path, options) {
140 not covered html.super_.call(path, options);
15 not covered }
16 not covered
17 not covered /*!
18 not covered * Inherit from Type
19 not covered */
20 not covered
211100%util.inherits(html, super_);
22 not covered
23 not covered
24 not covered /*!
25 not covered * Export class
26 not covered */
27 not covered
281100%module.exports = exports = html;

lib/list.js

18% block coverage
504 SLOC
LineHitsStatementsSourceAction
11100%var _ = require('underscore'),
21100% async = require('async'),
31100% moment = require('moment'),
41100% keystone = require('../'),
51100% schemaPlugins = require('./schemaPlugins'),
61100% utils = require('keystone-utils'),
71100% Field = require('./field'),
81100% UpdateHandler = require('./updateHandler'),
91100% queryfilterlib = require('queryfilter');
10 not covered
11 not covered
12 not covered /**
13 not covered * List Class
14 not covered *
15 not covered * @param {String} key
16 not covered * @param {Object} options
17 not covered * @api public
18 not covered */
19 not covered
20 not covered function List(key, options) {
2130100% if (!(this instanceof List)) return new List(key, options);
22 not covered
2315100% this.options = utils.options({ schema: {
2415100% collection: keystone.prefixModel(key)
25 not covered },
26 not covered noedit: false,
27 not covered
2815100% this.key = key;
2915100% this.path = this.get('path') || utils.keyToPath(key, true);
3015100% this.schema = new keystone.mongoose.Schema({}, this.options.schema);
3115100% this.uiElements = [];
3215100% this.underscoreMethods = {};
3315100% this.fields = {};
3415100% this.fieldTypes = {};
3515100% this.relationships = {};
36 not covered
3715100% this.mappings = { name: null, createdBy: null, createdOn: null, modifiedBy: null, modifiedOn: null };
38 not covered
39 not covered // set mappings
401550% _.each(this.options.map, function(val, key) { not covered this.map(key, val);}, this);
41 not covered
4215100% Object.defineProperty(this, 'label', { get: function() {
430 not covered return this.get('label') || this.set('label', utils.plural(utils.keyToLabel(key)));
44 not covered }});
45 not covered
4615100% Object.defineProperty(this, 'singular', { get: function() {
470 not covered return this.get('singular') || this.set('singular', utils.singular(this.label));
48 not covered }});
49 not covered
5015100% Object.defineProperty(this, 'plural', { get: function() {
510 not covered return this.get('plural') || this.set('plural', utils.plural(this.singular));
52 not covered }});
53 not covered
5415100% Object.defineProperty(this, 'namePath', { get: function() {
550 not covered return this.mappings.name || '_id';
56 not covered }});
57 not covered
5815100% Object.defineProperty(this, 'nameField', { get: function() {
590 not covered return this.fields[this.mappings.name];
60 not covered }});
61 not covered
6215100% Object.defineProperty(this, 'nameIsVirtual', { get: function() {
630 not covered return this.model.schema.virtuals[this.mappings.name] ? true : false;
64 not covered }});
65 not covered
6615100% Object.defineProperty(this, 'nameIsEditable', { get: function() {
670 not covered return (this.fields[this.mappings.name] && this.fields[this.mappings.name].type === 'text') ? !this.fields[this.mappings.name].noedit : false;
68 not covered }});
69 not covered
7015100% Object.defineProperty(this, 'nameIsInitial', { get: function() {
710 not covered return (this.fields[this.mappings.name] && this.fields[this.mappings.name].options.initial === undefined);
72 not covered }});
73 not covered
7415100% var initialFields; // initialFields values are initialised once, on demand
7515100% Object.defineProperty(this, 'initialFields', { get: function() {
760 not covered return initialFields || (initialFields = _.filter(this.fields, function(i) { return i.initial; }));
77 not covered }});
78 not covered
79 not covered }
80 not covered
81 not covered
82 not covered /**
83 not covered * Sets list options
84 not covered *
85 not covered * ####Example:
86 not covered *
87 not covered * list.set('test', value) // sets the 'test' option to `value`
88 not covered *
89 not covered * @param {String} key
90 not covered * @param {String} value
91 not covered * @api public
92 not covered */
93 not covered
941100%List.prototype.set = function(key, value) {
95198100% if (arguments.length === 1) {
96194100% return this.options[key];
97 not covered }
984100% this.options[key] = value;
994100% return value;
100 not covered };
101 not covered
102 not covered
103 not covered /**
104 not covered * Gets list options
105 not covered *
106 not covered * ####Example:
107 not covered *
108 not covered * list.get('test') // returns the 'test' value
109 not covered *
110 not covered * @param {String} key
111 not covered * @method get
112 not covered * @api public
113 not covered */
114 not covered
1151100%List.prototype.get = List.prototype.set;
116 not covered
117 not covered
118 not covered /**
119 not covered * Maps a built-in field (e.g. name) to a specific path
120 not covered *
121 not covered * @api public
122 not covered */
123 not covered
1241100%List.prototype.map = function(field, path) {
12526100% if (path) {
12622100% this.mappings[field] = path;
127 not covered }
12826100% return this.mappings[field];
129 not covered };
130 not covered
131 not covered
132 not covered /**
133 not covered * Checks to see if a field path matches a currently unmapped path, and
134 not covered * if so, adds a mapping for it.
135 not covered *
136 not covered * @api private
137 not covered */
138 not covered
1391100%List.prototype.automap = function(field) {
14050100% if (_.has(this.mappings, field.path) && !this.mappings[field.path]) {
14118100% this.map(field.path, field.path);
142 not covered }
143 not covered
144 not covered
145 not covered /**
146 not covered * Adds one or more fields to the List
147 not covered * Based on Mongoose's Schema.add
148 not covered *
149 not covered * @api public
150 not covered */
151 not covered
1521100%List.prototype.add = function() {
15323100% var add = function(obj, prefix) {
15425100% prefix = prefix || '';
15525100% var keys = Object.keys(obj);
156 not covered
15777100% for (var i = 0; i < keys.length; ++i) {
15852100% var key = keys[i];
159 not covered
16052100% if (null === obj[key]) {
1610 not covered throw new Error('Invalid value for schema path `'+ prefix + key +'`');
162 not covered }
163 not covered
16452100% if (utils.isObject(obj[key]) && (!obj[key].constructor || 'Object' === obj[key].constructor.name) && (!obj[key].type || obj[key].type.type)) {
1652100% if (Object.keys(obj[key]).length) {
166 not covered // nested object, e.g. { last: { name: String }}
1672100% this.schema.nested[this.path] = true;
1682100% add(obj[key], prefix + key + '.');
169 not covered } else {
1700 not covered addField(prefix + key, obj[key]); // mixed type field
171 not covered }
17250100% addField(prefix + key, obj[key]);
173 not covered }
174 not covered
17523100% var addField = function(path, options) {
17650100% this.uiElements.push({ type: 'field',
17750100% field: this.field(path, options)
178 not covered });
179 not covered }.bind(this);
180 not covered
18123100% _.each(arguments, function(def) {
18223100% if ('string' === typeof def) {
1830 not covered if (def === '>>>') {
1840 not covered this.uiElements.push({
185 not covered type: 'indent'
1860 not covered } else if (def === '<<<') {
1870 not covered this.uiElements.push({
188 not covered type: 'outdent'
189 not covered } else {
1900 not covered this.uiElements.push({
191 not covered }
1922333% if (def.heading && not covered 'string' === typeof def.heading {
1930 not covered this.uiElements.push({
194 not covered type: 'heading',
19523100% add(def);
196 not covered }
197 not covered
19823100% return this;
199 not covered
200 not covered
201 not covered
202 not covered /**
203 not covered * Adds preset patterns of fields and behaviours to the Schema
204 not covered *
205 not covered * @api public
206 not covered */
207 not covered
2081100%List.prototype.addPattern = function(pattern) {
2092100% var warningMsg =
210 not covered 'Use of List.addPattern("standard meta") is now deprecated and will be removed\n' +
2112100% 'in future versions of KeystoneJS. It has been replaced with the List "track" option.\n\n' +
2122100% 'See http://keystonejs.com/docs/database/#lists-options for more information.';
213 not covered
2142100% switch (pattern) {
215 not covered
216 not covered case 'standard meta':
217 not covered // enable track options if it's not already enabled
2182100% if (!this.get('track')) {
2192100% this.set('track', true);
220 not covered }
2212100% this.set('track simulate standard meta', true);
2222100% keystone.console.err('Deprecation Warning', warningMsg);
223 not covered break;
2242100% return this;
225 not covered
226 not covered
227 not covered
228 not covered /**
229 not covered * Creates a new field at the specified path, with the provided options.
230 not covered * If no options are provides, returns the field at the specified path.
231 not covered *
232 not covered * @api public
233 not covered */
234 not covered
2351100%List.prototype.field = function(path, options) {
236196100% if (arguments.length === 1) {
237146100% return this.fields[path];
238 not covered }
23950100% if ('function' === typeof options) {
2407100% options = { type: options };
241 not covered }
24250100% if (this.get('noedit')) {
2430 not covered options.noedit = true;
244 not covered }
24550100% if (!options.note && this.get('notes')) {
2460 not covered options.note = this.get('notes')[path];
247 not covered }
24850100% if ('function' !== typeof options.type) {
2490 not covered throw new Error('Fields must be specified with a type function');
250 not covered }
25150100% if (options.type.prototype.__proto__ !== Field.prototype) {
252 not covered
253 not covered // Convert native field types to their default Keystone counterpart
254 not covered
25530100% if (options.type === String)
25615100% options.type = Field.Types.Text;
25715100% else if (options.type === Number)
2580 not covered options.type = Field.Types.Number;
25915100% else if (options.type === Boolean)
2601100% options.type = Field.Types.Boolean;
26114100% else if (options.type === Date)
26214100% options.type = Field.Types.Datetime;
263 not covered else
2640 not covered throw new Error('Unrecognised field constructor: ' + options.type);
265 not covered
266 not covered }
267 not covered
268 not covered // Note the presence of this field type for client-side script optimisation
26950100% this.fieldTypes[options.type.name] = true;
270 not covered
271 not covered // Wysiwyg HTML fields are handled as a special case so we can include TinyMCE as required
27250100% if (options.type.name === 'html' && options.wysiwyg) {
2730 not covered this.fieldTypes.wysiwyg = true;
274 not covered }
27550100% var field = new options.type(this, path, options);
276 not covered
27750100% this.fields[path] = field;
27850100% return field;
279 not covered
280 not covered
281 not covered
282 not covered /**
283 not covered * Adds a method to the underscoreMethods collection on the list, which is then
284 not covered * added to the schema before the list is registered with mongoose.
285 not covered *
286 not covered * @api public
287 not covered */
288 not covered
2891100%List.prototype.underscoreMethod = function(path, fn) {
290136100% var target = this.underscoreMethods;
291136100% path = path.split('.');
292136100% var last = path.pop();
293 not covered
294136100% path.forEach(function(part) {
295149100% if (!target[part]) target[part] = {};
296149100% target = target[part];
297 not covered });
298 not covered
299136100% target[last] = fn;
300 not covered
301136100% return this;
302 not covered
303 not covered
304 not covered
305 not covered /**
306 not covered * Default Sort Field
307 not covered *
308 not covered * @api public
309 not covered */
310 not covered
3111100%Object.defineProperty(List.prototype, 'defaultSort', { get: function() {
3120 not covered var ds = this.get('defaultSort');
313 not covered
3140 not covered return (ds === '__default__') ? (this.get('sortable') ? 'sortOrder' : this.namePath) : ds;
315 not covered
316 not covered }, set: function(value) {
3170 not covered this.set('defaultSort', value);
318 not covered
319 not covered
320 not covered
321 not covered /**
322 not covered * Expands a comma-separated string or array of columns into valid column objects.
323 not covered *
324 not covered * Columns can be:
325 not covered * - A Field, in the format "field|width"
326 not covered * - A Field in a single related List, in the format "list:field|width"
327 not covered * - Any valid path in the Schema, in the format "path|width"
328 not covered *
329 not covered * The width part is optional, and can be in the format "n%" or "npx".
330 not covered *
331 not covered * The path __name__ is automatically mapped to the namePath of the List.
332 not covered *
333 not covered * The field or path for the name of the item (defaults to ID if not set or detected)
334 not covered * is automatically prepended if not explicitly included.
335 not covered *
336 not covered * @api private
337 not covered */
338 not covered
3391100%List.prototype.expandColumns = function(cols) {
3400 not covered if (typeof cols === 'string') {
3410 not covered cols = cols.split(',');
342 not covered }
3430 not covered if (!Array.isArray(cols)) {
3440 not covered throw new Error('List.expandColumns: cols must be an array.');
345 not covered }
3460 not covered var list = this,
347 not covered
3480 not covered var getCol = function(def) {
3490 not covered if (def.path === '__name__') {
3500 not covered def.path = list.namePath;
351 not covered }
3520 not covered var field = list.fields[def.path],
353 not covered
3540 not covered if (field) {
355 not covered
3560 not covered col = {
3570 not covered label: def.label || field.label
358 not covered };
359 not covered
3600 not covered if (col.type === 'relationship') {
361 not covered
3620 not covered col.refList = col.field.refList;
363 not covered
364 not covered if (col.refList) {
3650 not covered col.refPath = def.subpath || col.refList.namePath;
3660 not covered col.subField = col.refList.fields[col.refPath];
3670 not covered col.populate = { path: col.field.path, subpath: col.refPath };
368 not covered }
3690 not covered if (!def.label && def.subpath) {
3700 not covered col.label = field.label + ': ' + (col.subField ? col.subField.label : utils.keyToLabel(def.subpath));
371 not covered }
3720 not covered } else if (list.model.schema.paths[def.path] || list.model.schema.virtuals[def.path]) {
373 not covered // column refers to a path in the schema
3740 not covered col = {
3750 not covered label: def.label || utils.keyToLabel(def.path)
376 not covered };
377 not covered }
3780 not covered if (col) {
379 not covered
3800 not covered col.width = def.width;
381 not covered
3820 not covered if (col.path === list.namePath) {
3830 not covered col.isName = true;
3840 not covered nameCol = col;
385 not covered }
3860 not covered if (field && field.col) {
3870 not covered _.extend(col, field.col);
388 not covered }
3890 not covered return col;
390 not covered };
391 not covered
3920 not covered for (var i = 0; i < cols.length; i++) {
393 not covered
3940 not covered var def = {};
395 not covered
3960 not covered if (typeof cols[i] === 'string') {
397 not covered
3980 not covered var parts = cols[i].trim().split('|');
399 not covered
4000 not covered def.width = parts[1] || false;
401 not covered
4020 not covered parts = parts[0].split(':');
403 not covered
4040 not covered def.path = parts[0];
4050 not covered def.subpath = parts[1];
406 not covered
407 not covered }
408 not covered
4090 not covered if (!utils.isObject(def) || !def.path) {
4100 not covered throw new Error('List.expandColumns: column definition must contain a path.');
411 not covered }
4120 not covered var col = getCol(def);
413 not covered
4140 not covered if (col) {
4150 not covered expanded.push(col);
416 not covered }
4170 not covered if (!nameCol) {
4180 not covered nameCol = getCol({ path: list.namePath });
4190 not covered if (nameCol) {
4200 not covered expanded.unshift(nameCol);
421 not covered }
4220 not covered return expanded;
423 not covered
424 not covered
425 not covered
426 not covered /**
427 not covered * Specified select and populate options for a query based the provided columns.
428 not covered *
429 not covered * @param {Query} query
430 not covered * @param {Array} columns
431 not covered * @api private
432 not covered */
433 not covered
4341100%List.prototype.selectColumns = function(q, cols) { // Populate relationship columns
4350 not covered var select = [],
436 not covered
4370 not covered cols.forEach(function(col) {
4380 not covered select.push(col.path);
439 not covered if (col.populate) {
4400 not covered if (!populate[col.populate.path]) {
4410 not covered populate[col.populate.path] = [];
442 not covered }
4430 not covered populate[col.populate.path].push(col.populate.subpath);
444 not covered }
445 not covered });
446 not covered
4470 not covered q.select(select.join(' '));
448 not covered
449 not covered for (path in populate) {
4500 not covered if ( populate.hasOwnProperty(path) ) {
4510 not covered q.populate(path, populate[path].join(' '));
452 not covered }
453 not covered
454 not covered
455 not covered /**
456 not covered * Default Column Fields
457 not covered *
458 not covered * @api public
459 not covered */
460 not covered
4611100%Object.defineProperty(List.prototype, 'defaultColumns', { get: function() {
4620 not covered if (!this._defaultColumns) {
4630 not covered this._defaultColumns = this.expandColumns(this.get('defaultColumns'));
464 not covered }
465 not covered
4660 not covered return this._defaultColumns;
467 not covered
468 not covered }, set: function(value) {
469 not covered
4700 not covered this.set('defaultColumns', value);
4710 not covered delete this._defaultColumns;
472 not covered
473 not covered
474 not covered
475 not covered /**
476 not covered * Registers relationships to this list defined on others
477 not covered *
478 not covered * @api public
479 not covered */
480 not covered
4811100%List.prototype.relationship = function(def) {
4820 not covered if (arguments.length > 1) {
4830 not covered _.map(arguments, function(def) { this.relationship(def); }, this);
4840 not covered return this;
485 not covered }
4860 not covered if ('string' === typeof def) {
4870 not covered def = { ref: def };
488 not covered }
4890 not covered if (!def.ref) {
4900 not covered throw new Error('List Relationships must be specified with an object containing ref (' + this.key + ')');
491 not covered }
4920 not covered if (!def.refPath) {
4930 not covered def.refPath = utils.downcase(this.key);
494 not covered }
4950 not covered if (!def.path) {
4960 not covered def.path = utils.keyToProperty(def.ref, true);
497 not covered }
4980 not covered Object.defineProperty(def, 'refList', {
4990 not covered return keystone.list(def.ref);
500 not covered }
501 not covered
5020 not covered Object.defineProperty(def, 'isValid', {
5030 not covered return keystone.list(def.ref) ? true : false;
504 not covered }
505 not covered
5060 not covered this.relationships[def.path] = def;
507 not covered
5080 not covered return this;
509 not covered
510 not covered
511 not covered
512 not covered /**
513 not covered * Registers the Schema with Mongoose, and the List with Keystone
514 not covered *
515 not covered * Also adds default fields and virtuals to the schema for the list
516 not covered *
517 not covered * @api public
518 not covered */
519 not covered
5201100%List.prototype.register = function() {
52115100% var list = this;
522 not covered
52315100% this.schema.virtual('list').get(function () {
5240 not covered return list;
525 not covered });
526 not covered
52715100% if (this.get('sortable')) {
5280 not covered schemaPlugins.sortable.apply(this);
529 not covered }
53015100% if (this.get('autokey')) {
5310 not covered schemaPlugins.autokey.apply(this);
532 not covered }
53315100% if (this.get('track')) {
53412100% schemaPlugins.track.apply(this);
535 not covered }
53612100% if (!_.isEmpty(this.relationships)) {
5370 not covered this.schema.methods.getRelated = schemaPlugins.methods.getRelated;
5380 not covered this.schema.methods.populateRelated = schemaPlugins.methods.populateRelated;
5390 not covered if (!this.schema.options.toObject) this.schema.options.toObject = {};
5400 not covered this.schema.options.toObject.transform = schemaPlugins.options.transform;
541 not covered }
54212100% this.schema.virtual('_').get(function() {
5434100% if (!this.__methods) {
5441100% this.__methods = utils.bindMethods(list.underscoreMethods, this);
545 not covered }
5464100% return this.__methods;
547 not covered });
548 not covered
54912100% this.schema.method('getUpdateHandler', function(req, res, ops) {
5509100% return new UpdateHandler(list, this, req, res, ops);
551 not covered });
552 not covered
55312100% this.model = keystone.mongoose.model(this.key, this.schema);
554 not covered
55512100% require('../').list(this);
556 not covered
55712100% return this;
558 not covered };
559 not covered
560 not covered
561 not covered /**
562 not covered * Gets the name of the provided document from the correct path
563 not covered *
564 not covered * ####Example:
565 not covered *
566 not covered * var name = list.getDocumentName(item)
567 not covered *
568 not covered * @param {Object} item
569 not covered * @param {Boolean} escape - causes HTML entities to be encoded
570 not covered * @api public
571 not covered */
572 not covered
5731100%List.prototype.getDocumentName = function(doc, escape) {
5740 not covered var name = String(this.nameField ? this.nameField.format(doc) : doc.get(this.namePath));
5750 not covered return (escape) ? utils.encodeHTMLEntities(name) : name;
576 not covered };
577 not covered
578 not covered
579 not covered /**
580 not covered * Processes a filter string into a filters object
581 not covered *
582 not covered * @param {String} filters
583 not covered * @api private
584 not covered */
585 not covered
5861100%List.prototype.processFilters = function(q) {
5870 not covered var me = this;
5880 not covered var filters = {};
5890 not covered queryfilterlib.QueryFilters.create(q).getFilters().forEach(function(filter){
5900 not covered filter.path = filter.key; // alias for b/c
5910 not covered filter.field = me.fields[filter.key];
5920 not covered filters[filter.path] = filter;
593 not covered });
5940 not covered return filters;
595 not covered };
596 not covered
597 not covered
598 not covered /**
599 not covered * Gets filters for a Mongoose query that will search for the provided string,
600 not covered * based on the searchFields List option.
601 not covered *
602 not covered * Also accepts a filters object from `processFilters()`, any of which may
603 not covered * override the search string.
604 not covered *
605 not covered * ####Example:
606 not covered *
607 not covered * list.getSearchFilters('jed') // returns { name: /jed/i }
608 not covered *
609 not covered * @param {String} query
610 not covered * @param {Object} additional filters
611 not covered * @api public
612 not covered */
613 not covered
6141100%List.prototype.getSearchFilters = function(search, add) {
6150 not covered var filters = {},
616 not covered
6170 not covered search = String(search || '').trim();
618 not covered
619 not covered if (search.length) {
6200 not covered var searchFilter,
6210 not covered searchParts = search.split(' '),
6220 not covered searchRx = new RegExp(utils.escapeRegExp(search), 'i'),
6230 not covered splitSearchRx = new RegExp((searchParts.length > 1) ? _.map(searchParts, utils.escapeRegExp).join('|') : search, 'i'),
6240 not covered searchFields = this.get('searchFields'),
625 not covered searchFilters = [],
6260 not covered searchIdField = utils.isValidObjectId(search);
627 not covered
6280 not covered if ('string' === typeof searchFields) {
6290 not covered searchFields = searchFields.split(',');
630 not covered }
6310 not covered searchFields.forEach(function(path) {
6320 not covered path = path.trim();
633 not covered
6340 not covered if (path === '__name__') {
6350 not covered path = list.mappings.name;
636 not covered }
637 not covered
6380 not covered var field = list.fields[path];
639 not covered
6400 not covered if (field && field.type === 'name') {
641 not covered
6420 not covered var first = {};
6430 not covered first[field.paths.first] = splitSearchRx;
644 not covered
6450 not covered var last = {};
6460 not covered last[field.paths.last] = splitSearchRx;
647 not covered
6480 not covered searchFilter = {};
6490 not covered searchFilter.$or = [first, last];
6500 not covered searchFilters.push(searchFilter);
651 not covered
652 not covered } else {
653 not covered
6540 not covered searchFilter = {};
6550 not covered searchFilter[path] = searchRx;
6560 not covered searchFilters.push(searchFilter);
657 not covered }
658 not covered
659 not covered if (list.autokey) {
6600 not covered searchFilter = {};
6610 not covered searchFilter[list.autokey.path] = searchRx;
6620 not covered searchFilters.push(searchFilter);
663 not covered }
6640 not covered if (searchIdField) {
6650 not covered searchFilter = {};
6660 not covered searchFilter._id = search;
6670 not covered searchFilters.push(searchFilter);
668 not covered }
6690 not covered if (searchFilters.length > 1) {
6700 not covered filters.$or = searchFilters;
671 not covered } else if (searchFilters.length) {
6720 not covered filters = searchFilters[0];
673 not covered }
6740 not covered if (add) {
6750 not covered _.each(add, function(filter) {
6760 not covered var cond, path = filter.key, value = filter.value;
677 not covered
678 not covered switch (filter.field.type) {
679 not covered
680 not covered case 'boolean':
6810 not covered if (value) {
6820 not covered filters[path] = true;
683 not covered } else {
6840 not covered filters[path] = { $ne: true };
685 not covered }
6860 not covered _.each({address:'street1', suburb:'suburb', state:'state', postcode:'postcode', country:'country'}, function(pathKey, valueKey){
6870 not covered var value = filter[valueKey];
6880 not covered if ( value ) {
6890 not covered filters[filter.field.paths[pathKey]] = new RegExp(utils.escapeRegExp(value), 'i');
690 not covered }
691 not covered break;
6920 not covered if (value) {
693 not covered if (filter.field.many) {
6940 not covered filters[path] = (filter.inverse) ? { $nin: [value] } : { $in: [value] };
695 not covered } else {
6960 not covered filters[path] = (filter.inverse) ? { $ne: value } : value;
697 not covered }
6980 not covered filters[path] = (filter.inverse) ? { $not: { $size: 0 } } : { $size: 0 };
699 not covered } else {
7000 not covered filters[path] = (filter.inverse) ? { $ne: null } : null;
701 not covered }
7020 not covered filters[path] = (filter.inverse) ? { $ne: value } : value;
703 not covered } else {
7040 not covered filters[path] = (filter.inverse) ? { $nin: ['', null] } : { $in: ['', null] };
705 not covered }
7060 not covered if (filter.operator === 'bt') {
7070 not covered value = [
7080 not covered utils.number(value[0]),
7090 not covered utils.number(value[1])
710 not covered ];
7110 not covered if ( !isNaN(value[0]) && !isNaN(value[1]) ) {
7120 not covered filters[path] = {
713 not covered $gte: value[0],
7140 not covered filters[path] = null;
715 not covered }
7160 not covered value = utils.number(value);
7170 not covered if ( !isNaN(value) ) {
7180 not covered if (filter.operator === 'gt') {
7190 not covered filters[path] = { $gt: value};
720 not covered }
7210 not covered else if (filter.operator === 'lt') {
7220 not covered filters[path] = { $lt: value};
723 not covered }
7240 not covered filters[path] = value;
725 not covered }
7260 not covered filters[path] = null;
727 not covered }
7280 not covered if (filter.operator === 'bt') {
7290 not covered value = [
7300 not covered moment(value[0]),
7310 not covered moment(value[1])
732 not covered ];
7330 not covered if ( (value[0] && value[0].isValid()) && (value[1] && value[0].isValid()) ) {
7340 not covered filters[path] = {
7350 not covered $gte: moment(value[0]).startOf('day').toDate(),
7360 not covered $lte: moment(value[1]).endOf('day').toDate()
737 not covered };
738 not covered }
7390 not covered value = moment(value);
7400 not covered if (value && value.isValid()) {
7410 not covered var start = moment(value).startOf('day').toDate();
7420 not covered var end = moment(value).endOf('day').toDate();
7430 not covered if (filter.operator === 'gt') {
7440 not covered filters[path] = { $gt: end };
7450 not covered } else if (filter.operator === 'lt') {
7460 not covered filters[path] = { $lt: start };
747 not covered } else {
7480 not covered filters[path] = { $lte: end, $gte: start };
749 not covered }
7500 not covered if (value) {
7510 not covered cond = new RegExp('^' + utils.escapeRegExp(value) + '$', 'i');
7520 not covered filters[path] = filter.inverse ? { $not: cond } : cond;
753 not covered } else {
7540 not covered filters[path] = { $nin: ['', null] };
755 not covered } else {
7560 not covered filters[path] = { $in: ['', null] };
757 not covered }
7580 not covered } else if (value) {
7590 not covered cond = new RegExp(utils.escapeRegExp(value), 'i');
7600 not covered filters[path] = filter.inverse ? { $not: cond } : cond;
761 not covered }
762 not covered }
7630 not covered return filters;
764 not covered
765 not covered
766 not covered
767 not covered /**
768 not covered * Updates every document in a List,
769 not covered * setting the provided data on each.
770 not covered *
771 not covered * @param {Object} data
772 not covered * @param {Function} callback (optional)
773 not covered * @api public
774 not covered */
775 not covered
7761100%List.prototype.updateAll = function(data, callback) {
7770 not covered if ('function' === typeof data) {
7780 not covered callback = data;
7790 not covered data = null;
780 not covered }
7810 not covered callback = callback || function() {};
782 not covered
7830 not covered this.model.find(function(err, results) {
7840 not covered if (err) return callback(err);
785 not covered
7860 not covered async.eachSeries(results, function(doc, next) {
7870 not covered if (data) {
7880 not covered doc.set(data);
789 not covered }
790 not covered
7910 not covered doc.save(next);
792 not covered
793 not covered }, function(err) {
7940 not covered callback(err);
795 not covered });
796 not covered
797 not covered });
798 not covered
799 not covered
800 not covered
801 not covered /**
802 not covered * Gets a unique value from a generator method by checking for documents with the same value.
803 not covered *
804 not covered * To avoid infinite loops when a unique value cannot be found, it will bail and pass back an
805 not covered * undefined value after 10 attemptes.
806 not covered *
807 not covered * WARNING: Because there will always be a small amount of time between checking for an
808 not covered * existing value and saving a document, race conditions can occur and it is possible that
809 not covered * another document has the 'unique' value assigned at the same time.
810 not covered *
811 not covered * Because of this, if true uniqueness is required, you should also create a unique index on
812 not covered * the database path, and handle duplicate errors thrown on save.
813 not covered *
814 not covered * @param {String} path to check for uniqueness
815 not covered * @param {Function} generator method to call to generate a new value
816 not covered * @param {Number} the maximum number of attempts (optional, defaults to 10)
817 not covered * @param {Function} callback(err, uniqueValue)
818 not covered * @api public
819 not covered */
820 not covered
8211100%List.prototype.getUniqueValue = function(path, generator, limit, callback) {
8220 not covered var model = this.model,
823 not covered
8240 not covered if (utils.isFunction(limit)) {
8250 not covered callback = limit;
8260 not covered limit = 10;
827 not covered }
8280 not covered if (utils.isArray(generator)) {
829 not covered
8300 not covered var fn = generator[0],
831 not covered
8320 not covered generator = function() {
8330 not covered return fn.apply(this, args);
834 not covered };
835 not covered
836 not covered }
837 not covered
8380 not covered var check = function() {
8390 not covered if (count++ > 10) {
8400 not covered return callback(undefined, undefined);
841 not covered }
8420 not covered value = generator();
843 not covered
8440 not covered model.count().where(path, value).exec(function(err, matches) {
8450 not covered if (err) return callback(err);
8460 not covered if (matches) return check();
8470 not covered callback(undefined, value);
848 not covered });
849 not covered
850 not covered
8510 not covered check();
852 not covered
853 not covered
854 not covered /**
855 not covered * Generate page array for pagination
856 not covered *
857 not covered * @param {Number} the maximum number pages to display in the pagination
858 not covered * @param {Object} page options
859 not covered * @api public
860 not covered */
8611100%List.prototype.getPages = function(options, maxPages) {
8620 not covered var surround = Math.floor(maxPages / 2),
8630 not covered firstPage = maxPages ? Math.max(1, options.currentPage - surround) : 1,
8640 not covered padRight = Math.max(((options.currentPage - surround) - 1) * -1, 0),
8650 not covered lastPage = maxPages ? Math.min(options.totalPages, options.currentPage + surround + padRight) : options.totalPages,
8660 not covered padLeft = Math.max(((options.currentPage + surround) - lastPage), 0);
867 not covered
8680 not covered options.pages = [];
869 not covered
8700 not covered firstPage = Math.max(Math.min(firstPage, firstPage - padLeft), 1);
871 not covered
8720 not covered for (var i = firstPage; i <= lastPage; i++) {
8730 not covered options.pages.push(i);
874 not covered }
8750 not covered if (firstPage !== 1) {
8760 not covered options.pages.shift();
8770 not covered options.pages.unshift('...');
878 not covered }
8790 not covered if (lastPage !== Number(options.totalPages)) {
8800 not covered options.pages.pop();
8810 not covered options.pages.push('...');
882 not covered }
883 not covered
884 not covered /**
885 not covered * Gets a special Query object that will paginate documents in the list
886 not covered *
887 not covered * ####Example:
888 not covered *
889 not covered * list.paginate({
890 not covered * page: 1,
891 not covered * perPage: 100,
892 not covered * maxPages: 10
893 not covered * }).exec(function(err, results) {
894 not covered * // do something
895 not covered * });
896 not covered *
897 not covered * @param {Object} options
898 not covered * @param {Function} callback (optional)
899 not covered * @api public
900 not covered */
901 not covered
9021100%List.prototype.paginate = function(options, callback) {
9030 not covered var list = this, model = this.model;
904 not covered
9050 not covered options = options || {};
906 not covered
9070 not covered var query = model.find(options.filters);
908 not covered
9090 not covered query._original_exec = query.exec;
9100 not covered query._original_sort = query.sort;
9110 not covered query._original_select = query.select;
912 not covered
9130 not covered var currentPage = Number(options.page) || 1,
9140 not covered resultsPerPage = Number(options.perPage) || 50,
9150 not covered maxPages = Number(options.maxPages) || 10,
9160 not covered skip = (currentPage - 1) * resultsPerPage;
917 not covered
9180 not covered list.pagination = { maxPages: maxPages };
919 not covered
920 not covered // as of mongoose 3.7.x, we need to defer sorting and field selection
921 not covered // until after the count has been executed
922 not covered
9230 not covered query.select = function() {
9240 not covered options.select = arguments[0];
9250 not covered return query;
926 not covered };
927 not covered
9280 not covered query.sort = function() {
9290 not covered options.sort = arguments[0];
9300 not covered return query;
931 not covered };
932 not covered
9330 not covered query.exec = function(callback) {
9340 not covered query.count(function(err, count) {
9350 not covered if (err) return callback(err);
936 not covered
9370 not covered query.find().limit(resultsPerPage).skip(skip);
938 not covered
939 not covered // apply the select and sort options before calling exec
9400 not covered query._original_select(options.select);
941 not covered }
9420 not covered query._original_sort(options.sort);
943 not covered }
9440 not covered query._original_exec(function(err, results) {
9450 not covered if (err) return callback(err);
946 not covered
9470 not covered var totalPages = Math.ceil(count / resultsPerPage);
948 not covered
9490 not covered var rtn = {
950 not covered total: count,
9510 not covered previous: (currentPage > 1) ? (currentPage - 1) : false,
9520 not covered next: (currentPage < totalPages) ? (currentPage + 1) : false,
9530 not covered first: skip + 1,
9540 not covered last: skip + results.length
955 not covered };
956 not covered
9570 not covered list.getPages(rtn, maxPages);
958 not covered
9590 not covered callback(err, rtn);
960 not covered
961 not covered });
962 not covered
963 not covered
9640 not covered if (callback) {
9650 not covered return query(callback);
966 not covered } else {
9670 not covered return query;
968 not covered }
969 not covered
970 not covered
971 not covered /*!
972 not covered * Export class
973 not covered */
974 not covered
9751100%exports = module.exports = List;

lib/schemaPlugins.js

24% block coverage
222 SLOC
LineHitsStatementsSourceAction
11100%var keystone = require('../'),
21100% Types = require('./fieldTypes'),
31100% _ = require('underscore'),
41100% async = require('async'),
51100% utils = require('keystone-utils');
6 not covered
71100%var methods = module.exports.methods = {};
81100%var options = module.exports.options = {};
9 not covered
101100%exports.sortable = function() {
110 not covered var list = this;
12 not covered
130 not covered this.schema.add({
14 not covered
150 not covered this.schema.pre('save', function(next) {
160 not covered if (typeof this.sortOrder === 'number') {
170 not covered return next();
18 not covered }
19 not covered
200 not covered var item = this;
21 not covered
220 not covered list.model.findOne().sort('-sortOrder').exec(function(err, max) {
230 not covered item.sortOrder = (max && max.sortOrder) ? max.sortOrder + 1 : 0;
240 not covered next();
25 not covered });
26 not covered
27 not covered
28 not covered
291100%exports.autokey = function() {
300 not covered var autokey = this.autokey = this.get('autokey'),
31 not covered
320 not covered if (!autokey.from) {
330 not covered var fromMsg = 'Invalid List Option (autokey) for ' + list.key + ' (from is required)\n';
340 not covered throw new Error(fromMsg);
35 not covered }
360 not covered if (!autokey.path) {
370 not covered var pathMsg = 'Invalid List Option (autokey) for ' + list.key + ' (path is required)\n';
380 not covered throw new Error(pathMsg);
39 not covered }
400 not covered if ('string' === typeof autokey.from) {
410 not covered autokey.from = autokey.from.split(' ');
42 not covered }
430 not covered autokey.from = autokey.from.map(function(i) {
440 not covered i = i.split(':');
450 not covered return { path: i[0], format: i[1] };
46 not covered });
47 not covered
480 not covered def[autokey.path] = {
49 not covered
500 not covered def[autokey.path].index = { unique: true };
51 not covered }
520 not covered this.schema.add(def);
53 not covered
540 not covered var getUniqueKey = function(doc, src, callback) {
550 not covered var q = list.model.find().where(autokey.path, src);
56 not covered
570 not covered if (_.isObject(autokey.unique)) {
580 not covered _.each(autokey.unique, function(k, v) {
590 not covered if (_.isString(v) && v.charAt(0) === ':') {
600 not covered q.where(k, doc.get(v.substr(1)));
61 not covered } else {
620 not covered q.where(k, v);
63 not covered }
64 not covered });
650 not covered q.exec(function(err, results) {
660 not covered if (err) {
670 not covered callback(err);
680 not covered } else if (results.length && (results.length > 1 || results[0].id != doc.id)) {
690 not covered var inc = src.match(/^(.+)\-(\d+)$/);
700 not covered if (inc && inc.length === 3) {
710 not covered src = inc[1];
720 not covered inc = '-' + ((inc[2] * 1) + 1);
73 not covered } else {
740 not covered inc = '-1';
75 not covered }
760 not covered return getUniqueKey(doc, src + inc, callback);
77 not covered } else {
780 not covered doc.set(autokey.path, src);
790 not covered return callback();
80 not covered }
81 not covered });
82 not covered };
83 not covered
840 not covered this.schema.pre('save', function(next) {
850 not covered var modified = false,
86 not covered
870 not covered autokey.from.forEach(function(ops) {
88 not covered if (list.fields[ops.path]) {
890 not covered values.push(list.fields[ops.path].format(this, ops.format));
900 not covered if (list.fields[ops.path].isModified(this)) {
910 not covered modified = true;
92 not covered }
93 not covered } else {
940 not covered values.push(this.get(ops.path));
95 not covered // virtual paths are always assumed to have changed, except 'id'
960 not covered if (ops.path !== 'id' && list.schema.pathType(ops.path) === 'virtual' || this.isModified(ops.path)) {
970 not covered modified = true;
98 not covered }
99 not covered }
100 not covered
101 not covered // if has a value and is unmodified or fixed, don't update it
1020 not covered if ((!modified || autokey.fixed) && this.get(autokey.path)) {
1030 not covered return next();
104 not covered }
1050 not covered var newKey = utils.slug(values.join(' ') || this.id);
106 not covered
107 not covered if (autokey.unique) {
1080 not covered return getUniqueKey(this, newKey, next);
109 not covered } else {
1100 not covered this.set(autokey.path, newKey);
1110 not covered return next();
112 not covered }
113 not covered
114 not covered
115 not covered /**
116 not covered * List track option
117 not covered *
118 not covered * When enabled, it tracks when a document are created/updated,
119 not covered * as well as the user who created/updated it.
120 not covered */
1211100%exports.track = function() {
12212100% var list = this,
12312100% options = list.get('track'),
12412100% userModel = keystone.get('user model'),
125 not covered defaultOptions = {
126 not covered
127 not covered // ensure track is a boolean or an object
12812100% if (!_.isBoolean(options) && !_.isObject(options) ) {
129 not covered throw new Error('Invalid List "track" option for ' + list.key + '\n' +
1301100% '"track" must be a boolean or an object.\n\n' +
1311100% 'See http://keystonejs.com/docs/database/#lists-options for more information.');
132 not covered }
13311100% if (_.isBoolean(options)) {
134 not covered // if { track: true } set all track fields to true
1354100% if (options) {
1364100% options = { createdAt: true, createdBy: true, updatedAt: true, updatedBy: true };
137 not covered createdAt: true,
1380 not covered return;
139 not covered }
14011100% if (!options.createdAt && !options.createdBy && !options.updatedAt && !options.updatedBy) {
1411100% return;
142 not covered }
14310100% if (list.get('track simulate standard meta')) {
1442100% if (!options.createdAt) {
1450 not covered options.createdAt = true;
146 not covered }
1472100% if (!options.updatedAt) {
1480 not covered options.updatedAt = true;
149 not covered }
15010100% options = _.extend({}, defaultOptions, options);
151 not covered
152 not covered // validate option fields
15310100% _.each(options, function(value, key) {
15439100% var fieldName;
155 not covered
156 not covered // make sure it's a valid track option field
15739100% if (_.has(defaultOptions, key)) {
158 not covered // make sure the option field value is either a boolean or a string
15938100% if (!_.isBoolean(value) && !_.isString(value)) {
160 not covered throw new Error('Invalid List "track" option for ' + list.key + '\n' +
1611100% '"' + key + '" must be a boolean or a string.\n\n' +
1621100% 'See http://keystonejs.com/docs/database/#lists-options for more information.');
163 not covered }
16437100% if (value) {
165 not covered // determine
16629100% fieldName = value === true ? key : value;
16729100% options[key] = fieldName;
168 not covered
16929100% switch(key) {
170 not covered case 'createdAt':
17115100% fields[fieldName] = { type: Date, hidden: true, index: true };
172 not covered break;
17314100% fields[fieldName] = { type: Types.Relationship, ref: userModel, hidden: true, index: true };
174 not covered break;
175 not covered throw new Error('Invalid List "track" option for ' + list.key + '\n' +
1761100% 'valid field options are "createdAt", "createdBy", "updatedAt", an "updatedBy".\n\n' +
1771100% 'See http://keystonejs.com/docs/database/#lists-options for more information.');
178 not covered }
179 not covered
180 not covered // simulate standard meta by mapping to existing createdAt/updatedAt fields
1818100% if (list.get('track simulate standard meta')) {
1822100% list.map('createdOn', options.createdAt);
1832100% list.schema.virtual('createdOn') .get(function () {
184 not covered .get(function () {
1854100% return this.get('createdAt');
186 not covered });
1872100% list.map('updatedOn', options.updatedAt);
1882100% list.schema.virtual('updatedOn') .get(function () {
189 not covered .get(function () {
1904100% return this.get('updatedAt');
191 not covered });
1928100% if (_.isEmpty(fields)) {
1930 not covered return;
194 not covered }
1958100% list.add(fields);
196 not covered
197 not covered // add the pre-save schema plugin
1988100% list.schema.pre('save', function (next) {
19916100% var now = new Date();
200 not covered
201 not covered // set createdAt/createdBy on new docs
2026100% this.set(options.createdAt, now);
203 not covered }
2048100% if (options.createdBy && this._req_user) {
2056100% this.set(options.createdBy, this._req_user._id);
206 not covered }
20716100% if (this.isNew || this.isModified()) {
208 not covered if (options.updatedAt) {
20916100% this.set(options.updatedAt, now);
210 not covered }
21116100% if (options.updatedBy && this._req_user) {
21216100% this.set(options.updatedBy, this._req_user._id);
213 not covered }
21416100% next();
215 not covered });
216 not covered
217 not covered
2181100%methods.getRelated = function(paths, callback, nocollapse) {
2190 not covered var item = this,
220 not covered
2210 not covered if ('function' !== typeof callback) {
2220 not covered throw new Error('List.getRelated(paths, callback, nocollapse) requires a callback function.');
223 not covered }
2240 not covered if ('string' === typeof paths) {
2250 not covered var pathsArr = paths.split(' ');
2260 not covered var lastPath = '';
2270 not covered paths = [];
2280 not covered for (var i = 0; i < pathsArr.length; i++) {
2290 not covered lastPath += (lastPath.length ? ' ' : '') + pathsArr[i];
2300 not covered if (lastPath.indexOf('[') < 0 || lastPath.charAt(lastPath.length - 1) === ']') {
2310 not covered paths.push(lastPath);
2320 not covered lastPath = '';
233 not covered }
2340 not covered _.each(paths, function(options) {
2350 not covered var populateString = '';
236 not covered
2370 not covered if ('string' === typeof options) {
2380 not covered if (options.indexOf('[') > 0) {
2390 not covered populateString = options.substring(options.indexOf('[') + 1, options.indexOf(']'));
2400 not covered options = options.substr(0,options.indexOf('['));
241 not covered }
2420 not covered options = { path: options };
243 not covered }
2440 not covered options.populate = options.populate || [];
2450 not covered options.related = options.related || [];
246 not covered
2470 not covered var relationship = list.relationships[options.path];
2480 not covered if (!relationship) throw new Error('List.getRelated: list ' + list.key + ' does not have a relationship ' + options.path + '.');
249 not covered
2500 not covered var refList = keystone.list(relationship.ref);
2510 not covered if (!refList) throw new Error('List.getRelated: list ' + relationship.ref + ' does not exist.');
252 not covered
2530 not covered var relField = refList.fields[relationship.refPath];
2540 not covered if (!relField || relField.type !== 'relationship') throw new Error('List.getRelated: relationship ' + relationship.ref + ' on list ' + list.key + ' refers to a path (' + relationship.refPath + ') which is not a relationship field.');
255 not covered
256 not covered if (populateString.length) {
2570 not covered _.each(populateString.split(' '), function(key) {
2580 not covered options.related.push(key);
259 not covered else
2600 not covered options.populate.push(key);
261 not covered });
262 not covered
263 not covered }
264 not covered
2650 not covered queue[relationship.path] = function(done) {
2660 not covered var query = refList.model.find().where(relField.path);
267 not covered
268 not covered if (options.populate)
2690 not covered query.populate(options.populate);
270 not covered
271 not covered if (relField.many) {
2720 not covered query.in([item.id]);
273 not covered } else {
2740 not covered query.equals(item.id);
275 not covered }
2760 not covered query.sort(options.sort || relationship.sort || refList.defaultSort);
277 not covered
278 not covered if (options.related.length) {
2790 not covered query.exec(function(err, results) {
2800 not covered if (err || !results.length) {
2810 not covered return done(err, results);
282 not covered }
2830 not covered async.parallel(results.map(function(item) {
2840 not covered return function(done) {
2850 not covered item.populateRelated(options.related, done);
286 not covered };
287 not covered }),
288 not covered function(err) {
2890 not covered done(err, results);
290 not covered }
291 not covered );
292 not covered });
293 not covered } else {
2940 not covered query.exec(done);
295 not covered }
296 not covered
2970 not covered if (!item._populatedRelationships) item._populatedRelationships = {};
2980 not covered item._populatedRelationships[relationship.path] = true;
299 not covered
300 not covered
3010 not covered async.parallel(queue, function(err, results) {
3020 not covered if (!nocollapse && results && paths.length === 1) {
3030 not covered results = results[paths[0]];
304 not covered }
3050 not covered callback(err, results);
306 not covered });
307 not covered
308 not covered
3091100%methods.populateRelated = function(rel, callback) {
3100 not covered var item = this;
311 not covered
3120 not covered if ('function' !== typeof callback) {
3130 not covered throw new Error('List.populateRelated(rel, callback) requires a callback function.');
314 not covered }
3150 not covered this.getRelated(rel, function(err, results) {
3160 not covered _.each(results, function(data, key) {
3170 not covered item[key] = data;
318 not covered });
3190 not covered callback(err, results);
320 not covered }, true);
321 not covered
322 not covered
3231100%options.transform = function(doc, ret) { if (doc._populatedRelationships) {
3240 not covered _.each(doc._populatedRelationships, function(on, key) {
3250 not covered if (!on) return;
3260 not covered ret[key] = doc[key];
327 not covered });
328 not covered }

lib/fieldTypes/index.js

100% line coverage
100% statement coverage
0% block coverage
26 SLOC
LineHitsStatementsSourceAction
11100%exports.AzureFile = require('./azurefile');
21100%exports.Boolean = require('./boolean');
31100%exports.CloudinaryImage = require('./cloudinaryimage');
41100%exports.CloudinaryImages = require('./cloudinaryimages');
51100%exports.Code = require('./code');
61100%exports.Color = require('./color');
71100%exports.Date = require('./date');
81100%exports.Datetime = require('./datetime');
91100%exports.Email = require('./email');
101100%exports.Embedly = require('./embedly');
111100%exports.Html = require('./html');
121100%exports.Key = require('./key');
131100%exports.LocalFile = require('./localfile');
141100%exports.Location = require('./location');
151100%exports.Markdown = require('./markdown');
161100%exports.Money = require('./money');
171100%exports.Name = require('./name');
181100%exports.Number = require('./number');
191100%exports.Password = require('./password');
201100%exports.Relationship = require('./relationship');
211100%exports.S3File = require('./s3file');
221100%exports.Select = require('./select');
231100%exports.Text = require('./text');
241100%exports.Textarea = require('./textarea');
251100%exports.Url = require('./url');
261100%exports.LocalFiles = require('./localfiles');

lib/fieldTypes/azurefile.js

0% block coverage
149 SLOC
LineHitsStatementsSourceAction
1 not covered /*!
2 not covered * Module dependencies.
3 not covered */
4 not covered
51100%var _ = require('underscore'),
61100% moment = require('moment'),
71100% keystone = require('../../'),
81100% async = require('async'),
91100% util = require('util'),
101100% azure = require('azure'),
111100% utils = require('keystone-utils'),
121100% super_ = require('../field');
13 not covered
14 not covered
15 not covered /**
16 not covered * AzureFile FieldType Constructor
17 not covered * @extends Field
18 not covered * @api public
19 not covered */
20 not covered
21 not covered function azurefile(list, path, options) {
22 not covered
230 not covered this._underscoreMethods = ['format', 'uploadFile'];
24 not covered
25 not covered // event queues
260 not covered this._pre = {
27 not covered
28 not covered // TODO: implement filtering, usage disabled for now
290 not covered options.nofilter = true;
30 not covered
31 not covered // TODO: implement initial form, usage disabled for now
32 not covered if (options.initial) {
330 not covered throw new Error('Invalid Configuration\n\nAzureFile fields (' + list.key + '.' + path + ') do not currently support being used as initial fields.\n');
34 not covered }
35 not covered
360 not covered azurefile.super_.call(this, list, path, options);
37 not covered
38 not covered // validate azurefile config (has to happen after super_.call)
390 not covered if (!this.azurefileconfig) {
40 not covered throw new Error('Invalid Configuration\n\n' +
410 not covered 'AzureFile fields (' + list.key + '.' + path + ') require the "azurefile config" option to be set.\n\n' +
420 not covered 'See http://keystonejs.com/docs/configuration#services-azure for more information.\n');
43 not covered }
44 not covered
450 not covered process.env.AZURE_STORAGE_ACCOUNT = this.azurefileconfig.account;
460 not covered process.env.AZURE_STORAGE_ACCESS_KEY = this.azurefileconfig.key;
47 not covered
480 not covered this.azurefileconfig.container = this.azurefileconfig.container || 'keystone';
49 not covered
500 not covered var self = this;
510 not covered options.filenameFormatter = options.filenameFormatter || function(item, filename) { return filename; };
520 not covered options.containerFormatter = options.containerFormatter || function(item, filename) { return self.azurefileconfig.container; };
53 not covered
54 not covered // Could be more pre- hooks, just upload for now
550 not covered if (options.pre && options.pre.upload) {
560 not covered this._pre.upload = this._pre.upload.concat(options.pre.upload);
57 not covered }
58 not covered
59 not covered }
60 not covered
61 not covered /*!
62 not covered * Inherit from Field
63 not covered */
64 not covered
651100%util.inherits(azurefile, super_);
66 not covered
67 not covered /**
68 not covered * Exposes the custom or keystone s3 config settings
69 not covered */
70 not covered
711100%Object.defineProperty(azurefile.prototype, 'azurefileconfig', { get: function() {
720 not covered return this.options.azurefileconfig || keystone.get('azurefile config');
73 not covered }});
74 not covered
75 not covered
76 not covered /**
77 not covered * Allows you to add pre middleware after the field has been initialised
78 not covered *
79 not covered * @api public
80 not covered */
81 not covered
821100%azurefile.prototype.pre = function(event, fn) {
830 not covered if (!this._pre[event]) {
840 not covered throw new Error('AzureFile (' + this.list.key + '.' + this.path + ') error: azurefile.pre()\n\n' +
85 not covered }
860 not covered this._pre[event].push(fn);
870 not covered return this;
88 not covered };
89 not covered
90 not covered
91 not covered /**
92 not covered * Registers the field on the List's Mongoose Schema.
93 not covered *
94 not covered * @api public
95 not covered */
96 not covered
971100%azurefile.prototype.addToSchema = function() {
980 not covered var field = this,
99 not covered
1000 not covered var paths = this.paths = {
1010 not covered filename: this._path.append('.filename'),
1020 not covered path: this._path.append('.path'),
1030 not covered size: this._path.append('.size'),
1040 not covered filetype: this._path.append('.filetype'),
1050 not covered url: this._path.append('.url'),
1060 not covered etag: this._path.append('.etag'),
1070 not covered container: this._path.append('.container'),
108 not covered // virtuals
1090 not covered exists: this._path.append('.exists'),
1100 not covered upload: this._path.append('_upload'),
1110 not covered action: this._path.append('_action')
112 not covered };
113 not covered
1140 not covered var schemaPaths = this._path.addTo({}, {
115 not covered
1160 not covered schema.add(schemaPaths);
117 not covered
1180 not covered var exists = function(item) {
1190 not covered return (item.get(paths.url) ? true : false);
120 not covered };
121 not covered
122 not covered // The .exists virtual indicates whether a file is stored
1230 not covered schema.virtual(paths.exists).get(function() {
1240 not covered return schemaMethods.exists.apply(this);
125 not covered });
126 not covered
1270 not covered var reset = function(item) {
1280 not covered item.set(field.path, {
129 not covered };
130 not covered
1310 not covered var schemaMethods = {
1320 not covered return exists(this);
133 not covered },
1340 not covered var self = this;
135 not covered
136 not covered try {
1370 not covered azure.createBlobService().deleteBlob(this.get(paths.container), this.get(paths.filename), function(error) {});
138 not covered } catch(e) {}
1390 not covered reset(self);
140 not covered },
1410 not covered var self = this;
142 not covered try {
1430 not covered azure.createBlobService().blobService.deleteBlob(this.get(paths.container), this.get(paths.filename), function(error) {
1440 not covered if(!error){}
145 not covered });
146 not covered } catch(e) {}
1470 not covered reset(self);
148 not covered }
149 not covered
1500 not covered _.each(schemaMethods, function(fn, key) {
1510 not covered field.underscoreMethod(key, fn);
152 not covered });
153 not covered
154 not covered // expose a method on the field to call schema methods
1550 not covered this.apply = function(item, method) {
1560 not covered return schemaMethods[method].apply(item, Array.prototype.slice.call(arguments, 2));
157 not covered };
158 not covered
1590 not covered this.bindUnderscoreMethods();
160 not covered };
161 not covered
162 not covered
163 not covered /**
164 not covered * Formats the field value
165 not covered *
166 not covered * @api public
167 not covered */
168 not covered
1691100%azurefile.prototype.format = function(item) {
1700 not covered return item.get(this.paths.url);
171 not covered };
172 not covered
173 not covered
174 not covered /**
175 not covered * Detects whether the field has been modified
176 not covered *
177 not covered * @api public
178 not covered */
179 not covered
1801100%azurefile.prototype.isModified = function(item) {
1810 not covered return item.isModified(this.paths.url);
182 not covered };
183 not covered
184 not covered
185 not covered /**
186 not covered * Validates that a value for this field has been provided in a data object
187 not covered *
188 not covered * @api public
189 not covered */
190 not covered
1911100%azurefile.prototype.validateInput = function(data) { // TODO - how should file field input be validated?
1920 not covered return true;
193 not covered };
194 not covered
195 not covered
196 not covered /**
197 not covered * Updates the value for this field in the item from a data object
198 not covered *
199 not covered * @api public
200 not covered */
201 not covered
2021100%azurefile.prototype.updateItem = function(item, data) { // TODO - direct updating of data (not via upload) };
203 not covered
204 not covered
205 not covered /**
206 not covered * Uploads the file for this field
207 not covered *
208 not covered * @api public
209 not covered */
210 not covered
2111100%azurefile.prototype.uploadFile = function(item, file, update, callback) {
2120 not covered var field = this,
2130 not covered prefix = field.options.datePrefix ? moment().format(field.options.datePrefix) + '-' : '',
2140 not covered name = prefix + file.name;
215 not covered
2160 not covered if (field.options.allowedTypes && !_.contains(field.options.allowedTypes, file.type)){
2170 not covered return callback(new Error('Unsupported File Type: '+file.type));
218 not covered }
2190 not covered if ('function' == typeof update) {
2200 not covered callback = update;
2210 not covered update = false;
222 not covered }
2230 not covered var doUpload = function() {
2240 not covered var blobService = azure.createBlobService();
2250 not covered var container = field.options.containerFormatter(item, file.name);
226 not covered
2270 not covered blobService.createContainerIfNotExists(container, {publicAccessLevel : 'blob'}, function(error){
2280 not covered if(error){
2290 not covered return callback(error);
230 not covered }
231 not covered
2320 not covered blobService.createBlockBlobFromFile(container, field.options.filenameFormatter(item, file.name), file.path, function(error, blob, res){
2330 not covered if(error){
2340 not covered return callback(error);
235 not covered } else {
2360 not covered var fileData = {
237 not covered filename: blob.blob,
2380 not covered url: 'http://' + field.azurefileconfig.account + '.blob.core.windows.net/' + container + '/' + blob.blob
239 not covered };
240 not covered
2410 not covered if (update) {
2420 not covered item.set(field.path, fileData);
243 not covered }
2440 not covered callback(null, fileData);
245 not covered }
246 not covered });
247 not covered };
248 not covered
2490 not covered async.eachSeries(this._pre.upload, function(fn, next) {
2500 not covered fn(item, file, next);
251 not covered }, function(err) {
2520 not covered if (err) return callback(err);
2530 not covered doUpload();
254 not covered });
255 not covered };
256 not covered
257 not covered
258 not covered /**
259 not covered * Returns a callback that handles a standard form submission for the field
260 not covered *
261 not covered * Expected form parts are
262 not covered * - `field.paths.action` in `req.body` (`clear` or `delete`)
263 not covered * - `field.paths.upload` in `req.files` (uploads the file to s3file)
264 not covered *
265 not covered * @api public
266 not covered */
267 not covered
2681100%azurefile.prototype.getRequestHandler = function(item, req, paths, callback) {
2690 not covered var field = this;
270 not covered
2710 not covered if (utils.isFunction(paths)) {
2720 not covered callback = paths;
2730 not covered paths = field.paths;
2740 not covered } else if (!paths) {
2750 not covered paths = field.paths;
276 not covered }
2770 not covered callback = callback || function() {};
278 not covered
2790 not covered return function() {
2800 not covered var action = req.body[paths.action];
281 not covered
2820 not covered if (/^(delete|reset)$/.test(action))
2830 not covered field.apply(item, action);
284 not covered }
2850 not covered if (req.files && req.files[paths.upload] && req.files[paths.upload].size) {
2860 not covered return field.uploadFile(item, req.files[paths.upload], true, callback);
287 not covered }
2880 not covered return callback();
289 not covered
290 not covered
291 not covered
292 not covered
293 not covered /**
294 not covered * Immediately handles a standard form submission for the field (see `getRequestHandler()`)
295 not covered *
296 not covered * @api public
297 not covered */
298 not covered
2991100%azurefile.prototype.handleRequest = function(item, req, paths, callback) {
3000 not covered this.getRequestHandler(item, req, paths, callback)();
301 not covered };
302 not covered
303 not covered
304 not covered /*!
305 not covered * Export class
306 not covered */
307 not covered
3081100%exports = module.exports = azurefile;

lib/field.js

25% block coverage
158 SLOC
LineHitsStatementsSourceAction
1 not covered /*!
2 not covered * Module dependencies.
3 not covered */
4 not covered
51100%var _ = require('underscore'),
61100% marked = require('marked'),
71100% Path = require('./path'),
81100% fspath = require('path'),
91100% jade = require('jade'),
101100% fs = require('fs'),
111100% keystone = require('../'),
121100% utils = require('keystone-utils'),
13 not covered compiledTemplates = {};
14 not covered
15 not covered
16 not covered /**
17 not covered * Field Constructor
18 not covered * =================
19 not covered *
20 not covered * Extended by fieldType Classes, should not be used directly.
21 not covered *
22 not covered * @api public
23 not covered */
24 not covered
25 not covered function Field(list, path, options) {
26 not covered
27 not covered // Set field properties and options
2850100% this.list = list;
2950100% this._path = new Path(path);
3050100% this.path = path;
31 not covered
3250100% this.type = this.constructor.name;
3350100% this.options = utils.options(this.defaults, options);
3450100% this.label = options.label || utils.keyToLabel(this.path);
3550100% this.typeDescription = options.typeDescription || this.typeDescription || this.type;
36 not covered
37 not covered // Add the field to the schema
3850100% this.list.automap(this);
3950100% this.addToSchema();
40 not covered
41 not covered // Warn on required fields that aren't initial
42 not covered if (this.options.required &&
4350100% this.options.initial === undefined &&
4450100% this.options.default === undefined &&
4550100% !this.options.value &&
4650100% !this.list.get('nocreate') &&
4750100% this.path !== this.list.mappings.name
48 not covered ) {
49 not covered console.error('\nError: Invalid Configuration\n\n' +
500 not covered 'Field (' + list.key + '.' + path + ') is required but not initial, and has no default or generated value.\n' +
510 not covered 'Please provide a default, remove the required setting, or set initial: false to override this error.\n');
520 not covered process.exit(1);
53 not covered }
54 not covered
55 not covered // Set up templates
5650100% this.templateDir = fspath.normalize(options.templateDir || (__dirname + '../../templates/fields/' + this.type));
57 not covered
5850100% var defaultTemplates = {
5950100% form: this.templateDir + '/' + 'form.jade',
6050100% initial: this.templateDir + '/' + 'initial.jade'
61 not covered };
62 not covered
6350100% this.templates = utils.options(defaultTemplates, this.options.templates);
64 not covered
65 not covered // Add pre-save handler to the list if this field watches others
66 not covered if (this.options.watch) {
670 not covered this.list.schema.pre('save', this.getPreSaveWatcher());
68 not covered }
69 not covered
70 not covered // Convert notes from markdown to html
7150100% var note = null;
7250100% Object.defineProperty(this, 'note', { get: function() {
730 not covered return (note === null) ? (note = (this.options.note) ? marked(this.options.note) : '') : note;
74 not covered } });
75 not covered
76 not covered }
77 not covered
781100%Field.prototype.getPreSaveWatcher = function() {
790 not covered var field = this,
80 not covered
810 not covered if (this.options.watch === true) {
82 not covered // watch == true means always apply the value method
830 not covered applyValue = function() { return true; };
84 not covered } else {
850 not covered if (_.isString(this.options.watch)) {
860 not covered this.options.watch = this.options.watch.split(' ');
87 not covered }
880 not covered if (_.isFunction(this.options.watch)) {
890 not covered applyValue = this.options.watch;
900 not covered } else if (_.isArray(this.options.watch)) {
910 not covered applyValue = function(item) {
920 not covered var pass = false;
930 not covered field.options.watch.forEach(function(path) {
940 not covered if (item.isModified(path)) pass = true;
95 not covered });
960 not covered return pass;
97 not covered };
980 not covered } else if (_.isObject(this.options.watch)) {
990 not covered applyValue = function(item) {
1000 not covered var pass = false;
1010 not covered _.each(field.options.watch, function(value, path) {
1020 not covered if (item.isModified(path) && item.get(path) === value) pass = true;
103 not covered });
1040 not covered return pass;
105 not covered };
106 not covered }
1070 not covered if (!applyValue) {
1080 not covered console.error('\nError: Invalid Configuration\n\n' +
1090 not covered process.exit(1);
110 not covered }
1110 not covered if (!_.isFunction(this.options.value)) {
1120 not covered console.error('\nError: Invalid Configuration\n\n' +
1130 not covered process.exit(1);
114 not covered }
1150 not covered return function(next) {
1160 not covered if (!applyValue(this)) {
1170 not covered return next();
118 not covered }
1190 not covered this.set(field.path, field.options.value.call(this));
1200 not covered next();
121 not covered };
122 not covered
123 not covered
1241100%exports = module.exports = Field;
125 not covered
126 not covered
127 not covered /** Getter properties for the Field prototype */
128 not covered
129150%Object.defineProperty(Field.prototype, 'width', { get: function() { not covered return this.options.width || 'full';} }); // !! field width is, for certain types, overridden by css
130150%Object.defineProperty(Field.prototype, 'initial', { get: function() { not covered return this.options.initial || false;} });
13137100%Object.defineProperty(Field.prototype, 'required', { get: function() { return this.options.required || false; } })
132150%Object.defineProperty(Field.prototype, 'col', { get: function() { not covered return this.options.col || false;} });
13337100%Object.defineProperty(Field.prototype, 'noedit', { get: function() { return this.options.noedit || false; } })
134150%Object.defineProperty(Field.prototype, 'nocol', { get: function() { not covered return this.options.nocol || false;} });
135150%Object.defineProperty(Field.prototype, 'nosort', { get: function() { not covered return this.options.nosort || false;} });
136150%Object.defineProperty(Field.prototype, 'nofilter', { get: function() { not covered return this.options.nofilter || false;} });
137150%Object.defineProperty(Field.prototype, 'collapse', { get: function() { not covered return this.options.collapse || false;} });
138150%Object.defineProperty(Field.prototype, 'hidden', { get: function() { not covered return this.options.hidden || false;} });
139150%Object.defineProperty(Field.prototype, 'dependsOn', { get: function() { not covered return this.options.dependsOn || false;} });
140 not covered
141 not covered
142 not covered
143 not covered /**
144 not covered * Default method to register the field on the List's Mongoose Schema.
145 not covered * Overridden by some fieldType Classes
146 not covered *
147 not covered * @api public
148 not covered */
149 not covered
1501100%Field.prototype.addToSchema = function() {
1513467% var ops = (this._nativeType) ? _.defaults({ type: this._nativeType }, this.options) : not covered this.options
152 not covered
15334100% this.list.schema.path(this.path, ops);
154 not covered
15534100% this.bindUnderscoreMethods();
156 not covered
157 not covered
1581100%Field.prototype.bindUnderscoreMethods = function(methods) {
15950100% var field = this;
160 not covered
161 not covered // automatically bind underscore methods specified by the _underscoreMethods property
162 not covered // always include the 'update' method
163 not covered
16450100% (this._underscoreMethods || []).concat({ fn: 'updateItem', as: 'update' }, (methods || [])).forEach(function(method) {
165136100% if ('string' === typeof method) {
16686100% method = { fn: method, as: method };
167 not covered }
168136100% if ('function' !== typeof field[method.fn]) {
1690 not covered throw new Error('Invalid underscore method (' + method.fn + ') applied to ' + field.list.key + '.' + field.path + ' (' + field.type + ')');
170 not covered }
171136100% field.underscoreMethod(method.as, function() {
1724100% var args = [this].concat(Array.prototype.slice.call(arguments));
1734100% return field[method.fn].apply(field, args);
174 not covered });
175 not covered });
176 not covered
177 not covered
178 not covered
179 not covered /**
180 not covered * Adds a method to the underscoreMethods collection on the field's list,
181 not covered * with a path prefix to match this field's path and bound to the document
182 not covered *
183 not covered * @api public
184 not covered */
185 not covered
1861100%Field.prototype.underscoreMethod = function(path, fn) {
187136100% this.list.underscoreMethod(this.path + '.' + path, function() {
1884100% return fn.apply(this, arguments);
189 not covered });
190 not covered };
191 not covered
192 not covered
193 not covered /**
194 not covered * Default method to format the field value for display
195 not covered * Overridden by some fieldType Classes
196 not covered *
197 not covered * @api public
198 not covered */
199 not covered
2001100%Field.prototype.format = function(item) {
2010 not covered return item.get(this.path);
202 not covered };
203 not covered
204 not covered
205 not covered /**
206 not covered * Default method to detect whether the field has been modified in an item
207 not covered * Overridden by some fieldType Classes
208 not covered *
209 not covered * @api public
210 not covered */
211 not covered
2121100%Field.prototype.isModified = function(item) {
2130 not covered return item.isModified(this.path);
214 not covered };
215 not covered
216 not covered
217 not covered /**
218 not covered * Validates that a value for this field has been provided in a data object
219 not covered * Overridden by some fieldType Classes
220 not covered *
221 not covered * @api public
222 not covered */
223 not covered
2241100%Field.prototype.validateInput = function(data, required, item) {
2259100% if (!required) return true;
2260 not covered if (!(this.path in data) && item && item.get(this.path)) return true;
2270 not covered if ('string' === typeof data[this.path]) {
2280 not covered return (data[this.path].trim()) ? true : false;
229 not covered } else {
2300 not covered return (data[this.path]) ? true : false;
231 not covered }
232 not covered
233 not covered
234 not covered /**
235 not covered * Updates the value for this field in the item from a data object
236 not covered * Overridden by some fieldType Classes
237 not covered *
238 not covered * @api public
239 not covered */
240 not covered
2411100%Field.prototype.updateItem = function(item, data) {
24212100% var value = this.getValueFromData(data);
243 not covered
24412100% if (value !== undefined && value != item.get(this.path)) {
24512100% item.set(this.path, value);
246 not covered }
247 not covered
248 not covered /**
249 not covered * Retrieves the value from an object, whether the path is nested or flattened
250 not covered *
251 not covered * @api public
252 not covered */
253 not covered
2541100%Field.prototype.getValueFromData = function(data) {
25517100% return this.path in data ? data[this.path] : this._path.get(data);
256 not covered };
257 not covered
258 not covered /**
259 not covered * Compiles a field template and caches it
260 not covered *
261 not covered * @api public
262 not covered */
263 not covered
2641100%Field.prototype.compile = function(type, callback) {
2650 not covered var templatePath = this.templates[type];
266 not covered
2670 not covered if (!compiledTemplates[templatePath]) {
2680 not covered fs.readFile(templatePath, 'utf8', function(err, file) {
2690 not covered if (!err){
2700 not covered compiledTemplates[templatePath] = jade.compile(file, {
271 not covered filename: templatePath,
2720 not covered pretty: keystone.get('env') !== 'production'
273 not covered });
274 not covered }
2750 not covered if (callback) return callback();
276 not covered });
2770 not covered } else if (callback) {
2780 not covered return callback();
279 not covered }
280 not covered
281 not covered /**
282 not covered * Compiles a field template and caches it
283 not covered *
284 not covered * @api public
285 not covered */
286 not covered
2871100%Field.prototype.render = function(type, item, locals) {
2880 not covered var templatePath = this.templates[type];
289 not covered
290 not covered // Compile the template synchronously if it hasn't already been compiled
2910 not covered if (!compiledTemplates[templatePath]) {
292 not covered
2930 not covered var file = fs.readFileSync(templatePath, 'utf8');
294 not covered
2950 not covered compiledTemplates[templatePath] = jade.compile(file, {
2960 not covered pretty: keystone.get('env') !== 'production'
297 not covered });
298 not covered
299 not covered }
300 not covered
3010 not covered return compiledTemplates[templatePath](_.extend(locals || {}, {
302 not covered

lib/path.js

80% block coverage
47 SLOC
LineHitsStatementsSourceAction
11100%var utils = require('keystone-utils');
2 not covered
3 not covered /**
4 not covered * Path Class
5 not covered *
6 not covered * @api public
7 not covered */
8 not covered
91100%exports = module.exports = function Path(str) {
1063100% if (!(this instanceof Path)) {
110 not covered return new Path(str);
12 not covered }
1363100% this.original = str;
14 not covered
1563100% var parts = this.parts = str.split('.');
1663100% var last = this.last = this.parts[this.parts.length-1];
1763100% var exceptLast = [];
18 not covered
1993100% for (var i = 0; i < parts.length-1; i++) {
2030100% exceptLast.push(parts[i]);
21 not covered }
2263100% this.exceptLast = exceptLast.join('.');
23 not covered
2463100% this.addTo = function(obj, val) {
252100% var o = obj;
268100% for (var i = 0; i < parts.length - 1; i++) {
276100% if (!utils.isObject(o[parts[i]]))
284100% o[parts[i]] = {};
296100% o = o[parts[i]];
30 not covered }
312100% o[last] = val;
322100% return obj;
33 not covered };
34 not covered
3563100% this.get = function(obj) {
3614100% var o = obj;
3734100% for (var i = 0; i < parts.length; i++) {
3829100% if (typeof o !== 'object') return undefined;
3920100% o = o[parts[i]];
40 not covered }
415100% return o;
42 not covered };
43 not covered
4463100% this.prependToLast = function(prepend, titlecase) {
453100% var rtn = '';
469100% for (var i = 0; i < parts.length - 1; i++) {
476100% rtn += parts[i] + '.';
48 not covered }
493100% return rtn + (prepend || '') + (titlecase ? utils.upcase(last) : last);
50 not covered };
51 not covered
5263100% this.append = function(append) {
5373100% return str + append;
54 not covered };
55 not covered
5663100% this.flatten = function(titlecase) {
572100% return utils.camelcase(parts.join('_'), titlecase ? true : false);
58 not covered };
59 not covered
6063100% this.flattenplural = function(titlecase) {
610 not covered return utils.camelcase([].concat(exceptLast).concat(utils.plural(last)).join('_'), titlecase ? true : false);
62 not covered };
63 not covered
6463100% this.flattensingular = function(titlecase) {
650 not covered return utils.camelcase([].concat(exceptLast).concat(utils.singular(last)).join('_'), titlecase ? true : false);
66 not covered };
67 not covered
6863100% return this;
69 not covered

lib/fieldTypes/boolean.js

62% block coverage
20 SLOC
LineHitsStatementsSourceAction
1 not covered /*!
2 not covered * Module dependencies.
3 not covered */
4 not covered
51100%var util = require('util'), super_ = require('../field');
6 not covered
7 not covered /**
8 not covered * Boolean FieldType Constructor
9 not covered * @extends Field
10 not covered * @api public
11 not covered */
12 not covered
13 not covered function boolean(list, path, options) {
142100% this._nativeType = Boolean;
15267% this.indent = (options.indent) ? not covered true: false;
162100% boolean.super_.call(this, list, path, options);
17 not covered }
18 not covered
19 not covered /*!
20 not covered * Inherit from Field
21 not covered */
22 not covered
231100%util.inherits(boolean, super_);
24 not covered
25 not covered
26 not covered /**
27 not covered * Validates that a truthy value for this field has been provided in a data object.
28 not covered *
29 not covered * Useful for checkboxes that are required to be true (e.g. agreed to terms and cond's)
30 not covered *
31 not covered * @api public
32 not covered */
33 not covered
341100%boolean.prototype.validateInput = function(data, required) {
350 not covered if (required) {
360 not covered return (data[this.path] === true || data[this.path] === 'true') ? true : false;
37 not covered } else {
380 not covered return true;
39 not covered }
40 not covered
41 not covered
42 not covered /**
43 not covered * Updates the value for this field in the item from a data object.
44 not covered * Only updates the value if it has changed.
45 not covered * Treats a true boolean or string == 'true' as true, everything else as false.
46 not covered *
47 not covered * @api public
48 not covered */
49 not covered
501100%boolean.prototype.updateItem = function(item, data) {
515100% var value = this.getValueFromData(data);
52 not covered
535100% if (value === true || value === 'true') {
544100% if (!item.get(this.path)) {
554100% item.set(this.path, true);
56 not covered }
571100% } else if (item.get(this.path) !== false) {
581100% item.set(this.path, false);
59 not covered }
60 not covered
61 not covered
62 not covered /*!
63 not covered * Export class
64 not covered */
65 not covered
661100%exports = module.exports = boolean;

lib/fieldTypes/cloudinaryimage.js

0% block coverage
176 SLOC
LineHitsStatementsSourceAction
1 not covered /*!
2 not covered * Module dependencies.
3 not covered */
4 not covered
51100%var _ = require('underscore'),
61100% keystone = require('../../'),
71100% util = require('util'),
81100% cloudinary = require('cloudinary'),
91100% utils = require('keystone-utils'),
101100% super_ = require('../field');
11 not covered
12 not covered /**
13 not covered * CloudinaryImage FieldType Constructor
14 not covered * @extends Field
15 not covered * @api public
16 not covered */
17 not covered
18 not covered function cloudinaryimage(list, path, options) {
19 not covered
200 not covered this._underscoreMethods = ['format'];
21 not covered
22 not covered // TODO: implement filtering, usage disabled for now
230 not covered options.nofilter = true;
24 not covered
25 not covered // TODO: implement initial form, usage disabled for now
26 not covered if (options.initial) {
270 not covered throw new Error(
280 not covered 'Invalid Configuration\n\n' +
29 not covered );
30 not covered }
31 not covered
320 not covered cloudinaryimage.super_.call(this, list, path, options);
33 not covered
34 not covered // validate cloudinary config
350 not covered if (!keystone.get('cloudinary config')) {
360 not covered throw new Error(
37 not covered 'Invalid Configuration\n\n' +
380 not covered 'CloudinaryImage fields (' + list.key + '.' + this.path + ') require the "cloudinary config" option to be set.\n\n' +
390 not covered 'See http://keystonejs.com/docs/configuration/#cloudinary for more information.\n'
40 not covered );
41 not covered }
42 not covered
43 not covered }
44 not covered
45 not covered /*!
46 not covered * Inherit from Field
47 not covered */
48 not covered
491100%util.inherits(cloudinaryimage, super_);
50 not covered
51 not covered
52 not covered /**
53 not covered * Registers the field on the List's Mongoose Schema.
54 not covered *
55 not covered * @api public
56 not covered */
57 not covered
581100%cloudinaryimage.prototype.addToSchema = function() {
590 not covered var field = this,
60 not covered
610 not covered var paths = this.paths = {
620 not covered public_id: this._path.append('.public_id'),
630 not covered version: this._path.append('.version'),
640 not covered signature: this._path.append('.signature'),
650 not covered format: this._path.append('.format'),
660 not covered resource_type: this._path.append('.resource_type'),
670 not covered url: this._path.append('.url'),
680 not covered width: this._path.append('.width'),
690 not covered height: this._path.append('.height'),
700 not covered secure_url: this._path.append('.secure_url'),
71 not covered // virtuals
720 not covered exists: this._path.append('.exists'),
730 not covered folder: this._path.append('.folder'),
74 not covered // form paths
750 not covered upload: this._path.append('_upload'),
760 not covered action: this._path.append('_action'),
770 not covered select: this._path.append('_select')
78 not covered };
79 not covered
800 not covered var schemaPaths = this._path.addTo({}, {
81 not covered
820 not covered schema.add(schemaPaths);
83 not covered
840 not covered var exists = function(item) {
850 not covered return (item.get(paths.public_id) ? true : false);
86 not covered };
87 not covered
88 not covered // The .exists virtual indicates whether an image is stored
890 not covered schema.virtual(paths.exists).get(function() {
900 not covered return schemaMethods.exists.apply(this);
91 not covered });
92 not covered
930 not covered var folder = function(item) {
940 not covered var folderValue = null;
95 not covered
960 not covered if (keystone.get('cloudinary folders')) {
97 not covered if (field.options.folder) {
980 not covered folderValue = field.options.folder;
99 not covered } else {
1000 not covered var folderList = keystone.get('cloudinary prefix') ? [keystone.get('cloudinary prefix')] : [];
1010 not covered folderList.push(field.list.path);
1020 not covered folderList.push(field.path);
1030 not covered folderValue = folderList.join('/');
104 not covered }
1050 not covered return folderValue;
106 not covered };
107 not covered
108 not covered // The .folder virtual returns the cloudinary folder used to upload/select images
1090 not covered schema.virtual(paths.folder).get(function() {
1100 not covered return schemaMethods.folder.apply(this);
111 not covered });
112 not covered
1130 not covered var src = function(item, options) {
1140 not covered if (!exists(item)) {
1150 not covered return '';
116 not covered }
1170 not covered options = ('object' === typeof options) ? options : {};
118 not covered
1190 not covered if (!('fetch_format' in options) && keystone.get('cloudinary webp') !== false) {
1200 not covered options.fetch_format = "auto";
121 not covered }
1220 not covered if (!('progressive' in options) && keystone.get('cloudinary progressive') !== false) {
1230 not covered options.progressive = true;
124 not covered }
1250 not covered if (!('secure' in options) && keystone.get('cloudinary secure')) {
1260 not covered options.secure = true;
127 not covered }
1280 not covered return cloudinary.url(item.get(paths.public_id) + '.' + item.get(paths.format), options);
129 not covered
130 not covered
1310 not covered var reset = function(item) {
1320 not covered item.set(field.path, {
133 not covered };
134 not covered
1350 not covered var addSize = function(options, width, height, other) {
1360 not covered if (width) options.width = width;
1370 not covered if (height) options.height = height;
1380 not covered if ('object' === typeof other) {
1390 not covered _.extend(options, other);
140 not covered }
1410 not covered return options;
142 not covered };
143 not covered
1440 not covered var schemaMethods = {
1450 not covered return exists(this);
146 not covered },
1470 not covered return folder(this);
148 not covered },
1490 not covered return src(this, options);
150 not covered },
1510 not covered return exists(this) ? cloudinary.image(this.get(field.path), options) : '';
152 not covered },
1530 not covered return src(this, addSize({ crop: 'scale' }, width, height, options));
154 not covered },
1550 not covered return src(this, addSize({ crop: 'fill', gravity: 'faces' }, width, height, options));
156 not covered },
1570 not covered return src(this, addSize({ crop: 'lfill', gravity: 'faces' }, width, height, options));
158 not covered },
1590 not covered return src(this, addSize({ crop: 'fit' }, width, height, options));
160 not covered },
1610 not covered return src(this, addSize({ crop: 'limit' }, width, height, options));
162 not covered },
1630 not covered return src(this, addSize({ crop: 'pad' }, width, height, options));
164 not covered },
1650 not covered return src(this, addSize({ crop: 'lpad' }, width, height, options));
166 not covered },
1670 not covered return src(this, addSize({ crop: 'crop', gravity: 'faces' }, width, height, options));
168 not covered },
1690 not covered return src(this, addSize({ crop: 'thumb', gravity: 'faces' }, width, height, options));
170 not covered },
1710 not covered reset(this);
172 not covered },
1730 not covered cloudinary.uploader.destroy(this.get(paths.public_id), function() {});
1740 not covered reset(this);
175 not covered }
176 not covered
1770 not covered _.each(schemaMethods, function(fn, key) {
1780 not covered field.underscoreMethod(key, fn);
179 not covered });
180 not covered
181 not covered // expose a method on the field to call schema methods
1820 not covered this.apply = function(item, method) {
1830 not covered return schemaMethods[method].apply(item, Array.prototype.slice.call(arguments, 2));
184 not covered };
185 not covered
1860 not covered this.bindUnderscoreMethods();
187 not covered };
188 not covered
189 not covered
190 not covered /**
191 not covered * Formats the field value
192 not covered *
193 not covered * @api public
194 not covered */
195 not covered
1961100%cloudinaryimage.prototype.format = function(item) {
1970 not covered return item.get(this.paths.url);
198 not covered };
199 not covered
200 not covered
201 not covered /**
202 not covered * Detects whether the field has been modified
203 not covered *
204 not covered * @api public
205 not covered */
206 not covered
2071100%cloudinaryimage.prototype.isModified = function(item) {
2080 not covered return item.isModified(this.paths.url);
209 not covered };
210 not covered
211 not covered
212 not covered /**
213 not covered * Validates that a value for this field has been provided in a data object
214 not covered *
215 not covered * @api public
216 not covered */
217 not covered
2181100%cloudinaryimage.prototype.validateInput = function(data) { // TODO - how should image field input be validated?
2190 not covered return true;
220 not covered };
221 not covered
222 not covered
223 not covered /**
224 not covered * Updates the value for this field in the item from a data object
225 not covered *
226 not covered * @api public
227 not covered */
228 not covered
2291100%cloudinaryimage.prototype.updateItem = function(item, data) {
2300 not covered var paths = this.paths;
231 not covered
2320 not covered var setValue = function(key) {
2330 not covered var index = paths[key].indexOf(".");
2340 not covered var field = paths[key].substr(0, index);
235 not covered // Note we allow implicit conversion here so that numbers submitted as strings in the data object
2360 not covered if (data[field] && data[field][key] && data[field][key] != item.get(paths[key])) {
2370 not covered item.set(paths[key], data[field][key] || null);
238 not covered }
239 not covered
2400 not covered _.each(['public_id', 'version', 'signature', 'format', 'resource_type', 'url', 'width', 'height', 'secure_url'], setValue);
241 not covered };
242 not covered
243 not covered
244 not covered /**
245 not covered * Returns a callback that handles a standard form submission for the field
246 not covered *
247 not covered * Expected form parts are
248 not covered * - `field.paths.action` in `req.body` (`clear` or `delete`)
249 not covered * - `field.paths.upload` in `req.files` (uploads the image to cloudinary)
250 not covered *
251 not covered * @api public
252 not covered */
253 not covered
2541100%cloudinaryimage.prototype.getRequestHandler = function(item, req, paths, callback) {
2550 not covered var field = this;
256 not covered
2570 not covered if (utils.isFunction(paths)) {
2580 not covered callback = paths;
2590 not covered paths = field.paths;
2600 not covered } else if (!paths) {
2610 not covered paths = field.paths;
262 not covered }
2630 not covered callback = callback || function() {};
264 not covered
2650 not covered return function() {
2660 not covered var action = req.body[paths.action];
267 not covered
2680 not covered if (/^(delete|reset)$/.test(action)) {
2690 not covered field.apply(item, action);
270 not covered }
2710 not covered if (req.body && req.body[paths.select]) {
272 not covered
2730 not covered cloudinary.api.resource(req.body[paths.select], function(result) {
2740 not covered callback(result.error);
275 not covered } else {
2760 not covered item.set(field.path, result);
2770 not covered callback();
278 not covered }
279 not covered });
280 not covered
2810 not covered } else if (req.files && req.files[paths.upload] && req.files[paths.upload].size) {
282 not covered
2830 not covered var tp = keystone.get('cloudinary prefix') || '';
284 not covered
285 not covered if (tp.length) {
2860 not covered tp += '_';
287 not covered }
2880 not covered var uploadOptions = {
2890 not covered tags: [tp + field.list.path + '_' + field.path, tp + field.list.path + '_' + field.path + '_' + item.id]
290 not covered };
291 not covered
2920 not covered if (item.get(field.paths.folder)) {
2930 not covered uploadOptions.folder = item.get(field.paths.folder);
294 not covered }
2950 not covered if (keystone.get('cloudinary prefix')) {
2960 not covered uploadOptions.tags.push(keystone.get('cloudinary prefix'));
297 not covered }
2980 not covered if (keystone.get('env') !== 'production') {
2990 not covered uploadOptions.tags.push(tp + 'dev');
300 not covered }
3010 not covered var publicIdValue = item.get(field.options.publicID);
3020 not covered if (publicIdValue) {
3030 not covered uploadOptions.public_id = publicIdValue;
304 not covered }
3050 not covered if (field.options.autoCleanup && item.get(field.paths.exists)) {
3060 not covered field.apply(item, 'delete');
307 not covered }
3080 not covered cloudinary.uploader.upload(req.files[paths.upload].path, function(result) {
3090 not covered callback(result.error);
310 not covered } else {
3110 not covered item.set(field.path, result);
3120 not covered callback();
313 not covered }
314 not covered }, uploadOptions);
315 not covered
316 not covered } else {
3170 not covered callback();
318 not covered }
319 not covered
320 not covered
321 not covered
322 not covered /**
323 not covered * Immediately handles a standard form submission for the field (see `getRequestHandler()`)
324 not covered *
325 not covered * @api public
326 not covered */
327 not covered
3281100%cloudinaryimage.prototype.handleRequest = function(item, req, paths, callback) {
3290 not covered this.getRequestHandler(item, req, paths, callback)();
330 not covered };
331 not covered
332 not covered
333 not covered /*!
334 not covered * Export class
335 not covered */
336 not covered
3371100%exports = module.exports = cloudinaryimage;

lib/fieldTypes/cloudinaryimages.js

0% block coverage
162 SLOC
LineHitsStatementsSourceAction
1 not covered /*!
2 not covered * Module dependencies.
3 not covered */
4 not covered
51100%var _ = require('underscore'),
61100% keystone = require('../../'),
71100% util = require('util'),
81100% cloudinary = require('cloudinary'),
91100% utils = require('keystone-utils'),
101100% super_ = require('../field'),
111100% async = require('async');
12 not covered
13 not covered /**
14 not covered * CloudinaryImages FieldType Constructor
15 not covered * @extends Field
16 not covered * @api public
17 not covered */
18 not covered
19 not covered function cloudinaryimages(list, path, options) {
20 not covered
210 not covered this._underscoreMethods = ['format'];
22 not covered
23 not covered // TODO: implement filtering, usage disabled for now
240 not covered options.nofilter = true;
25 not covered // TODO: implement initial form, usage disabled for now
26 not covered if (options.initial) {
270 not covered throw new Error('Invalid Configuration\n\n' +
28 not covered }
29 not covered
300 not covered cloudinaryimages.super_.call(this, list, path, options);
31 not covered
32 not covered // validate cloudinary config
330 not covered if (!keystone.get('cloudinary config')) {
34 not covered throw new Error('Invalid Configuration\n\n' +
350 not covered 'CloudinaryImages fields (' + list.key + '.' + this.path + ') require the "cloudinary config" option to be set.\n\n' +
360 not covered 'See http://keystonejs.com/docs/configuration/#cloudinary for more information.\n');
37 not covered }
38 not covered
39 not covered }
40 not covered
41 not covered /*!
42 not covered * Inherit from Field
43 not covered */
44 not covered
451100%util.inherits(cloudinaryimages, super_);
46 not covered
47 not covered
48 not covered /**
49 not covered * Registers the field on the List's Mongoose Schema.
50 not covered *
51 not covered * @api public
52 not covered */
53 not covered
541100%cloudinaryimages.prototype.addToSchema = function() {
550 not covered var mongoose = keystone.mongoose;
56 not covered
570 not covered var field = this,
58 not covered
590 not covered var paths = this.paths = {
600 not covered upload: this._path.append('_upload'),
610 not covered uploads: this._path.append('_uploads'),
620 not covered action: this._path.append('_action'),
630 not covered order: this._path.append('_order')
64 not covered };
65 not covered
660 not covered var ImageSchema = new mongoose.Schema({
67 not covered
680 not covered var src = function(img, options) {
690 not covered if (keystone.get('cloudinary secure')) {
700 not covered options = options || {};
710 not covered options.secure = true;
72 not covered }
730 not covered return img.public_id ? cloudinary.url(img.public_id + '.' + img.format, options) : '';
74 not covered };
75 not covered
760 not covered var addSize = function(options, width, height) {
770 not covered if (width) options.width = width;
780 not covered if (height) options.height = height;
790 not covered return options;
80 not covered };
81 not covered
820 not covered ImageSchema.method('src', function(options) {
830 not covered return src(this, options);
84 not covered });
85 not covered
860 not covered ImageSchema.method('scale', function(width, height) {
870 not covered return src(this, addSize({ crop: 'scale' }, width, height));
88 not covered });
89 not covered
900 not covered ImageSchema.method('fill', function(width, height) {
910 not covered return src(this, addSize({ crop: 'fill', gravity: 'faces' }, width, height));
92 not covered });
93 not covered
940 not covered ImageSchema.method('lfill', function(width, height) {
950 not covered return src(this, addSize({ crop: 'lfill', gravity: 'faces' }, width, height));
96 not covered });
97 not covered
980 not covered ImageSchema.method('fit', function(width, height) {
990 not covered return src(this, addSize({ crop: 'fit' }, width, height));
100 not covered });
101 not covered
1020 not covered ImageSchema.method('limit', function(width, height) {
1030 not covered return src(this, addSize({ crop: 'limit' }, width, height));
104 not covered });
105 not covered
1060 not covered ImageSchema.method('pad', function(width, height) {
1070 not covered return src(this, addSize({ crop: 'pad' }, width, height));
108 not covered });
109 not covered
1100 not covered ImageSchema.method('lpad', function(width, height) {
1110 not covered return src(this, addSize({ crop: 'lpad' }, width, height));
112 not covered });
113 not covered
1140 not covered ImageSchema.method('crop', function(width, height) {
1150 not covered return src(this, addSize({ crop: 'crop', gravity: 'faces' }, width, height));
116 not covered });
117 not covered
1180 not covered ImageSchema.method('thumbnail', function(width, height) {
1190 not covered return src(this, addSize({ crop: 'thumb', gravity: 'faces' }, width, height));
120 not covered });
121 not covered
1220 not covered schema.add(this._path.addTo({}, [ImageSchema]));
123 not covered
1240 not covered this.removeImage = function(item, id, method, callback) {
1250 not covered var images = item.get(field.path);
1260 not covered if ('number' != typeof id) {
1270 not covered for (var i = 0; i < images.length; i++) {
1280 not covered if (images[i].public_id == id) {
1290 not covered id = i;
130 not covered break;
1310 not covered var img = images[id];
1320 not covered if (!img) return;
1330 not covered if (method == 'delete') {
1340 not covered cloudinary.uploader.destroy(img.public_id, function() {});
135 not covered }
1360 not covered images.splice(id, 1);
1370 not covered if (callback) {
1380 not covered item.save(('function' != typeof callback) ? callback : undefined);
139 not covered }
140 not covered
1410 not covered this.underscoreMethod('remove', function(id, callback) {
1420 not covered field.removeImage(this, id, 'remove', callback);
143 not covered });
144 not covered
1450 not covered this.underscoreMethod('delete', function(id, callback) {
1460 not covered field.removeImage(this, id, 'delete', callback);
147 not covered });
148 not covered
1490 not covered this.bindUnderscoreMethods();
150 not covered };
151 not covered
152 not covered
153 not covered /**
154 not covered * Formats the field value
155 not covered *
156 not covered * @api public
157 not covered */
158 not covered
1591100%cloudinaryimages.prototype.format = function(item) {
1600 not covered return _.map(item.get(this.path), function(img) {
1610 not covered return img.src();
162 not covered }).join(', ');
163 not covered };
164 not covered
165 not covered
166 not covered /**
167 not covered * Detects whether the field has been modified
168 not covered *
169 not covered * @api public
170 not covered */
171 not covered
1721100%cloudinaryimages.prototype.isModified = function(item) { // TODO - how should this be detected?
1730 not covered return true;
174 not covered };
175 not covered
176 not covered
177 not covered /**
178 not covered * Validates that a value for this field has been provided in a data object
179 not covered *
180 not covered * @api public
181 not covered */
182 not covered
1831100%cloudinaryimages.prototype.validateInput = function(data) { // TODO - how should image field input be validated?
1840 not covered return true;
185 not covered };
186 not covered
187 not covered
188 not covered /**
189 not covered * Updates the value for this field in the item from a data object
190 not covered *
191 not covered * @api public
192 not covered */
193 not covered
1941100%cloudinaryimages.prototype.updateItem = function(item, data) { // TODO - direct updating of data (not via upload) };
195 not covered
196 not covered
197 not covered /**
198 not covered * Returns a callback that handles a standard form submission for the field
199 not covered *
200 not covered * Expected form parts are
201 not covered * - `field.paths.action` in `req.body` in syntax `delete:public_id,public_id|remove:public_id,public_id`
202 not covered * - `field.paths.upload` in `req.files` (uploads the images to cloudinary)
203 not covered *
204 not covered * @api public
205 not covered */
206 not covered
2071100%cloudinaryimages.prototype.getRequestHandler = function(item, req, paths, callback) {
2080 not covered var field = this;
209 not covered
2100 not covered if (utils.isFunction(paths)) {
2110 not covered callback = paths;
2120 not covered paths = field.paths;
2130 not covered } else if (!paths) {
2140 not covered paths = field.paths;
215 not covered }
2160 not covered callback = callback || function() {};
217 not covered
2180 not covered return function() {
2190 not covered var images = item.get(field.path),
220 not covered
2210 not covered images.sort(function(a, b) {
2220 not covered return (newOrder.indexOf(a.public_id) > newOrder.indexOf(b.public_id)) ? 1 : -1;
223 not covered });
224 not covered }
2250 not covered if (req.body && req.body[paths.action]) {
2260 not covered var actions = req.body[paths.action].split('|');
227 not covered
2280 not covered actions.forEach(function(action) {
2290 not covered action = action.split(':');
2300 not covered var method = action[0],
231 not covered
2320 not covered if (!method.match(/^(remove|delete)$/) || !ids) return;
233 not covered
2340 not covered ids.split(',').forEach(function(id) {
2350 not covered field.removeImage(item, id, method);
236 not covered });
237 not covered });
238 not covered }
2390 not covered var uploads = JSON.parse(req.body[paths.uploads]);
240 not covered
2410 not covered uploads.forEach(function(file) {
2420 not covered item.get(field.path).push(file);
243 not covered });
244 not covered }
2450 not covered if (req.files && req.files[paths.upload]) {
2460 not covered var files = _.flatten(req.files[paths.upload]);
247 not covered
2480 not covered var tp = keystone.get('cloudinary prefix') || '';
249 not covered
250 not covered if (tp.length)
2510 not covered tp += '_';
252 not covered
2530 not covered var uploadOptions = {
2540 not covered tags: [tp + field.list.path + '_' + field.path, tp + field.list.path + '_' + field.path + '_' + item.id]
255 not covered };
256 not covered
2570 not covered if (keystone.get('cloudinary prefix'))
2580 not covered uploadOptions.tags.push(keystone.get('cloudinary prefix'));
259 not covered
2600 not covered if (keystone.get('env') != 'production')
2610 not covered uploadOptions.tags.push(tp + 'dev');
262 not covered
2630 not covered async.each(files, function(file, next) {
2640 not covered if (!file.size) return next();
265 not covered
2660 not covered cloudinary.uploader.upload(file.path, function(result) {
267 not covered if (result.error) {
2680 not covered return next(result.error);
269 not covered } else {
2700 not covered item.get(field.path).push(result);
2710 not covered return next();
272 not covered }
273 not covered }, uploadOptions);
274 not covered
275 not covered }, function(err) {
2760 not covered return callback(err);
277 not covered });
278 not covered } else {
2790 not covered return callback();
280 not covered }
281 not covered
282 not covered
283 not covered
284 not covered /**
285 not covered * Immediately handles a standard form submission for the field (see `getRequestHandler()`)
286 not covered *
287 not covered * @api public
288 not covered */
289 not covered
2901100%cloudinaryimages.prototype.handleRequest = function(item, req, paths, callback) {
2910 not covered this.getRequestHandler(item, req, paths, callback)();
292 not covered };
293 not covered
294 not covered
295 not covered /*!
296 not covered * Export class
297 not covered */
298 not covered
2991100%exports = module.exports = cloudinaryimages;

lib/fieldTypes/code.js

0% block coverage
39 SLOC
LineHitsStatementsSourceAction
1 not covered /*!
2 not covered * Module dependencies.
3 not covered */
4 not covered
51100%var util = require('util'), super_ = require('../field');
6 not covered
7 not covered /**
8 not covered * Code FieldType Constructor
9 not covered * @extends Field
10 not covered * @api public
11 not covered */
12 not covered
13 not covered function code(list, path, options) {
14 not covered
150 not covered this._nativeType = String;
16 not covered
170 not covered this.height = options.height || 180;
180 not covered this.lang = options.lang;
190 not covered this.mime = getMime(this.lang);
20 not covered
21 not covered // TODO: implement initial form, usage disabled for now
22 not covered if (options.initial) {
230 not covered throw new Error('Invalid Configuration\n\n' +
24 not covered }
25 not covered
260 not covered code.super_.call(this, list, path, options);
27 not covered
28 not covered }
29 not covered
30 not covered /*!
31 not covered * Inherit from Field
32 not covered */
33 not covered
341100%util.inherits(code, super_);
35 not covered
36 not covered
37 not covered /**
38 not covered * Gets the mime type for the specified language
39 not covered * @api private
40 not covered */
41 not covered
42 not covered function getMime(lang) {
43 not covered
440 not covered var mime;
45 not covered
460 not covered switch (lang) {
47 not covered case 'c':
480 not covered mime = 'text/x-csrc'; break;
49 not covered case 'c++':
50 not covered case 'objetivec':
510 not covered mime = 'text/x-c++src'; break;
52 not covered case 'css':
530 not covered mime = 'text/css'; break;
54 not covered case 'asp':
550 not covered mime = 'application/x-aspx'; break;
56 not covered case 'c#':
570 not covered mime = 'text/x-csharp'; break;
58 not covered case 'vb':
590 not covered mime = 'text/x-vb'; break;
60 not covered case 'xml':
610 not covered mime = 'text/xml'; break;
62 not covered case 'php':
630 not covered mime = 'application/x-httpd-php'; break;
64 not covered case 'html':
650 not covered mime = 'text/html'; break;
66 not covered case 'ini':
670 not covered mime = 'text/x-properties'; break;
68 not covered case 'js':
690 not covered mime = 'text/javascript'; break;
70 not covered case 'java':
710 not covered mime = 'text/x-java'; break;
72 not covered case 'coffee':
730 not covered mime = 'text/x-coffeescript'; break;
74 not covered case 'lisp':
750 not covered mime = 'text/x-common-lisp'; break;
76 not covered case 'perl':
770 not covered mime = 'text/x-perl'; break;
78 not covered case 'python':
790 not covered mime = 'text/x-python'; break;
80 not covered case 'sql':
810 not covered mime = 'text/x-sql'; break;
82 not covered case 'json':
830 not covered mime = 'application/json'; break;
84 not covered case 'less':
850 not covered mime = 'text/x-less'; break;
86 not covered case 'sass':
870 not covered mime = 'text/x-sass'; break;
88 not covered case 'sh':
890 not covered mime = 'text/x-sh'; break;
90 not covered case 'ruby':
910 not covered mime = 'text/x-ruby'; break;
92 not covered case 'jsp':
930 not covered mime = 'application/x-jsp'; break;
94 not covered case 'tpl':
950 not covered mime = 'text/x-smarty'; break;
96 not covered case 'jade':
970 not covered mime = 'text/x-jade'; break;
98 not covered }
99 not covered
1000 not covered return mime;
101 not covered
102 not covered }
103 not covered
104 not covered
105 not covered /*!
106 not covered * Export class
107 not covered */
108 not covered
1091100%exports = module.exports = code;

lib/fieldTypes/color.js

0% block coverage
7 SLOC
LineHitsStatementsSourceAction
1 not covered /*!
2 not covered * Module dependencies.
3 not covered */
4 not covered
51100%var util = require('util'), super_ = require('../field');
6 not covered
7 not covered /**
8 not covered * Color FieldType Constructor
9 not covered * @extends Field
10 not covered * @api public
11 not covered */
12 not covered
13 not covered function color(list, path, options) {
140 not covered this._nativeType = String;
150 not covered this._underscoreMethods = [];
160 not covered color.super_.call(this, list, path, options);
17 not covered }
18 not covered
19 not covered /*!
20 not covered * Inherit from Field
21 not covered */
22 not covered
231100%util.inherits(color, super_);
24 not covered
25 not covered
26 not covered /*!
27 not covered * Export class
28 not covered */
29 not covered
301100%exports = module.exports = color;

lib/fieldTypes/date.js

29% block coverage
43 SLOC
LineHitsStatementsSourceAction
1 not covered /*!
2 not covered * Module dependencies.
3 not covered */
4 not covered
51100%var util = require('util'),
61100% moment = require('moment'),
71100% super_ = require('../field');
8 not covered
9 not covered /**
10 not covered * Date FieldType Constructor
11 not covered * @extends Field
12 not covered * @api public
13 not covered */
14 not covered
15 not covered function date(list, path, options) {
161100% this._nativeType = Date;
171100% this._underscoreMethods = ['format', 'moment', 'parse'];
18 not covered
19175% this._formatString = (options.format === false) ? not covered false: (options.format || 'Do MMM YYYY');
201100% if (this._formatString && 'string' !== typeof this._formatString) {
210 not covered throw new Error('FieldType.Date: options.format must be a string.');
22 not covered }
23 not covered
241100% date.super_.call(this, list, path, options);
25 not covered }
26 not covered
27 not covered /*!
28 not covered * Inherit from Field
29 not covered */
30 not covered
311100%util.inherits(date, super_);
32 not covered
33 not covered
34 not covered /**
35 not covered * Formats the field value
36 not covered *
37 not covered * @api public
38 not covered */
39 not covered
401100%date.prototype.format = function(item, format) {
412100% if (format || this._formatString) {
42288% return item.get(this.path) ? moment(item.get(this.path)).format(format || this._formatString) : not covered ''
43 not covered } else {
440 not covered return item.get(this.path) || '';
45 not covered }
46 not covered
47 not covered
48 not covered /**
49 not covered * Returns a new `moment` object with the field value
50 not covered *
51 not covered * @api public
52 not covered */
53 not covered
541100%date.prototype.moment = function(item) {
551100% return moment(item.get(this.path));
56 not covered };
57 not covered
58 not covered
59 not covered /**
60 not covered * Parses input using moment, sets the value, and returns the moment object.
61 not covered *
62 not covered * @api public
63 not covered */
64 not covered
651100%date.prototype.parse = function(item) {
661100% var newValue = moment.apply(moment, Array.prototype.slice.call(arguments, 1));
67186% item.set(this.path, (newValue && newValue.isValid()) ? newValue.toDate() : not covered null;
681100% return newValue;
69 not covered };
70 not covered
71 not covered /**
72 not covered * Checks that a valid date has been provided in a data object
73 not covered *
74 not covered * An empty value clears the stored value and is considered valid
75 not covered *
76 not covered * @api public
77 not covered */
78 not covered
791100%date.prototype.validateInput = function(data, required, item) {
800 not covered if (!(this.path in data) && item && item.get(this.path)) return true;
81 not covered
820 not covered var newValue = moment(data[this.path]);
83 not covered
840 not covered if (required && (!newValue || !newValue.isValid())) {
850 not covered return false;
860 not covered } else if (data[this.path] && newValue && !newValue.isValid()) {
870 not covered return false;
88 not covered } else {
890 not covered return true;
90 not covered }
91 not covered
92 not covered
93 not covered /**
94 not covered * Updates the value for this field in the item from a data object
95 not covered *
96 not covered * @api public
97 not covered */
98 not covered
991100%date.prototype.updateItem = function(item, data) {
1000 not covered if (!(this.path in data))
1010 not covered return;
102 not covered
1030 not covered var newValue = moment(data[this.path]);
104 not covered
1050 not covered if (newValue && newValue.isValid()) {
1060 not covered if (!item.get(this.path) || !newValue.isSame(item.get(this.path))) {
1070 not covered item.set(this.path, newValue.toDate());
108 not covered }
1090 not covered } else if (item.get(this.path)) {
1100 not covered item.set(this.path, null);
111 not covered }
112 not covered
113 not covered
114 not covered /*!
115 not covered * Export class
116 not covered */
117 not covered
1181100%exports = module.exports = date;

lib/fieldTypes/datetime.js

35% block coverage
54 SLOC
LineHitsStatementsSourceAction
1 not covered /*!
2 not covered * Module dependencies.
3 not covered */
4 not covered
51100%var util = require('util'),
61100% moment = require('moment'),
71100% super_ = require('../field');
8 not covered
91100%var parseFormats = ['YYYY-MM-DD', 'YYYY-MM-DD h:m:s a', 'YYYY-MM-DD h:m a', 'YYYY-MM-DD H:m:s', 'YYYY-MM-DD H:m'];
10 not covered
11 not covered /**
12 not covered * DateTime FieldType Constructor
13 not covered * @extends Field
14 not covered * @api public
15 not covered */
16 not covered
17 not covered function datetime(list, path, options) {
18 not covered
1915100% this._nativeType = Date;
2015100% this._underscoreMethods = ['format', 'moment', 'parse'];
2115100% this.typeDescription = 'date and time';
22 not covered
231575% this._formatString = (options.format === false) ? not covered false: (options.format || 'Do MMM YYYY hh:mm:ss a');
2415100% if (this._formatString && 'string' !== typeof this._formatString) {
250 not covered throw new Error('FieldType.DateTime: options.format must be a string.');
26 not covered }
27 not covered
2815100% datetime.super_.call(this, list, path, options);
29 not covered
3015100% this.paths = {
3115100% date: this._path.append('_date'),
3215100% time: this._path.append('_time')
33 not covered };
34 not covered
35 not covered }
36 not covered
37 not covered /*!
38 not covered * Inherit from Field
39 not covered */
40 not covered
411100%util.inherits(datetime, super_);
42 not covered
43 not covered
44 not covered /**
45 not covered * Formats the field value
46 not covered *
47 not covered * @api public
48 not covered */
491100%datetime.prototype.format = function(item, format) {
500 not covered if (format || this._formatString) {
510 not covered return item.get(this.path) ? moment(item.get(this.path)).format(format || this._formatString) : '';
52 not covered } else {
530 not covered return item.get(this.path) || '';
54 not covered }
55 not covered
56 not covered
57 not covered /**
58 not covered * Returns a new `moment` object with the field value
59 not covered *
60 not covered * @api public
61 not covered */
62 not covered
631100%datetime.prototype.moment = function(item) {
640 not covered return moment(item.get(this.path));
65 not covered };
66 not covered
67 not covered
68 not covered /**
69 not covered * Parses input using moment, sets the value, and returns the moment object.
70 not covered *
71 not covered * @api public
72 not covered */
73 not covered
741100%datetime.prototype.parse = function(item) {
750 not covered var newValue = moment.apply(moment, Array.prototype.slice.call(arguments, 1));
760 not covered item.set(this.path, (newValue && newValue.isValid()) ? newValue.toDate() : null);
770 not covered return newValue;
78 not covered };
79 not covered
80 not covered
81 not covered /**
82 not covered * Get the value from a data object; may be simple or a pair of fields
83 not covered *
84 not covered * @api private
85 not covered */
86 not covered
871100%datetime.prototype.getInputFromData = function(data) {
882860% if (this.paths.date in data && not covered this.paths.time in data {
890 not covered return (data[this.paths.date] + ' ' + data[this.paths.time]).trim();
90 not covered } else {
9128100% return data[this.path];
92 not covered }
93 not covered
94 not covered
95 not covered /**
96 not covered * Checks that a valid date has been provided in a data object
97 not covered *
98 not covered * An empty value clears the stored value and is considered valid
99 not covered *
100 not covered * @api public
101 not covered */
102 not covered
1031100%datetime.prototype.validateInput = function(data, required, item) {
1041447% if (!(this.path in data && not covered !(this.paths.date in data && this.paths.time in data) && item && not covered item.get(this.path) not covered return true;
105 not covered
10614100% var newValue = moment(this.getInputFromData(data), parseFormats);
107 not covered
1081429% if (required && not covered !newValue || !newValue.isValid()) {
1090 not covered return false;
1101450% } else if (this.getInputFromData(data) && not covered newValue&& not covered !newValue.isValid() {
1110 not covered return false;
112 not covered } else {
11314100% return true;
114 not covered }
115 not covered
116 not covered
117 not covered /**
118 not covered * Updates the value for this field in the item from a data object
119 not covered *
120 not covered * @api public
121 not covered */
122 not covered
1231100%datetime.prototype.updateItem = function(item, data) {
1241478% if (!(this.path in data || (this.paths.date in data && not covered this.paths.time in data))
12514100% return;
126 not covered
1270 not covered var newValue = moment(this.getInputFromData(data), parseFormats);
128 not covered
1290 not covered if (newValue && newValue.isValid()) {
1300 not covered if (!item.get(this.path) || !newValue.isSame(item.get(this.path))) {
1310 not covered item.set(this.path, newValue.toDate());
132 not covered }
1330 not covered } else if (item.get(this.path)) {
1340 not covered item.set(this.path, null);
135 not covered }
136 not covered
137 not covered
138 not covered /*!
139 not covered * Export class
140 not covered */
141 not covered
1421100%exports = module.exports = datetime;

lib/fieldTypes/email.js

0% block coverage
32 SLOC
LineHitsStatementsSourceAction
1 not covered /*!
2 not covered * Module dependencies.
3 not covered */
4 not covered
51100%var util = require('util'),
61100% utils = require('keystone-utils'),
71100% super_ = require('../field'),
81100% crypto = require('crypto');
9 not covered
10 not covered /**
11 not covered * Email FieldType Constructor
12 not covered * @extends Field
13 not covered * @api public
14 not covered */
15 not covered
16 not covered function email(list, path, options) {
170 not covered this._nativeType = String;
180 not covered this._underscoreMethods = ['gravatarUrl'];
190 not covered this.typeDescription = 'email address';
200 not covered email.super_.call(this, list, path, options);
21 not covered }
22 not covered
23 not covered /*!
24 not covered * Inherit from Field
25 not covered */
26 not covered
271100%util.inherits(email, super_);
28 not covered
29 not covered
30 not covered /**
31 not covered * Validates that a valid email has been provided in a data object
32 not covered *
33 not covered * @api public
34 not covered */
35 not covered
361100%email.prototype.validateInput = function(data, required, item) { if (data[this.path]) {
370 not covered return utils.isEmail(data[this.path]);
38 not covered } else {
390 not covered return (!required || (!(this.path in data) && item && item.get(this.path))) ? true : false;
40 not covered }
41 not covered
42 not covered
43 not covered /**
44 not covered * Updates the value for this field in the item from a data object
45 not covered * Ensures that the email address is lowercase
46 not covered *
47 not covered * @api public
48 not covered */
49 not covered
501100%email.prototype.updateItem = function(item, data) {
510 not covered var newValue = data[this.path];
52 not covered
530 not covered if ('string' === typeof newValue) {
540 not covered newValue = newValue.toLowerCase();
55 not covered }
560 not covered if (this.path in data && data[this.path] !== item.get(this.path)) {
570 not covered item.set(this.path, data[this.path]);
58 not covered }
59 not covered
60 not covered
61 not covered /**
62 not covered * Generate a gravatar image request url
63 not covered *
64 not covered * @api public
65 not covered */
661100%email.prototype.gravatarUrl = function(item, size, defaultImage, rating) {
670 not covered var value = item.get(this.path);
68 not covered
690 not covered if ('string' !== typeof value) {
700 not covered return '';
71 not covered }
720 not covered return [
730 not covered crypto.createHash('md5').update(value.toLowerCase().trim()).digest('hex'),
74 not covered
75 not covered // size of images ranging from 1 to 2048 pixels, square
760 not covered '?s=' + (/^(?:[1-9][0-9]{0,2}|1[0-9]{3}|20[0-3][0-9]|204[0-8])$/.test(size) ? size : 80),
77 not covered
78 not covered // default image url encoded href or one of the built in options: 404, mm, identicon, monsterid, wavatar, retro, blank
790 not covered '&d=' + (defaultImage ? encodeURIComponent(defaultImage) : 'identicon'),
80 not covered
81 not covered // rating, g, pg, r or x
820 not covered '&r=' + (/^(?:g|pg|r|x)$/i.test(rating) ? rating.toLowerCase() : 'g')
83 not covered ].join('');
84 not covered };
85 not covered
86 not covered /*!
87 not covered * Export class
88 not covered */
89 not covered
901100%exports = module.exports = email;

lib/fieldTypes/embedly.js

0% block coverage
94 SLOC
LineHitsStatementsSourceAction
1 not covered /*!
2 not covered * Module dependencies.
3 not covered */
4 not covered
51100%var _ = require('underscore'),
61100% keystone = require('../../'),
71100% util = require('util'),
81100% EmbedlyAPI = require('embedly'),
91100% super_ = require('../field');
10 not covered
11 not covered /**
12 not covered * Embedly FieldType Constructor
13 not covered *
14 not covered * Reqires the option `from` to refer to another path in the schema
15 not covered * that provides the url to expand
16 not covered *
17 not covered * @extends Field
18 not covered * @api public
19 not covered */
20 not covered
21 not covered function embedly(list, path, options) {
22 not covered
230 not covered this._underscoreMethods = ['reset'];
240 not covered this.fromPath = options.from;
250 not covered this.embedlyOptions = options.options || {};
26 not covered
27 not covered // TODO: implement filtering, usage disabled for now
280 not covered options.nofilter = true;
29 not covered
30 not covered // check and api key has been set, or bail.
310 not covered if (!keystone.get('embedly api key')) {
32 not covered throw new Error('Invalid Configuration\n\n' +
330 not covered 'Embedly fields (' + list.key + '.' + this.path + ') require the "embedly api key" option to be set.\n\n' +
340 not covered 'See http://keystonejs.com/docs/configuration/#embedly for more information.\n');
35 not covered }
36 not covered
37 not covered // ensure a fromPath has been defined
380 not covered if (!options.from) {
39 not covered throw new Error('Invalid Configuration\n\n' +
400 not covered 'Embedly fields (' + list.key + '.' + path + ') require a fromPath option to be set.\n' +
410 not covered 'See http://keystonejs.com/docs/database/#field_embedly for more information.\n');
42 not covered }
43 not covered
44 not covered // embedly fields cannot be set as initial fields
45 not covered if (options.initial) {
460 not covered throw new Error('Invalid Configuration\n\n' +
47 not covered }
48 not covered
490 not covered embedly.super_.call(this, list, path, options);
50 not covered
51 not covered }
52 not covered
53 not covered /*!
54 not covered * Inherit from Field
55 not covered */
56 not covered
571100%util.inherits(embedly, super_);
58 not covered
59 not covered
60 not covered /**
61 not covered * Registers the field on the List's Mongoose Schema.
62 not covered *
63 not covered * @api public
64 not covered */
65 not covered
661100%embedly.prototype.addToSchema = function() {
670 not covered var field = this,
68 not covered
690 not covered this.paths = {
700 not covered exists: this._path.append('.exists'),
710 not covered type: this._path.append('.type'),
720 not covered title: this._path.append('.title'),
730 not covered url: this._path.append('.url'),
740 not covered width: this._path.append('.width'),
750 not covered height: this._path.append('.height'),
760 not covered version: this._path.append('.version'),
770 not covered description: this._path.append('.description'),
780 not covered html: this._path.append('.html'),
790 not covered authorName: this._path.append('.authorName'),
800 not covered authorUrl: this._path.append('.authorUrl'),
810 not covered providerName: this._path.append('.providerName'),
820 not covered providerUrl: this._path.append('.providerUrl'),
830 not covered thumbnailUrl: this._path.append('.thumbnailUrl'),
840 not covered thumbnailWidth: this._path.append('.thumbnailWidth'),
850 not covered thumbnailHeight: this._path.append('.thumbnailHeight')
86 not covered };
87 not covered
880 not covered schema.nested[this.path] = true;
890 not covered schema.add({
90 not covered exists: Boolean,
910 not covered schema.pre('save', function(next) {
920 not covered if (!this.isModified(field.fromPath)) {
930 not covered return next();
94 not covered }
95 not covered
960 not covered var fromValue = this.get(field.fromPath);
97 not covered
980 not covered if (!fromValue) {
990 not covered field.reset(this);
1000 not covered return next();
101 not covered }
1020 not covered var post = this;
103 not covered
1040 not covered new EmbedlyAPI({ key: keystone.get('embedly api key') }, function(err, api) {
1050 not covered if (err) {
1060 not covered console.error('Error creating Embedly api:');
1070 not covered console.error(err, api);
1080 not covered field.reset(this);
1090 not covered return next();
110 not covered }
1110 not covered var opts = _.defaults({ url: fromValue }, field.embedlyOptions);
112 not covered
1130 not covered api.oembed(opts, function(err, objs) {
1140 not covered if (err) {
1150 not covered console.error('Embedly API Error:');
1160 not covered console.error(err, objs);
1170 not covered field.reset(post);
118 not covered } else {
1190 not covered var data = objs[0];
1200 not covered if (data && data.type !== 'error') {
1210 not covered post.set(field.path, {
122 not covered exists: true,
123 not covered type: data.type,
1240 not covered field.reset(post);
125 not covered }
1260 not covered return next();
127 not covered
128 not covered });
129 not covered
130 not covered
1310 not covered this.bindUnderscoreMethods();
132 not covered
133 not covered
134 not covered
135 not covered /**
136 not covered * Resets the value of the field
137 not covered *
138 not covered * @api public
139 not covered */
140 not covered
1411100%embedly.prototype.reset = function(item) {
1420 not covered return item.set(item.set(this.path, {
143 not covered };
144 not covered
145 not covered
146 not covered /**
147 not covered * Formats the field value
148 not covered *
149 not covered * @api public
150 not covered */
151 not covered
1521100%embedly.prototype.format = function(item) {
1530 not covered return item.get(this.paths.html);
154 not covered };
155 not covered
156 not covered
157 not covered /**
158 not covered * Detects whether the field has been modified
159 not covered *
160 not covered * @api public
161 not covered */
162 not covered
1631100%embedly.prototype.isModified = function(item) { // Assume that it has changed if the url is different
1640 not covered return item.isModified(this.paths.url);
165 not covered };
166 not covered
167 not covered
168 not covered /**
169 not covered * Validates that a value for this field has been provided in a data object
170 not covered *
171 not covered * @api public
172 not covered */
173 not covered
1741100%embedly.prototype.validateInput = function(data) { // TODO: I don't think embedly fields need to be validated...
1750 not covered return true;
176 not covered };
177 not covered
178 not covered
179 not covered /**
180 not covered * Updates the value for this field in the item from a data object
181 not covered *
182 not covered * @api public
183 not covered */
184 not covered
1851100%embedly.prototype.updateItem = function(item, data) { // TODO: This could be more granular and check for actual changes to values, // see the Location field for an example
1860 not covered return item.set(item.set(this.path, {
187 not covered };
188 not covered
189 not covered
190 not covered /*!
191 not covered * Export class
192 not covered */
193 not covered
1941100%exports = module.exports = embedly;

lib/fieldTypes/html.js

0% block coverage
9 SLOC
LineHitsStatementsSourceAction
1 not covered /*!
2 not covered * Module dependencies.
3 not covered */
4 not covered
51100%var util = require('util'), super_ = require('../field');
6 not covered
7 not covered /**
8 not covered * HTML FieldType Constructor
9 not covered * @extends Field
10 not covered * @api public
11 not covered */
12 not covered
13 not covered function html(list, path, options) {
14 not covered
150 not covered this._nativeType = String;
16 not covered
17 not covered // TODO: implement filtering, usage disabled for now
180 not covered options.nofilter = true;
19 not covered
200 not covered this.wysiwyg = (options.wysiwyg) ? true : false;
210 not covered this.height = options.height || 180;
22 not covered
230 not covered html.super_.call(this, list, path, options);
24 not covered
25 not covered }
26 not covered
27 not covered /*!
28 not covered * Inherit from Field
29 not covered */
30 not covered
311100%util.inherits(html, super_);
32 not covered
33 not covered
34 not covered /*!
35 not covered * Export class
36 not covered */
37 not covered
381100%exports = module.exports = html;

lib/fieldTypes/key.js

0% block coverage
23 SLOC
LineHitsStatementsSourceAction
1 not covered /*!
2 not covered * Module dependencies.
3 not covered */
4 not covered
51100%var util = require('util'),
61100% utils = require('keystone-utils'),
71100% super_ = require('../field');
8 not covered
9 not covered /**
10 not covered * Key FieldType Constructor
11 not covered * @extends Field
12 not covered * @api public
13 not covered */
14 not covered
15 not covered function key(list, path, options) {
160 not covered this._nativeType = String;
170 not covered this.separator = options.separator || '-';
180 not covered key.super_.call(this, list, path, options);
19 not covered }
20 not covered
21 not covered /*!
22 not covered * Inherit from Field
23 not covered */
24 not covered
251100%util.inherits(key, super_);
26 not covered
27 not covered
28 not covered /**
29 not covered * Generates a valid key from a string
30 not covered *
31 not covered * @api public
32 not covered */
33 not covered
341100%key.prototype.generateKey = function(str) {
350 not covered return utils.slug(String(str), this.separator);
36 not covered };
37 not covered
38 not covered
39 not covered /**
40 not covered * Checks that a valid key has been provided in a data object
41 not covered *
42 not covered * @api public
43 not covered */
44 not covered
451100%key.prototype.validateInput = function(data, required, item) {
460 not covered if (!(this.path in data) && item && item.get(this.path)) return true;
47 not covered
480 not covered var newValue = this.generateKey(data[this.path]);
49 not covered
500 not covered return (newValue || !required);
51 not covered
52 not covered
53 not covered
54 not covered /**
55 not covered * Updates the value for this field in the item from a data object
56 not covered *
57 not covered * @api public
58 not covered */
59 not covered
601100%key.prototype.updateItem = function(item, data) {
610 not covered if (!(this.path in data)) {
620 not covered return;
63 not covered }
640 not covered var newValue = this.generateKey(data[this.path]);
65 not covered
660 not covered if (item.get(this.path) !== newValue) {
670 not covered item.set(this.path, newValue);
68 not covered }
69 not covered
70 not covered
71 not covered /*!
72 not covered * Export class
73 not covered */
74 not covered
751100%exports = module.exports = key;

lib/fieldTypes/localfile.js

0% block coverage
158 SLOC
LineHitsStatementsSourceAction
1 not covered /*!
2 not covered * Module dependencies.
3 not covered */
4 not covered
51100%var fs = require('fs'),
61100% path = require('path'),
71100% _ = require('underscore'),
81100% moment = require('moment'),
91100% async = require('async'),
101100% util = require('util'),
111100% utils = require('keystone-utils'),
121100% super_ = require('../field');
13 not covered
14 not covered /**
15 not covered * localfile FieldType Constructor
16 not covered * @extends Field
17 not covered * @api public
18 not covered */
19 not covered
20 not covered function localfile(list, path, options) {
210 not covered this._underscoreMethods = ['format', 'uploadFile'];
22 not covered
23 not covered // event queues
240 not covered this._pre = {
25 not covered
260 not covered this._post = {
27 not covered
28 not covered // TODO: implement filtering, usage disabled for now
290 not covered options.nofilter = true;
30 not covered
31 not covered // TODO: implement initial form, usage disabled for now
32 not covered if (options.initial) {
330 not covered throw new Error('Invalid Configuration\n\n' +
34 not covered }
35 not covered
360 not covered localfile.super_.call(this, list, path, options);
37 not covered
38 not covered // validate destination dir
390 not covered if (!options.dest) {
400 not covered throw new Error('Invalid Configuration\n\n' +
41 not covered }
42 not covered
43 not covered // Allow hook into before and after
440 not covered if (options.pre && options.pre.move) {
450 not covered this._pre.move = this._pre.move.concat(options.pre.move);
46 not covered }
47 not covered
480 not covered if (options.post && options.post.move) {
490 not covered this._post.move = this._post.move.concat(options.post.move);
50 not covered }
51 not covered }
52 not covered
53 not covered /*!
54 not covered * Inherit from Field
55 not covered */
56 not covered
571100%util.inherits(localfile, super_);
58 not covered
59 not covered
60 not covered /**
61 not covered * Allows you to add pre middleware after the field has been initialised
62 not covered *
63 not covered * @api public
64 not covered */
65 not covered
661100%localfile.prototype.pre = function(event, fn) {
670 not covered if (!this._pre[event]) {
680 not covered throw new Error('localfile (' + this.list.key + '.' + this.path + ') error: localfile.pre()\n\n' +
69 not covered }
700 not covered this._pre[event].push(fn);
710 not covered return this;
72 not covered };
73 not covered
74 not covered
75 not covered /**
76 not covered * Allows you to add post middleware after the field has been initialised
77 not covered *
78 not covered * @api public
79 not covered */
80 not covered
811100%localfile.prototype.post = function(event, fn) {
820 not covered if (!this._post[event]) {
830 not covered throw new Error('localfile (' + this.list.key + '.' + this.path + ') error: localfile.post()\n\n' +
84 not covered }
850 not covered this._post[event].push(fn);
860 not covered return this;
87 not covered };
88 not covered
89 not covered
90 not covered /**
91 not covered * Registers the field on the List's Mongoose Schema.
92 not covered *
93 not covered * @api public
94 not covered */
95 not covered
961100%localfile.prototype.addToSchema = function() {
970 not covered var field = this,
98 not covered
990 not covered var paths = this.paths = {
1000 not covered filename: this._path.append('.filename'),
1010 not covered path: this._path.append('.path'),
1020 not covered size: this._path.append('.size'),
1030 not covered filetype: this._path.append('.filetype'),
104 not covered // virtuals
1050 not covered exists: this._path.append('.exists'),
1060 not covered upload: this._path.append('_upload'),
1070 not covered action: this._path.append('_action')
108 not covered };
109 not covered
1100 not covered var schemaPaths = this._path.addTo({}, {
111 not covered
1120 not covered schema.add(schemaPaths);
113 not covered
1140 not covered var exists = function(item) {
1150 not covered var filepath = item.get(paths.path),
116 not covered
1170 not covered if (!filepath || !filename) {
1180 not covered return false;
119 not covered }
1200 not covered return fs.existsSync(path.join(filepath, filename));
121 not covered };
122 not covered
123 not covered // The .exists virtual indicates whether a file is stored
1240 not covered schema.virtual(paths.exists).get(function() {
1250 not covered return schemaMethods.exists.apply(this);
126 not covered });
127 not covered
1280 not covered var reset = function(item) {
1290 not covered item.set(field.path, {
130 not covered };
131 not covered
1320 not covered var schemaMethods = {
1330 not covered return exists(this);
134 not covered },
1350 not covered reset(this);
136 not covered },
1370 not covered if (exists(this)) {
1380 not covered fs.unlinkSync(path.join(this.get(paths.path), this.get(paths.filename)));
139 not covered }
1400 not covered reset(this);
141 not covered }
142 not covered
1430 not covered _.each(schemaMethods, function(fn, key) {
1440 not covered field.underscoreMethod(key, fn);
145 not covered });
146 not covered
147 not covered // expose a method on the field to call schema methods
1480 not covered this.apply = function(item, method) {
1490 not covered return schemaMethods[method].apply(item, Array.prototype.slice.call(arguments, 2));
150 not covered };
151 not covered
1520 not covered this.bindUnderscoreMethods();
153 not covered };
154 not covered
155 not covered
156 not covered /**
157 not covered * Formats the field value
158 not covered *
159 not covered * Delegates to the options.format function if it exists.
160 not covered * @api public
161 not covered */
162 not covered
1631100%localfile.prototype.format = function(item) {
1640 not covered if (this.hasFormatter()) {
1650 not covered return this.options.format(item, item[this.path]);
166 not covered }
1670 not covered return this.href(item);
168 not covered };
169 not covered
170 not covered
171 not covered /**
172 not covered * Detects the field have formatter function
173 not covered *
174 not covered * @api public
175 not covered */
176 not covered
1771100%localfile.prototype.hasFormatter = function() {
1780 not covered return 'function' === typeof this.options.format;
179 not covered };
180 not covered
181 not covered
182 not covered /**
183 not covered * Return objects href
184 not covered *
185 not covered * @api public
186 not covered */
187 not covered
1881100%localfile.prototype.href = function(item) { if (this.options.prefix) {
1890 not covered return this.options.prefix + '/' + item.get(this.paths.filename);
190 not covered }
1910 not covered return path.join(item.get(this.paths.path), item.get(this.paths.filename));
192 not covered };
193 not covered
194 not covered
195 not covered /**
196 not covered * Detects whether the field has been modified
197 not covered *
198 not covered * @api public
199 not covered */
200 not covered
2011100%localfile.prototype.isModified = function(item) {
2020 not covered return item.isModified(this.paths.path);
203 not covered };
204 not covered
205 not covered
206 not covered /**
207 not covered * Validates that a value for this field has been provided in a data object
208 not covered *
209 not covered * @api public
210 not covered */
211 not covered
2121100%localfile.prototype.validateInput = function(data) { // TODO - how should file field input be validated?
2130 not covered return true;
214 not covered };
215 not covered
216 not covered
217 not covered /**
218 not covered * Updates the value for this field in the item from a data object
219 not covered *
220 not covered * @api public
221 not covered */
222 not covered
2231100%localfile.prototype.updateItem = function(item, data) { // TODO - direct updating of data (not via upload) };
224 not covered
225 not covered
226 not covered /**
227 not covered * Uploads the file for this field
228 not covered *
229 not covered * @api public
230 not covered */
231 not covered
2321100%localfile.prototype.uploadFile = function(item, file, update, callback) {
2330 not covered var field = this,
2340 not covered prefix = field.options.datePrefix ? moment().format(field.options.datePrefix) + '-' : '',
2350 not covered name = prefix + file.name;
236 not covered
2370 not covered if (field.options.allowedTypes && !_.contains(field.options.allowedTypes, file.type)){
2380 not covered return callback(new Error('Unsupported File Type: '+file.type));
239 not covered }
2400 not covered if ('function' === typeof update) {
2410 not covered callback = update;
2420 not covered update = false;
243 not covered }
2440 not covered var doMove = function(callback) {
2450 not covered if ('function' === typeof field.options.filename) {
2460 not covered name = field.options.filename(item, name);
247 not covered }
2480 not covered fs.rename(file.path, path.join(field.options.dest, name), function(err) {
2490 not covered if (err) return callback(err);
250 not covered
2510 not covered var fileData = {
252 not covered filename: name,
253 not covered
2540 not covered if (update) {
2550 not covered item.set(field.path, fileData);
256 not covered }
2570 not covered callback(null, fileData);
258 not covered
259 not covered };
260 not covered
2610 not covered async.eachSeries(this._pre.move, function(fn, next) {
2620 not covered fn(item, file, next);
263 not covered }, function(err) {
264 not covered
2650 not covered if (err) return callback(err);
266 not covered
2670 not covered doMove(function(err, fileData) {
2680 not covered if (err) return callback(err);
269 not covered
2700 not covered async.eachSeries(field._post.move, function(fn, next) {
2710 not covered fn(item, file, fileData, next);
272 not covered }, function(err) {
2730 not covered if (err) return callback(err);
2740 not covered callback(null, fileData);
275 not covered });
276 not covered });
277 not covered
278 not covered };
279 not covered
280 not covered
281 not covered /**
282 not covered * Returns a callback that handles a standard form submission for the field
283 not covered *
284 not covered * Expected form parts are
285 not covered * - `field.paths.action` in `req.body` (`clear` or `delete`)
286 not covered * - `field.paths.upload` in `req.files` (uploads the file to localfile)
287 not covered *
288 not covered * @api public
289 not covered */
290 not covered
2911100%localfile.prototype.getRequestHandler = function(item, req, paths, callback) {
2920 not covered var field = this;
293 not covered
2940 not covered if (utils.isFunction(paths)) {
2950 not covered callback = paths;
2960 not covered paths = field.paths;
2970 not covered } else if (!paths) {
2980 not covered paths = field.paths;
299 not covered }
3000 not covered callback = callback || function() {};
301 not covered
3020 not covered return function() {
3030 not covered var action = req.body[paths.action];
304 not covered
3050 not covered if (/^(delete|reset)$/.test(action)) {
3060 not covered field.apply(item, action);
307 not covered }
3080 not covered if (req.files && req.files[paths.upload] && req.files[paths.upload].size) {
3090 not covered return field.uploadFile(item, req.files[paths.upload], true, callback);
310 not covered }
3110 not covered return callback();
312 not covered
313 not covered
314 not covered
315 not covered
316 not covered /**
317 not covered * Immediately handles a standard form submission for the field (see `getRequestHandler()`)
318 not covered *
319 not covered * @api public
320 not covered */
321 not covered
3221100%localfile.prototype.handleRequest = function(item, req, paths, callback) {
3230 not covered this.getRequestHandler(item, req, paths, callback)();
324 not covered };
325 not covered
326 not covered
327 not covered /*!
328 not covered * Export class
329 not covered */
330 not covered
3311100%exports = module.exports = localfile;

lib/fieldTypes/location.js

27% block coverage
249 SLOC
LineHitsStatementsSourceAction
1 not covered /*!
2 not covered * Module dependencies.
3 not covered */
4 not covered
51100%var _ = require('underscore'),
61100% keystone = require('../../'),
71100% querystring = require('querystring'),
81100% https = require('https'),
91100% util = require('util'),
101100% utils = require('keystone-utils'),
111100% super_ = require('../field');
12 not covered
131100%var RADIUS_KM = 6371, RADIUS_MILES = 3959;
14 not covered
15 not covered /**
16 not covered * Location FieldType Constructor
17 not covered * @extends Field
18 not covered * @api public
19 not covered */
20 not covered
21 not covered function location(list, path, options) {
22 not covered
232100% this._underscoreMethods = ['format', 'googleLookup', 'kmFrom', 'milesFrom'];
24 not covered
25275% this.enableMapsAPI = keystone.get('google api key') ? not covered true: false;
26 not covered
272100% if (!options.defaults) {
282100% options.defaults = {};
29 not covered }
30 not covered
31 not covered if (options.required) {
321100% if (Array.isArray(options.required)) {
33 not covered // required can be specified as an array of paths
341100% this.requiredPaths = options.required;
350 not covered } else if ('string' === typeof options.required) {
36 not covered // or it can be specified as a comma-delimited list
370 not covered this.requiredPaths = options.required.replace(/,/g, ' ').split(/\s+/);
38 not covered }
39 not covered // options.required should always be simplified to a boolean
401100% options.required = true;
41 not covered }
42 not covered // default this.requiredPaths
432100% if (!this.requiredPaths) {
441100% this.requiredPaths = ['street1', 'suburb'];
45 not covered }
46 not covered
472100% location.super_.call(this, list, path, options);
48 not covered
49 not covered }
50 not covered
51 not covered /*!
52 not covered * Inherit from Field
53 not covered */
54 not covered
551100%util.inherits(location, super_);
56 not covered
57 not covered
58 not covered /**
59 not covered * Registers the field on the List's Mongoose Schema.
60 not covered *
61 not covered * @api public
62 not covered */
63 not covered
641100%location.prototype.addToSchema = function() {
652100% var field = this, schema = this.list.schema, options = this.options;
66 not covered
672100% var paths = this.paths = {
682100% number: this._path.append('.number'),
692100% name: this._path.append('.name'),
702100% street1: this._path.append('.street1'),
712100% street2: this._path.append('.street2'),
722100% suburb: this._path.append('.suburb'),
732100% state: this._path.append('.state'),
742100% postcode: this._path.append('.postcode'),
752100% country: this._path.append('.country'),
762100% geo: this._path.append('.geo'),
772100% geo_lat: this._path.append('.geo_lat'),
782100% geo_lng: this._path.append('.geo_lng'),
792100% serialised: this._path.append('.serialised'),
802100% improve: this._path.append('_improve'),
812100% overwrite: this._path.append('_improve_overwrite')
82 not covered };
83 not covered
842100% var getFieldDef = function(type, key) {
8518100% var def = { type: type };
86 not covered if (options.defaults[key]) {
870 not covered def.default = options.defaults[key];
88 not covered }
8918100% return def;
90 not covered };
91 not covered
922100% schema.nested[this.path] = true;
932100% schema.add({
942100% number: getFieldDef(String, 'number'),
952100% name: getFieldDef(String, 'name'),
962100% street1: getFieldDef(String, 'street1'),
972100% street2: getFieldDef(String, 'street2'),
982100% street3: getFieldDef(String, 'street3'),
992100% suburb: getFieldDef(String, 'suburb'),
1002100% state: getFieldDef(String, 'state'),
1012100% postcode: getFieldDef(String, 'postcode'),
1022100% country: getFieldDef(String, 'country'),
103 not covered geo: { type: [Number], index: '2dsphere' }
1042100% }, this.path + '.');
1052100% schema.virtual(paths.serialised).get(function() {
1060 not covered return _.compact([
1070 not covered this.get(paths.number),
1080 not covered this.get(paths.name),
1090 not covered this.get(paths.street1),
1100 not covered this.get(paths.street2),
1110 not covered this.get(paths.suburb),
1120 not covered this.get(paths.state),
1130 not covered this.get(paths.postcode),
1140 not covered this.get(paths.country)
115 not covered ]).join(', ');
116 not covered });
117 not covered
118 not covered // pre-save hook to fix blank geo fields
119 not covered // see http://stackoverflow.com/questions/16388836/does-applying-a-2dsphere-index-on-a-mongoose-schema-force-the-location-field-to
1202100% schema.pre('save', function(next) {
1210 not covered var obj = field._path.get(this);
1220 not covered if (Array.isArray(obj.geo) && (obj.geo.length !== 2 || (obj.geo[0] === null && obj.geo[1] === null))) {
1230 not covered obj.geo = undefined;
124 not covered }
1250 not covered next();
126 not covered });
127 not covered
1282100% this.bindUnderscoreMethods();
129 not covered
130 not covered
131 not covered
132 not covered /**
133 not covered * Formats a list of the values stored by the field. Only paths that
134 not covered * have values will be included.
135 not covered *
136 not covered * Optionally provide a space-separated list of values to include.
137 not covered *
138 not covered * Delimiter defaults to `', '`.
139 not covered *
140 not covered * @api public
141 not covered */
142 not covered
1431100%location.prototype.format = function(item, values, delimiter) {
1440 not covered if (!values) {
1450 not covered return item.get(this.paths.serialised);
146 not covered }
1470 not covered var paths = this.paths;
148 not covered
1490 not covered values = values.split(' ').map(function(i) {
1500 not covered return item.get(paths[i]);
151 not covered });
152 not covered
1530 not covered return _.compact(values).join(delimiter || ', ');
154 not covered
155 not covered
156 not covered
157 not covered /**
158 not covered * Detects whether the field has been modified
159 not covered *
160 not covered * @api public
161 not covered */
162 not covered
1631100%location.prototype.isModified = function(item) {
164 not covered return item.isModified(this.paths.number) ||
1650 not covered item.isModified(this.paths.name) ||
1660 not covered item.isModified(this.paths.street1) ||
1670 not covered item.isModified(this.paths.street2) ||
1680 not covered item.isModified(this.paths.suburb) ||
1690 not covered item.isModified(this.paths.state) ||
1700 not covered item.isModified(this.paths.postcode) ||
1710 not covered item.isModified(this.paths.country) ||
1720 not covered item.isModified(this.paths.geo);
173 not covered };
174 not covered
175 not covered
176 not covered /**
177 not covered * Validates that a value for this field has been provided in a data object
178 not covered *
179 not covered * options.required specifies an array or space-delimited list of paths that
180 not covered * are required (defaults to street1, suburb)
181 not covered *
182 not covered * @api public
183 not covered */
184 not covered
1851100%location.prototype.validateInput = function(data, required, item) {
1866100% if (!required) {
1870 not covered return true;
188 not covered }
1896100% var paths = this.paths,
1906100% nested = this._path.get(data),
1916100% values = nested || data,
192 not covered valid = true;
193 not covered
1946100% this.requiredPaths.forEach(function(path) {
19512100% if (nested) {
196275% if (!(path in values) && not covered item&& not covered item.get(paths[path]) {
1970 not covered return;
198 not covered }
1992100% if (!values[path]) {
2000 not covered valid = false;
201 not covered }
202 not covered } else {
20310100% if (!(paths[path] in values) && item && item.get(paths[path])) {
2040 not covered return;
205 not covered }
20610100% if (!values[paths[path]]) {
2075100% valid = false;
208 not covered }
209 not covered
2106100% return valid;
211 not covered
212 not covered
213 not covered
214 not covered /**
215 not covered * Updates the value for this field in the item from a data object
216 not covered *
217 not covered * @api public
218 not covered */
219 not covered
2201100%location.prototype.updateItem = function(item, data) {
2214100% var paths = this.paths, fieldKeys = ['number', 'name', 'street1', 'street2', 'suburb', 'state', 'postcode', 'country'], geoKeys = ['geo', 'geo_lat', 'geo_lng'],
2224100% valueKeys = fieldKeys.concat(geoKeys),
223 not covered valuePaths = valueKeys,
2244100% values = this._path.get(data);
225 not covered
2264100% if (!values) {
227 not covered // Handle flattened values
2283100% valuePaths = valueKeys.map(function(i) {
22933100% return paths[i];
230 not covered });
2313100% values = _.pick(data, valuePaths);
232 not covered }
2334100% valuePaths = _.object(valueKeys, valuePaths);
234 not covered
2354100% var setValue = function(key) {
23632100% if (valuePaths[key] in values && values[valuePaths[key]] !== item.get(paths[key])) {
23716100% item.set(paths[key], values[valuePaths[key]] || null);
238 not covered }
239 not covered
2404100% _.each(fieldKeys, setValue);
241 not covered
2424100% if (valuePaths.geo in values) {
243 not covered
2442100% var oldGeo = item.get(paths.geo) || [], newGeo = values[valuePaths.geo];
245 not covered
2462100% if (!Array.isArray(newGeo) || newGeo.length !== 2) {
2470 not covered newGeo = [];
248 not covered }
249267% if (newGeo[0] !== oldGeo[0] || not covered newGeo[1] !== oldGeo[1] {
2502100% item.set(paths.geo, newGeo);
251 not covered }
2522100% } else if (valuePaths.geo_lat in values && valuePaths.geo_lng in values) {
253 not covered
2542100% var lat = utils.number(values[valuePaths.geo_lat]), lng = utils.number(values[valuePaths.geo_lng]);
255 not covered
2562100% item.set(paths.geo, (lat && lng) ? [lng, lat] : undefined);
257 not covered
258 not covered
259 not covered
260 not covered /**
261 not covered * Returns a callback that handles a standard form submission for the field
262 not covered *
263 not covered * Handles:
264 not covered * - `field.paths.improve` in `req.body` - improves data via `.googleLookup()`
265 not covered * - `field.paths.overwrite` in `req.body` - in conjunction with `improve`, overwrites existing data
266 not covered *
267 not covered * @api public
268 not covered */
269 not covered
2701100%location.prototype.getRequestHandler = function(item, req, paths, callback) {
2710 not covered var field = this;
272 not covered
2730 not covered if (utils.isFunction(paths)) {
2740 not covered callback = paths;
2750 not covered paths = field.paths;
2760 not covered } else if (!paths) {
2770 not covered paths = field.paths;
278 not covered }
2790 not covered callback = callback || function() {};
280 not covered
2810 not covered return function() {
2820 not covered var update = req.body[paths.overwrite] ? 'overwrite' : true;
283 not covered
2840 not covered if (req.body && req.body[paths.improve]) {
2850 not covered field.googleLookup(item, false, update, function() {
2860 not covered callback();
287 not covered });
288 not covered } else {
2890 not covered callback();
290 not covered }
291 not covered
292 not covered
293 not covered
294 not covered /**
295 not covered * Immediately handles a standard form submission for the field (see `getRequestHandler()`)
296 not covered *
297 not covered * @api public
298 not covered */
299 not covered
3001100%location.prototype.handleRequest = function(item, req, paths, callback) {
3010 not covered this.getRequestHandler(item, req, paths, callback)();
302 not covered };
303 not covered
304 not covered
305 not covered /**
306 not covered * Internal Google geocode request method
307 not covered *
308 not covered * @api private
309 not covered */
310 not covered
311 not covered function doGoogleGeocodeRequest(address, region, callback) {
312 not covered
313 not covered // https://developers.google.com/maps/documentation/geocoding/
314 not covered // Use of the Google Geocoding API is subject to a query limit of 2,500 geolocation requests per day, except with an enterprise license.
315 not covered // Note: the Geocoding API may only be used in conjunction with a Google map; geocoding results without displaying them on a map is prohibited.
316 not covered // Please make sure your Keystone app complies with the Google Maps API License.
317 not covered
3180 not covered var options = {
319 not covered
3200 not covered if (arguments.length === 2 && _.isFunction(region)) {
3210 not covered callback = region;
3220 not covered region = null;
323 not covered }
324 not covered
3250 not covered if (region) {
3260 not covered options.region = region;
327 not covered }
328 not covered
3290 not covered var endpoint = 'https://maps.googleapis.com/maps/api/geocode/json?' + querystring.stringify(options);
330 not covered
3310 not covered https.get(endpoint, function(res) {
3320 not covered var data = [];
3330 not covered res.on('data', function(chunk) {
3340 not covered data.push(chunk);
335 not covered })
336 not covered .on('end', function() {
3370 not covered var dataBuff = data.join('').trim();
3380 not covered var result;
339 not covered try {
3400 not covered result = JSON.parse(dataBuff);
341 not covered }
342 not covered catch (exp) {
3430 not covered result = {'status_code': 500, 'status_text': 'JSON Parse Failed', 'status': 'UNKNOWN_ERROR'};
344 not covered }
3450 not covered callback(null, result);
346 not covered });
347 not covered })
3480 not covered callback(err);
349 not covered });
350 not covered }
351 not covered
352 not covered
353 not covered /**
354 not covered * Autodetect the full address and lat, lng from the stored value.
355 not covered *
356 not covered * Uses Google's Maps API and may only be used in conjunction with a Google map.
357 not covered * Geocoding results without displaying them on a map is prohibited.
358 not covered * Please make sure your Keystone app complies with the Google Maps API License.
359 not covered *
360 not covered * Internal status codes mimic the Google API status codes.
361 not covered *
362 not covered * @api private
363 not covered */
364 not covered
3651100%location.prototype.googleLookup = function(item, region, update, callback) {
3660 not covered if (_.isFunction(update)) {
3670 not covered callback = update;
3680 not covered update = false;
369 not covered }
3700 not covered var field = this,
3710 not covered stored = item.get(this.path),
3720 not covered address = item.get(this.paths.serialised);
373 not covered
3740 not covered if (address.length === 0) {
3750 not covered return callback({'status_code': 500, 'status_text': 'No address to geocode', 'status': 'NO_ADDRESS'});
376 not covered }
3770 not covered doGoogleGeocodeRequest(address, region || keystone.get('default region'), function(err, geocode){
3780 not covered if (err || geocode.status !== 'OK') {
3790 not covered return callback(err);
380 not covered }
381 not covered
382 not covered // use the first result
383 not covered // if there were no results in the array, status would be ZERO_RESULTS
3840 not covered var result = geocode.results[0];
385 not covered
386 not covered // parse the address components into a location object
387 not covered
3880 not covered var location = {};
389 not covered
3900 not covered _.each(result.address_components, function(val){
3910 not covered if ( _.indexOf(val.types,'street_number') >= 0 ) {
3920 not covered location.street1 = location.street1 || [];
3930 not covered location.street1.push(val.long_name);
394 not covered }
3950 not covered if ( _.indexOf(val.types,'route') >= 0 ) {
3960 not covered location.street1 = location.street1 || [];
3970 not covered location.street1.push(val.short_name);
398 not covered }
399 not covered // in some cases, you get suburb, city as locality - so only use the first
4000 not covered if ( _.indexOf(val.types,'locality') >= 0 && !location.suburb) {
4010 not covered location.suburb = val.long_name;
402 not covered }
4030 not covered if ( _.indexOf(val.types,'administrative_area_level_1') >= 0 ) {
4040 not covered location.state = val.short_name;
405 not covered }
4060 not covered if ( _.indexOf(val.types,'country') >= 0 ) {
4070 not covered location.country = val.long_name;
408 not covered }
4090 not covered if ( _.indexOf(val.types,'postal_code') >= 0 ) {
4100 not covered location.postcode = val.short_name;
411 not covered }
412 not covered
4130 not covered if (Array.isArray(location.street1)) {
4140 not covered location.street1 = location.street1.join(' ');
415 not covered }
4160 not covered location.geo = [
417 not covered
418 not covered //console.log('------ Google Geocode Results ------');
419 not covered //console.log(address);
420 not covered //console.log(result);
421 not covered //console.log(location);
422 not covered
4230 not covered if (update === 'overwrite') {
4240 not covered item.set(field.path, location);
4250 not covered } else if (update) {
4260 not covered _.each(location, function(value, key) {
4270 not covered if (key === 'geo') {
4280 not covered return;
429 not covered }
4300 not covered if (!stored[key]) {
4310 not covered item.set(field.paths[key], value);
432 not covered }
433 not covered });
4340 not covered if (!Array.isArray(stored.geo) || !stored.geo[0] || !stored.geo[1]) {
4350 not covered item.set(field.paths.geo, location.geo);
436 not covered }
4370 not covered callback(null, location, result);
438 not covered
439 not covered };
440 not covered
441 not covered
442 not covered /**
443 not covered * Internal Distance calculation function
444 not covered *
445 not covered * See http://en.wikipedia.org/wiki/Haversine_formula
446 not covered *
447 not covered * @api private
448 not covered */
449 not covered
450 not covered function calculateDistance(point1, point2) {
451 not covered
4520 not covered var dLng = (point2[0] - point1[0]) * Math.PI / 180;
4530 not covered var dLat = (point2[1] - point1[1]) * Math.PI / 180;
4540 not covered var lat1 = (point1[1]) * Math.PI / 180;
4550 not covered var lat2 = (point2[1]) * Math.PI / 180;
456 not covered
4570 not covered var a = Math.sin(dLat/2) * Math.sin(dLat/2) + Math.sin(dLng/2) * Math.sin(dLng/2) * Math.cos(lat1) * Math.cos(lat2);
4580 not covered var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
459 not covered
4600 not covered return c;
461 not covered
462 not covered }
463 not covered
464 not covered
465 not covered /**
466 not covered * Returns the distance from a [lat, lng] point in kilometres
467 not covered *
468 not covered * @api public
469 not covered */
470 not covered
4711100%location.prototype.kmFrom = function(item, point) {
4720 not covered return calculateDistance(this.get(this.paths.geo), point) * RADIUS_KM;
473 not covered };
474 not covered
475 not covered
476 not covered /**
477 not covered * Returns the distance from a [lat, lng] point in miles
478 not covered *
479 not covered * @api public
480 not covered */
481 not covered
4821100%location.prototype.milesFrom = function(item, point) {
4830 not covered return calculateDistance(this.get(this.paths.geo), point) * RADIUS_MILES;
484 not covered };
485 not covered
486 not covered
487 not covered /*!
488 not covered * Export class
489 not covered */
490 not covered
4911100%exports = module.exports = location;

lib/fieldTypes/markdown.js

0% block coverage
44 SLOC
LineHitsStatementsSourceAction
1 not covered /*!
2 not covered * Module dependencies.
3 not covered */
4 not covered
51100%var util = require('util'),
61100% marked = require('marked'),
71100% super_ = require('../field');
8 not covered
9 not covered /**
10 not covered * Markdown FieldType Constructor
11 not covered * @extends Field
12 not covered * @api public
13 not covered */
14 not covered
15 not covered function markdown(list, path, options) {
16 not covered
17 not covered // TODO: implement filtering, usage disabled for now
180 not covered options.nofilter = true;
19 not covered
200 not covered this.height = options.height || 90;
210 not covered markdown.super_.call(this, list, path, options);
22 not covered
23 not covered }
24 not covered
25 not covered /*!
26 not covered * Inherit from Field
27 not covered */
28 not covered
291100%util.inherits(markdown, super_);
30 not covered
31 not covered
32 not covered /**
33 not covered * Registers the field on the List's Mongoose Schema.
34 not covered *
35 not covered * Adds String properties for .markdown and .html markdown, and a setter
36 not covered * for .markdown that generates html when it is updated.
37 not covered *
38 not covered * @api public
39 not covered */
40 not covered
411100%markdown.prototype.addToSchema = function() {
420 not covered var schema = this.list.schema;
43 not covered
440 not covered var paths = this.paths = {
450 not covered md: this._path.append('.md'),
460 not covered html: this._path.append('.html')
47 not covered };
48 not covered
490 not covered var setMarkdown = function(value) {
500 not covered if (value === this.get(paths.md)) {
510 not covered return value;
52 not covered }
530 not covered if (typeof value === 'string') {
540 not covered this.set(paths.html, marked(value));
550 not covered return value;
56 not covered } else {
570 not covered this.set(paths.html, undefined);
580 not covered return undefined;
59 not covered }
60 not covered
610 not covered schema.nested[this.path] = true;
620 not covered schema.add({
63 not covered html: { type: String },
640 not covered this.bindUnderscoreMethods();
65 not covered };
66 not covered
67 not covered
68 not covered /**
69 not covered * Formats the field value
70 not covered *
71 not covered * @api public
72 not covered */
73 not covered
741100%markdown.prototype.format = function(item) {
750 not covered return item.get(this.paths.html);
76 not covered };
77 not covered
78 not covered
79 not covered /**
80 not covered * Validates that a value for this field has been provided in a data object
81 not covered *
82 not covered * Will accept either the field path, or paths.md
83 not covered *
84 not covered * @api public
85 not covered */
86 not covered
871100%markdown.prototype.validateInput = function(data, required, item) {
880 not covered if (!(this.path in data || this.paths.md in data) && item && item.get(this.paths.md)) return true;
89 not covered
900 not covered return (!required || data[this.path] || data[this.paths.md]) ? true : false;
91 not covered
92 not covered
93 not covered
94 not covered /**
95 not covered * Detects whether the field has been modified
96 not covered *
97 not covered * @api public
98 not covered */
99 not covered
1001100%markdown.prototype.isModified = function(item) {
1010 not covered return item.isModified(this.paths.md);
102 not covered };
103 not covered
104 not covered
105 not covered /**
106 not covered * Updates the value for this field in the item from a data object
107 not covered *
108 not covered * Will accept either the field path, or paths.md
109 not covered *
110 not covered * @api public
111 not covered */
112 not covered
1131100%markdown.prototype.updateItem = function(item, data) {
1140 not covered if (this.path in data) {
1150 not covered item.set(this.paths.md, data[this.path]);
1160 not covered } else if (this.paths.md in data) {
1170 not covered item.set(this.paths.md, data[this.paths.md]);
118 not covered }
119 not covered
120 not covered
121 not covered /*!
122 not covered * Export class
123 not covered */
124 not covered
1251100%exports = module.exports = markdown;

lib/fieldTypes/money.js

0% block coverage
33 SLOC
LineHitsStatementsSourceAction
1 not covered /*!
2 not covered * Module dependencies.
3 not covered */
4 not covered
51100%var util = require('util'),
61100% numeral = require('numeral'),
71100% utils = require('keystone-utils'),
81100% super_ = require('../field');
9 not covered
10 not covered /**
11 not covered * Money FieldType Constructor
12 not covered * @extends Field
13 not covered * @api public
14 not covered */
15 not covered
16 not covered function money(list, path, options) {
170 not covered this._nativeType = Number;
180 not covered this._underscoreMethods = ['format'];
190 not covered this._formatString = (options.format === false) ? false : (options.format || '$0,0.00');
200 not covered if (this._formatString && 'string' !== typeof this._formatString) {
210 not covered throw new Error('FieldType.Money: options.format must be a string.');
22 not covered }
230 not covered money.super_.call(this, list, path, options);
24 not covered }
25 not covered
26 not covered /*!
27 not covered * Inherit from Field
28 not covered */
29 not covered
301100%util.inherits(money, super_);
31 not covered
32 not covered
33 not covered /**
34 not covered * Formats the field value
35 not covered *
36 not covered * @api public
37 not covered */
38 not covered
391100%money.prototype.format = function(item, format) {
400 not covered if (format || this._formatString) {
410 not covered return ('number' === typeof item.get(this.path)) ? numeral(item.get(this.path)).format(format || this._formatString) : '';
42 not covered } else {
430 not covered return item.get(this.path) || '';
44 not covered }
45 not covered
46 not covered
47 not covered /**
48 not covered * Checks that a valid number has been provided in a data object
49 not covered *
50 not covered * An empty value clears the stored value and is considered valid
51 not covered *
52 not covered * @api public
53 not covered */
54 not covered
551100%money.prototype.validateInput = function(data, required, item) {
560 not covered if (!(this.path in data) && item && (item.get(this.path) || item.get(this.path) === 0)) return true;
57 not covered
58 not covered if (data[this.path]) {
590 not covered var newValue = utils.number(data[this.path]);
600 not covered return (!isNaN(newValue));
61 not covered } else {
620 not covered return (required) ? false : true;
63 not covered }
64 not covered
65 not covered
66 not covered /**
67 not covered * Updates the value for this field in the item from a data object
68 not covered *
69 not covered * @api public
70 not covered */
71 not covered
721100%money.prototype.updateItem = function(item, data) {
730 not covered if (!(this.path in data))
740 not covered return;
75 not covered
760 not covered var newValue = utils.number(data[this.path]);
77 not covered
780 not covered if (!isNaN(newValue)) {
790 not covered if (newValue !== item.get(this.path)) {
800 not covered item.set(this.path, newValue);
81 not covered }
820 not covered } else if ('number' === typeof item.get(this.path)) {
830 not covered item.set(this.path, null);
84 not covered }
85 not covered
86 not covered
87 not covered /*!
88 not covered * Export class
89 not covered */
90 not covered
911100%exports = module.exports = money;

lib/fieldTypes/name.js

0% block coverage
63 SLOC
LineHitsStatementsSourceAction
1 not covered /*!
2 not covered * Module dependencies.
3 not covered */
4 not covered
51100%var _ = require('underscore'),
61100% util = require('util'),
71100% super_ = require('../field');
8 not covered
9 not covered /**
10 not covered * Name FieldType Constructor
11 not covered * @extends Field
12 not covered * @api public
13 not covered */
14 not covered
15 not covered function name(list, path, options) {
16 not covered // TODO: implement filtering, hard-coded as disabled for now
170 not covered options.nofilter = true;
180 not covered name.super_.call(this, list, path, options);
19 not covered }
20 not covered
21 not covered /*!
22 not covered * Inherit from Field
23 not covered */
24 not covered
251100%util.inherits(name, super_);
26 not covered
27 not covered
28 not covered /**
29 not covered * Registers the field on the List's Mongoose Schema.
30 not covered *
31 not covered * Adds String properties for .first and .last name, and a virtual
32 not covered * with get() and set() methods for .full
33 not covered *
34 not covered * @api public
35 not covered */
36 not covered
371100%name.prototype.addToSchema = function() {
380 not covered var schema = this.list.schema;
39 not covered
400 not covered var paths = this.paths = {
410 not covered first: this._path.append('.first'),
420 not covered last: this._path.append('.last'),
430 not covered full: this._path.append('.full')
44 not covered };
45 not covered
460 not covered schema.nested[this.path] = true;
470 not covered schema.add({
48 not covered first: String,
490 not covered schema.virtual(paths.full).get(function () {
500 not covered return _.compact([this.get(paths.first), this.get(paths.last)]).join(' ');
51 not covered });
52 not covered
530 not covered schema.virtual(paths.full).set(function(value) {
540 not covered if (typeof value !== 'string') {
550 not covered this.set(paths.first, undefined);
560 not covered this.set(paths.last, undefined);
570 not covered return;
58 not covered }
59 not covered
600 not covered var split = value.split(' ');
610 not covered this.set(paths.first, split.shift());
620 not covered this.set(paths.last, split.join(' ') || undefined);
63 not covered
64 not covered
650 not covered this.bindUnderscoreMethods();
66 not covered };
67 not covered
68 not covered
69 not covered /**
70 not covered * Formats the field value
71 not covered *
72 not covered * @api public
73 not covered */
74 not covered
751100%name.prototype.format = function(item) {
760 not covered return item.get(this.paths.full);
77 not covered };
78 not covered
79 not covered
80 not covered /**
81 not covered * Validates that a value for this field has been provided in a data object
82 not covered *
83 not covered * @api public
84 not covered */
85 not covered
861100%name.prototype.validateInput = function(data, required, item) { // Input is valid if none was provided, but the item has data
870 not covered if (!(this.path in data || this.paths.first in data || this.paths.last in data || this.paths.full in data) && item && item.get(this.paths.full)) return true;
88 not covered
890 not covered if (!required) return true;
90 not covered
910 not covered if (_.isObject(data[this.path])) {
920 not covered return (data[this.path].full || data[this.path].first || data[this.path].last) ? true : false;
93 not covered } else {
940 not covered return (data[this.paths.full] || data[this.paths.first] || data[this.paths.last]) ? true : false;
95 not covered }
96 not covered
97 not covered
98 not covered /**
99 not covered * Detects whether the field has been modified
100 not covered *
101 not covered * @api public
102 not covered */
103 not covered
1041100%name.prototype.isModified = function(item) {
1050 not covered return item.isModified(this.paths.first) || item.isModified(this.paths.last);
106 not covered };
107 not covered
108 not covered
109 not covered /**
110 not covered * Updates the value for this field in the item from a data object
111 not covered *
112 not covered * @api public
113 not covered */
114 not covered
1151100%name.prototype.updateItem = function(item, data) {
1160 not covered if (!_.isObject(data)) return;
117 not covered
1180 not covered var paths = this.paths,
119 not covered
1200 not covered if (this.path in data && _.isString(data[this.path])) {
121 not covered
1220 not covered item.set(paths.full, data[this.path]);
123 not covered
1240 not covered } else if (this.path in data && _.isObject(data[this.path])) {
125 not covered
1260 not covered var valueObj = data[this.path];
127 not covered
1280 not covered setValue = function(key) {
1290 not covered if (key in valueObj && valueObj[key] !== item.get(paths[key])) {
1300 not covered item.set(paths[key], valueObj[key]);
131 not covered }
132 not covered
1330 not covered setValue = function(key) {
1340 not covered if (paths[key] in data && data[paths[key]] !== item.get(paths[key])) {
1350 not covered item.set(paths[key], data[paths[key]]);
136 not covered }
137 not covered
1380 not covered if (setValue) {
1390 not covered _.each(['full', 'first', 'last'], setValue);
140 not covered }
141 not covered
142 not covered
143 not covered /*!
144 not covered * Export class
145 not covered */
146 not covered
1471100%exports = module.exports = name;

lib/fieldTypes/number.js

0% block coverage
33 SLOC
LineHitsStatementsSourceAction
1 not covered /*!
2 not covered * Module dependencies.
3 not covered */
4 not covered
51100%var util = require('util'),
61100% numeral = require('numeral'),
71100% utils = require('keystone-utils'),
81100% super_ = require('../field');
9 not covered
10 not covered /**
11 not covered * Number FieldType Constructor
12 not covered * @extends Field
13 not covered * @api public
14 not covered */
15 not covered
16 not covered function number(list, path, options) {
170 not covered this._nativeType = Number;
180 not covered this._underscoreMethods = ['format'];
190 not covered this._formatString = (options.format === false) ? false : (options.format || '0,0[.][000000000000]');
200 not covered if (this._formatString && 'string' !== typeof this._formatString) {
210 not covered throw new Error('FieldType.Number: options.format must be a string.');
22 not covered }
230 not covered number.super_.call(this, list, path, options);
24 not covered }
25 not covered
26 not covered /*!
27 not covered * Inherit from Field
28 not covered */
29 not covered
301100%util.inherits(number, super_);
31 not covered
32 not covered
33 not covered /**
34 not covered * Formats the field value
35 not covered *
36 not covered * @api public
37 not covered */
38 not covered
391100%number.prototype.format = function(item, format) {
400 not covered if (format || this._formatString) {
410 not covered return ('number' === typeof item.get(this.path)) ? numeral(item.get(this.path)).format(format || this._formatString) : '';
42 not covered } else {
430 not covered return item.get(this.path) || '';
44 not covered }
45 not covered
46 not covered
47 not covered /**
48 not covered * Checks that a valid number has been provided in a data object
49 not covered *
50 not covered * An empty value clears the stored value and is considered valid
51 not covered *
52 not covered * @api public
53 not covered */
54 not covered
551100%number.prototype.validateInput = function(data, required, item) {
560 not covered if (!(this.path in data) && item && (item.get(this.path) || item.get(this.path) === 0)) return true;
57 not covered
58 not covered if (data[this.path]) {
590 not covered var newValue = utils.number(data[this.path]);
600 not covered return (!isNaN(newValue));
61 not covered } else {
620 not covered return (required) ? false : true;
63 not covered }
64 not covered
65 not covered
66 not covered /**
67 not covered * Updates the value for this field in the item from a data object
68 not covered *
69 not covered * @api public
70 not covered */
71 not covered
721100%number.prototype.updateItem = function(item, data) {
730 not covered if (!(this.path in data))
740 not covered return;
75 not covered
760 not covered var newValue = utils.number(data[this.path]);
77 not covered
780 not covered if (!isNaN(newValue)) {
790 not covered if (newValue !== item.get(this.path)) {
800 not covered item.set(this.path, newValue);
81 not covered }
820 not covered } else if ('number' === typeof item.get(this.path)) {
830 not covered item.set(this.path, null);
84 not covered }
85 not covered
86 not covered
87 not covered /*!
88 not covered * Export class
89 not covered */
90 not covered
911100%exports = module.exports = number;

lib/fieldTypes/password.js

0% block coverage
59 SLOC
LineHitsStatementsSourceAction
1 not covered /*!
2 not covered * Module dependencies.
3 not covered */
4 not covered
51100%var _ = require('underscore'),
61100% util = require('util'),
71100% bcrypt = require('bcrypt-nodejs'),
81100% super_ = require('../field');
9 not covered
10 not covered /**
11 not covered * password FieldType Constructor
12 not covered * @extends Field
13 not covered * @api public
14 not covered */
15 not covered
16 not covered function password(list, path, options) {
170 not covered this.workFactor = options.workFactor || 10;
180 not covered this._nativeType = String;
190 not covered this._underscoreMethods = ['format', 'compare'];
200 not covered options.nosort = true; // You can't sort on password fields
21 not covered // TODO: implement filtering, hard-coded as disabled for now
220 not covered options.nofilter = true;
230 not covered password.super_.call(this, list, path, options);
24 not covered }
25 not covered
26 not covered /*!
27 not covered * Inherit from Field
28 not covered */
29 not covered
301100%util.inherits(password, super_);
31 not covered
32 not covered /**
33 not covered * Registers the field on the List's Mongoose Schema.
34 not covered *
35 not covered * Adds ...
36 not covered *
37 not covered * @api public
38 not covered */
391100%password.prototype.addToSchema = function() {
400 not covered var field = this,
41 not covered
420 not covered this.paths = {
430 not covered confirm: this.options.confirmPath || this._path.append('_confirm')
44 not covered };
45 not covered
460 not covered schema.path(this.path, _.defaults({ type: String }, this.options));
47 not covered
480 not covered schema.pre('save', function(next) {
490 not covered if (!this.isModified(field.path))
500 not covered return next();
51 not covered
520 not covered if (!this.get(field.path)) {
530 not covered this.set(field.path, undefined);
540 not covered return next();
55 not covered }
56 not covered
570 not covered var item = this;
58 not covered
590 not covered bcrypt.genSalt(field.workFactor, function(err, salt) {
600 not covered if (err)
610 not covered return next(err);
62 not covered
630 not covered bcrypt.hash(item.get(field.path), salt, function () {}, function(err, hash) {
640 not covered if (err)
650 not covered return next(err);
66 not covered
67 not covered // override the cleartext password with the hashed one
680 not covered item.set(field.path, hash);
690 not covered next();
70 not covered });
71 not covered });
72 not covered
73 not covered
740 not covered this.bindUnderscoreMethods();
75 not covered
76 not covered
77 not covered
78 not covered /**
79 not covered * Formats the field value
80 not covered *
81 not covered * Password fields are always formatted as a random no. of asterisks,
82 not covered * because the saved hash should never be displayed nor the length
83 not covered * of the actual password hinted at.
84 not covered *
85 not covered * @api public
86 not covered */
87 not covered
881100%password.prototype.format = function(item) {
890 not covered if (!item.get(this.path)) return '';
900 not covered var len = Math.round(Math.random() * 4) + 6;
910 not covered var stars = '';
920 not covered for (var i = 0; i < len; i++) stars += '*';
930 not covered return stars;
94 not covered };
95 not covered
96 not covered
97 not covered /**
98 not covered * Compares
99 not covered *
100 not covered * @api public
101 not covered */
102 not covered
1031100%password.prototype.compare = function(item, candidate, callback) {
1040 not covered if ('function' !== typeof callback) throw new Error('Password.compare() requires a callback function.');
1050 not covered var value = item.get(this.path);
1060 not covered if (!value) return callback(null, false);
1070 not covered bcrypt.compare(candidate, item.get(this.path), callback);
108 not covered };
109 not covered
110 not covered
111 not covered /**
112 not covered * If password fields are required, check that either a value has been
113 not covered * provided or already exists in the field.
114 not covered *
115 not covered * Otherwise, input is always considered valid, as providing an empty
116 not covered * value will not change the password.
117 not covered *
118 not covered * @api public
119 not covered */
120 not covered
1211100%password.prototype.validateInput = function(data, required, item) {
1220 not covered if (!required) {
1230 not covered return true;
124 not covered }
1250 not covered if (item) {
1260 not covered return (data[this.path] || item.get(this.path)) ? true : false;
127 not covered } else {
1280 not covered return data[this.path] ? true : false;
129 not covered }
130 not covered
131 not covered
132 not covered /*!
133 not covered * Export class
134 not covered */
135 not covered
1361100%exports = module.exports = password;

lib/fieldTypes/relationship.js

17% block coverage
91 SLOC
LineHitsStatementsSourceAction
1 not covered /*!
2 not covered * Module dependencies.
3 not covered */
4 not covered
51100%var _ = require('underscore'),
61100% keystone = require('../../'),
71100% util = require('util'),
81100% utils = require('keystone-utils'),
91100% super_ = require('../field');
10 not covered
11 not covered /**
12 not covered * Relationship FieldType Constructor
13 not covered * @extends Field
14 not covered * @api public
15 not covered */
16 not covered
17 not covered function relationship(list, path, options) {
18 not covered
191467% this.many = (options.many) ? not covered true: false;
2014100% this.filters = options.filters;
2114100% this._nativeType = keystone.mongoose.Schema.Types.ObjectId;
2214100% this._underscoreMethods = ['format'];
23 not covered
2414100% relationship.super_.call(this, list, path, options);
25 not covered
26 not covered }
27 not covered
28 not covered /*!
29 not covered * Inherit from Field
30 not covered */
31 not covered
321100%util.inherits(relationship, super_);
33 not covered
34 not covered
35 not covered /**
36 not covered * Registers the field on the List's Mongoose Schema.
37 not covered *
38 not covered * @api public
39 not covered */
40 not covered
411100%relationship.prototype.addToSchema = function() {
4214100% var field = this, schema = this.list.schema;
43 not covered
4414100% this.paths = {
4514100% refList: this.options.refListPath || this._path.append('RefList')
46 not covered };
47 not covered
4814100% var def = { type: this._nativeType, ref: this.options.ref };
49 not covered
501475% schema.path(this.path, this.many ? not covered [def]: def);
51 not covered
5214100% schema.virtual(this.paths.refList).get(function () {
530 not covered return keystone.list(field.options.ref);
54 not covered });
55 not covered
56 not covered if (this.many) {
570 not covered this.underscoreMethod('contains', function(find) {
580 not covered var value = this.populated(field.path) || this.get(field.path);
590 not covered if ('object' === typeof find) {
600 not covered find = find.id;
61 not covered }
620 not covered var result = _.some(value, function(value) {
630 not covered return (value + '' === find);
64 not covered });
650 not covered return result;
66 not covered });
67 not covered }
6814100% this.bindUnderscoreMethods();
69 not covered
70 not covered
71 not covered /**
72 not covered * Formats the field value
73 not covered *
74 not covered * @api public
75 not covered */
761100%relationship.prototype.format = function(item) {
770 not covered var value = item.get(this.path);
78 not covered // force the formatted value to be a - unexpected things happen with ObjectIds.
790 not covered return this.many ? value.join(', ') : (value || '') + '';
80 not covered };
81 not covered
82 not covered
83 not covered /**
84 not covered * Validates that a value for this field has been provided in a data object
85 not covered *
86 not covered * @api public
87 not covered */
88 not covered
891100%relationship.prototype.validateInput = function(data, required, item) {
9014100% if (!required) return true;
910 not covered if (!(this.path in data) && item && ((this.many && item.get(this.path).length) || item.get(this.path))) return true;
92 not covered
930 not covered if ('string' === typeof data[this.path]) {
940 not covered return (data[this.path].trim()) ? true : false;
95 not covered } else {
960 not covered return (data[this.path]) ? true : false;
97 not covered }
98 not covered
99 not covered
100 not covered /**
101 not covered * Updates the value for this field in the item from a data object.
102 not covered * Only updates the value if it has changed.
103 not covered * Treats an empty string as a null value.
104 not covered *
105 not covered * @api public
106 not covered */
107 not covered
1081100%relationship.prototype.updateItem = function(item, data) {
10914100% if (!(this.path in data)) {
11014100% return;
111 not covered }
1120 not covered if (item.populated(this.path)) {
1130 not covered throw new Error('fieldTypes.relationship.updateItem() Error - You cannot update populated relationships.');
114 not covered }
1150 not covered var arr = item.get(this.path),
1160 not covered _old = arr.map(function(i) { return String(i); }),
117 not covered _new = data[this.path];
118 not covered
1190 not covered if (!utils.isArray(_new)) {
1200 not covered _new = String(_new || '').split(',');
121 not covered }
1220 not covered _new = _.compact(_new);
123 not covered
124 not covered // remove ids
1250 not covered _.difference(_old, _new).forEach(function(val) {
1260 not covered arr.pull(val);
127 not covered });
128 not covered // add new ids
1290 not covered _.difference(_new, _old).forEach(function(val) {
1300 not covered arr.push(val);
131 not covered });
132 not covered
133 not covered } else {
134 not covered if (data[this.path]) {
1350 not covered if (data[this.path] !== item.get(this.path)) {
1360 not covered item.set(this.path, data[this.path]);
137 not covered }
1380 not covered } else if (item.get(this.path)) {
1390 not covered item.set(this.path, null);
140 not covered }
141 not covered
142 not covered
143 not covered /**
144 not covered * Returns true if the relationship configuration is valid
145 not covered *
146 not covered * @api public
147 not covered */
148 not covered
1491100%Object.defineProperty(relationship.prototype, 'isValid', { get: function() {
1500 not covered return keystone.list(this.options.ref) ? true : false;
151 not covered }
152 not covered
153 not covered
154 not covered /**
155 not covered * Returns the Related List
156 not covered *
157 not covered * @api public
158 not covered */
159 not covered
1601100%Object.defineProperty(relationship.prototype, 'refList', { get: function() {
1610 not covered return keystone.list(this.options.ref);
162 not covered }
163 not covered
164 not covered
165 not covered /**
166 not covered * Whether the field has any filters defined
167 not covered *
168 not covered * @api public
169 not covered */
170 not covered
1711100%Object.defineProperty(relationship.prototype, 'hasFilters', { get: function() {
1720 not covered return (this.filters && _.keys(this.filters).length);
173 not covered }
174 not covered
175 not covered
176 not covered /**
177 not covered * Adds relationship filters to a query
178 not covered *
179 not covered * @api public
180 not covered */
181 not covered
1821100%relationship.prototype.addFilters = function(query, item) {
1830 not covered _.each(this.filters, function(filters, path) {
1840 not covered if (!utils.isObject(filters)) {
1850 not covered filters = { equals: filters };
186 not covered }
1870 not covered query.where(path);
1880 not covered _.each(filters, function(value, method) {
1890 not covered if ('string' === typeof value && value.substr(0,1) === ':') {
1900 not covered if (!item) {
1910 not covered return;
192 not covered }
1930 not covered value = item.get(value.substr(1));
194 not covered }
1950 not covered query[method](value);
196 not covered });
197 not covered });
198 not covered
199 not covered
200 not covered
201 not covered /*!
202 not covered * Export class
203 not covered */
204 not covered
2051100%exports = module.exports = relationship;

lib/fieldTypes/s3file.js

0% block coverage
134 SLOC
LineHitsStatementsSourceAction
1 not covered /*!
2 not covered * Module dependencies.
3 not covered */
4 not covered
51100%var _ = require('underscore'),
61100% moment = require('moment'),
71100% keystone = require('../../'),
81100% async = require('async'),
91100% util = require('util'),
101100% knox = require('knox'),
11 not covered // s3 = require('s3'),
121100% utils = require('keystone-utils'),
131100% super_ = require('../field');
14 not covered
15 not covered /**
16 not covered * S3File FieldType Constructor
17 not covered * @extends Field
18 not covered * @api public
19 not covered */
20 not covered
21 not covered function s3file(list, path, options) {
22 not covered
230 not covered this._underscoreMethods = ['format', 'uploadFile'];
24 not covered
25 not covered // event queues
260 not covered this._pre = {
27 not covered
28 not covered // TODO: implement filtering, usage disabled for now
290 not covered options.nofilter = true;
30 not covered
31 not covered // TODO: implement initial form, usage disabled for now
32 not covered if (options.initial) {
330 not covered throw new Error('Invalid Configuration\n\n' +
34 not covered }
35 not covered
360 not covered s3file.super_.call(this, list, path, options);
37 not covered
38 not covered // validate s3 config (has to happen after super_.call)
390 not covered if (!this.s3config) {
40 not covered throw new Error('Invalid Configuration\n\n' +
410 not covered 'S3File fields (' + list.key + '.' + path + ') require the "s3 config" option to be set.\n\n' +
420 not covered 'See http://keystonejs.com/docs/configuration/#services-amazons3 for more information.\n');
43 not covered }
44 not covered
45 not covered // Could be more pre- hooks, just upload for now
460 not covered if (options.pre && options.pre.upload) {
470 not covered this._pre.upload = this._pre.upload.concat(options.pre.upload);
48 not covered }
49 not covered
50 not covered }
51 not covered
52 not covered /*!
53 not covered * Inherit from Field
54 not covered */
55 not covered
561100%util.inherits(s3file, super_);
57 not covered
58 not covered /**
59 not covered * Exposes the custom or keystone s3 config settings
60 not covered */
61 not covered
621100%Object.defineProperty(s3file.prototype, 's3config', { get: function() {
630 not covered return this.options.s3config || keystone.get('s3 config');
64 not covered }});
65 not covered
66 not covered
67 not covered /**
68 not covered * Allows you to add pre middleware after the field has been initialised
69 not covered *
70 not covered * @api public
71 not covered */
72 not covered
731100%s3file.prototype.pre = function(event, fn) {
740 not covered if (!this._pre[event]) {
750 not covered throw new Error('S3File (' + this.list.key + '.' + this.path + ') error: s3field.pre()\n\n' +
76 not covered }
770 not covered this._pre[event].push(fn);
780 not covered return this;
79 not covered };
80 not covered
81 not covered
82 not covered /**
83 not covered * Registers the field on the List's Mongoose Schema.
84 not covered *
85 not covered * @api public
86 not covered */
87 not covered
881100%s3file.prototype.addToSchema = function() {
890 not covered var field = this,
90 not covered
910 not covered var paths = this.paths = {
920 not covered filename: this._path.append('.filename'),
930 not covered path: this._path.append('.path'),
940 not covered size: this._path.append('.size'),
950 not covered filetype: this._path.append('.filetype'),
960 not covered url: this._path.append('.url'),
97 not covered // virtuals
980 not covered exists: this._path.append('.exists'),
990 not covered upload: this._path.append('_upload'),
1000 not covered action: this._path.append('_action')
101 not covered };
102 not covered
1030 not covered var schemaPaths = this._path.addTo({}, {
104 not covered
1050 not covered schema.add(schemaPaths);
106 not covered
1070 not covered var exists = function(item) {
1080 not covered return (item.get(paths.url) ? true : false);
109 not covered };
110 not covered
111 not covered // The .exists virtual indicates whether a file is stored
1120 not covered schema.virtual(paths.exists).get(function() {
1130 not covered return schemaMethods.exists.apply(this);
114 not covered });
115 not covered
1160 not covered var reset = function(item) {
1170 not covered item.set(field.path, {
118 not covered };
119 not covered
1200 not covered var schemaMethods = {
1210 not covered return exists(this);
122 not covered },
1230 not covered reset(this);
124 not covered },
1250 not covered var client = knox.createClient(field.s3config);
1260 not covered client.deleteFile(this.get(paths.path) + this.get(paths.filename), function(err, res){ res ? res.resume() : false; });
127 not covered } catch(e) {}
1280 not covered reset(this);
129 not covered }
130 not covered
1310 not covered _.each(schemaMethods, function(fn, key) {
1320 not covered field.underscoreMethod(key, fn);
133 not covered });
134 not covered
135 not covered // expose a method on the field to call schema methods
1360 not covered this.apply = function(item, method) {
1370 not covered return schemaMethods[method].apply(item, Array.prototype.slice.call(arguments, 2));
138 not covered };
139 not covered
1400 not covered this.bindUnderscoreMethods();
141 not covered };
142 not covered
143 not covered
144 not covered /**
145 not covered * Formats the field value
146 not covered *
147 not covered * @api public
148 not covered */
149 not covered
1501100%s3file.prototype.format = function(item) {
1510 not covered return item.get(this.paths.url);
152 not covered };
153 not covered
154 not covered
155 not covered /**
156 not covered * Detects whether the field has been modified
157 not covered *
158 not covered * @api public
159 not covered */
160 not covered
1611100%s3file.prototype.isModified = function(item) {
1620 not covered return item.isModified(this.paths.url);
163 not covered };
164 not covered
165 not covered
166 not covered /**
167 not covered * Validates that a value for this field has been provided in a data object
168 not covered *
169 not covered * @api public
170 not covered */
171 not covered
1721100%s3file.prototype.validateInput = function(data) { // TODO - how should file field input be validated?
1730 not covered return true;
174 not covered };
175 not covered
176 not covered
177 not covered /**
178 not covered * Updates the value for this field in the item from a data object
179 not covered *
180 not covered * @api public
181 not covered */
182 not covered
1831100%s3file.prototype.updateItem = function(item, data) { // TODO - direct updating of data (not via upload) };
184 not covered
185 not covered
186 not covered /**
187 not covered * Uploads the file for this field
188 not covered *
189 not covered * @api public
190 not covered */
191 not covered
1921100%s3file.prototype.uploadFile = function(item, file, update, callback) {
1930 not covered var field = this,
1940 not covered path = field.options.s3path ? field.options.s3path + '/' : '',
1950 not covered prefix = field.options.datePrefix ? moment().format(field.options.datePrefix) + '-' : '',
1960 not covered name = prefix + file.name;
197 not covered
1980 not covered if (field.options.allowedTypes && !_.contains(field.options.allowedTypes, file.type)){
1990 not covered return callback(new Error('Unsupported File Type: '+file.type));
200 not covered }
2010 not covered if ('function' == typeof update) {
2020 not covered callback = update;
2030 not covered update = false;
204 not covered }
2050 not covered var doUpload = function() {
2060 not covered knox.createClient(field.s3config).putFile(file.path, path + name, {
2070 not covered if (res) res.resume();
2080 not covered if (err) return callback(err);
209 not covered
2100 not covered var protocol = (field.s3config.protocol && field.s3config.protocol + ':') || '',
211 not covered
2120 not covered var fileData = {
213 not covered filename: name,
214 not covered
2150 not covered if (update) {
2160 not covered item.set(field.path, fileData);
217 not covered }
2180 not covered callback(null, fileData);
219 not covered
220 not covered };
221 not covered
2220 not covered async.eachSeries(this._pre.upload, function(fn, next) {
2230 not covered fn(item, file, next);
224 not covered }, function(err) {
2250 not covered if (err) return callback(err);
2260 not covered doUpload();
227 not covered });
228 not covered
229 not covered
230 not covered
231 not covered /**
232 not covered * Returns a callback that handles a standard form submission for the field
233 not covered *
234 not covered * Expected form parts are
235 not covered * - `field.paths.action` in `req.body` (`clear` or `delete`)
236 not covered * - `field.paths.upload` in `req.files` (uploads the file to s3file)
237 not covered *
238 not covered * @api public
239 not covered */
240 not covered
2411100%s3file.prototype.getRequestHandler = function(item, req, paths, callback) {
2420 not covered var field = this;
243 not covered
2440 not covered if (utils.isFunction(paths)) {
2450 not covered callback = paths;
2460 not covered paths = field.paths;
2470 not covered } else if (!paths) {
2480 not covered paths = field.paths;
249 not covered }
2500 not covered callback = callback || function() {};
251 not covered
2520 not covered return function() {
2530 not covered var action = req.body[paths.action];
254 not covered
2550 not covered if (/^(delete|reset)$/.test(action))
2560 not covered field.apply(item, action);
257 not covered }
2580 not covered if (req.files && req.files[paths.upload] && req.files[paths.upload].size) {
2590 not covered return field.uploadFile(item, req.files[paths.upload], true, callback);
260 not covered }
2610 not covered return callback();
262 not covered
263 not covered
264 not covered
265 not covered
266 not covered /**
267 not covered * Immediately handles a standard form submission for the field (see `getRequestHandler()`)
268 not covered *
269 not covered * @api public
270 not covered */
271 not covered
2721100%s3file.prototype.handleRequest = function(item, req, paths, callback) {
2730 not covered this.getRequestHandler(item, req, paths, callback)();
274 not covered };
275 not covered
276 not covered
277 not covered /*!
278 not covered * Export class
279 not covered */
280 not covered
2811100%exports = module.exports = s3file;

lib/fieldTypes/select.js

0% block coverage
71 SLOC
LineHitsStatementsSourceAction
1 not covered /*!
2 not covered * Module dependencies.
3 not covered */
4 not covered
51100%var _ = require('underscore'),
61100% util = require('util'),
71100% utils = require('keystone-utils'),
81100% super_ = require('../field');
9 not covered
10 not covered /**
11 not covered * Select FieldType Constructor
12 not covered * @extends Field
13 not covered * @api public
14 not covered */
15 not covered
16 not covered function select(list, path, options) {
17 not covered
180 not covered this._nativeType = (options.numeric) ? Number : String;
190 not covered this._underscoreMethods = ['format'];
20 not covered
210 not covered this.ui = options.ui || 'select';
22 not covered
230 not covered if (typeof options.options === 'string') {
240 not covered options.options = options.options.split(',');
25 not covered }
26 not covered
270 not covered if (!Array.isArray(options.options)) {
280 not covered throw new Error('Select fields require an options array.');
29 not covered }
30 not covered
310 not covered this.ops = options.options.map(function(i) {
320 not covered var op = _.isString(i) ? { value: i.trim(), label: utils.keyToLabel(i) } : i;
330 not covered if (!_.isObject(op)) {
340 not covered op = { label: ''+i, value: ''+i };
35 not covered }
360 not covered if (options.numeric && !_.isNumber(op.value)) {
370 not covered op.value = Number(op.value);
38 not covered }
390 not covered return op;
40 not covered });
41 not covered
42 not covered // undefined options.emptyOption defaults to true
430 not covered if (options.emptyOption === undefined) {
440 not covered options.emptyOption = true;
45 not covered }
46 not covered
47 not covered // ensure this.emptyOption is a boolean
480 not covered this.emptyOption = options.emptyOption ? true : false;
49 not covered
50 not covered // cached maps for options, labels and values
510 not covered this.map = utils.optionsMap(this.ops);
520 not covered this.labels = utils.optionsMap(this.ops, 'label');
530 not covered this.values = _.pluck(this.ops, 'value');
54 not covered
550 not covered select.super_.call(this, list, path, options);
56 not covered }
57 not covered
58 not covered /*!
59 not covered * Inherit from Field
60 not covered */
61 not covered
621100%util.inherits(select, super_);
63 not covered
64 not covered /**
65 not covered * Registers the field on the List's Mongoose Schema.
66 not covered *
67 not covered * Adds a virtual for accessing the label of the selected value,
68 not covered * and statics to the Schema for converting a value to a label,
69 not covered * and retrieving all of the defined options.
70 not covered *
71 not covered * @api public
72 not covered */
73 not covered
741100%select.prototype.addToSchema = function() {
750 not covered var field = this,
76 not covered
770 not covered this.paths = {
780 not covered data: this.options.dataPath || this._path.append('Data'),
790 not covered label: this.options.labelPath || this._path.append('Label'),
800 not covered options: this.options.optionsPath || this._path.append('Options'),
810 not covered map: this.options.optionsMapPath || this._path.append('OptionsMap')
82 not covered };
83 not covered
840 not covered schema.path(this.path, _.defaults({
850 not covered return (val === '') ? undefined : val;
86 not covered }
87 not covered
880 not covered schema.virtual(this.paths.data).get(function () {
890 not covered return field.map[this.get(field.path)];
90 not covered });
91 not covered
920 not covered schema.virtual(this.paths.label).get(function () {
930 not covered return field.labels[this.get(field.path)];
94 not covered });
95 not covered
960 not covered schema.virtual(this.paths.options).get(function() {
970 not covered return field.ops;
98 not covered });
99 not covered
1000 not covered schema.virtual(this.paths.map).get(function() {
1010 not covered return field.map;
102 not covered });
103 not covered
1040 not covered this.underscoreMethod('pluck', function(property, d) {
1050 not covered var option = this.get(field.paths.data);
1060 not covered return (option) ? option[property] : d;
107 not covered });
108 not covered
1090 not covered this.bindUnderscoreMethods();
110 not covered
111 not covered
112 not covered /**
113 not covered * Retrieves a shallow clone of the options array
114 not covered *
115 not covered * @api public
116 not covered */
117 not covered
1181100%select.prototype.cloneOps = function() {
1190 not covered return _.map(this.ops, _.clone);
120 not covered };
121 not covered
122 not covered
123 not covered /**
124 not covered * Retrieves a shallow clone of the options map
125 not covered *
126 not covered * @api public
127 not covered */
128 not covered
1291100%select.prototype.cloneMap = function() {
1300 not covered return utils.optionsMap(this.ops, true);
131 not covered };
132 not covered
133 not covered
134 not covered /**
135 not covered * Validates that a valid option has been provided in a data object
136 not covered *
137 not covered * @api public
138 not covered */
139 not covered
1401100%select.prototype.validateInput = function(data, required, item) { if (data[this.path]) {
1410 not covered return (data[this.path] in this.map) ? true : false;
142 not covered } else {
1430 not covered return (!required || (!(this.path in data) && item && item.get(this.path))) ? true : false;
144 not covered }
145 not covered
146 not covered
147 not covered /**
148 not covered * Formats the field value
149 not covered *
150 not covered * @api public
151 not covered */
152 not covered
1531100%select.prototype.format = function(item) {
1540 not covered return this.labels[item.get(this.path)];
155 not covered };
156 not covered
157 not covered
158 not covered /*!
159 not covered * Export class
160 not covered */
161 not covered
1621100%exports = module.exports = select;

lib/fieldTypes/text.js

50% block coverage
11 SLOC
LineHitsStatementsSourceAction
1 not covered /*!
2 not covered * Module dependencies.
3 not covered */
4 not covered
51100%var util = require('util'),
61100% utils = require('keystone-utils'),
71100% super_ = require('../field');
8 not covered
9 not covered /**
10 not covered * Text FieldType Constructor
11 not covered * @extends Field
12 not covered * @api public
13 not covered */
14 not covered
15 not covered function text(list, path, options) {
1616100% this._nativeType = String;
1716100% this._underscoreMethods = ['crop'];
1816100% text.super_.call(this, list, path, options);
19 not covered }
20 not covered
21 not covered /*!
22 not covered * Inherit from Field
23 not covered */
24 not covered
251100%util.inherits(text, super_);
26 not covered
27 not covered
28 not covered /**
29 not covered * Crops the string to the specifed length.
30 not covered *
31 not covered * @api public
32 not covered */
33 not covered
341100%text.prototype.crop = function(item, length, append, preserveWords) {
350 not covered return utils.cropString(item.get(this.path), length, append, preserveWords);
36 not covered };
37 not covered
38 not covered
39 not covered /*!
40 not covered * Export class
41 not covered */
42 not covered
431100%exports = module.exports = text;

lib/fieldTypes/textarea.js

0% block coverage
15 SLOC
LineHitsStatementsSourceAction
1 not covered /*!
2 not covered * Module dependencies.
3 not covered */
4 not covered
51100%var util = require('util'),
61100% utils = require('keystone-utils'),
71100% super_ = require('../field');
8 not covered
9 not covered /**
10 not covered * Text FieldType Constructor
11 not covered * @extends Field
12 not covered * @api public
13 not covered */
14 not covered
15 not covered function textarea(list, path, options) {
160 not covered this._nativeType = String;
170 not covered this._underscoreMethods = ['format', 'crop'];
180 not covered this.height = options.height || 90;
190 not covered textarea.super_.call(this, list, path, options);
20 not covered }
21 not covered
22 not covered /*!
23 not covered * Inherit from Field
24 not covered */
25 not covered
261100%util.inherits(textarea, super_);
27 not covered
28 not covered
29 not covered /**
30 not covered * Formats the field value
31 not covered *
32 not covered * @api public
33 not covered */
34 not covered
351100%textarea.prototype.format = function(item) {
360 not covered return utils.textToHTML(item.get(this.path));
37 not covered };
38 not covered
39 not covered
40 not covered /**
41 not covered * Crops the string to the specifed length.
42 not covered *
43 not covered * @api public
44 not covered */
45 not covered
461100%textarea.prototype.crop = function(item, length, append, preserveWords) {
470 not covered return utils.cropString(item.get(this.path), length, append, preserveWords);
48 not covered };
49 not covered
50 not covered
51 not covered /*!
52 not covered * Export class
53 not covered */
54 not covered
551100%exports = module.exports = textarea;

lib/fieldTypes/url.js

0% block coverage
10 SLOC
LineHitsStatementsSourceAction
1 not covered /*!
2 not covered * Module dependencies.
3 not covered */
4 not covered
51100%var util = require('util'), super_ = require('../field');
6 not covered
7 not covered /**
8 not covered * URL FieldType Constructor
9 not covered * @extends Field
10 not covered * @api public
11 not covered */
12 not covered
13 not covered function url(list, path, options) {
140 not covered this._nativeType = String;
150 not covered this._underscoreMethods = ['format'];
160 not covered url.super_.call(this, list, path, options);
17 not covered }
18 not covered
19 not covered /*!
20 not covered * Inherit from Field
21 not covered */
22 not covered
231100%util.inherits(url, super_);
24 not covered
25 not covered
26 not covered /**
27 not covered * Formats the field value
28 not covered *
29 not covered * Strips the leading protocol from the value for simpler display
30 not covered *
31 not covered * @api public
32 not covered */
33 not covered
341100%url.prototype.format = function(item) {
350 not covered return (item.get(this.path) || '').replace(/^[a-zA-Z]\:\/\//, '');
36 not covered };
37 not covered
38 not covered
39 not covered // TODO: Proper url validation
40 not covered
41 not covered
42 not covered /*!
43 not covered * Export class
44 not covered */
45 not covered
461100%exports = module.exports = url;

lib/fieldTypes/localfiles.js

0% block coverage
172 SLOC
LineHitsStatementsSourceAction
1 not covered /*!
2 not covered * Module dependencies.
3 not covered */
4 not covered
51100%var fs = require('fs'),
61100% path = require('path'),
71100% _ = require('underscore'),
81100% moment = require('moment'),
91100% keystone = require('../../'),
101100% async = require('async'),
111100% util = require('util'),
121100% utils = require('keystone-utils'),
131100% super_ = require('../field'),
141100% async = require('async');
15 not covered
16 not covered /**
17 not covered * localfiles FieldType Constructor
18 not covered * @extends Field
19 not covered * @api public
20 not covered */
21 not covered
22 not covered function localfiles(list, path, options) {
230 not covered this._underscoreMethods = ['format', 'uploadFiles'];
24 not covered
25 not covered // event queues
260 not covered this._pre = {
27 not covered
280 not covered this._post = {
29 not covered
30 not covered // TODO: implement filtering, usage disabled for now
310 not covered options.nofilter = true;
32 not covered
33 not covered // TODO: implement initial form, usage disabled for now
34 not covered if (options.initial) {
350 not covered throw new Error('Invalid Configuration\n\n' +
36 not covered }
37 not covered
380 not covered localfiles.super_.call(this, list, path, options);
39 not covered
40 not covered // validate destination dir
410 not covered if (!options.dest) {
420 not covered throw new Error('Invalid Configuration\n\n' +
43 not covered }
44 not covered
45 not covered // Allow hook into before and after
460 not covered if (options.pre && options.pre.move) {
470 not covered this._pre.move = this._pre.move.concat(options.pre.move);
48 not covered }
49 not covered
500 not covered if (options.post && options.post.move) {
510 not covered this._post.move = this._post.move.concat(options.post.move);
52 not covered }
53 not covered }
54 not covered
55 not covered /*!
56 not covered * Inherit from Field
57 not covered */
58 not covered
591100%util.inherits(localfiles, super_);
60 not covered
61 not covered
62 not covered /**
63 not covered * Allows you to add pre middleware after the field has been initialised
64 not covered *
65 not covered * @api public
66 not covered */
67 not covered
681100%localfiles.prototype.pre = function(event, fn) {
690 not covered if (!this._pre[event]) {
700 not covered throw new Error('localfiles (' + this.list.key + '.' + this.path + ') error: localfiles.pre()\n\n' +
71 not covered }
720 not covered this._pre[event].push(fn);
730 not covered return this;
74 not covered };
75 not covered
76 not covered
77 not covered /**
78 not covered * Allows you to add post middleware after the field has been initialised
79 not covered *
80 not covered * @api public
81 not covered */
82 not covered
831100%localfiles.prototype.post = function(event, fn) {
840 not covered if (!this._post[event]) {
850 not covered throw new Error('localfiles (' + this.list.key + '.' + this.path + ') error: localfiles.post()\n\n' +
86 not covered }
870 not covered this._post[event].push(fn);
880 not covered return this;
89 not covered };
90 not covered
91 not covered
92 not covered /**
93 not covered * Registers the field on the List's Mongoose Schema.
94 not covered *
95 not covered * @api public
96 not covered */
97 not covered
981100%localfiles.prototype.addToSchema = function() {
990 not covered var field = this,
1000 not covered var mongoose = keystone.mongoose;
101 not covered
1020 not covered var paths = this.paths = {
1030 not covered filename: this._path.append('.filename'),
1040 not covered path: this._path.append('.path'),
1050 not covered size: this._path.append('.size'),
1060 not covered filetype: this._path.append('.filetype'),
107 not covered // virtuals
1080 not covered exists: this._path.append('.exists'),
1090 not covered upload: this._path.append('_upload'),
1100 not covered action: this._path.append('_action'),
1110 not covered order: this._path.append('_order'),
112 not covered };
113 not covered
1140 not covered var schemaPaths = new mongoose.Schema({
115 not covered
116 not covered // var schemaPaths = this._path.addTo({}, {
117 not covered // filename: String,
118 not covered // path: String,
119 not covered // size: Number,
120 not covered // filetype: String
121 not covered // });
122 not covered
1230 not covered schema.add(this._path.addTo({}, [schemaPaths]));
124 not covered
1250 not covered var exists = function(item) {
1260 not covered var filepaths = item.get(paths.path),
127 not covered
1280 not covered if (!filepaths || !filename) {
1290 not covered return false;
130 not covered }
1310 not covered return fs.existsSync(path.join(filepaths, filename));
132 not covered };
133 not covered
134 not covered // The .exists virtual indicates whether a file is stored
1350 not covered schema.virtual(paths.exists).get(function() {
1360 not covered return schemaMethods.exists.apply(this);
137 not covered });
138 not covered
1390 not covered var reset = function(item) {
1400 not covered item.set(field.path, {
141 not covered };
142 not covered
1430 not covered var schemaMethods = {
1440 not covered return exists(this);
145 not covered },
1460 not covered reset(this);
147 not covered },
1480 not covered if (exists(this)) {
1490 not covered fs.unlinkSync(path.join(this.get(paths.path), this.get(paths.filename)));
150 not covered }
1510 not covered reset(this);
152 not covered }
153 not covered
1540 not covered _.each(schemaMethods, function(fn, key) {
1550 not covered field.underscoreMethod(key, fn);
156 not covered });
157 not covered
158 not covered // expose a method on the field to call schema methods
1590 not covered this.apply = function(item, method) {
1600 not covered return schemaMethods[method].apply(item, Array.prototype.slice.call(arguments, 2));
161 not covered };
162 not covered
1630 not covered this.bindUnderscoreMethods();
164 not covered };
165 not covered
166 not covered
167 not covered /**
168 not covered * Formats the field value
169 not covered *
170 not covered * @api public
171 not covered */
172 not covered
1731100%localfiles.prototype.format = function(item) {
1740 not covered return path.join(item.get(this.paths.path), item.get(this.paths.filename));
175 not covered };
176 not covered
177 not covered
178 not covered /**
179 not covered * Detects whether the field has been modified
180 not covered *
181 not covered * @api public
182 not covered */
183 not covered
1841100%localfiles.prototype.isModified = function(item) {
1850 not covered return item.isModified(this.paths.path);
186 not covered };
187 not covered
188 not covered
189 not covered /**
190 not covered * Validates that a value for this field has been provided in a data object
191 not covered *
192 not covered * @api public
193 not covered */
194 not covered
1951100%localfiles.prototype.validateInput = function(data) { // TODO - how should file field input be validated?
1960 not covered return true;
197 not covered };
198 not covered
199 not covered
200 not covered /**
201 not covered * Updates the value for this field in the item from a data object
202 not covered *
203 not covered * @api public
204 not covered */
205 not covered
2061100%localfiles.prototype.updateItem = function(item, data) { // TODO - direct updating of data (not via upload) };
207 not covered
208 not covered
209 not covered /**
210 not covered * Uploads the file for this field
211 not covered *
212 not covered * @api public
213 not covered */
214 not covered
2151100%localfiles.prototype.uploadFiles = function(item, files, update, callback) {
2160 not covered var field = this;
2170 not covered var fileDatas = [];
218 not covered
2190 not covered _.each(files, function(file){
2200 not covered var prefix = field.options.datePrefix ? moment().format(field.options.datePrefix) + '-' : '',
221 not covered
2220 not covered if (field.options.allowedTypes && !_.contains(field.options.allowedTypes, file.type)){
2230 not covered return callback(new Error('Unsupported File Type: '+file.type));
224 not covered }
225 not covered
2260 not covered if ('function' === typeof update) {
2270 not covered callback = update;
2280 not covered update = false;
229 not covered }
2300 not covered var doMove = function(callback) {
2310 not covered if ('function' === typeof field.options.filename) {
2320 not covered name = field.options.filename(item, name);
233 not covered }
2340 not covered fs.rename(file.path, path.join(field.options.dest, name), function(err) {
2350 not covered if (err) return callback(err);
236 not covered
2370 not covered var fileData = {
238 not covered filename: name,
239 not covered
2400 not covered if (update) {
2410 not covered item.set(field.path, fileData);
242 not covered } else {
2430 not covered item.get(field.path).push(fileData);
244 not covered }
2450 not covered fileDatas.push(fileData);
2460 not covered callback(null, fileData);
247 not covered });
248 not covered
249 not covered
2500 not covered async.eachSeries(this._pre.move, function(fn, next) {
2510 not covered fn(item, file, next);
252 not covered }, function(err) {
2530 not covered if (err) return callback(err);
254 not covered
2550 not covered doMove(function(err, fileData) {
2560 not covered if (err) return callback(err);
257 not covered
2580 not covered async.eachSeries(field._post.move, function(fn, next) {
2590 not covered fn(item, file, fileData, next);
260 not covered }, function(err) {
2610 not covered if (err) return callback(err);
2620 not covered if (fileDatas.length === files.length) {
2630 not covered callback(null, fileDatas);
264 not covered }
265 not covered });
266 not covered });
267 not covered });
268 not covered
269 not covered
270 not covered
271 not covered
272 not covered /**
273 not covered * Returns a callback that handles a standard form submission for the field
274 not covered *
275 not covered * Expected form parts are
276 not covered * - `field.paths.action` in `req.body` (`clear` or `delete`)
277 not covered * - `field.paths.upload` in `req.files` (uploads the file to localfiles)
278 not covered *
279 not covered * @api public
280 not covered */
281 not covered
2821100%localfiles.prototype.getRequestHandler = function(item, req, paths, callback) {
2830 not covered var field = this;
284 not covered
2850 not covered if (utils.isFunction(paths)) {
2860 not covered callback = paths;
2870 not covered paths = field.paths;
2880 not covered } else if (!paths) {
2890 not covered paths = field.paths;
290 not covered }
2910 not covered callback = callback || function() {};
292 not covered
2930 not covered return function() {
2940 not covered var files = item.get(field.path),
295 not covered
2960 not covered files.sort(function(a, b) {
2970 not covered return (newOrder.indexOf(a._id.toString()) > newOrder.indexOf(b._id.toString())) ? 1 : -1;
298 not covered });
299 not covered }
3000 not covered if (req.body && req.body[paths.action]) {
3010 not covered var actions = req.body[paths.action].split('|');
302 not covered
3030 not covered actions.forEach(function(action) {
3040 not covered action = action.split(':');
305 not covered
3060 not covered var method = action[0],
307 not covered
3080 not covered if (!(/^(delete|reset)$/.test(method)) || !ids) return;
309 not covered
3100 not covered ids.split(',').forEach(function(id) {
3110 not covered field.apply(item, action);
312 not covered });
313 not covered
314 not covered });
315 not covered }
3160 not covered if (req.files && req.files[paths.upload] && (req.files[paths.upload].length > 0)) {
3170 not covered return field.uploadFiles(item, req.files[paths.upload], false, callback);
318 not covered }
3190 not covered return callback();
320 not covered
321 not covered
322 not covered
323 not covered
324 not covered /**
325 not covered * Immediately handles a standard form submission for the field (see `getRequestHandler()`)
326 not covered *
327 not covered * @api public
328 not covered */
329 not covered
3301100%localfiles.prototype.handleRequest = function(item, req, paths, callback) {
3310 not covered this.getRequestHandler(item, req, paths, callback)();
332 not covered };
333 not covered
334 not covered
335 not covered /*!
336 not covered * Export class
337 not covered */
338 not covered
3391100%exports = module.exports = localfiles;

lib/updateHandler.js

24% block coverage
126 SLOC
LineHitsStatementsSourceAction
11100%var _ = require('underscore'), keystone = require('../');
2 not covered
3 not covered /**
4 not covered * UpdateHandler Class
5 not covered *
6 not covered * @param {Object} item to update
7 not covered * @api public
8 not covered */
9 not covered
10 not covered function UpdateHandler(list, item, req, res, options) {
11 not covered
129100% if (!(this instanceof UpdateHandler))
130 not covered return new UpdateHandler(list, item);
14 not covered
159100% this.list = list;
169100% this.item = item;
179100% this.req = req;
189100% this.res = res;
199100% this.user = req.user;
209100% this.options = options || {};
21 not covered
229100% if (!this.options.errorMessage) {
239100% this.options.errorMessage = 'There was a problem saving your changes:';
24 not covered }
25 not covered
26 not covered if (this.options.user) {
270 not covered this.user = this.options.user;
28 not covered }
29 not covered
309100% this.validationMethods = {};
319100% this.validationErrors = {};
32 not covered
33 not covered }
34 not covered
35 not covered
36 not covered /**
37 not covered * Adds a custom validation method for a given path
38 not covered *
39 not covered * @param {string} path to call method for
40 not covered * @param {function} method to call
41 not covered * @api public
42 not covered */
43 not covered
441100%UpdateHandler.prototype.validate = function(path, fn) {
450 not covered this.validationMethods[path] = fn;
460 not covered return this;
47 not covered };
48 not covered
49 not covered
50 not covered /**
51 not covered * Adds a validationError to the updateHandler; can be used before
52 not covered * `.process()` is called to handle errors generated by custom pre-
53 not covered * processing.
54 not covered *
55 not covered * @param {string} path that failed validation
56 not covered * @param {string} message to display
57 not covered * @param {string} error type (defaults to 'required')
58 not covered * @api public
59 not covered */
60 not covered
611100%UpdateHandler.prototype.addValidationError = function(path, msg, type) {
620 not covered this.validationErrors[path] = {
630 not covered type: type || 'required'
64 not covered };
650 not covered return this;
66 not covered };
67 not covered
68 not covered
69 not covered /**
70 not covered * Processes data from req.body, req.query, or any data source.
71 not covered *
72 not covered * Options:
73 not covered * - fields (comma-delimited list or array of field paths)
74 not covered * - flashErrors (boolean, default false; whether to push validation errors to req.flash)
75 not covered * - ignoreNoedit (boolean, default false; whether to ignore noedit settings on fields)
76 not covered * - validationErrors (object; validation errors from previous form handling that should be included)
77 not covered *
78 not covered * @param {Object} data
79 not covered * @param {Object} options (can be comma-delimited list of fields) (optional)
80 not covered * @param {Function} callback (optional)
81 not covered * @api public
82 not covered */
83 not covered
841100%UpdateHandler.prototype.process = function(data, options, callback) {
859100% var usingDefaultFields = false;
86 not covered
879100% if ('function' === typeof options) {
889100% callback = options;
899100% options = null;
90 not covered }
919100% if (!options) {
929100% options = {};
930 not covered } else if ('string' === typeof options) {
940 not covered options = { fields: options };
95 not covered }
969100% if (!options.fields) {
979100% options.fields = _.keys(this.list.fields);
989100% usingDefaultFields = true;
990 not covered } else if ('string' === typeof options.fields) {
1000 not covered options.fields = options.fields.split(',').map(function(i) { return i.trim(); });
101 not covered }
1029100% options.required = options.required || {};
1039100% options.errorMessage = options.errorMessage || this.options.errorMessage;
1049100% options.invalidMessages = options.invalidMessages || {};
1059100% options.requiredMessages = options.requiredMessages || {};
106 not covered
107 not covered // Parse a string of required fields into field paths
1089100% if ('string' === typeof options.required) {
1090 not covered var requiredFields = options.required.split(',').map(function(i) { return i.trim(); });
1100 not covered options.required = {};
1110 not covered requiredFields.forEach(function(path) {
1120 not covered options.required[path] = true;
113 not covered });
114 not covered }
1159100% options.fields.forEach(function(path) {
1163780% var field = (path instanceof keystone.Field) ? not covered path: this.list.field(path);
11737100% if (field && field.required) {
1180 not covered options.required[path] = true;
119 not covered }
120 not covered
121 not covered // TODO: The whole progress queue management code could be a lot neater...
122 not covered
1239100% var actionQueue = [],
1249100% addValidationError = this.addValidationError.bind(this),
125 not covered validationErrors = this.validationErrors;
126 not covered
1279100% var progress = function(err) {
1289100% if (err) {
129 not covered if (options.logErrors) {
1300 not covered console.log('Error saving changes to ' + this.item.list.singular + ' ' + this.item.id + ':');
1310 not covered console.log(err);
132 not covered }
1330 not covered callback(err, this);
1349100% } else if (_.size(validationErrors)) {
135 not covered if (options.flashErrors) {
1360 not covered this.req.flash('error', {
137 not covered type: 'ValidationError',
1380 not covered list: _.pluck(validationErrors, 'message')
139 not covered });
140 not covered }
1410 not covered callback({
142 not covered } else if (actionQueue.length) {
1430 not covered actionQueue.pop()();
144 not covered } else {
1459100% saveItem();
146 not covered }
147 not covered
1489100% var saveItem = function() { // Make current user available to pre/post save events
1499100% this.item._req_user = this.user;
150 not covered
1519100% this.item.save(function(err) {
1529100% if (err) {
1530 not covered if (err.name === 'ValidationError') {
154 not covered // don't log simple validation errors
155 not covered if (options.flashErrors) {
1560 not covered this.req.flash('error', {
157 not covered type: 'ValidationError',
158 not covered title: options.errorMessage,
1590 not covered list: _.pluck(err.errors, 'message')
160 not covered });
161 not covered }
1620 not covered console.log('Error saving changes to ' + this.item.list.singular + ' ' + this.item.id + ':');
1630 not covered console.log(err);
164 not covered }
1650 not covered this.req.flash('error', 'There was an error saving your changes: ' + err.message + ' (' + err.name + (err.type ? ': ' + err.type : '') + ')');
166 not covered }
1679100% return callback(err, this);
168 not covered }.bind(this));
169 not covered }.bind(this);
170 not covered
1719100% options.fields.forEach(function(path) { // console.log('Processing field ' + path);
17237100% var message;
173 not covered
1743780% var field = (path instanceof keystone.Field) ? not covered path: this.list.field(path), invalidated = false;
175 not covered
17637100% if (!field) {
1770 not covered throw new Error('UpdateHandler.process called with invalid field path: ' + path);
178 not covered }
179 not covered
180 not covered // skip uneditable fields
1813775% if (usingDefaultFields && field.noedit && not covered !options.ignoreNoedit {
182 not covered // console.log('Skipping field ' + path + ' (noedit: true)');
1830 not covered return;
184 not covered }
1850 not covered actionQueue.push(field.getRequestHandler(this.item, this.req, options.paths, function(err) {
1860 not covered if (err && options.flashErrors) {
1870 not covered this.req.flash('error', field.label + ' upload failed - ' + err.message);
188 not covered }
1890 not covered progress(err);
190 not covered }.bind(this)));
191 not covered break;
1920 not covered actionQueue.push(field.getRequestHandler(this.item, this.req, options.paths, function(err) {
1930 not covered if (err && options.flashErrors) {
1940 not covered this.req.flash('error', field.label + ' improve failed - ' + (err.status_text || err.status));
195 not covered }
1960 not covered progress(err);
197 not covered }.bind(this)));
198 not covered break;
1990 not covered if (!data[field.path] && (!options.required[field.path] || this.item.get(field.path))) {
2000 not covered return;
201 not covered }
2020 not covered if (data[field.path] !== data[field.paths.confirm]) {
2030 not covered message = options.invalidMessages[field.path + '_match'] || 'Passwords must match';
2040 not covered addValidationError(field.path, message);
2050 not covered invalidated = true;
206 not covered }
20737100% if (!invalidated && !field.validateInput(data)) {
208 not covered // console.log('Field ' + field.path + ' is invalid');
2090 not covered message = options.invalidMessages[field.path] || field.options.invalidMessage || 'Please enter a valid ' + field.typeDescription + ' in the ' + field.label + ' field';
2100 not covered addValidationError(field.path, message);
2110 not covered invalidated = true;
212 not covered }
2133767% if (!invalidated && options.required[field.path] && not covered !field.validateInput(data, true, this.item) {
214 not covered // console.log('Field ' + field.path + ' is required, but not provided.');
2150 not covered message = options.requiredMessages[field.path] || field.options.requiredMessage || field.label + ' is required';
2160 not covered addValidationError(field.path, message);
2170 not covered invalidated = true;
218 not covered }
21937100% if (!invalidated && this.validationMethods[field.path]) {
2200 not covered message = this.validationMethods[field.path](data);
2210 not covered if (message) {
2220 not covered addValidationError(field.path, message);
223 not covered }
22437100% field.updateItem(this.item, data);
225 not covered
226 not covered
2279100% progress();
228 not covered
229 not covered
230 not covered
231 not covered /*!
232 not covered * Export class
233 not covered */
234 not covered
2351100%exports = module.exports = UpdateHandler;

lib/view.js

34% block coverage
127 SLOC
LineHitsStatementsSourceAction
1 not covered /*!
2 not covered * Module dependencies.
3 not covered */
4 not covered
51100%var _ = require('underscore'),
61100% keystone = require('../'),
71100% async = require('async'),
81100% utils = require('keystone-utils');
9 not covered
10 not covered /**
11 not covered * View Constructor
12 not covered * =================
13 not covered *
14 not covered * Helper to simplify view logic in a Keystone application
15 not covered *
16 not covered * @api public
17 not covered */
18 not covered
19 not covered function View(req, res) {
20 not covered
2113100% if (!req || req.constructor.name !== 'IncomingMessage') {
220 not covered throw new Error('Keystone.View Error: Express request object is required.');
23 not covered }
24 not covered
2513100% if (!res || res.constructor.name !== 'ServerResponse') {
260 not covered throw new Error('Keystone.View Error: Express response object is required.');
27 not covered }
28 not covered
2913100% this.req = req;
3013100% this.res = res;
31 not covered
3213100% this.initQueue = []; // executed first in series
3313100% this.actionQueue = []; // executed second in parallel, if optional conditions are met
3413100% this.queryQueue = []; // executed third in parallel
3513100% this.renderQueue = []; // executed fourth in parallel
36 not covered
37 not covered }
38 not covered
391100%module.exports = exports = View;
40 not covered
41 not covered
42 not covered /**
43 not covered * Adds a method (or array of methods) to be executed in parallel
44 not covered * to the `init`, `action` or `render` queue.
45 not covered *
46 not covered * @api public
47 not covered */
48 not covered
491100%View.prototype.on = function(on) {
5012100% var req = this.req, callback = arguments[1], values;
51 not covered
5212100% if ('function' === typeof on) {
53 not covered
540 not covered if (on()) {
550 not covered this.actionQueue.push(callback);
56 not covered }
5712100% } else if (utils.isObject(on)) {
58 not covered
591100% var check = function(value, path) {
601100% var ctx = req, parts = path.split('.');
61 not covered
622100% for (var i = 0; i < parts.length - 1; i++) {
631100% if (!ctx[parts[i]]) {
640 not covered return false;
65 not covered }
661100% ctx = ctx[parts[i]];
67 not covered }
681100% path = _.last(parts);
69 not covered
70160% return (value === true && not covered path in ctx ? not covered true: (ctx[path] === value);
71 not covered
72 not covered
731100% if (_.every(on, check)) {
741100% this.actionQueue.push(callback);
75 not covered }
7611100% } else if (on === 'get' || on === 'post' || on === 'put' || on === 'delete') {
77 not covered
7810100% if (req.method !== on.toUpperCase()) {
792100% return;
80 not covered }
818100% if (arguments.length === 3) {
82 not covered
836100% if (utils.isString(arguments[1])) {
840 not covered values = {};
850 not covered values[arguments[1]] = true;
86 not covered } else {
876100% values = arguments[1];
88 not covered }
896100% callback = arguments[2];
90 not covered
916100% var ctx = (on === 'post' || on === 'put') ? req.body : req.query;
92 not covered
936100% if (_.every(values || {}, function(value, path) {
946100% return (value === true && path in ctx) ? true : (ctx[path] === value);
95 not covered })) {
963100% this.actionQueue.push(callback);
97 not covered }
982100% this.actionQueue.push(callback);
99 not covered }
1001100% } else if (on === 'init') {
101 not covered
1021100% this.initQueue.push(callback);
103 not covered
1040 not covered } else if (on === 'render') {
105 not covered
1060 not covered this.renderQueue.push(callback);
107 not covered
10810100% return this;
109 not covered
110 not covered
1111100%var QueryCallbacks = function(options) {
1120 not covered if (utils.isString(options)) {
1130 not covered options = { then: options };
114 not covered } else {
1150 not covered options = options || {};
116 not covered }
1170 not covered this.callbacks = {};
1180 not covered if (options.err) this.callbacks.err = options.err;
1190 not covered if (options.none) this.callbacks.none = options.none;
1200 not covered if (options.then) this.callbacks.then = options.then;
1210 not covered return this;
122 not covered };
123 not covered
124125%QueryCallbacks.prototype.has = function(fn) { not covered return (fn in this.callbacks);};
125133%QueryCallbacks.prototype.err = function(fn) { not covered this.callbacks.err = fn; return this;};
126133%QueryCallbacks.prototype.none = function(fn) { not covered this.callbacks.none = fn; return this;};
127133%QueryCallbacks.prototype.then = function(fn) { not covered this.callbacks.then = fn; return this;};
128 not covered
129 not covered
130 not covered /**
131 not covered * Queues a mongoose query for execution before the view is rendered.
132 not covered * The results of the query are set in `locals[key]`.
133 not covered *
134 not covered * Keys can be nested paths, containing objects will be created as required.
135 not covered *
136 not covered * The third argument `then` can be a method to call after the query is completed
137 not covered * like function(err, results, callback), or a `populatedRelated` definition
138 not covered * (string or array).
139 not covered *
140 not covered * Examples:
141 not covered *
142 not covered * view.query('books', keystone.list('Book').model.find());
143 not covered *
144 not covered * an array of books from the database will be added to locals.books. You can
145 not covered * also nest properties on the locals variable.
146 not covered *
147 not covered * view.query(
148 not covered * 'admin.books',
149 not covered * keystone.list('Book').model.find().where('user', 'Admin')
150 not covered * );
151 not covered *
152 not covered * locals.admin.books will be the result of the query
153 not covered * views.query().then is always called if it is available
154 not covered *
155 not covered * view.query('books', keystone.list('Book').model.find())
156 not covered * .then(function (err, results, next) {
157 not covered * if (err) return next(err);
158 not covered * console.log(results);
159 not covered * next();
160 not covered * });
161 not covered *
162 not covered * @api public
163 not covered */
164 not covered
1651100%View.prototype.query = function(key, query, options) {
1660 not covered var locals = this.res.locals,
1670 not covered parts = key.split('.'),
168 not covered chain = new QueryCallbacks(options);
1690 not covered key = parts.pop();
170 not covered
1710 not covered for (var i = 0; i < parts.length; i++) {
1720 not covered if (!locals[parts[i]]) {
1730 not covered locals[parts[i]] = {};
174 not covered }
1750 not covered locals = locals[parts[i]];
176 not covered }
1770 not covered this.queryQueue.push(function(next) {
1780 not covered query.exec(function(err, results) {
1790 not covered locals[key] = results;
1800 not covered var callbacks = chain.callbacks;
181 not covered
1820 not covered if (err) {
1830 not covered if ('err' in callbacks) {
184 not covered /* Will pass errors into the err callback
185 not covered *
1860 not covered return callbacks.err(err, next);
187 not covered }
1880 not covered if ((!results || (utils.isArray(results) && !results.length)) && 'none' in callbacks) {
189 not covered /* If there are no results view.query().none will be called
1900 not covered return callbacks.none(next);
1910 not covered } else if ('then' in callbacks) {
1920 not covered if (utils.isFunction(callbacks.then)) {
1930 not covered return callbacks.then(err, results, next);
194 not covered } else {
1950 not covered return keystone.populateRelated(results, callbacks.then, next);
196 not covered }
1970 not covered return next(err);
198 not covered
199 not covered });
200 not covered
2010 not covered return chain;
202 not covered };
203 not covered
204 not covered
205 not covered /**
206 not covered * Executes the current queue of init and action methods in series, and
207 not covered * then executes the render function. If renderFn is a string, it is provided
208 not covered * to `res.render`.
209 not covered *
210 not covered * It is expected that *most* init stacks require processing in series,
211 not covered * but it is safe to execute actions in parallel.
212 not covered *
213 not covered * If there are several init methods that should be run in parallel, queue
214 not covered * them as an array, e.g. `view.on('init', [first, second])`.
215 not covered *
216 not covered * @api public
217 not covered */
2181100%View.prototype.render = function(renderFn, locals, callback) {
21912100% var req = this.req, res = this.res;
220 not covered
22112100% if ('string' === typeof renderFn) {
2220 not covered var viewPath = renderFn;
2230 not covered renderFn = function() {
2240 not covered if ('function' === typeof locals) {
2250 not covered locals = locals();
226 not covered }
2270 not covered this.res.render(viewPath, locals, callback);
228 not covered }.bind(this);
22912100% if ('function' !== typeof renderFn) {
2300 not covered throw new Error('Keystone.View.render() renderFn must be a templatePath (string) or a function.');
231 not covered }
23212100% this.initQueue.push(this.actionQueue);
23312100% this.initQueue.push(this.queryQueue);
234 not covered
23512100% var preRenderQueue = [];
236 not covered
237 not covered // Add Keystone's global pre('render') queue
23812100% keystone._pre.render.forEach(function(fn) {
2390 not covered preRenderQueue.push(function(next) {
2400 not covered fn(req, res, next);
241 not covered });
242 not covered });
243 not covered
24412100% this.initQueue.push(preRenderQueue);
24512100% this.initQueue.push(this.renderQueue);
246 not covered
24712100% async.eachSeries(this.initQueue, function(i, next) {
24849100% if (Array.isArray(i)) {
249 not covered // process nested arrays in parallel
25048100% async.parallel(i, next);
2511100% } else if ('function' === typeof i) {
252 not covered // process single methods in series
2531100% i(next);
254 not covered } else {
2550 not covered throw new Error('Keystone.View.render() events must be functions.');
256 not covered }
257 not covered }, function(err) {
25812100% renderFn(err, req, res);
259 not covered });
260 not covered

lib/email.js

0% block coverage
196 SLOC
LineHitsStatementsSourceAction
11100%var _ = require('underscore'),
21100% keystone = require('../'),
31100% fs = require('fs'),
41100% util = require('util'),
51100% path = require('path'),
61100% moment = require('moment'),
71100% utils = require('keystone-utils'),
81100% mandrillapi = require('mandrill-api');
9 not covered
101100%var templateCache = {};
11 not covered
121100%var defaultConfig = { templateExt: 'jade',
131100% templateEngine: require('jade'),
141100% templateBasePath: path.normalize(path.join(__dirname, '..', 'templates', 'helpers', 'emails')),
15 not covered mandrill: {
16 not covered
17 not covered
18 not covered /** Custom Errors */
19 not covered
201100%var ErrorNoEmailTemplateName = function() {
210 not covered Error.apply(this, arguments);
220 not covered Error.captureStackTrace(this, arguments.callee);
230 not covered this.message = 'No email templateName specified.';
240 not covered this.name = 'ErrorNoEmailTemplateName';
25 not covered };
261100%util.inherits(ErrorNoEmailTemplateName, Error);
27 not covered
281100%var ErrorEmailsPathNotSet = function() {
290 not covered Error.apply(this, arguments);
300 not covered Error.captureStackTrace(this, arguments.callee);
310 not covered this.message = 'Keystone has not been configured for email support. Set the `emails` option in your configuration.';
320 not covered this.name = 'ErrorEmailsPathNotSet';
33 not covered };
341100%util.inherits(ErrorEmailsPathNotSet, Error);
35 not covered
361100%var ErrorEmailOptionsRequired = function() {
370 not covered Error.apply(this, arguments);
380 not covered Error.captureStackTrace(this, arguments.callee);
390 not covered this.message = 'The keystone.Email class requires a templateName or options argument to be provided.';
400 not covered this.name = 'ErrorEmailOptionsRequired';
41 not covered };
421100%util.inherits(ErrorEmailsPathNotSet, Error);
43 not covered
44 not covered
45 not covered /** Helper methods */
46 not covered
471100%var getEmailsPath = function() {
480 not covered var path = keystone.getPath('emails');
490 not covered if (path) {
500 not covered return path;
51 not covered }
520 not covered throw new ErrorEmailsPathNotSet();
53 not covered };
54 not covered
55 not covered
56 not covered /** CSS Helper methods */
57 not covered
581100%var templateCSSMethods = { shadeColor: function(color, percent) {
590 not covered var num = parseInt(color.slice(1),16), amt = Math.round(2.55 * percent), R = (num >> 16) + amt, G = (num >> 8 & 0x00FF) + amt, B = (num & 0x0000FF) + amt;
600 not covered return '#' + (0x1000000 + (R<255?R<1?0:R:255)*0x10000 + (G<255?G<1?0:G:255)*0x100 + (B<255?B<1?0:B:255)).toString(16).slice(1);
61 not covered }
62 not covered
63 not covered
64 not covered /**
65 not covered * Email Class
66 not covered * ===========
67 not covered *
68 not covered * Helper class for sending emails with Mandrill.
69 not covered *
70 not covered * New instances take a `templatePath` string which must be a folder in the
71 not covered * emails path, and must contain either `templateName/email.templateExt` or `templateName.templateExt`
72 not covered * which is used as the template for the HTML part of the email.
73 not covered *
74 not covered * Once created, emails can be rendered or sent.
75 not covered *
76 not covered * Requires the `emails` path option and the `mandrill api key` option to be
77 not covered * set on Keystone.
78 not covered *
79 not covered * @api public
80 not covered */
81 not covered
821100%var Email = function(options) { // Passing a string will use Email.defaults for everything but template name
830 not covered if ('string' === typeof(options)) {
840 not covered options = {
85 not covered templateName: options
860 not covered } else if (!utils.isObject(options)) {
870 not covered throw new ErrorEmailOptionsRequired();
88 not covered }
890 not covered this.templateName = options.templateName;
900 not covered this.templateExt = options.templateExt || Email.defaults.templateExt;
910 not covered this.templateEngine = options.templateEngine || Email.defaults.templateEngine;
920 not covered this.templateBasePath = options.templateBasePath || Email.defaults.templateBasePath;
93 not covered
940 not covered if (!this.templateName) {
950 not covered throw new ErrorNoEmailTemplateName();
96 not covered }
970 not covered return this;
98 not covered
99 not covered
100 not covered /**
101 not covered * Renders the email and passes it to the callback. Used by `email.send()` but
102 not covered * can also be called directly to generate a preview.
103 not covered *
104 not covered * @param {Object} locals - object of local variables provided to the template
105 not covered * @param {Function} callback(err, email)
106 not covered *
107 not covered * @api public
108 not covered */
109 not covered
1101100%Email.prototype.render = function(locals, callback) {
1110 not covered if ('function' === typeof locals && !callback) {
1120 not covered callback = locals;
1130 not covered locals = {};
114 not covered }
1150 not covered locals = ('object' === typeof locals) ? locals : {};
1160 not covered callback = ('function' === typeof callback) ? callback : function() {};
117 not covered
1180 not covered if (keystone.get('email locals')) {
1190 not covered _.defaults(locals, keystone.get('email locals'));
120 not covered }
1210 not covered _.defaults(locals, {
1220 not covered brand: keystone.get('brand'),
123 not covered theme: {},
124 not covered css: templateCSSMethods
125 not covered
1260 not covered if (!locals.theme.buttons) {
1270 not covered locals.theme.buttons = {};
128 not covered }
1290 not covered this.compileTemplate(function(err) {
1300 not covered if (err) {
1310 not covered return callback(err);
132 not covered }
133 not covered
1340 not covered var html = templateCache[this.templateName](locals);
135 not covered
136 not covered // ensure extended characters are replaced
1370 not covered html = html.replace(/[\u007f-\uffff]/g, function(c) {
1380 not covered return '&#x'+('0000'+c.charCodeAt(0).toString(16)).slice(-4)+';';
139 not covered });
140 not covered
141 not covered // process email rules
1420 not covered var rules = keystone.get('email rules');
143 not covered
1440 not covered if (rules) {
145 not covered
1460 not covered if (!Array.isArray(rules)) {
1470 not covered rules = [rules];
148 not covered }
1490 not covered _.each(rules, function(rule) {
1500 not covered if (rule.find && rule.replace) {
151 not covered
1520 not covered var find = rule.find,
153 not covered
1540 not covered if ('string' === typeof find) {
1550 not covered find = new RegExp(find, 'gi');
156 not covered }
157 not covered
1580 not covered html = html.replace(find, replace);
159 not covered }
160 not covered
161 not covered }
162 not covered
1630 not covered callback(null, {
164 not covered
165 not covered
166 not covered
167 not covered
168 not covered /**
169 not covered * Loads the template. Looks for `templateName.templateExt`, followed by `templateName/email.templateExt`
170 not covered *
171 not covered * @param {Function} callback(err)
172 not covered *
173 not covered * @api private
174 not covered */
175 not covered
1761100%Email.prototype.loadTemplate = function(callback) {
1770 not covered var fsTemplatePath = path.join(Email.getEmailsPath(), this.templateName + '.' + this.templateExt);
178 not covered
1790 not covered fs.readFile(fsTemplatePath, function(err, contents) {
1800 not covered if (err) {
1810 not covered if (err.code === 'ENOENT') {
182 not covered
1830 not covered fsTemplatePath = path.join(Email.getEmailsPath(), this.templateName, 'email.' + this.templateExt);
184 not covered
1850 not covered fs.readFile(fsTemplatePath, function(err, contents) {
1860 not covered callback(err, fsTemplatePath, contents);
187 not covered });
188 not covered
189 not covered } else {
1900 not covered return callback(err);
191 not covered }
192 not covered } else {
1930 not covered return callback(err, fsTemplatePath, contents);
194 not covered }
195 not covered
196 not covered
197 not covered /**
198 not covered * Ensures the template for the email has been compiled
199 not covered *
200 not covered * @param {Function} callback(err)
201 not covered *
202 not covered * @api private
203 not covered */
204 not covered
2051100%Email.prototype.compileTemplate = function(callback) {
2060 not covered if (keystone.get('env') === 'production' && templateCache[this.templateName]) {
2070 not covered return process.nextTick(callback);
208 not covered }
2090 not covered this.loadTemplate(function(err, filename, contents) {
2100 not covered if (err) return callback(err);
211 not covered
2120 not covered var template = this.templateEngine.compile(contents.toString(), Email.defaults.compilerOptions || { filename: fs.realpathSync(filename), basedir: this.templateBasePath });
213 not covered
2140 not covered templateCache[this.templateName] = template;
215 not covered
2160 not covered callback();
217 not covered
218 not covered }.bind(this));
219 not covered
220 not covered
221 not covered /**
222 not covered * Prepares the email and sends it
223 not covered *
224 not covered * Options:
225 not covered *
226 not covered * - mandrill
227 not covered * Initialised Mandrill API instance
228 not covered *
229 not covered * - tags
230 not covered * Array of tags to send to Mandrill
231 not covered *
232 not covered * - to
233 not covered * Object / String or Array of Objects / Strings to send to, e.g.
234 not covered * ['jed@team9.com.au', { email: 'jed.watson@gmail.com' }]
235 not covered * { email: 'jed@team9.com.au' }
236 not covered * 'jed@team9.com.au'
237 not covered *
238 not covered * - fromName
239 not covered * Name to send from
240 not covered *
241 not covered * - fromEmail
242 not covered * Email address to send from
243 not covered *
244 not covered * For compatibility with older implementations, send supports providing
245 not covered * locals and options objects as the first and second arguments, and the
246 not covered * callback as the third.
247 not covered *
248 not covered * @param {Object} options (passed to `email.render()`)
249 not covered * @param {Function} callback(err, info)
250 not covered *
251 not covered * @api private
252 not covered */
253 not covered
2541100%Email.prototype.send = function(options, callback) {
2550 not covered var locals = options;
256 not covered
2570 not covered if (arguments.length === 3 || !utils.isFunction(callback)) {
2580 not covered callback = arguments[2];
2590 not covered options = arguments[1] || arguments[0];
260 not covered }
2610 not covered this.render(locals, function(err, email) {
2620 not covered callback = ('function' === typeof callback) ? callback : function() {};
263 not covered
2640 not covered if (err) {
2650 not covered return callback(err);
266 not covered }
267 not covered
2680 not covered if (!utils.isObject(options)) {
2690 not covered return callback({
270 not covered from: 'Email.send',
271 not covered key: 'invalid options',
2720 not covered if ('string' === typeof options.from) {
2730 not covered options.fromName = options.from;
2740 not covered options.fromEmail = options.from;
2750 not covered } else if (utils.isObject(options.from)) {
2760 not covered options.fromName = utils.isObject(options.from.name) ? options.from.name.full : options.from.name;
2770 not covered options.fromEmail = options.from.email;
278 not covered }
2790 not covered if (!options.fromName || !options.fromEmail) {
2800 not covered return callback({
281 not covered from: 'Email.send',
2820 not covered if (!options.mandrill) {
2830 not covered if (!keystone.get('mandrill api key'))
2840 not covered return callback({
285 not covered from: 'Email.send',
2860 not covered options.mandrill = new mandrillapi.Mandrill(keystone.get('mandrill api key'));
287 not covered }
2880 not covered options.tags = utils.isArray(options.tags) ? options.tags : [];
2890 not covered options.tags.push('sent:' + moment().format('YYYY-MM-DD'));
2900 not covered options.tags.push(this.templateName);
291 not covered
2920 not covered if (keystone.get('env') === 'development') {
2930 not covered options.tags.push('development');
294 not covered }
2950 not covered var recipients = [],
296 not covered
2970 not covered options.to = Array.isArray(options.to) ? options.to : [options.to];
298 not covered
2990 not covered for (var i = 0; i < options.to.length; i++) {
300 not covered
3010 not covered if ('string' === typeof options.to[i]) {
3020 not covered options.to[i] = { email: options.to[i] };
3030 not covered } else if ('object' === typeof options.to[i]) {
3040 not covered if (!options.to[i].email) {
3050 not covered return callback({
306 not covered from: 'Email.send',
3070 not covered message: 'Recipient ' + (i + 1) + ' does not have a valid email address.'
308 not covered });
3090 not covered return callback({
3100 not covered message: 'Recipient ' + (i + 1) + ' is not a string or an object.'
311 not covered });
312 not covered }
3130 not covered var recipient = { email: options.to[i].email },
314 not covered
3150 not covered if ('string' === typeof options.to[i].name) {
3160 not covered recipient.name = options.to[i].name;
3170 not covered vars.push({ name: 'name', content: options.to[i].name });
3180 not covered } else if ('object' === typeof options.to[i].name) {
3190 not covered recipient.name = options.to[i].name.full || '';
3200 not covered vars.push({ name: 'name', content: options.to[i].name.full || '' });
3210 not covered vars.push({ name: 'first_name', content: options.to[i].name.first || '' });
3220 not covered vars.push({ name: 'last_name', content: options.to[i].name.last || '' });
323 not covered }
3240 not covered recipients.push(recipient);
325 not covered
3260 not covered mergeVars.push({
327 not covered }
3280 not covered var onSuccess = function(info) {
3290 not covered callback(null, info);
330 not covered };
331 not covered
3320 not covered var onFail = function(info) {
3330 not covered callback({
334 not covered };
335 not covered
3360 not covered var message = {
337 not covered
3380 not covered _.defaults(message, options.mandrillOptions);
3390 not covered _.defaults(message, Email.defaults.mandrill);
340 not covered
3410 not covered return process.nextTick(function() {
3420 not covered options.mandrill.messages.send({ message: message }, onSuccess, onFail);
343 not covered });
344 not covered
345 not covered
346 not covered
3471100%Email.getEmailsPath = getEmailsPath;
3481100%Email.templateCache = templateCache;
3491100%Email.templateCSSMethods = templateCSSMethods;
3501100%Email.defaults = defaultConfig;
351 not covered
3521100%exports = module.exports = Email;

lib/security/csrf.js

93% block coverage
45 SLOC
LineHitsStatementsSourceAction
11100%var crypto = require('crypto'), utils = require('keystone-utils');
2 not covered
31100%exports.TOKEN_KEY = '_csrf';
41100%exports.LOCAL_KEY = 'csrf_token_key';
51100%exports.LOCAL_VALUE = 'csrf_token_value';
61100%exports.SECRET_KEY = exports.TOKEN_KEY + '_secret';
71100%exports.SECRET_LENGTH = 10;
8 not covered
9 not covered function tokenize(salt, secret) {
1010100% return salt + crypto.createHash('sha1').update(salt + secret).digest('base64');
11 not covered }
12 not covered
131100%exports.createSecret = function() {
145100% return crypto.pseudoRandomBytes(exports.SECRET_LENGTH).toString('base64');
15 not covered };
16 not covered
171100%exports.getSecret = function(req) {
187100% return req.session[exports.SECRET_KEY] || (req.session[exports.SECRET_KEY] = exports.createSecret());
19 not covered };
20 not covered
211100%exports.createToken = function(req) {
225100% return tokenize(utils.randomString(exports.SECRET_LENGTH), exports.getSecret(req));
23 not covered };
24 not covered
251100%exports.getToken = function(req, res) {
262100% return res.locals[exports.LOCAL_VALUE] || (res.locals[exports.LOCAL_VALUE] = exports.createToken(req));
27 not covered };
28 not covered
291100%exports.requestToken = function(req) {
306100% if (req.body && req.body[exports.TOKEN_KEY]) {
313100% return req.body[exports.TOKEN_KEY];
323100% } else if (req.query && req.query[exports.TOKEN_KEY]) {
331100% return req.query[exports.TOKEN_KEY];
34 not covered }
352100% return '';
36 not covered };
37 not covered
381100%exports.validate = function(req, token) {
395100% if (arguments.length === 1) {
403100% token = exports.requestToken(req);
41 not covered }
425100% if (typeof token !== 'string') {
430 not covered return false;
44 not covered }
455100% return token === tokenize(token.slice(0, exports.SECRET_LENGTH), req.session[exports.SECRET_KEY]);
46 not covered };
47 not covered
481100%exports.middleware = { init: function(req, res, next) {
491100% res.locals[exports.LOCAL_KEY] = exports.LOCAL_VALUE;
501100% exports.getToken(req, res);
511100% next();
52 not covered },
533100% if (req.method === 'GET' || req.method === 'HEAD' || req.method === 'OPTIONS') {
541100% return next();
55 not covered }
562100% if (exports.validate(req)) {
571100% next();
58 not covered } else {
591100% res.statusCode = 403;
601100% next(new Error('CSRF token mismatch'));
61 not covered }

lib/session.js

0% block coverage
68 SLOC
LineHitsStatementsSourceAction
11100%var keystone = require('../'), crypto = require('crypto');
2 not covered
3 not covered /**
4 not covered * Creates a hash of str with Keystone's cookie secret.
5 not covered * Only hashes the first half of the string.
6 not covered */
71100%var hash = function(str) { // force type
80 not covered str = '' + str;
9 not covered // get the first half
100 not covered str = str.substr(0, Math.round(str.length / 2));
11 not covered // hash using sha256
12 not covered return crypto
130 not covered .createHmac('sha256', keystone.get('cookie secret'))
140 not covered .update(str)
150 not covered .digest('base64')
160 not covered .replace(/\=+$/, '');
17 not covered };
18 not covered
19 not covered /**
20 not covered * Signs in a user user matching the lookup filters
21 not covered *
22 not covered * @param {Object} lookup - must contain email and password
23 not covered * @param {Object} req - express request object
24 not covered * @param {Object} res - express response object
25 not covered * @param {function()} onSuccess callback, is passed the User instance
26 not covered * @param {function()} onFail callback
27 not covered */
28 not covered
291100%exports.signin = function(lookup, req, res, onSuccess, onFail) {
300 not covered if (!lookup) {
310 not covered return onFail(new Error('session.signin requires a User ID or Object as the first argument'));
32 not covered }
330 not covered var User = keystone.list(keystone.get('user model'));
34 not covered
350 not covered var doSignin = function(user) {
360 not covered req.session.regenerate(function() {
370 not covered req.user = user;
380 not covered req.session.userId = user.id;
39 not covered
40 not covered // if the user has a password set, store a persistence cookie to resume sessions
410 not covered if (keystone.get('cookie signin') && user.password) {
420 not covered var userToken = user.id + ':' + hash(user.password);
430 not covered res.cookie('keystone.uid', userToken, { signed: true, httpOnly: true });
44 not covered }
450 not covered onSuccess(user);
46 not covered
47 not covered };
48 not covered
490 not covered if ('string' === typeof lookup.email && 'string' === typeof lookup.password) {
50 not covered
510 not covered User.model.findOne({ email: lookup.email }).exec(function(err, user) {
520 not covered if (user) {
530 not covered user._.password.compare(lookup.password, function(err, isMatch) {
540 not covered if (!err && isMatch) {
550 not covered doSignin(user);
56 not covered }
57 not covered else {
580 not covered onFail(err);
59 not covered }
60 not covered });
610 not covered onFail(err);
62 not covered }
63 not covered
640 not covered lookup = '' + lookup;
65 not covered
660 not covered var userId = (lookup.indexOf(':') > 0) ? lookup.substr(0, lookup.indexOf(':')) : lookup,
67 not covered
680 not covered User.model.findById(userId).exec(function(err, user) {
690 not covered if (user && (!passwordCheck || passwordCheck === hash(user.password))) {
700 not covered doSignin(user);
71 not covered } else {
720 not covered onFail(err);
73 not covered }
74 not covered });
75 not covered }
76 not covered
77 not covered
78 not covered /**
79 not covered * Signs the current user out and resets the session
80 not covered *
81 not covered * @param {Object} req - express request object
82 not covered * @param {Object} res - express response object
83 not covered * @param {function()} next callback
84 not covered */
85 not covered
861100%exports.signout = function(req, res, next) {
870 not covered res.clearCookie('keystone.uid');
880 not covered req.user = null;
89 not covered
900 not covered req.session.regenerate(next);
91 not covered
92 not covered
93 not covered
94 not covered /**
95 not covered * Middleware to ensure session persistence across server restarts
96 not covered *
97 not covered * Looks for a userId cookie, and if present, and there is no user signed in,
98 not covered * automatically signs the user in.
99 not covered *
100 not covered * @param {Object} req - express request object
101 not covered * @param {Object} res - express response object
102 not covered * @param {function()} next callback
103 not covered */
104 not covered
1051100%exports.persist = function(req, res, next) {
1060 not covered var User = keystone.list(keystone.get('user model'));
107 not covered
1080 not covered if (keystone.get('cookie signin') && !req.session.userId && req.signedCookies['keystone.uid'] && req.signedCookies['keystone.uid'].indexOf(':') > 0) {
109 not covered
1100 not covered var _next = function() { next(); }; // otherwise the matching user is passed to next() which messes with the middleware signature
1110 not covered exports.signin(req.signedCookies['keystone.uid'], req, res, _next, _next);
112 not covered
113 not covered } else if (req.session.userId) {
114 not covered
1150 not covered User.model.findById(req.session.userId).exec(function(err, user) {
1160 not covered if (err) {
1170 not covered return next(err);
118 not covered }
119 not covered
1200 not covered req.user = user;
1210 not covered next();
122 not covered
123 not covered
124 not covered }
125 not covered else {
1260 not covered next();
127 not covered }
128 not covered
129 not covered
130 not covered /**
131 not covered * Middleware to enable access to Keystone
132 not covered *
133 not covered * Bounces the user to the signin screen if they are not signed in or do not have permission.
134 not covered *
135 not covered * @param {Object} req - express request object
136 not covered * @param {Object} res - express response object
137 not covered * @param {function()} next callback
138 not covered */
139 not covered
1401100%exports.keystoneAuth = function(req, res, next) {
1410 not covered if (!req.user || !req.user.canAccessKeystone) {
1420 not covered var from = new RegExp('^\/keystone\/?$', 'i').test(req.url) ? '' : '?from=' + req.url;
1430 not covered return res.redirect(keystone.get('signin url') + from);
144 not covered }
1450 not covered next();
146 not covered

lib/asyncdi.js

40% block coverage
57 SLOC
LineHitsStatementsSourceAction
11100%var _ = require('underscore'), async = require('async');
2 not covered
3 not covered /**
4 not covered * Asynchronous Dependency Injection Library
5 not covered */
6 not covered
7 not covered // This regex detects the arguments portion of a function definition
8 not covered // Thanks to Angular for the regex
91100%var FN_ARGS = /^function\s*[^\(]*\(\s*([^\)]*)\)/m;
10 not covered
11 not covered
12 not covered /**
13 not covered * Calling the module directly returns a new Wrapper instance
14 not covered *
15 not covered * @param {Function} fn
16 not covered * @return {Wrapper}
17 not covered */
181100%exports = module.exports = function(fn) {
198100% return new Wrapper(fn);
20 not covered };
21 not covered
22 not covered /**
23 not covered * Wrapper Class
24 not covered *
25 not covered * @param {Function} fn
26 not covered * @param {Object} context
27 not covered */
281100%var Wrapper = exports.Wrapper = function Wrapper(fn, context) { // Ensure a new instance has been created. // Calling Wrapper as a function will return a new instance instead.
299100% if (!(this instanceof Wrapper)) {
300 not covered return new Wrapper(fn, context);
31 not covered }
329100% if (!_.isFunction(fn)) {
330 not covered throw new Error('AsyncDI Wrapper must be initialised with a function');
34 not covered }
359100% this.fn = fn;
36 not covered
379100% this.deps = fn.toString().match(FN_ARGS)[1].split(',').map(function(i) { return i.trim(); });
38 not covered
399100% this.requires = {};
409100% this.deps.forEach(function(i) {
419100% this.requires[i] = true;
42 not covered }, this);
43 not covered
449100% if (_.last(this.deps) === 'callback') {
452100% this.isAsync = true;
462100% this.deps.pop();
47 not covered }
489100% this._context = context;
49 not covered
509100% this._provides = {};
51 not covered
529100% this._arguments = [];
53 not covered
54 not covered
551100%_.extend(Wrapper.prototype, { /** * Registers dependencies that can be provided to the function * @param {Object} provides map of key: value pairs * @return {Wrapper} this instance */ provides: function(provides) {
560 not covered _.extend(this._provides, provides);
570 not covered this._arguments = _.map(this.deps, function(key) {
580 not covered return this._provides[key];
59 not covered }, this);
600 not covered return this;
61 not covered },
62 not covered
630 not covered this._context = context;
640 not covered return this;
65 not covered },
663100% if (arguments.length === 1) {
672100% callback = context;
682100% context = this._context;
69 not covered }
701100% var asyncArgs = this._arguments.slice();
71 not covered // push the callback onto the new arguments array
721100% asyncArgs.push(callback);
73 not covered // call the function
741100% this.fn.apply(context, asyncArgs);
75 not covered } else {
762100% if (callback) {
77 not covered // If a callback is provided, it must use the error-first arguments pattern.
781100% callback(null, this.fn.apply(context, this._arguments));
79 not covered } else {
801100% return this.fn.apply(context, this._arguments);
81 not covered }
820 not covered var wrapper = this;
83 not covered if (this.isAsync) {
840 not covered return async.each(arr, function(item, cb) {
850 not covered wrapper.call(item, cb);
86 not covered }, callback);
87 not covered } else {
880 not covered arr.each(function(item) {
890 not covered wrapper.call(item);
90 not covered });
910 not covered if (callback) { callback(); }
92 not covered }
930 not covered var wrapper = this;
94 not covered if (this.isAsync) {
950 not covered async.map(arr, function(item, cb) {
960 not covered wrapper.call(item, cb);
97 not covered }, callback);
98 not covered } else {
990 not covered callback(null, arr.map(function(item) {
1000 not covered return wrapper.call(item);
101 not covered }));
102 not covered }

lib/security/ipRangeRestrict.js

0% line coverage
0% statement coverage
0% block coverage

lib/updates.js

0% line coverage
0% statement coverage
0% block coverage

routes/api/cloudinary.js

0% line coverage
0% statement coverage
0% block coverage

routes/api/list.js

0% line coverage
0% statement coverage
0% block coverage

routes/download/list.js

0% line coverage
0% statement coverage
0% block coverage

routes/views/home.js

0% line coverage
0% statement coverage
0% block coverage

routes/views/item.js

0% line coverage
0% statement coverage
0% block coverage

routes/views/list.js

0% line coverage
0% statement coverage
0% block coverage

routes/views/signin.js

0% line coverage
0% statement coverage
0% block coverage

routes/views/signout.js

0% line coverage
0% statement coverage
0% block coverage