Coverage
32% line coverage
25% statement coverage
12% block coverage
4601 SLOC
index.js
53% line coverage go jump to first missed line
52% statement coverage go jump to first missed statement
16% block coverage
184 SLOC
Line | Hits | Statements | Source | Action |
---|---|---|---|---|
1 | 1 | 100% | var fs = require('fs'), | |
2 | 1 | 100% | path = require('path'), | |
3 | 1 | 100% | _ = require('underscore'), | |
4 | 1 | 100% | express = require('express'), | |
5 | 1 | 100% | async = require('async'), | |
6 | 1 | 100% | jade = require('jade'), | |
7 | 1 | 100% | moment = require('moment'), | |
8 | 1 | 100% | numeral = require('numeral'), | |
9 | 1 | 100% | cloudinary = require('cloudinary'), | |
10 | 1 | 100% | utils = require('keystone-utils'); | |
11 | not covered | |||
12 | 1 | 100% | 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 */ | |||
20 | 1 | 100% | var moduleRoot = (function(_rootPath) { | |
21 | 1 | 100% | var parts = _rootPath.split(path.sep); | |
22 | 1 | 100% | parts.pop(); //get rid of /node_modules from the end of the path | |
23 | 1 | 100% | return parts.join(path.sep); | |
24 | 1 | 50% | })(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 | |||
33 | 1 | 100% | var Keystone = function() { | |
34 | 1 | 100% | this.lists = {}; | |
35 | 1 | 100% | this.paths = {}; | |
36 | 1 | 100% | this._options = { 'name': 'Keystone', 'brand': 'Keystone', 'compress': true, 'headless': false, 'logger': 'dev', 'auto update': false, 'model prefix': null }; | |
37 | not covered 'name': 'Keystone', | |||
38 | 1 | 100% | this._pre = { routes: [], render: [] }; | |
39 | 1 | 100% | this._redirects = {}; | |
40 | not covered | |||
41 | 1 | 100% | this.express = express; | |
42 | not covered | |||
43 | 1 | 100% | this.set('env', process.env.NODE_ENV || 'development'); | |
44 | not covered | |||
45 | 1 | 100% | this.set('port', process.env.PORT || process.env.OPENSHIFT_NODEJS_PORT); | |
46 | 1 | 100% | this.set('host', process.env.HOST || process.env.IP || process.env.OPENSHIFT_NODEJS_IP); | |
47 | 1 | 100% | this.set('listen', process.env.LISTEN); | |
48 | not covered | |||
49 | 1 | 100% | this.set('ssl', process.env.SSL); | |
50 | 1 | 100% | this.set('ssl port', process.env.SSL_PORT); | |
51 | 1 | 100% | this.set('ssl host', process.env.SSL_HOST || process.env.SSL_IP); | |
52 | 1 | 100% | this.set('ssl key', process.env.SSL_KEY); | |
53 | 1 | 100% | this.set('ssl cert', process.env.SSL_CERT); | |
54 | not covered | |||
55 | 1 | 100% | this.set('cookie secret', process.env.COOKIE_SECRET); | |
56 | 1 | 83% | this.set('cookie signin', (this.get('env') === 'development') ? true : not covered false; | |
57 | not covered | |||
58 | 1 | 100% | this.set('embedly api key', process.env.EMBEDLY_API_KEY || process.env.EMBEDLY_APIKEY); | |
59 | 1 | 100% | this.set('mandrill api key', process.env.MANDRILL_API_KEY || process.env.MANDRILL_APIKEY); | |
60 | 1 | 100% | this.set('mandrill username', process.env.MANDRILL_USERNAME); | |
61 | 1 | 100% | this.set('google api key', process.env.GOOGLE_BROWSER_KEY); | |
62 | 1 | 100% | this.set('google server api key', process.env.GOOGLE_SERVER_KEY); | |
63 | 1 | 100% | this.set('ga property', process.env.GA_PROPERTY); | |
64 | 1 | 100% | this.set('ga domain', process.env.GA_DOMAIN); | |
65 | 1 | 100% | this.set('chartbeat property', process.env.CHARTBEAT_PROPERTY); | |
66 | 1 | 100% | this.set('chartbeat domain', process.env.CHARTBEAT_DOMAIN); | |
67 | 1 | 100% | this.set('allowed ip ranges', process.env.ALLOWED_IP_RANGES); | |
68 | not covered | |||
69 | 1 | 100% | if (process.env.S3_BUCKET && process.env.S3_KEY && process.env.S3_SECRET) { | |
70 | 0 | 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 } | |||
72 | 1 | 100% | if (process.env.AZURE_STORAGE_ACCOUNT && process.env.AZURE_STORAGE_ACCESS_KEY) { | |
73 | 0 | not covered this.set('azurefile config', { account: process.env.AZURE_STORAGE_ACCOUNT, key: process.env.AZURE_STORAGE_ACCESS_KEY }); | ||
74 | not covered } | |||
75 | 0 | not covered this.set('cloudinary config', true); | ||
76 | not covered } | |||
77 | 1 | 100% | this.initAPI = require('./lib/middleware/initAPI')(this); | |
78 | not covered | |||
79 | not covered | |||
80 | 1 | 100% | _.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 | |||
94 | 1 | 100% | Keystone.prototype.pre = function(event, fn) { | |
95 | 0 | not covered if (!this._pre[event]) { | ||
96 | 0 | not covered throw new Error('keystone.pre() Error: event ' + event + ' does not exist.'); | ||
97 | not covered } | |||
98 | 0 | not covered this._pre[event].push(fn); | ||
99 | 0 | not covered return this; | ||
100 | not covered }; | |||
101 | not covered | |||
102 | not covered | |||
103 | 1 | 100% | Keystone.prototype.prefixModel = function (key) { | |
104 | 15 | 100% | var modelPrefix = this.get('model prefix'); | |
105 | not covered | |||
106 | 15 | 100% | if (modelPrefix) | |
107 | 0 | not covered key = modelPrefix + '_' + key; | ||
108 | not covered | |||
109 | 15 | 100% | return require('mongoose/lib/utils').toCollectionName(key); | |
110 | not covered }; | |||
111 | not covered | |||
112 | not covered /* Attach core functionality to Keystone.prototype */ | |||
113 | 1 | 100% | Keystone.prototype.init = require('./lib/core/init'); | |
114 | 1 | 100% | Keystone.prototype.initNav = require('./lib/core/initNav'); | |
115 | 1 | 100% | Keystone.prototype.connect = require('./lib/core/connect'); | |
116 | 1 | 100% | Keystone.prototype.start = require('./lib/core/start'); | |
117 | 1 | 100% | Keystone.prototype.mount = require('./lib/core/mount'); | |
118 | 1 | 100% | Keystone.prototype.routes = require('./lib/core/routes'); | |
119 | 1 | 100% | Keystone.prototype.static = require('./lib/core/static'); | |
120 | 1 | 100% | Keystone.prototype.importer = require('./lib/core/importer'); | |
121 | 1 | 100% | Keystone.prototype.createItems = require('./lib/core/createItems'); | |
122 | 1 | 100% | Keystone.prototype.redirect = require('./lib/core/redirect'); | |
123 | 1 | 100% | Keystone.prototype.list = require('./lib/core/list'); | |
124 | 1 | 100% | Keystone.prototype.getOrphanedLists = require('./lib/core/getOrphanedLists'); | |
125 | 1 | 100% | Keystone.prototype.bindEmailTestRoutes = require('./lib/core/bindEmailTestRoutes'); | |
126 | 1 | 100% | 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 | |||
135 | 1 | 100% | var keystone = module.exports = exports = new Keystone(); | |
136 | not covered | |||
137 | not covered // Expose modules and Classes | |||
138 | 1 | 100% | keystone.utils = utils; | |
139 | 1 | 100% | keystone.content = require('./lib/content'); | |
140 | 1 | 100% | keystone.List = require('./lib/list'); | |
141 | 1 | 100% | keystone.Field = require('./lib/field'); | |
142 | 1 | 100% | keystone.Field.Types = require('./lib/fieldTypes'); | |
143 | 1 | 100% | keystone.View = require('./lib/view'); | |
144 | 1 | 100% | keystone.Email = require('./lib/email'); | |
145 | not covered | |||
146 | 1 | 100% | keystone.security = { | |
147 | 1 | 100% | 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 | |||
163 | 1 | 100% | Keystone.prototype.import = function(dirname) { | |
164 | 0 | not covered var initialPath = path.join(moduleRoot, dirname); | ||
165 | not covered | |||
166 | 0 | not covered var doImport = function(fromPath) { | ||
167 | 0 | not covered var imported = {}; | ||
168 | not covered | |||
169 | 0 | not covered fs.readdirSync(fromPath).forEach(function(name) { | ||
170 | 0 | not covered var fsPath = path.join(fromPath, name), | ||
171 | not covered | |||
172 | not covered // recur | |||
173 | 0 | not covered if (info.isDirectory()) { | ||
174 | 0 | not covered imported[name] = doImport(fsPath); | ||
175 | not covered } else { | |||
176 | 0 | not covered var parts = name.split('.'); | ||
177 | 0 | not covered var ext = parts.pop(); | ||
178 | 0 | not covered if (ext === 'js' || ext === 'coffee') { | ||
179 | 0 | not covered imported[parts.join('-')] = require(fsPath); | ||
180 | not covered } | |||
181 | not covered | |||
182 | 0 | not covered return imported; | ||
183 | not covered }; | |||
184 | not covered | |||
185 | 0 | 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 | |||
193 | 1 | 100% | Keystone.prototype.applyUpdates = function(callback) { | |
194 | 0 | 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 | |||
204 | 1 | 100% | Keystone.prototype.render = function(req, res, view, ext) { | |
205 | 0 | not covered var keystone = this; | ||
206 | not covered | |||
207 | 0 | not covered var templatePath = __dirname + '/templates/views/' + view + '.jade'; | ||
208 | not covered | |||
209 | 0 | not covered var jadeOptions = { | ||
210 | 0 | not covered pretty: keystone.get('env') !== 'production' | ||
211 | not covered }; | |||
212 | not covered | |||
213 | 0 | not covered var compileTemplate = function() { | ||
214 | 0 | not covered return jade.compile(fs.readFileSync(templatePath, 'utf8'), jadeOptions); | ||
215 | not covered }; | |||
216 | not covered | |||
217 | 0 | not covered var template = this.get('viewCache') | ||
218 | 0 | not covered ? templateCache[view] || (templateCache[view] = compileTemplate()) | ||
219 | 0 | not covered : compileTemplate(); | ||
220 | not covered | |||
221 | 0 | not covered var flashMessages = { | ||
222 | 0 | not covered info: res.req.flash('info'), | ||
223 | 0 | not covered success: res.req.flash('success'), | ||
224 | 0 | not covered warning: res.req.flash('warning'), | ||
225 | 0 | not covered error: res.req.flash('error'), | ||
226 | 0 | not covered hilight: res.req.flash('hilight') | ||
227 | not covered }; | |||
228 | not covered | |||
229 | 0 | not covered var locals = { | ||
230 | 0 | not covered env: this.get('env'), | ||
231 | 0 | not covered brand: keystone.get('brand'), | ||
232 | not covered nav: keystone.nav, | |||
233 | 0 | not covered messages: _.any(flashMessages, function(msgs) { return msgs.length; }) ? flashMessages : false, | ||
234 | not covered lists: keystone.lists, | |||
235 | 0 | not covered signout: this.get('signout url'), | ||
236 | 0 | not covered backUrl: this.get('back url') || '/', | ||
237 | not covered section: {}, | |||
238 | 0 | not covered csrf_token_value: keystone.security.csrf.getToken(req, res), | ||
239 | 0 | not covered csrf_query: '&' + keystone.security.csrf.TOKEN_KEY + '=' + keystone.security.csrf.getToken(req, res), | ||
240 | not covered ga: { | |||
241 | 0 | not covered property: this.get('ga property'), | ||
242 | 0 | not covered domain: this.get('ga domain') | ||
243 | not covered }, | |||
244 | 0 | not covered enableImages: keystone.get('wysiwyg images') ? true : false, | ||
245 | 0 | not covered enableCloudinaryUploads: keystone.get('wysiwyg cloudinary images') ? true : false, | ||
246 | 0 | not covered additionalButtons: keystone.get('wysiwyg additional buttons') || '', | ||
247 | 0 | not covered additionalPlugins: keystone.get('wysiwyg additional plugins') || '', | ||
248 | 0 | not covered additionalOptions: keystone.get('wysiwyg additional options') || {}, | ||
249 | 0 | not covered overrideToolbar: keystone.get('wysiwyg override toolbar'), | ||
250 | 0 | not covered skin: keystone.get('wysiwyg skin') || 'keystone', | ||
251 | 0 | not covered menubar: keystone.get('wysiwyg menubar') | ||
252 | not covered } | |||
253 | not covered | |||
254 | 0 | not covered _.extend(locals, ext); | ||
255 | not covered | |||
256 | 0 | not covered if (keystone.get('cloudinary config')) { | ||
257 | not covered try { | |||
258 | 0 | not covered var cloudinaryUpload = cloudinary.uploader.direct_upload(); | ||
259 | 0 | not covered locals.cloudinary = { | ||
260 | 0 | not covered cloud_name: keystone.get('cloudinary config').cloud_name, | ||
261 | 0 | 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, | |||
264 | 0 | not covered prefix: keystone.get('cloudinary prefix') || '', | ||
265 | not covered uploader: cloudinary.uploader | |||
266 | 0 | not covered locals.cloudinary_js_config = cloudinary.cloudinary_js_config(); | ||
267 | not covered } catch(e) { | |||
268 | 0 | not covered if (e === 'Must supply api_key') { | ||
269 | 0 | not covered throw new Error('Invalid Cloudinary Config Provided\n\n' + | ||
270 | not covered } else { | |||
271 | 0 | not covered throw e; | ||
272 | not covered } | |||
273 | 0 | not covered locals.fieldLocals = _.pick(locals, '_', 'moment', 'numeral', 'env', 'js', 'utils', 'user', 'cloudinary'); | ||
274 | not covered | |||
275 | 0 | not covered var html = template(_.extend(locals, ext)); | ||
276 | not covered | |||
277 | 0 | 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 | |||
289 | 1 | 100% | Keystone.prototype.populateRelated = function(docs, relationships, callback) { | |
290 | 0 | not covered if (Array.isArray(docs)) { | ||
291 | 0 | not covered async.each(docs, function(doc, done) { | ||
292 | 0 | not covered doc.populateRelated(relationships, done); | ||
293 | not covered }, callback); | |||
294 | 0 | not covered } else if (docs && docs.populateRelated) { | ||
295 | 0 | not covered docs.populateRelated(relationships, callback); | ||
296 | not covered } else { | |||
297 | 0 | 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 | |||
306 | 1 | 100% | Keystone.prototype.console = {}; | |
307 | 1 | 100% | Keystone.prototype.console.err = function(type, msg) { | |
308 | 2 | 100% | if (keystone.get('logger')) { | |
309 | 2 | 100% | var dashes = '\n------------------------------------------------\n'; | |
310 | 2 | 100% | 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 | |||
319 | 1 | 100% | keystone.version = require('./package.json').version; | |
320 | not covered | |||
321 | not covered | |||
322 | not covered // Expose Modules | |||
323 | 1 | 100% | keystone.session = require('./lib/session'); |
lib/core/options.js
49% line coverage go jump to first missed line
25% statement coverage go jump to first missed statement
22% block coverage
57 SLOC
Line | Hits | Statements | Source | Action |
---|---|---|---|---|
1 | 1 | 100% | var path = require('path'), | |
2 | 1 | 100% | _ = require('underscore'), | |
3 | 1 | 100% | cloudinary = require('cloudinary'), | |
4 | 1 | 100% | mandrillapi = require('mandrill-api'), | |
5 | 1 | 100% | utils = require('keystone-utils'); | |
6 | not covered | |||
7 | not covered function options(moduleRoot) { | |||
8 | not covered | |||
9 | 1 | 100% | 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 | |||
17 | 1 | 100% | 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 */ | |||
30 | 1 | 100% | exports.set = function(key, value) { | |
31 | 54 | 100% | if (arguments.length === 1) { | |
32 | 32 | 100% | return this._options[key]; | |
33 | not covered } | |||
34 | 0 | not covered if (this.get('logger')) { | ||
35 | 0 | not covered console.log('\nWarning: the `' + key + '` option has been deprecated. Please use `' + remappedOptions[key] + '` instead.\n\n' + | ||
36 | not covered } | |||
37 | 0 | not covered key = remappedOptions[key]; | ||
38 | not covered } | |||
39 | 22 | 100% | switch (key) { | |
40 | not covered case 'cloudinary config': | |||
41 | 0 | not covered if (_.isObject(value)) { | ||
42 | 0 | not covered cloudinary.config(value); | ||
43 | not covered } | |||
44 | 0 | not covered value = cloudinary.config(); | ||
45 | not covered break; | |||
46 | 1 | 100% | if (value) { | |
47 | 0 | not covered this.mandrillAPI = new mandrillapi.Mandrill(value); | ||
48 | not covered } | |||
49 | 0 | not covered if (value === true && !this.get('session')) { | ||
50 | 0 | not covered this.set('session', true); | ||
51 | not covered } | |||
52 | 0 | not covered this.nav = this.initNav(value); | ||
53 | not covered break; | |||
54 | 0 | not covered if ('string' !== typeof value) { | ||
55 | 0 | not covered if (Array.isArray(value) && (value.length === 2 || value.length === 3)) { | ||
56 | 0 | 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' + | ||
57 | 0 | not covered value = (value.length === 2) ? 'mongodb://' + value[0] + '/' + value[1] : 'mongodb://' + value[0] + ':' + value[2] + '/' + value[1]; | ||
58 | not covered } else { | |||
59 | 0 | not covered console.error('\nInvalid Configuration:\nThe `mongo` option must be a mongodb connection string, e.g. mongodb://localhost/db_name\n'); | ||
60 | 0 | not covered process.exit(1); | ||
61 | not covered } | |||
62 | 22 | 100% | this._options[key] = value; | |
63 | 22 | 100% | 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 | |||
78 | 1 | 100% | exports.options = function(options) { | |
79 | 3 | 100% | if (!arguments.length) | |
80 | 0 | not covered return this._options; | ||
81 | 3 | 100% | if (utils.isObject(options)) { | |
82 | 0 | not covered var keys = Object.keys(options), | ||
83 | not covered i = keys.length, | |||
84 | 0 | not covered while (i--) { | ||
85 | 0 | not covered k = keys[i]; | ||
86 | 0 | not covered this.set(k, options[k]); | ||
87 | not covered } | |||
88 | 3 | 100% | 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 | |||
103 | 1 | 100% | 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 | |||
117 | 1 | 100% | exports.getPath = function(key, defaultValue) { | |
118 | 0 | 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 | |||
128 | 1 | 100% | exports.expandPath = function(pathValue) { | |
129 | 0 | not covered pathValue = ('string' === typeof pathValue && pathValue.substr(0,1) !== path.sep && pathValue.substr(1,2) !== ':\\') | ||
130 | 0 | not covered ? path.join(moduleRoot, pathValue) | ||
131 | 0 | not covered : pathValue; | ||
132 | 0 | not covered return pathValue; | ||
133 | not covered }; | |||
134 | not covered | |||
135 | 1 | 100% | return exports; | |
136 | not covered | |||
137 | not covered } | |||
138 | not covered | |||
139 | 1 | 100% | module.exports = options; |
lib/core/init.js
100% line coverage
100% statement coverage
100% block coverage
8 SLOC
Line | Hits | Statements | Source | Action |
---|---|---|---|---|
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 | |||
16 | 1 | 100% | var express = require('express'); | |
17 | not covered | |||
18 | not covered function init(options) { | |||
19 | not covered | |||
20 | 3 | 100% | this.options(options); | |
21 | not covered | |||
22 | 3 | 100% | if (!this.app) { | |
23 | 1 | 100% | this.app = express(); | |
24 | not covered } | |||
25 | not covered | |||
26 | 3 | 100% | if (!this.mongoose) { | |
27 | 1 | 100% | this.connect(require('mongoose')); | |
28 | not covered } | |||
29 | not covered | |||
30 | 3 | 100% | return this; | |
31 | not covered | |||
32 | not covered } | |||
33 | not covered | |||
34 | 1 | 100% | module.exports = init; |
lib/core/initNav.js
8% line coverage go jump to first missed line
8% statement coverage go jump to first missed statement
0% block coverage
35 SLOC
Line | Hits | Statements | Source | Action |
---|---|---|---|---|
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 | |||
8 | 1 | 100% | var _ = require('underscore'), utils = require('keystone-utils'); | |
9 | not covered | |||
10 | not covered function initNav(sections) { | |||
11 | not covered | |||
12 | 0 | not covered var keystone = this; | ||
13 | not covered | |||
14 | 0 | not covered var nav = { | ||
15 | not covered | |||
16 | 0 | not covered if (!sections) { | ||
17 | 0 | not covered sections = {}; | ||
18 | 0 | not covered nav.flat = true; | ||
19 | 0 | not covered _.each(this.lists, function(list) { | ||
20 | 0 | not covered if (list.get('hidden')) return; | ||
21 | 0 | not covered sections[list.path] = [list.path]; | ||
22 | not covered }); | |||
23 | not covered } | |||
24 | not covered | |||
25 | 0 | not covered _.each(sections, function(section, key) { | ||
26 | 0 | not covered if ('string' === typeof section) { | ||
27 | 0 | not covered section = [section]; | ||
28 | not covered } | |||
29 | 0 | not covered section = { | ||
30 | not covered lists: section, | |||
31 | 0 | not covered label: nav.flat ? keystone.list(section[0]).label : utils.keyToLabel(key) | ||
32 | not covered }; | |||
33 | 0 | not covered section.key = key; | ||
34 | 0 | not covered section.lists = _.map(section.lists, function(i) { | ||
35 | 0 | not covered var msg, list = keystone.list(i); | ||
36 | 0 | not covered if (!list) { | ||
37 | 0 | not covered msg = 'Invalid Keystone Option (nav): list ' + i + ' has not been defined.\n'; | ||
38 | 0 | not covered throw new Error(msg); | ||
39 | not covered } | |||
40 | 0 | not covered if (list.get('hidden')) { | ||
41 | 0 | not covered msg = 'Invalid Keystone Option (nav): list ' + i + ' is hidden.\n'; | ||
42 | 0 | not covered throw new Error(msg); | ||
43 | not covered } | |||
44 | 0 | not covered nav.by.list[list.key] = section; | ||
45 | 0 | not covered return list; | ||
46 | not covered }); | |||
47 | not covered if (section.lists.length) { | |||
48 | 0 | not covered nav.sections.push(section); | ||
49 | 0 | not covered nav.by.section[section.key] = section; | ||
50 | not covered } | |||
51 | not covered | |||
52 | 0 | not covered return nav; | ||
53 | not covered } | |||
54 | not covered | |||
55 | 1 | 100% | module.exports = initNav; |
lib/core/connect.js
71% line coverage go jump to first missed line
77% statement coverage go jump to first missed statement
75% block coverage
7 SLOC
Line | Hits | Statements | Source | Action |
---|---|---|---|---|
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 | |||
16 | 6 | 100% | for (var i = 0; i < arguments.length; i++) { | |
17 | 3 | 100% | if (arguments[i].constructor.name === 'Mongoose') { | |
18 | not covered // detected Mongoose | |||
19 | 3 | 100% | this.mongoose = arguments[i]; | |
20 | 0 | not covered } else if (arguments[i].name === 'app') { | ||
21 | not covered // detected Express app | |||
22 | 0 | not covered this.app = arguments[i]; | ||
23 | not covered } | |||
24 | not covered } | |||
25 | 3 | 100% | return this; | |
26 | not covered } | |||
27 | not covered | |||
28 | 1 | 100% | module.exports = connect; |
lib/core/start.js
5% line coverage go jump to first missed line
3% statement coverage go jump to first missed statement
0% block coverage
96 SLOC
Line | Hits | Statements | Source | Action |
---|---|---|---|---|
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 | |||
46 | 1 | 100% | var fs = require('fs'), | |
47 | 1 | 100% | http = require('http'), | |
48 | 1 | 100% | https = require('https'); | |
49 | not covered | |||
50 | 1 | 100% | var dashes = '\n------------------------------------------------\n'; | |
51 | not covered | |||
52 | not covered function start(events) { | |||
53 | not covered | |||
54 | not covered // Validate arguments | |||
55 | not covered | |||
56 | 0 | not covered if ('function' === typeof events) { | ||
57 | 0 | not covered events = { onStart: events }; | ||
58 | not covered } | |||
59 | not covered | |||
60 | 0 | not covered if (!events) events = {}; | ||
61 | not covered | |||
62 | not covered // Ensure Keystone has been initialised | |||
63 | not covered | |||
64 | 0 | not covered if (!this.app) { | ||
65 | 0 | 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 | |||
70 | 0 | 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 | |||
75 | 0 | not covered var onMount = events.onMount; | ||
76 | not covered | |||
77 | 0 | not covered events.onMount = function() { | ||
78 | 0 | not covered onMount && onMount(); | ||
79 | not covered | |||
80 | 0 | not covered var startupMessages = ['KeystoneJS Started:'], | ||
81 | not covered | |||
82 | 0 | not covered var serverStarted = function() { | ||
83 | 0 | not covered waitForServers--; | ||
84 | 0 | not covered if (waitForServers) return; | ||
85 | 0 | not covered if (keystone.get('logger')) { | ||
86 | 0 | not covered console.log(dashes + startupMessages.join('\n') + dashes); | ||
87 | not covered } | |||
88 | 0 | not covered events.onStart && events.onStart(); | ||
89 | not covered }; | |||
90 | not covered | |||
91 | 0 | not covered keystone.httpServer = http.createServer(app); | ||
92 | 0 | not covered events.onHttpServerCreated && events.onHttpServerCreated(); | ||
93 | not covered | |||
94 | 0 | not covered var host = keystone.get('host'), | ||
95 | 0 | not covered port = keystone.get('port'), | ||
96 | 0 | not covered listen = keystone.get('listen'), | ||
97 | 0 | not covered ssl = keystone.get('ssl'); | ||
98 | not covered | |||
99 | 0 | not covered if (ssl !== 'only') { | ||
100 | not covered | |||
101 | 0 | not covered var httpStarted = function(msg) { | ||
102 | 0 | not covered return function() { | ||
103 | 0 | not covered startupMessages.push(msg); | ||
104 | 0 | not covered serverStarted(); | ||
105 | not covered }; | |||
106 | not covered }; | |||
107 | not covered | |||
108 | 0 | not covered if (port || port === 0) { | ||
109 | not covered | |||
110 | 0 | not covered app.set('port', port); | ||
111 | not covered | |||
112 | 0 | not covered var httpReadyMsg = keystone.get('name') + ' is ready'; | ||
113 | not covered | |||
114 | 0 | not covered if (host) { | ||
115 | 0 | not covered httpReadyMsg += ' on http://' + host; | ||
116 | 0 | not covered if (port) { | ||
117 | 0 | not covered httpReadyMsg += ':' + port; | ||
118 | not covered } | |||
119 | 0 | not covered keystone.httpServer.listen(port, host, httpStarted(httpReadyMsg)); | ||
120 | not covered } else { | |||
121 | 0 | not covered if (port) { | ||
122 | 0 | not covered httpReadyMsg += ' on port ' + port; | ||
123 | not covered } | |||
124 | 0 | not covered keystone.httpServer.listen(port, httpStarted(httpReadyMsg)); | ||
125 | not covered } | |||
126 | 0 | not covered } else if (host) { | ||
127 | not covered // start listening on a specific host address and default port 3000 | |||
128 | 0 | not covered app.set('port', 3000); | ||
129 | 0 | not covered keystone.httpServer.listen(3000, host, httpStarted(keystone.get('name') + ' is ready on ' + host + ':3000')); | ||
130 | 0 | not covered } else if (listen) { | ||
131 | not covered // start listening to a unix socket | |||
132 | 0 | not covered keystone.httpServer.listen(listen, httpStarted(keystone.get('name') + ' is ready' + (('string' === typeof listen) ? ' on ' + listen : ''))); | ||
133 | not covered } else { | |||
134 | 0 | not covered app.set('port', 3000); | ||
135 | 0 | not covered keystone.httpServer.listen(3000, httpStarted(keystone.get('name') + ' is ready on default port 3000')); | ||
136 | not covered } | |||
137 | 0 | not covered waitForServers--; | ||
138 | not covered } | |||
139 | 0 | not covered if (ssl) { | ||
140 | not covered | |||
141 | 0 | not covered var sslOpts = {}; | ||
142 | not covered | |||
143 | 0 | not covered if (keystone.get('ssl cert') && fs.existsSync(keystone.getPath('ssl cert'))) { | ||
144 | 0 | not covered sslOpts.cert = fs.readFileSync(keystone.getPath('ssl cert')); | ||
145 | not covered } | |||
146 | 0 | not covered if (keystone.get('ssl key') && fs.existsSync(keystone.getPath('ssl key'))) { | ||
147 | 0 | not covered sslOpts.key = fs.readFileSync(keystone.getPath('ssl key')); | ||
148 | not covered } | |||
149 | 0 | not covered if (!sslOpts.key || !sslOpts.cert) { | ||
150 | not covered | |||
151 | 0 | not covered if (ssl === 'only') { | ||
152 | 0 | not covered console.log(keystone.get('name') + ' failed to start: invalid ssl configuration'); | ||
153 | 0 | not covered process.exit(); | ||
154 | not covered } else { | |||
155 | 0 | not covered startupMessages.push('Warning: Invalid SSL Configuration'); | ||
156 | 0 | not covered serverStarted(); | ||
157 | not covered } | |||
158 | 0 | not covered var httpsStarted = function(msg) { | ||
159 | 0 | not covered return function() { | ||
160 | 0 | not covered startupMessages.push(msg); | ||
161 | 0 | not covered serverStarted(); | ||
162 | not covered }; | |||
163 | not covered }; | |||
164 | not covered | |||
165 | 0 | not covered keystone.httpsServer = https.createServer(sslOpts, app); | ||
166 | 0 | not covered events.onHttpsServerCreated && events.onHttpsServerCreated(); | ||
167 | not covered | |||
168 | 0 | not covered var sslHost = keystone.get('ssl host') || host, | ||
169 | not covered | |||
170 | 0 | not covered var httpsReadyMsg = (ssl === 'only') ? keystone.get('name') + ' (SSL) is ready on ' : 'SSL Server is ready on '; | ||
171 | not covered | |||
172 | 0 | not covered if (sslHost) { | ||
173 | 0 | not covered keystone.httpsServer.listen(sslPort, sslHost, httpsStarted(httpsReadyMsg + 'https://' + sslHost + ':' + sslPort)); | ||
174 | not covered } else { | |||
175 | 0 | not covered var httpsPortMsg = (keystone.get('ssl port')) ? 'port: ' + keystone.get('ssl port') : 'default port 3001'; | ||
176 | 0 | not covered keystone.httpsServer.listen(sslPort, httpsStarted(httpsReadyMsg + httpsPortMsg)); | ||
177 | not covered } | |||
178 | 0 | not covered waitForServers--; | ||
179 | not covered } | |||
180 | 0 | not covered process.on('uncaughtException', function(e) { | ||
181 | 0 | not covered if (e.code === 'EADDRINUSE') { | ||
182 | not covered console.log(dashes + | |||
183 | 0 | not covered keystone.get('name') + ' failed to start: address already in use\n' + | ||
184 | 0 | not covered 'Please check you are not already running a server on the specified port.\n'); | ||
185 | 0 | 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. | |||
188 | 0 | not covered console.log(e.stack || e); | ||
189 | 0 | not covered process.exit(1); | ||
190 | not covered } | |||
191 | not covered | |||
192 | not covered | |||
193 | not covered //mount the express app | |||
194 | 0 | not covered this.mount(events); | ||
195 | not covered | |||
196 | 0 | not covered return this; | ||
197 | not covered | |||
198 | not covered } | |||
199 | not covered | |||
200 | 1 | 100% | module.exports = start; |
lib/core/mount.js
2% line coverage go jump to first missed line
1% statement coverage go jump to first missed statement
0% block coverage
244 SLOC
Line | Hits | Statements | Source | Action |
---|---|---|---|---|
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 | |||
49 | 1 | 100% | var _ = require('underscore'), | |
50 | 1 | 100% | express = require('express'), | |
51 | 1 | 100% | path = require('path'), | |
52 | 1 | 100% | utils = require('keystone-utils'); | |
53 | not covered | |||
54 | 1 | 100% | 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 | |||
60 | 0 | not covered if (!this.app) { | ||
61 | 0 | not covered console.error('\nKeystoneJS Initialisaton Error:\n\napp must be initialised. Call keystone.init() or keystone.connect(new Express()) first.\n'); | ||
62 | 0 | not covered process.exit(1); | ||
63 | not covered } | |||
64 | not covered | |||
65 | not covered // Localise references to this for closures | |||
66 | not covered | |||
67 | 0 | 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 // | |||
72 | 0 | not covered this.nativeApp = true; | ||
73 | not covered | |||
74 | not covered // Initialise the mongo connection url | |||
75 | not covered | |||
76 | 0 | not covered if (!this.get('mongo')) { | ||
77 | 0 | not covered var dbName = this.get('db name') || utils.slug(this.get('name')); | ||
78 | 0 | 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; | ||
79 | 0 | not covered this.set('mongo', dbUrl); | ||
80 | not covered } | |||
81 | not covered | |||
82 | not covered // Initialise and validate session options | |||
83 | not covered | |||
84 | 0 | not covered if (!this.get('cookie secret')) { | ||
85 | 0 | not covered console.error('\nKeystoneJS Configuration Error:\n\nPlease provide a `cookie secret` value for session encryption.\n'); | ||
86 | 0 | not covered process.exit(1); | ||
87 | not covered } | |||
88 | not covered | |||
89 | 0 | not covered var sessionOptions = this.get('session options'); | ||
90 | not covered | |||
91 | 0 | not covered if (!_.isObject(sessionOptions)) { | ||
92 | 0 | not covered sessionOptions = {}; | ||
93 | not covered } | |||
94 | not covered | |||
95 | 0 | not covered if (!sessionOptions.key) { | ||
96 | 0 | not covered sessionOptions.key = 'keystone.sid'; | ||
97 | not covered } | |||
98 | not covered | |||
99 | 0 | not covered sessionOptions.cookieParser = express.cookieParser(this.get('cookie secret')); | ||
100 | not covered | |||
101 | 0 | not covered var sessionStore = this.get('session store'); | ||
102 | not covered | |||
103 | 0 | not covered if (sessionStore) { | ||
104 | not covered | |||
105 | 0 | 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 | |||
109 | 0 | not covered switch (sessionStore) { | ||
110 | not covered | |||
111 | not covered case 'mongo': | |||
112 | not covered // default session store for using MongoDB | |||
113 | 0 | not covered sessionStore = 'connect-mongo'; | ||
114 | not covered case 'connect-mongo': | |||
115 | 0 | not covered _.defaults(sessionStoreOptions, { | ||
116 | 0 | not covered url: this.get('mongo') | ||
117 | not covered }); | |||
118 | not covered break; | |||
119 | not covered | |||
120 | not covered case 'connect-mongostore': | |||
121 | 0 | not covered _.defaults(sessionStoreOptions, { | ||
122 | 0 | not covered if (!sessionStoreOptions.db) { | ||
123 | 0 | not covered console.error( | ||
124 | not covered '\nERROR: ' + sessionStore + ' requires `session store options` to be set.' + | |||
125 | 0 | not covered '\n' + | ||
126 | 0 | not covered '\nSee http://localhost:8080/docs/configuration#options-database for details.' + | ||
127 | 0 | not covered '\n'); | ||
128 | 0 | 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 | |||
134 | 0 | not covered sessionStore = 'connect-redis'; | ||
135 | not covered case 'connect-redis': | |||
136 | not covered break; | |||
137 | not covered | |||
138 | not covered default: | |||
139 | 0 | not covered console.error( | ||
140 | not covered '\nERROR: unsupported session store ' + sessionStore + '.' + | |||
141 | 0 | not covered '\n' + | ||
142 | 0 | not covered '\nSee http://localhost:8080/docs/configuration#options-database for details.' + | ||
143 | 0 | not covered '\n'); | ||
144 | 0 | 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 | |||
151 | 0 | not covered var _SessionStore = require(sessionStore)(express); | ||
152 | 0 | not covered sessionOptions.store = new _SessionStore(sessionStoreOptions); | ||
153 | not covered | |||
154 | not covered } catch(e) { | |||
155 | not covered | |||
156 | 0 | 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 | |||
159 | 0 | not covered var installName = (sessionStore === 'connect-redis') ? sessionStore + '@1.4.7' : sessionStore; | ||
160 | not covered | |||
161 | 0 | not covered console.error( | ||
162 | not covered '\nERROR: ' + sessionStore + ' not found.\n' + | |||
163 | 0 | not covered '\nPlease install ' + sessionStore + ' from npm to use it as a `session store` option.' + | ||
164 | 0 | not covered '\nYou can do this by running "npm install ' + installName + ' --save".' + | ||
165 | 0 | not covered '\n'); | ||
166 | 0 | not covered process.exit(1); | ||
167 | not covered | |||
168 | not covered } else { | |||
169 | 0 | 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 | |||
176 | 0 | not covered this.set('session options', sessionOptions); | ||
177 | not covered | |||
178 | not covered // wrangle arguments | |||
179 | not covered | |||
180 | 0 | not covered if (arguments.length === 1) { | ||
181 | 0 | not covered events = arguments[0]; | ||
182 | 0 | not covered mountPath = null; | ||
183 | not covered } | |||
184 | not covered | |||
185 | 0 | not covered if ('function' === typeof events) { | ||
186 | 0 | not covered events = { onMount: events }; | ||
187 | not covered } | |||
188 | not covered | |||
189 | 0 | 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 | |||
193 | 0 | not covered if (mountPath) { | ||
194 | not covered //fix root-relative keystone urls for assets (gets around having to re-write all the keystone templates) | |||
195 | 0 | not covered parentApp.all(/^\/keystone($|\/*)/, function(req, res, next) { | ||
196 | 0 | not covered req.url = mountPath + req.url; | ||
197 | 0 | not covered next(); | ||
198 | not covered }); | |||
199 | not covered | |||
200 | 0 | 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 | |||
207 | 0 | not covered if (this.get('custom engine')) { | ||
208 | 0 | 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 | |||
213 | 0 | not covered app.set('views', this.getPath('views') || path.sep + 'views'); | ||
214 | 0 | not covered app.set('view engine', this.get('view engine')); | ||
215 | not covered | |||
216 | not covered // Apply locals | |||
217 | not covered | |||
218 | 0 | not covered if (utils.isObject(this.get('locals'))) { | ||
219 | 0 | 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 | |||
224 | 0 | not covered if (this.get('env') !== 'production') { | ||
225 | 0 | not covered app.locals.pretty = true; | ||
226 | not covered } | |||
227 | not covered | |||
228 | not covered // Default view caching logic | |||
229 | not covered | |||
230 | 0 | 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 | |||
234 | 0 | not covered if (this.get('view cache') !== undefined) { | ||
235 | 0 | 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 | |||
240 | 0 | not covered if (this.get('compress')) { | ||
241 | 0 | not covered app.use(express.compress()); | ||
242 | not covered } | |||
243 | not covered | |||
244 | 0 | not covered if (this.get('favico')) { | ||
245 | 0 | not covered app.use(express.favicon(this.getPath('favico'))); | ||
246 | not covered } | |||
247 | not covered | |||
248 | 0 | not covered if (this.get('less')) { | ||
249 | 0 | not covered app.use(require('less-middleware')(this.getPath('less'))); | ||
250 | not covered } | |||
251 | not covered | |||
252 | 0 | not covered if (this.get('sass')) { | ||
253 | 0 | not covered var sass; | ||
254 | not covered try { | |||
255 | 0 | not covered sass = require('node-sass'); | ||
256 | not covered } catch(e) { | |||
257 | 0 | not covered if (e.code === 'MODULE_NOT_FOUND') { | ||
258 | 0 | not covered console.error( | ||
259 | not covered '\nERROR: node-sass not found.\n' + | |||
260 | 0 | not covered '\nPlease install the node-sass from npm to use the `sass` option.' + | ||
261 | 0 | not covered '\nYou can do this by running "npm install node-sass --save".\n' | ||
262 | not covered ); | |||
263 | 0 | not covered process.exit(1); | ||
264 | not covered } else { | |||
265 | 0 | not covered throw e; | ||
266 | not covered } | |||
267 | not covered } | |||
268 | 0 | not covered app.use(sass.middleware({ | ||
269 | 0 | not covered src: this.getPath('sass'), | ||
270 | 0 | not covered dest: this.getPath('sass'), | ||
271 | 0 | 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 | |||
277 | 0 | not covered var staticPaths = this.get('static'); | ||
278 | not covered | |||
279 | 0 | not covered if (_.isString(staticPaths)) { | ||
280 | 0 | not covered staticPaths = [staticPaths]; | ||
281 | not covered } | |||
282 | not covered | |||
283 | 0 | not covered if (_.isArray(staticPaths)) { | ||
284 | 0 | not covered _.each(staticPaths, function(value) { | ||
285 | 0 | 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 | |||
291 | 0 | not covered if (!this.get('headless')) { | ||
292 | 0 | not covered this.static(app); | ||
293 | not covered } | |||
294 | not covered | |||
295 | not covered // Handle dynamic requests | |||
296 | not covered | |||
297 | 0 | not covered if (this.get('logger')) { | ||
298 | 0 | not covered app.use(express.logger(this.get('logger'))); | ||
299 | not covered } | |||
300 | not covered | |||
301 | 0 | not covered if (this.get('file limit')) { | ||
302 | 0 | not covered app.use(express.limit(this.get('file limit'))); | ||
303 | not covered } | |||
304 | not covered | |||
305 | 0 | not covered app.use(express.bodyParser()); | ||
306 | 0 | not covered app.use(express.methodOverride()); | ||
307 | 0 | not covered app.use(sessionOptions.cookieParser); | ||
308 | 0 | not covered app.use(express.session(sessionOptions)); | ||
309 | 0 | not covered app.use(require('connect-flash')()); | ||
310 | not covered | |||
311 | 0 | not covered if (this.get('session') === true) { | ||
312 | 0 | not covered app.use(this.session.persist); | ||
313 | 0 | not covered } else if ('function' === typeof this.get('session')) { | ||
314 | 0 | 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 | |||
319 | 0 | not covered if (this.get('trust proxy') === true) { | ||
320 | 0 | not covered app.enable('trust proxy'); | ||
321 | not covered } else { | |||
322 | 0 | not covered app.disable('trust proxy'); | ||
323 | not covered } | |||
324 | not covered | |||
325 | not covered // Check for IP range restrictions | |||
326 | not covered | |||
327 | 0 | not covered if (this.get('allowed ip ranges')) { | ||
328 | 0 | not covered if (!app.get('trust proxy')) { | ||
329 | 0 | not covered console.log( | ||
330 | 0 | not covered 'KeystoneJS Initialisaton Error:\n\n' + | ||
331 | not covered ); | |||
332 | 0 | not covered process.exit(1); | ||
333 | not covered } | |||
334 | 0 | not covered var ipRangeMiddleware = require('./lib/security/ipRangeRestrict')( | ||
335 | 0 | not covered this.get('allowed ip ranges'), | ||
336 | not covered this.wrapHTMLError | |||
337 | 0 | not covered this.pre('routes', ipRangeMiddleware); | ||
338 | not covered } | |||
339 | not covered | |||
340 | not covered // Pre-route middleware | |||
341 | not covered | |||
342 | 0 | not covered this._pre.routes.forEach(function(fn) { | ||
343 | 0 | not covered app.use(fn); | ||
344 | not covered } | |||
345 | not covered catch(e) { | |||
346 | 0 | not covered if (keystone.get('logger')) { | ||
347 | 0 | not covered console.log('Invalid pre-route middleware provided'); | ||
348 | not covered } | |||
349 | 0 | not covered throw e; | ||
350 | not covered } | |||
351 | not covered | |||
352 | not covered // Route requests | |||
353 | not covered | |||
354 | 0 | not covered app.use(app.router); | ||
355 | not covered | |||
356 | not covered // Headless mode means don't bind the Keystone routes | |||
357 | not covered | |||
358 | 0 | not covered if (!this.get('headless')) { | ||
359 | 0 | not covered this.routes(app); | ||
360 | not covered } | |||
361 | not covered | |||
362 | not covered | |||
363 | not covered // Configure application routes | |||
364 | 0 | not covered if ('function' === typeof this.get('routes')) { | ||
365 | 0 | not covered this.get('routes')(app); | ||
366 | not covered } | |||
367 | not covered | |||
368 | not covered //prepare the error handlers; they should be called last | |||
369 | 0 | not covered var setHandlers = function () { | ||
370 | 0 | not covered if (Object.keys(keystone._redirects).length) { | ||
371 | 0 | not covered app.use(function(req, res, next) { | ||
372 | not covered if (keystone._redirects[req.path]) { | |||
373 | 0 | not covered res.redirect(keystone._redirects[req.path]); | ||
374 | not covered } else { | |||
375 | 0 | not covered next(); | ||
376 | not covered } | |||
377 | not covered }); | |||
378 | 0 | not covered var default404Handler = function(req, res, next) { | ||
379 | 0 | not covered res.status(404).send(keystone.wrapHTMLError('Sorry, no page could be found at this address (404)')); | ||
380 | not covered }; | |||
381 | not covered | |||
382 | 0 | not covered app.use(function(req, res, next) { | ||
383 | 0 | not covered var err404 = keystone.get('404'); | ||
384 | not covered | |||
385 | 0 | not covered if (err404) { | ||
386 | not covered try { | |||
387 | 0 | not covered if ('function' === typeof err404) { | ||
388 | 0 | not covered err404(req, res, next); | ||
389 | 0 | not covered } else if ('string' === typeof err404) { | ||
390 | 0 | not covered res.status(404).render(err404); | ||
391 | not covered } else { | |||
392 | 0 | not covered if (keystone.get('logger')) { | ||
393 | 0 | not covered console.log(dashes + 'Error handling 404 (not found): Invalid type (' + (typeof err404) + ') for 404 setting.' + dashes); | ||
394 | not covered } | |||
395 | 0 | not covered default404Handler(req, res, next); | ||
396 | not covered } | |||
397 | not covered } catch(e) { | |||
398 | 0 | not covered if (keystone.get('logger')) { | ||
399 | 0 | not covered console.log(dashes + 'Error handling 404 (not found):'); | ||
400 | 0 | not covered console.log(e); | ||
401 | 0 | not covered console.log(dashes); | ||
402 | not covered } | |||
403 | 0 | not covered default404Handler(req, res, next); | ||
404 | not covered } | |||
405 | 0 | not covered default404Handler(req, res, next); | ||
406 | not covered } | |||
407 | not covered | |||
408 | 0 | not covered var default500Handler = function(err, req, res, next) { | ||
409 | 0 | not covered if (keystone.get('logger')) { | ||
410 | 0 | not covered if (err instanceof Error) { | ||
411 | 0 | not covered console.log((err.type ? err.type + ' ' : '') + 'Error thrown for request: ' + req.url); | ||
412 | not covered } else { | |||
413 | 0 | not covered console.log('Error thrown for request: ' + req.url); | ||
414 | not covered } | |||
415 | 0 | not covered console.log(err.stack || err); | ||
416 | not covered } | |||
417 | 0 | not covered var msg = ''; | ||
418 | not covered | |||
419 | 0 | not covered if (keystone.get('env') === 'development') { | ||
420 | not covered | |||
421 | 0 | not covered if (err instanceof Error) { | ||
422 | not covered if (err.type) { | |||
423 | 0 | not covered msg += '<h2>' + err.type + '</h2>'; | ||
424 | not covered } | |||
425 | 0 | not covered msg += utils.textToHTML(err.message); | ||
426 | 0 | not covered } else if ('object' === typeof err) { | ||
427 | 0 | not covered msg += '<code>' + JSON.stringify(err) + '</code>'; | ||
428 | 0 | not covered } else if (err) { | ||
429 | 0 | not covered msg += err; | ||
430 | not covered } | |||
431 | 0 | not covered res.status(500).send(keystone.wrapHTMLError('Sorry, an error occurred loading the page (500)', msg)); | ||
432 | not covered }; | |||
433 | not covered | |||
434 | 0 | not covered app.use(function(err, req, res, next) { | ||
435 | 0 | not covered var err500 = keystone.get('500'); | ||
436 | not covered | |||
437 | 0 | not covered if (err500) { | ||
438 | not covered try { | |||
439 | 0 | not covered if ('function' === typeof err500) { | ||
440 | 0 | not covered err500(err, req, res, next); | ||
441 | 0 | not covered } else if ('string' === typeof err500) { | ||
442 | 0 | not covered res.locals.err = err; | ||
443 | 0 | not covered res.status(500).render(err500); | ||
444 | not covered } else { | |||
445 | 0 | not covered if (keystone.get('logger')) { | ||
446 | 0 | not covered console.log(dashes + 'Error handling 500 (error): Invalid type (' + (typeof err500) + ') for 500 setting.' + dashes); | ||
447 | not covered } | |||
448 | 0 | not covered default500Handler(err, req, res, next); | ||
449 | not covered } | |||
450 | not covered } catch(e) { | |||
451 | 0 | not covered if (keystone.get('logger')) { | ||
452 | 0 | not covered console.log(dashes + 'Error handling 500 (error):'); | ||
453 | 0 | not covered console.log(e); | ||
454 | 0 | not covered console.log(dashes); | ||
455 | not covered } | |||
456 | 0 | not covered default500Handler(err, req, res, next); | ||
457 | not covered } | |||
458 | 0 | not covered default500Handler(err, req, res, next); | ||
459 | not covered } | |||
460 | not covered } | |||
461 | 0 | not covered var mongoConnectionOpen = false; | ||
462 | not covered | |||
463 | not covered // support replica sets for mongoose | |||
464 | 0 | not covered if (this.get('mongo replica set')){ | ||
465 | not covered | |||
466 | 0 | not covered var replicaData = this.get('mongo replica set'); | ||
467 | 0 | not covered var replica = ""; | ||
468 | not covered | |||
469 | 0 | not covered var credentials = (replicaData.username && replicaData.password) ? replicaData.username +":"+replicaData.password+"@" : ''; | ||
470 | not covered | |||
471 | 0 | not covered replicaData.db.servers.forEach(function (server) { | ||
472 | 0 | not covered replica += "mongodb://"+credentials+server["host"]+":"+server["port"]+"/"+replicaData.db.name+","; | ||
473 | not covered }); | |||
474 | not covered | |||
475 | 0 | not covered var options = { | ||
476 | not covered | |||
477 | 0 | not covered this.mongoose.connect(replica, options); | ||
478 | not covered | |||
479 | not covered } else { | |||
480 | not covered | |||
481 | 0 | not covered this.mongoose.connect(this.get('mongo')); | ||
482 | not covered | |||
483 | not covered } | |||
484 | not covered | |||
485 | 0 | not covered this.mongoose.connection.on('error', function(err) { | ||
486 | 0 | not covered if (keystone.get('logger')) { | ||
487 | 0 | not covered console.log('------------------------------------------------'); | ||
488 | 0 | not covered console.log('Mongo Error:\n'); | ||
489 | 0 | not covered console.log(err); | ||
490 | not covered } | |||
491 | not covered | |||
492 | 0 | not covered if (mongoConnectionOpen) { | ||
493 | 0 | not covered throw new Error('Mongo Error'); | ||
494 | not covered } else { | |||
495 | 0 | not covered throw new Error('KeystoneJS (' + keystone.get('name') + ') failed to start'); | ||
496 | not covered } | |||
497 | not covered | |||
498 | not covered | |||
499 | 0 | not covered mongoConnectionOpen = true; | ||
500 | not covered | |||
501 | 0 | not covered if (keystone.get('auto update')) { | ||
502 | 0 | not covered var mounted = function () { | ||
503 | 0 | not covered events.onMount(); | ||
504 | 0 | not covered setHandlers(); | ||
505 | not covered } | |||
506 | 0 | not covered keystone.applyUpdates(mounted); | ||
507 | 0 | not covered events.onMount && events.onMount(); | ||
508 | 0 | not covered setHandlers(); | ||
509 | not covered } | |||
510 | not covered } | |||
511 | not covered | |||
512 | 1 | 100% | module.exports = mount; |
lib/core/routes.js
2% line coverage go jump to first missed line
1% statement coverage go jump to first missed statement
0% block coverage
42 SLOC
Line | Hits | Statements | Source | Action |
---|---|---|---|---|
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 | |||
17 | 0 | not covered this.app = app; | ||
18 | 0 | not covered var keystone = this; | ||
19 | not covered | |||
20 | not covered // ensure keystone nav has been initialised | |||
21 | 0 | not covered if (!this.nav) { | ||
22 | 0 | 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 | |||
26 | 0 | 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 | |||
31 | 0 | not covered if (this.get('auth') === true) { | ||
32 | not covered | |||
33 | 0 | not covered if (!this.get('signout url')) { | ||
34 | 0 | not covered this.set('signout url', '/keystone/signout'); | ||
35 | not covered } | |||
36 | 0 | not covered if (!this.get('signin url')) { | ||
37 | 0 | not covered this.set('signin url', '/keystone/signin'); | ||
38 | not covered } | |||
39 | not covered | |||
40 | 0 | not covered if (!this.nativeApp || !this.get('session')) { | ||
41 | 0 | not covered app.all('/keystone*', this.session.persist); | ||
42 | not covered } | |||
43 | not covered | |||
44 | 0 | not covered app.all('/keystone/signin', require('../../routes/views/signin')); | ||
45 | 0 | not covered app.all('/keystone/signout', require('../../routes/views/signout')); | ||
46 | 0 | not covered app.all('/keystone*', this.session.keystoneAuth); | ||
47 | not covered | |||
48 | 0 | not covered } else if ('function' === typeof this.get('auth')) { | ||
49 | 0 | not covered app.all('/keystone*', this.get('auth')); | ||
50 | not covered } | |||
51 | not covered | |||
52 | 0 | not covered var initList = function(protect) { | ||
53 | 0 | not covered return function(req, res, next) { | ||
54 | 0 | not covered req.list = keystone.list(req.params.list); | ||
55 | 0 | not covered if (!req.list || (protect && req.list.get('hidden'))) { | ||
56 | 0 | not covered req.flash('error', 'List ' + req.params.list + ' could not be found.'); | ||
57 | 0 | not covered return res.redirect('/keystone'); | ||
58 | not covered } | |||
59 | 0 | not covered next(); | ||
60 | not covered }; | |||
61 | not covered }; | |||
62 | not covered | |||
63 | not covered // Keystone Admin Route | |||
64 | 0 | not covered app.all('/keystone', require('../../routes/views/home')); | ||
65 | not covered | |||
66 | not covered // Email test routes | |||
67 | 0 | not covered if (this.get('email tests')) { | ||
68 | 0 | 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) | |||
72 | 0 | not covered if (keystone.get('wysiwyg cloudinary images')) { | ||
73 | 0 | not covered if (!keystone.get('cloudinary config')) { | ||
74 | 0 | 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 } | |||
76 | 0 | 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 | |||
80 | 0 | not covered if (keystone.get('cloudinary config')) { | ||
81 | 0 | not covered app.get('/keystone/api/cloudinary/get', require('../../routes/api/cloudinary').get); | ||
82 | 0 | 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 | |||
86 | 0 | not covered app.all('/keystone/api/:list/:action', initList(), require('../../routes/api/list')); | ||
87 | not covered | |||
88 | not covered // Generic Lists Download Route | |||
89 | 0 | not covered app.all('/keystone/download/:list', initList(), require('../../routes/download/list')); | ||
90 | not covered | |||
91 | not covered // List and Item Details Admin Routes | |||
92 | 0 | not covered app.all('/keystone/:list/:page([0-9]{1,5})?', initList(true), require('../../routes/views/list')); | ||
93 | 0 | not covered app.all('/keystone/:list/:item', initList(true), require('../../routes/views/item')); | ||
94 | not covered | |||
95 | 0 | not covered return this; | ||
96 | not covered | |||
97 | not covered } | |||
98 | not covered | |||
99 | 1 | 100% | module.exports = routes; |
lib/core/static.js
50% line coverage go jump to first missed line
18% statement coverage go jump to first missed statement
0% block coverage
6 SLOC
Line | Hits | Statements | Source | Action |
---|---|---|---|---|
1 | 1 | 100% | 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 | |||
14 | 0 | not covered app.use('/keystone', require('less-middleware')(__dirname + path.sep + '..' + path.sep + '..' + path.sep + 'public')); | ||
15 | 0 | not covered app.use('/keystone', express.static(__dirname + path.sep + '..' + path.sep + '..' + path.sep + 'public')); | ||
16 | not covered | |||
17 | 0 | not covered return this; | ||
18 | not covered | |||
19 | not covered } | |||
20 | not covered | |||
21 | 1 | 100% | module.exports = static; |
lib/core/importer.js
15% line coverage go jump to first missed line
11% statement coverage go jump to first missed statement
0% block coverage
20 SLOC
Line | Hits | Statements | Source | Action |
---|---|---|---|---|
1 | 1 | 100% | 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) { | |||
23 | 0 | not covered var imported = {}; | ||
24 | 0 | not covered var joinPath = function() { | ||
25 | 0 | not covered return '.' + path.sep + path.join.apply(path, arguments); | ||
26 | not covered }; | |||
27 | 0 | not covered var fsPath = joinPath(path.relative(process.cwd(), rel__dirname), from); | ||
28 | 0 | not covered fs.readdirSync(fsPath).forEach(function(name) { | ||
29 | 0 | not covered var info = fs.statSync(path.join(fsPath, name)); | ||
30 | not covered // recur | |||
31 | 0 | not covered if (info.isDirectory()) { | ||
32 | 0 | not covered imported[name] = importer(joinPath(from, name)); | ||
33 | not covered } else { | |||
34 | not covered // only import .js files | |||
35 | 0 | not covered var parts = name.split('.'); | ||
36 | 0 | not covered var ext = parts.pop(); | ||
37 | 0 | not covered if (ext === 'js' || ext === 'coffee') { | ||
38 | 0 | not covered imported[parts.join('-')] = require(path.join(rel__dirname, from, name)); | ||
39 | not covered } | |||
40 | 0 | not covered return imported; | ||
41 | not covered }); | |||
42 | 0 | not covered return imported; | ||
43 | not covered } | |||
44 | not covered | |||
45 | 0 | not covered return importer; | ||
46 | not covered | |||
47 | not covered } | |||
48 | not covered | |||
49 | 1 | 100% | module.exports = dispatchImporter; |
lib/core/createItems.js
2% line coverage go jump to first missed line
1% statement coverage go jump to first missed statement
0% block coverage
150 SLOC
Line | Hits | Statements | Source | Action |
---|---|---|---|---|
1 | not covered /** | |||
2 | not covered * Creates multiple items in one or more Lists | |||
3 | not covered */ | |||
4 | not covered | |||
5 | 1 | 100% | var _ = require('underscore'), | |
6 | 1 | 100% | async = require('async'), | |
7 | 1 | 100% | utils = require('keystone-utils'); | |
8 | not covered | |||
9 | not covered function createItems(data, ops, callback) { | |||
10 | not covered | |||
11 | 0 | not covered var keystone = this; | ||
12 | not covered | |||
13 | 0 | not covered var options = { | ||
14 | not covered | |||
15 | 0 | not covered var dashes = '------------------------------------------------'; | ||
16 | not covered | |||
17 | 0 | not covered if (!_.isObject(data)) { | ||
18 | 0 | not covered throw new Error('keystone.createItems() requires a data object as the first argument.'); | ||
19 | not covered } | |||
20 | not covered | |||
21 | 0 | not covered if (_.isObject(ops)) { | ||
22 | 0 | not covered _.extend(options, ops); | ||
23 | not covered } | |||
24 | not covered | |||
25 | 0 | not covered if (_.isFunction(ops)) { | ||
26 | 0 | not covered callback = ops; | ||
27 | not covered } | |||
28 | not covered | |||
29 | 0 | not covered var lists = _.keys(data), | ||
30 | not covered | |||
31 | not covered // logger function | |||
32 | not covered function writeLog(data) { | |||
33 | 0 | not covered console.log(keystone.get('name') + ": " + data); | ||
34 | not covered } | |||
35 | not covered | |||
36 | 0 | not covered async.waterfall([ | ||
37 | 0 | not covered async.eachSeries(lists, function(key, doneList) { | ||
38 | 0 | not covered var list = keystone.list(key), | ||
39 | not covered | |||
40 | 0 | not covered if (!list) { | ||
41 | not covered if (options.strict) { | |||
42 | 0 | not covered return doneList({ | ||
43 | not covered type: 'invalid list', | |||
44 | 0 | not covered message: 'List key ' + key + ' is invalid.' | ||
45 | not covered }); | |||
46 | not covered } | |||
47 | 0 | not covered writeLog('Skipping invalid list: ' + key); | ||
48 | not covered } | |||
49 | 0 | not covered return doneList(); | ||
50 | not covered } | |||
51 | 0 | not covered refs[list.key] = {}; | ||
52 | 0 | not covered stats[list.key] = { | ||
53 | not covered singular: list.singular, | |||
54 | 0 | not covered var itemsProcessed = 0, | ||
55 | not covered | |||
56 | 0 | not covered writeLog(dashes); | ||
57 | 0 | not covered writeLog('Processing list: ' + key); | ||
58 | 0 | not covered writeLog('Items to create: ' + totalItems); | ||
59 | 0 | not covered writeLog(dashes); | ||
60 | not covered } | |||
61 | 0 | not covered async.eachSeries(data[key], function(data, doneItem) { | ||
62 | 0 | not covered itemsProcessed++; | ||
63 | not covered | |||
64 | not covered // Evaluate function properties to allow generated values (excluding relationships) | |||
65 | 0 | not covered _.keys(data).forEach(function(i) { | ||
66 | 0 | not covered if (_.isFunction(data[i]) && relationshipPaths.indexOf(i) === -1) { | ||
67 | 0 | not covered data[i] = data[i](); | ||
68 | not covered if (options.verbose) { | |||
69 | 0 | not covered writeLog('Generated dynamic value for [' + i + ']: ' + data[i]); | ||
70 | not covered } | |||
71 | not covered } | |||
72 | not covered | |||
73 | 0 | not covered var doc = data.__doc = new list.model(); | ||
74 | not covered | |||
75 | 0 | not covered refs[list.key][data.__ref] = doc; | ||
76 | not covered } | |||
77 | 0 | not covered _.each(list.fields, function(field) { | ||
78 | 0 | not covered if (field.type !== 'relationship') { | ||
79 | 0 | not covered field.updateItem(doc, data); | ||
80 | not covered } | |||
81 | not covered | |||
82 | 0 | not covered var documentName = list.getDocumentName(doc); | ||
83 | 0 | not covered writeLog('Creating item [' + itemsProcessed + ' of ' + totalItems + '] - ' + documentName); | ||
84 | not covered } | |||
85 | 0 | not covered doc.save(doneItem); | ||
86 | 0 | not covered stats[list.key].created++; | ||
87 | not covered | |||
88 | not covered | |||
89 | not covered }, | |||
90 | not covered | |||
91 | 0 | not covered async.each(lists, function(key, doneList) { | ||
92 | 0 | not covered var list = keystone.list(key), | ||
93 | not covered | |||
94 | 0 | not covered if (!list || !relationships.length) { | ||
95 | 0 | not covered return doneList(); | ||
96 | not covered } | |||
97 | not covered | |||
98 | 0 | not covered var itemsProcessed = 0, | ||
99 | not covered | |||
100 | 0 | not covered writeLog(dashes); | ||
101 | 0 | not covered writeLog('Processing relationships for: ' + key); | ||
102 | 0 | not covered writeLog('Items to process: ' + totalItems); | ||
103 | 0 | not covered writeLog(dashes); | ||
104 | not covered } | |||
105 | 0 | not covered async.each(data[key], function(srcData, doneItem) { | ||
106 | 0 | not covered var doc = srcData.__doc, | ||
107 | not covered | |||
108 | 0 | not covered itemsProcessed++; | ||
109 | not covered | |||
110 | not covered if (options.verbose) { | |||
111 | 0 | not covered var documentName = list.getDocumentName(doc); | ||
112 | 0 | not covered writeLog('Processing item [' + itemsProcessed + ' of ' + totalItems + '] - ' + documentName); | ||
113 | not covered } | |||
114 | 0 | not covered async.each(relationships, function(field, doneField) { | ||
115 | 0 | not covered var fieldValue = null, | ||
116 | not covered | |||
117 | 0 | not covered if ( !field.path ) { | ||
118 | 0 | not covered writeLog("WARNING: Invalid relationship (undefined list path) [List: "+key+"]"); | ||
119 | 0 | not covered stats[list.key].warnings++; | ||
120 | 0 | not covered return doneField(); | ||
121 | not covered } | |||
122 | not covered else | |||
123 | 0 | not covered fieldValue = srcData[field.path]; | ||
124 | not covered | |||
125 | 0 | not covered if ( !field.refList ) { | ||
126 | 0 | not covered if ( fieldValue ) | ||
127 | not covered { | |||
128 | 0 | not covered writeLog("WARNING: Invalid relationship (undefined reference list) [list: "+key+"] [path: "+fieldValue+"]"); | ||
129 | 0 | not covered stats[list.key].warnings++; | ||
130 | not covered } | |||
131 | 0 | not covered return doneField(); | ||
132 | not covered } | |||
133 | 0 | not covered if ( !field.refList.key ) { | ||
134 | 0 | not covered writeLog("WARNING: Invalid relationship (undefined ref list key) [list: "+key+"] [field.refList: "+field.refList+"] [fieldValue: "+fieldValue+"]"); | ||
135 | 0 | not covered stats[list.key].warnings++; | ||
136 | 0 | not covered return doneField(); | ||
137 | not covered } | |||
138 | 0 | not covered refsLookup = refs[field.refList.key]; | ||
139 | not covered | |||
140 | 0 | not covered if (!fieldValue) { | ||
141 | 0 | not covered return doneField(); | ||
142 | not covered } | |||
143 | 0 | not covered if (_.isFunction(fieldValue)) { | ||
144 | not covered | |||
145 | 0 | not covered relationshipsUpdated++; | ||
146 | not covered | |||
147 | 0 | not covered var fn = fieldValue, | ||
148 | 0 | not covered lists = fn.toString().match(argsRegExp)[1].split(',').map(function(i) { return i.trim(); }), | ||
149 | 0 | not covered args = lists.map(function(i) { | ||
150 | 0 | not covered return keystone.list(i); | ||
151 | not covered }), | |||
152 | 0 | not covered query = fn.apply(keystone, args); | ||
153 | not covered | |||
154 | 0 | not covered query.exec(function(err, results) { | ||
155 | 0 | not covered doc.set(field.path, results || []); | ||
156 | not covered } else { | |||
157 | 0 | not covered doc.set(field.path, (results && results.length) ? results[0] : undefined); | ||
158 | not covered } | |||
159 | 0 | not covered doneField(err); | ||
160 | not covered }); | |||
161 | not covered | |||
162 | 0 | not covered } else if (_.isArray(fieldValue)) { | ||
163 | not covered | |||
164 | 0 | not covered var refsArr = _.compact(fieldValue.map(function(ref) { | ||
165 | 0 | not covered return refsLookup[ref] ? refsLookup[ref].id : undefined; | ||
166 | not covered })); | |||
167 | not covered | |||
168 | 0 | not covered if (options.strict && refsArr.length !== fieldValue.length) { | ||
169 | 0 | not covered return doneField({ | ||
170 | not covered type: 'invalid ref', | |||
171 | 0 | not covered message: 'Relationship ' + list.key + '.' + field.path + ' contains an invalid reference.' | ||
172 | not covered }); | |||
173 | 0 | not covered relationshipsUpdated++; | ||
174 | 0 | not covered doc.set(field.path, refsArr); | ||
175 | 0 | not covered doneField(); | ||
176 | not covered | |||
177 | 0 | not covered return doneField({ | ||
178 | 0 | not covered message: 'Single-value relationship ' + list.key + '.' + field.path + ' provided as an array.' | ||
179 | not covered }); | |||
180 | not covered } | |||
181 | 0 | not covered } else if (_.isString(fieldValue)) { | ||
182 | not covered | |||
183 | 0 | not covered var refItem = refsLookup[fieldValue]; | ||
184 | not covered | |||
185 | 0 | not covered if (!refItem) { | ||
186 | 0 | not covered return options.strict ? doneField({ | ||
187 | not covered type: 'invalid ref', | |||
188 | 0 | not covered message: 'Relationship ' + list.key + '.' + field.path + ' contains an invalid reference: "' + fieldValue + '".' | ||
189 | 0 | not covered }) : doneField(); | ||
190 | not covered } | |||
191 | 0 | not covered relationshipsUpdated++; | ||
192 | not covered | |||
193 | 0 | not covered doc.set(field.path, field.many ? [refItem.id] : refItem.id); | ||
194 | not covered | |||
195 | 0 | not covered doneField(); | ||
196 | not covered | |||
197 | 0 | not covered return doneField({ | ||
198 | 0 | not covered message: 'Relationship ' + list.key + '.' + field.path + ' contains an invalid data type.' | ||
199 | not covered }); | |||
200 | not covered } | |||
201 | 0 | not covered if (err) { | ||
202 | 0 | not covered return doneItem(err); | ||
203 | not covered } | |||
204 | 0 | not covered writeLog('Populated ' + utils.plural(relationshipsUpdated, '* relationship', '* relationships') + '.'); | ||
205 | not covered } | |||
206 | 0 | not covered if (relationshipsUpdated) { | ||
207 | 0 | not covered doc.save(doneItem); | ||
208 | not covered } else { | |||
209 | 0 | not covered doneItem(); | ||
210 | not covered } | |||
211 | not covered | |||
212 | not covered | |||
213 | not covered } | |||
214 | 0 | not covered if (err) { | ||
215 | 0 | not covered return callback && callback(err); | ||
216 | not covered } | |||
217 | 0 | not covered var msg = '\nSuccessfully created:\n'; | ||
218 | 0 | not covered _.each(stats, function(list) { | ||
219 | 0 | not covered msg += '\n* ' + utils.plural(list.created, '* ' + list.singular, '* ' + list.plural); | ||
220 | not covered if (list.warnings) { | |||
221 | 0 | not covered msg += '\n ' + utils.plural(list.warnings, '* warning', '* warnings'); | ||
222 | not covered } | |||
223 | not covered }); | |||
224 | 0 | not covered stats.message = msg + '\n'; | ||
225 | not covered | |||
226 | 0 | not covered callback(null, stats); | ||
227 | not covered | |||
228 | not covered | |||
229 | not covered } | |||
230 | not covered | |||
231 | 1 | 100% | module.exports = createItems; |
lib/core/redirect.js
37% line coverage go jump to first missed line
26% statement coverage go jump to first missed statement
0% block coverage
8 SLOC
Line | Hits | Statements | Source | Action |
---|---|---|---|---|
1 | 1 | 100% | 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 | |||
19 | 0 | not covered if (arguments.length === 1 && utils.isObject(arguments[0])) { | ||
20 | 0 | not covered _.extend(this._redirects, arguments[0]); | ||
21 | 0 | not covered } else if (arguments.length === 2 && 'string' === typeof arguments[0] && 'string' === typeof arguments[1]) { | ||
22 | 0 | not covered this._redirects[arguments[0]] = arguments[1]; | ||
23 | not covered } | |||
24 | not covered | |||
25 | 0 | not covered return this; | ||
26 | not covered | |||
27 | not covered } | |||
28 | not covered | |||
29 | 1 | 100% | module.exports = redirect; |
lib/core/list.js
83% line coverage go jump to first missed line
77% statement coverage go jump to first missed statement
100% block coverage
6 SLOC
Line | Hits | Statements | Source | Action |
---|---|---|---|---|
1 | not covered /** | |||
2 | not covered * Registers or retrieves a list | |||
3 | not covered */ | |||
4 | not covered | |||
5 | not covered function list(arg) { | |||
6 | 12 | 100% | if (arg && arg.constructor === this.List) { | |
7 | 12 | 100% | this.lists[arg.key] = arg; | |
8 | 12 | 100% | this.paths[arg.path] = arg.key; | |
9 | 12 | 100% | return arg; | |
10 | not covered } | |||
11 | 0 | not covered return this.lists[arg] || this.lists[this.paths[arg]]; | ||
12 | not covered } | |||
13 | not covered | |||
14 | 1 | 100% | module.exports = list; |
lib/core/getOrphanedLists.js
25% line coverage go jump to first missed line
17% statement coverage go jump to first missed statement
0% block coverage
8 SLOC
Line | Hits | Statements | Source | Action |
---|---|---|---|---|
1 | 1 | 100% | 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() { | |||
8 | 0 | not covered if (!this.nav) { | ||
9 | 0 | not covered return []; | ||
10 | not covered } | |||
11 | 0 | not covered return _.filter(this.lists, function(list, key) { | ||
12 | 0 | not covered if (list.get('hidden')) return false; | ||
13 | 0 | not covered return (!this.nav.by.list[key]) ? list : false; | ||
14 | not covered }.bind(this)); | |||
15 | not covered } | |||
16 | not covered | |||
17 | 1 | 100% | module.exports = getOrphanedLists; |
lib/core/bindEmailTestRoutes.js
8% line coverage go jump to first missed line
6% statement coverage go jump to first missed statement
0% block coverage
24 SLOC
Line | Hits | Statements | Source | Action |
---|---|---|---|---|
1 | 1 | 100% | var _ = require('underscore'); | |
2 | not covered | |||
3 | not covered function bindEmailTestRoutes(app, emails) { | |||
4 | not covered | |||
5 | 0 | not covered var keystone = this; | ||
6 | not covered | |||
7 | 0 | not covered var handleError = function(req, res, err) { | ||
8 | 0 | not covered res.err(err); | ||
9 | not covered } else { | |||
10 | 0 | 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 | |||
15 | 0 | not covered _.each(emails, function(vars, key) { | ||
16 | 0 | not covered var render = function(err, req, res, locals) { | ||
17 | 0 | not covered new keystone.Email(key).render(locals, function(err, email) { | ||
18 | 0 | not covered if (err) { | ||
19 | 0 | not covered handleError(req, res, err); | ||
20 | not covered } else { | |||
21 | 0 | not covered res.send(email.html); | ||
22 | not covered } | |||
23 | not covered }); | |||
24 | not covered }; | |||
25 | not covered | |||
26 | 0 | not covered app.get('/keystone/test-email/' + key, function(req, res) { | ||
27 | 0 | not covered if ('function' === typeof vars) { | ||
28 | 0 | not covered vars(req, res, function(err, locals) { | ||
29 | 0 | not covered render(err, req, res, locals); | ||
30 | not covered }); | |||
31 | not covered } else { | |||
32 | 0 | not covered render(null, req, res, vars); | ||
33 | not covered } | |||
34 | not covered }); | |||
35 | not covered | |||
36 | not covered | |||
37 | 0 | not covered return this; | ||
38 | not covered | |||
39 | not covered } | |||
40 | not covered | |||
41 | 1 | 100% | module.exports = bindEmailTestRoutes; |
lib/core/wrapHTMLError.js
20% line coverage go jump to first missed line
5% statement coverage go jump to first missed statement
0% block coverage
5 SLOC
Line | Hits | Statements | Source | Action |
---|---|---|---|---|
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>' + | |||
9 | 0 | not covered '<link rel=\'stylesheet\' href=\'/keystone/styles/error.css\'>' + | ||
10 | 0 | not covered '</head><body><div class=\'error\'><h1 class=\'error-title\'>' + title + '</h1>' + | ||
11 | 0 | not covered '<div class="error-message">' + (err || '') + '</div></div></body></html>'; | ||
12 | not covered } | |||
13 | not covered | |||
14 | 1 | 100% | module.exports = wrapHTMLError; |
lib/middleware/initAPI.js
20% line coverage go jump to first missed line
9% statement coverage go jump to first missed statement
12% block coverage
20 SLOC
Line | Hits | Statements | Source | Action |
---|---|---|---|---|
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 | |||
20 | 1 | 100% | exports = module.exports = function(keystone) { | |
21 | 1 | 100% | return function initAPI(req, res, next) { | |
22 | 0 | not covered res.apiResponse = function(status) { | ||
23 | 0 | not covered res.jsonp(status); | ||
24 | not covered else | |||
25 | 0 | not covered res.json(status); | ||
26 | not covered }; | |||
27 | not covered | |||
28 | 0 | not covered res.apiError = function(key, err, msg, code) { | ||
29 | 0 | not covered msg = msg || 'Error'; | ||
30 | 0 | not covered key = key || 'unknown error'; | ||
31 | 0 | not covered msg += ' (' + key + ')'; | ||
32 | 0 | not covered if (keystone.get('logger')) { | ||
33 | 0 | not covered console.log(msg + (err ? ':' : '')); | ||
34 | 0 | not covered if (err) { | ||
35 | 0 | not covered console.log(err); | ||
36 | not covered } | |||
37 | 0 | not covered res.status(code || 500); | ||
38 | 0 | not covered res.apiResponse({ error: key || 'error', detail: err }); | ||
39 | not covered }; | |||
40 | not covered | |||
41 | 0 | not covered next(); | ||
42 | not covered }; | |||
43 | not covered }; |
lib/content/index.js
20% line coverage go jump to first missed line
14% statement coverage go jump to first missed statement
2% block coverage
81 SLOC
Line | Hits | Statements | Source | Action |
---|---|---|---|---|
1 | 1 | 100% | var _ = require('underscore'), | |
2 | 1 | 100% | 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 | |||
13 | 1 | 100% | 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 | |||
29 | 1 | 100% | Content.prototype.fetch = function(page, callback) { | |
30 | 0 | not covered if (utils.isFunction(page)) { | ||
31 | 0 | not covered callback = page; | ||
32 | 0 | not covered page = null; | ||
33 | not covered } | |||
34 | 0 | not covered var content = this; | ||
35 | not covered | |||
36 | 0 | not covered if (!this.AppContent) { | ||
37 | 0 | not covered return callback({ error: 'invalid page', message: 'No pages have been registered.' }); | ||
38 | not covered } | |||
39 | 0 | not covered if (page) { | ||
40 | not covered | |||
41 | 0 | not covered if (!this.pages[page]) { | ||
42 | 0 | not covered return callback({ error: 'invalid page', message: 'The page ' + page + ' does not exist.' }); | ||
43 | not covered } | |||
44 | 0 | not covered this.AppContent.findOne({ key: page }, function(err, result) { | ||
45 | 0 | not covered if (err) return callback(err); | ||
46 | not covered | |||
47 | 0 | 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 | |||
53 | 0 | not covered this.AppContent.find(function(err, results) { | ||
54 | 0 | not covered if (err) return callback(err); | ||
55 | not covered | |||
56 | 0 | not covered var data = {}; | ||
57 | not covered | |||
58 | 0 | not covered results.forEach(function(i) { | ||
59 | not covered if (content.pages[i.key]) { | |||
60 | 0 | not covered data[i.key] = content.pages[i.key].populate(i.content.data); | ||
61 | not covered } | |||
62 | not covered }); | |||
63 | not covered | |||
64 | 0 | not covered _.each(content.pages, function(i) { | ||
65 | 0 | not covered if (!data[i.key]) { | ||
66 | 0 | not covered data[i.key] = i.populate(); | ||
67 | not covered } | |||
68 | not covered | |||
69 | 0 | 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 | |||
88 | 1 | 100% | Content.prototype.store = function(page, content, callback) { | |
89 | 0 | not covered if (!this.pages[page]) { | ||
90 | 0 | not covered return callback({ error: 'invalid page', message: 'The page ' + page + ' does not exist.' }); | ||
91 | not covered } | |||
92 | 0 | not covered content = this.pages[page].validate(content); | ||
93 | not covered | |||
94 | not covered // TODO: Handle validation errors | |||
95 | not covered | |||
96 | 0 | not covered this.AppContent.findOne({ key: page }, function(err, doc) { | ||
97 | 0 | not covered if (err) return callback(err); | ||
98 | not covered | |||
99 | 0 | not covered if (doc) { | ||
100 | not covered if (doc.content) { | |||
101 | 0 | not covered doc.history.push(doc.content); | ||
102 | not covered } | |||
103 | 0 | not covered _.defaults(content, doc.content); | ||
104 | not covered } else { | |||
105 | 0 | not covered doc = new content.AppContent({ key: page }); | ||
106 | not covered } | |||
107 | not covered | |||
108 | 0 | not covered doc.content = { data: this.pages[page].clean(content) }; | ||
109 | 0 | not covered doc.lastChangeDate = Date.now(); | ||
110 | not covered | |||
111 | 0 | 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 | |||
122 | 1 | 100% | Content.prototype.page = function(key, page) { | |
123 | 0 | not covered if (!this.pages) { | ||
124 | 0 | not covered this.pages = {}; | ||
125 | not covered } | |||
126 | 0 | not covered if (arguments.length === 1) { | ||
127 | not covered | |||
128 | 0 | not covered if (!this.pages[key]) { | ||
129 | 0 | not covered throw new Error('keystone.content.page() Error: page ' + key + ' cannot be registered more than once.'); | ||
130 | not covered } | |||
131 | 0 | not covered return this.pages[key]; | ||
132 | not covered | |||
133 | not covered } | |||
134 | not covered | |||
135 | 0 | not covered this.initModel(); | ||
136 | not covered | |||
137 | not covered if (this.pages[key]) { | |||
138 | 0 | not covered throw new Error('keystone.content.page() Error: page ' + key + ' cannot be registered more than once.'); | ||
139 | not covered } | |||
140 | 0 | not covered this.pages[key] = page; | ||
141 | not covered | |||
142 | 0 | 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 | |||
153 | 1 | 100% | Content.prototype.initModel = function() { | |
154 | 0 | not covered if (this.AppContent) return; | ||
155 | not covered | |||
156 | 0 | not covered var contentSchemaDef = { | ||
157 | not covered | |||
158 | 0 | not covered var ContentSchema = new keystone.mongoose.Schema(contentSchemaDef); | ||
159 | not covered | |||
160 | 0 | not covered var PageSchema = new keystone.mongoose.Schema({ | ||
161 | not covered | |||
162 | 0 | 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 | |||
173 | 1 | 100% | Content.prototype.editable = function(user, options) { | |
174 | 0 | not covered if (!user || !user.canAccessKeystone) { | ||
175 | 0 | not covered return undefined; | ||
176 | not covered } | |||
177 | 0 | not covered var list = keystone.list(options.list); | ||
178 | not covered | |||
179 | 0 | not covered if (!list) { | ||
180 | 0 | not covered return JSON.stringify({ type: 'error', err: 'list not found' }); | ||
181 | not covered } | |||
182 | 0 | not covered var data = { | ||
183 | not covered | |||
184 | not covered if (options.id) { | |||
185 | 0 | not covered data.id = options.id; | ||
186 | not covered } | |||
187 | 0 | 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 | |||
197 | 1 | 100% | module.exports = exports = new Content(); | |
198 | not covered | |||
199 | not covered // Expose Classes | |||
200 | 1 | 100% | exports.Page = require('./page'); | |
201 | 1 | 100% | exports.Types = require('./types'); |
lib/content/page.js
34% line coverage go jump to first missed line
29% statement coverage go jump to first missed statement
0% block coverage
55 SLOC
Line | Hits | Statements | Source | Action |
---|---|---|---|---|
1 | 1 | 100% | var _ = require('underscore'), | |
2 | 1 | 100% | keystone = require('../../'), | |
3 | not covered utils = keystone.utils, | |||
4 | 1 | 100% | 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 | |||
16 | 0 | not covered if (!(this instanceof Page)) | ||
17 | 0 | not covered return new Page(key, options); | ||
18 | not covered | |||
19 | 0 | not covered var page = this; | ||
20 | not covered | |||
21 | 0 | not covered this.options = utils.options({ | ||
22 | not covered | |||
23 | 0 | not covered this.key = key; | ||
24 | 0 | not covered this.fields = {}; | ||
25 | not covered | |||
26 | not covered } | |||
27 | not covered | |||
28 | 1 | 100% | Object.defineProperty(Page.prototype, 'name', { get: function() { | |
29 | 0 | 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 | |||
44 | 1 | 100% | Page.prototype.set = function(key, value) { | |
45 | 0 | not covered if (arguments.length === 1) { | ||
46 | 0 | not covered return this.options[key]; | ||
47 | not covered } | |||
48 | 0 | not covered this.options[key] = value; | ||
49 | 0 | 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 | |||
65 | 1 | 100% | 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 | |||
73 | 1 | 100% | Page.prototype.add = function(fields) { // TODO: nested paths | |
74 | 0 | not covered if (!utils.isObject(fields)) { | ||
75 | 0 | not covered throw new Error('keystone.content.Page.add() Error: fields must be an object.'); | ||
76 | not covered } | |||
77 | 0 | not covered _.each(fields, function(options, path) { | ||
78 | 0 | not covered if ('function' === typeof options) { | ||
79 | 0 | not covered options = { type: options }; | ||
80 | not covered } | |||
81 | not covered | |||
82 | 0 | not covered if ('function' !== typeof options.type) { | ||
83 | 0 | not covered throw new Error('Page fields must be specified with a type function'); | ||
84 | not covered } | |||
85 | 0 | not covered if (options.type.prototype.__proto__ !== Type.prototype) { | ||
86 | not covered | |||
87 | 0 | not covered if (options.type === String) | ||
88 | 0 | not covered options.type = keystone.content.Types.Text; | ||
89 | not covered | |||
90 | 0 | not covered throw new Error('Unrecognised field constructor: ' + options.type); | ||
91 | not covered | |||
92 | 0 | not covered this.fields[path] = new options.type(path, options); | ||
93 | not covered | |||
94 | not covered | |||
95 | 0 | 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 | |||
113 | 1 | 100% | Page.prototype.register = function() { | |
114 | 0 | 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 | |||
123 | 1 | 100% | Page.prototype.populate = function(data) { | |
124 | 0 | not covered if (!utils.isObject(data)) { | ||
125 | 0 | not covered data = {}; | ||
126 | not covered } | |||
127 | 0 | 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 | |||
136 | 1 | 100% | Page.prototype.validate = function(data) { | |
137 | 0 | not covered if (!_.isObject(data)) { | ||
138 | 0 | not covered data = {}; | ||
139 | not covered } | |||
140 | 0 | 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 | |||
149 | 1 | 100% | Page.prototype.clean = function(data) { | |
150 | 0 | not covered if (!_.isObject(data)) { | ||
151 | 0 | not covered data = {}; | ||
152 | not covered } | |||
153 | 0 | 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 | |||
161 | 1 | 100% | exports = module.exports = Page; |
lib/content/type.js
100% line coverage
100% statement coverage
0% block coverage
6 SLOC
Line | Hits | Statements | Source | Action |
---|---|---|---|---|
1 | not covered /*! | |||
2 | not covered * Module dependencies. | |||
3 | not covered */ | |||
4 | not covered | |||
5 | 1 | 100% | var _ = require('underscore'), | |
6 | 1 | 100% | 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 | |||
14 | 1 | 100% | 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 | |||
21 | 1 | 100% | module.exports = exports = Type; |
lib/content/types/index.js
100% line coverage
100% statement coverage
0% block coverage
2 SLOC
Line | Hits | Statements | Source | Action |
---|---|---|---|---|
1 | 1 | 100% | exports.Text = require('./text'); | |
2 | 1 | 100% | exports.Html = require('./html'); |
lib/content/types/text.js
80% line coverage go jump to first missed line
77% statement coverage go jump to first missed statement
0% block coverage
5 SLOC
Line | Hits | Statements | Source | Action |
---|---|---|---|---|
1 | not covered /*! | |||
2 | not covered * Module dependencies. | |||
3 | not covered */ | |||
4 | not covered | |||
5 | 1 | 100% | 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) { | |||
14 | 0 | 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 | |||
21 | 1 | 100% | util.inherits(text, super_); | |
22 | not covered | |||
23 | not covered | |||
24 | not covered /*! | |||
25 | not covered * Export class | |||
26 | not covered */ | |||
27 | not covered | |||
28 | 1 | 100% | module.exports = exports = text; |
lib/content/types/html.js
80% line coverage go jump to first missed line
77% statement coverage go jump to first missed statement
0% block coverage
5 SLOC
Line | Hits | Statements | Source | Action |
---|---|---|---|---|
1 | not covered /*! | |||
2 | not covered * Module dependencies. | |||
3 | not covered */ | |||
4 | not covered | |||
5 | 1 | 100% | 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) { | |||
14 | 0 | 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 | |||
21 | 1 | 100% | util.inherits(html, super_); | |
22 | not covered | |||
23 | not covered | |||
24 | not covered /*! | |||
25 | not covered * Export class | |||
26 | not covered */ | |||
27 | not covered | |||
28 | 1 | 100% | module.exports = exports = html; |
lib/list.js
34% line coverage go jump to first missed line
30% statement coverage go jump to first missed statement
18% block coverage
504 SLOC
Line | Hits | Statements | Source | Action |
---|---|---|---|---|
1 | 1 | 100% | var _ = require('underscore'), | |
2 | 1 | 100% | async = require('async'), | |
3 | 1 | 100% | moment = require('moment'), | |
4 | 1 | 100% | keystone = require('../'), | |
5 | 1 | 100% | schemaPlugins = require('./schemaPlugins'), | |
6 | 1 | 100% | utils = require('keystone-utils'), | |
7 | 1 | 100% | Field = require('./field'), | |
8 | 1 | 100% | UpdateHandler = require('./updateHandler'), | |
9 | 1 | 100% | 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) { | |||
21 | 30 | 100% | if (!(this instanceof List)) return new List(key, options); | |
22 | not covered | |||
23 | 15 | 100% | this.options = utils.options({ schema: { | |
24 | 15 | 100% | collection: keystone.prefixModel(key) | |
25 | not covered }, | |||
26 | not covered noedit: false, | |||
27 | not covered | |||
28 | 15 | 100% | this.key = key; | |
29 | 15 | 100% | this.path = this.get('path') || utils.keyToPath(key, true); | |
30 | 15 | 100% | this.schema = new keystone.mongoose.Schema({}, this.options.schema); | |
31 | 15 | 100% | this.uiElements = []; | |
32 | 15 | 100% | this.underscoreMethods = {}; | |
33 | 15 | 100% | this.fields = {}; | |
34 | 15 | 100% | this.fieldTypes = {}; | |
35 | 15 | 100% | this.relationships = {}; | |
36 | not covered | |||
37 | 15 | 100% | this.mappings = { name: null, createdBy: null, createdOn: null, modifiedBy: null, modifiedOn: null }; | |
38 | not covered | |||
39 | not covered // set mappings | |||
40 | 15 | 50% | _.each(this.options.map, function(val, key) { not covered this.map(key, val);}, this); | |
41 | not covered | |||
42 | 15 | 100% | Object.defineProperty(this, 'label', { get: function() { | |
43 | 0 | not covered return this.get('label') || this.set('label', utils.plural(utils.keyToLabel(key))); | ||
44 | not covered }}); | |||
45 | not covered | |||
46 | 15 | 100% | Object.defineProperty(this, 'singular', { get: function() { | |
47 | 0 | not covered return this.get('singular') || this.set('singular', utils.singular(this.label)); | ||
48 | not covered }}); | |||
49 | not covered | |||
50 | 15 | 100% | Object.defineProperty(this, 'plural', { get: function() { | |
51 | 0 | not covered return this.get('plural') || this.set('plural', utils.plural(this.singular)); | ||
52 | not covered }}); | |||
53 | not covered | |||
54 | 15 | 100% | Object.defineProperty(this, 'namePath', { get: function() { | |
55 | 0 | not covered return this.mappings.name || '_id'; | ||
56 | not covered }}); | |||
57 | not covered | |||
58 | 15 | 100% | Object.defineProperty(this, 'nameField', { get: function() { | |
59 | 0 | not covered return this.fields[this.mappings.name]; | ||
60 | not covered }}); | |||
61 | not covered | |||
62 | 15 | 100% | Object.defineProperty(this, 'nameIsVirtual', { get: function() { | |
63 | 0 | not covered return this.model.schema.virtuals[this.mappings.name] ? true : false; | ||
64 | not covered }}); | |||
65 | not covered | |||
66 | 15 | 100% | Object.defineProperty(this, 'nameIsEditable', { get: function() { | |
67 | 0 | 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 | |||
70 | 15 | 100% | Object.defineProperty(this, 'nameIsInitial', { get: function() { | |
71 | 0 | not covered return (this.fields[this.mappings.name] && this.fields[this.mappings.name].options.initial === undefined); | ||
72 | not covered }}); | |||
73 | not covered | |||
74 | 15 | 100% | var initialFields; // initialFields values are initialised once, on demand | |
75 | 15 | 100% | Object.defineProperty(this, 'initialFields', { get: function() { | |
76 | 0 | 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 | |||
94 | 1 | 100% | List.prototype.set = function(key, value) { | |
95 | 198 | 100% | if (arguments.length === 1) { | |
96 | 194 | 100% | return this.options[key]; | |
97 | not covered } | |||
98 | 4 | 100% | this.options[key] = value; | |
99 | 4 | 100% | 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 | |||
115 | 1 | 100% | 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 | |||
124 | 1 | 100% | List.prototype.map = function(field, path) { | |
125 | 26 | 100% | if (path) { | |
126 | 22 | 100% | this.mappings[field] = path; | |
127 | not covered } | |||
128 | 26 | 100% | 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 | |||
139 | 1 | 100% | List.prototype.automap = function(field) { | |
140 | 50 | 100% | if (_.has(this.mappings, field.path) && !this.mappings[field.path]) { | |
141 | 18 | 100% | 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 | |||
152 | 1 | 100% | List.prototype.add = function() { | |
153 | 23 | 100% | var add = function(obj, prefix) { | |
154 | 25 | 100% | prefix = prefix || ''; | |
155 | 25 | 100% | var keys = Object.keys(obj); | |
156 | not covered | |||
157 | 77 | 100% | for (var i = 0; i < keys.length; ++i) { | |
158 | 52 | 100% | var key = keys[i]; | |
159 | not covered | |||
160 | 52 | 100% | if (null === obj[key]) { | |
161 | 0 | not covered throw new Error('Invalid value for schema path `'+ prefix + key +'`'); | ||
162 | not covered } | |||
163 | not covered | |||
164 | 52 | 100% | if (utils.isObject(obj[key]) && (!obj[key].constructor || 'Object' === obj[key].constructor.name) && (!obj[key].type || obj[key].type.type)) { | |
165 | 2 | 100% | if (Object.keys(obj[key]).length) { | |
166 | not covered // nested object, e.g. { last: { name: String }} | |||
167 | 2 | 100% | this.schema.nested[this.path] = true; | |
168 | 2 | 100% | add(obj[key], prefix + key + '.'); | |
169 | not covered } else { | |||
170 | 0 | not covered addField(prefix + key, obj[key]); // mixed type field | ||
171 | not covered } | |||
172 | 50 | 100% | addField(prefix + key, obj[key]); | |
173 | not covered } | |||
174 | not covered | |||
175 | 23 | 100% | var addField = function(path, options) { | |
176 | 50 | 100% | this.uiElements.push({ type: 'field', | |
177 | 50 | 100% | field: this.field(path, options) | |
178 | not covered }); | |||
179 | not covered }.bind(this); | |||
180 | not covered | |||
181 | 23 | 100% | _.each(arguments, function(def) { | |
182 | 23 | 100% | if ('string' === typeof def) { | |
183 | 0 | not covered if (def === '>>>') { | ||
184 | 0 | not covered this.uiElements.push({ | ||
185 | not covered type: 'indent' | |||
186 | 0 | not covered } else if (def === '<<<') { | ||
187 | 0 | not covered this.uiElements.push({ | ||
188 | not covered type: 'outdent' | |||
189 | not covered } else { | |||
190 | 0 | not covered this.uiElements.push({ | ||
191 | not covered } | |||
192 | 23 | 33% | if (def.heading && not covered 'string' === typeof def.heading { | |
193 | 0 | not covered this.uiElements.push({ | ||
194 | not covered type: 'heading', | |||
195 | 23 | 100% | add(def); | |
196 | not covered } | |||
197 | not covered | |||
198 | 23 | 100% | 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 | |||
208 | 1 | 100% | List.prototype.addPattern = function(pattern) { | |
209 | 2 | 100% | var warningMsg = | |
210 | not covered 'Use of List.addPattern("standard meta") is now deprecated and will be removed\n' + | |||
211 | 2 | 100% | 'in future versions of KeystoneJS. It has been replaced with the List "track" option.\n\n' + | |
212 | 2 | 100% | 'See http://keystonejs.com/docs/database/#lists-options for more information.'; | |
213 | not covered | |||
214 | 2 | 100% | switch (pattern) { | |
215 | not covered | |||
216 | not covered case 'standard meta': | |||
217 | not covered // enable track options if it's not already enabled | |||
218 | 2 | 100% | if (!this.get('track')) { | |
219 | 2 | 100% | this.set('track', true); | |
220 | not covered } | |||
221 | 2 | 100% | this.set('track simulate standard meta', true); | |
222 | 2 | 100% | keystone.console.err('Deprecation Warning', warningMsg); | |
223 | not covered break; | |||
224 | 2 | 100% | 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 | |||
235 | 1 | 100% | List.prototype.field = function(path, options) { | |
236 | 196 | 100% | if (arguments.length === 1) { | |
237 | 146 | 100% | return this.fields[path]; | |
238 | not covered } | |||
239 | 50 | 100% | if ('function' === typeof options) { | |
240 | 7 | 100% | options = { type: options }; | |
241 | not covered } | |||
242 | 50 | 100% | if (this.get('noedit')) { | |
243 | 0 | not covered options.noedit = true; | ||
244 | not covered } | |||
245 | 50 | 100% | if (!options.note && this.get('notes')) { | |
246 | 0 | not covered options.note = this.get('notes')[path]; | ||
247 | not covered } | |||
248 | 50 | 100% | if ('function' !== typeof options.type) { | |
249 | 0 | not covered throw new Error('Fields must be specified with a type function'); | ||
250 | not covered } | |||
251 | 50 | 100% | 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 | |||
255 | 30 | 100% | if (options.type === String) | |
256 | 15 | 100% | options.type = Field.Types.Text; | |
257 | 15 | 100% | else if (options.type === Number) | |
258 | 0 | not covered options.type = Field.Types.Number; | ||
259 | 15 | 100% | else if (options.type === Boolean) | |
260 | 1 | 100% | options.type = Field.Types.Boolean; | |
261 | 14 | 100% | else if (options.type === Date) | |
262 | 14 | 100% | options.type = Field.Types.Datetime; | |
263 | not covered else | |||
264 | 0 | 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 | |||
269 | 50 | 100% | 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 | |||
272 | 50 | 100% | if (options.type.name === 'html' && options.wysiwyg) { | |
273 | 0 | not covered this.fieldTypes.wysiwyg = true; | ||
274 | not covered } | |||
275 | 50 | 100% | var field = new options.type(this, path, options); | |
276 | not covered | |||
277 | 50 | 100% | this.fields[path] = field; | |
278 | 50 | 100% | 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 | |||
289 | 1 | 100% | List.prototype.underscoreMethod = function(path, fn) { | |
290 | 136 | 100% | var target = this.underscoreMethods; | |
291 | 136 | 100% | path = path.split('.'); | |
292 | 136 | 100% | var last = path.pop(); | |
293 | not covered | |||
294 | 136 | 100% | path.forEach(function(part) { | |
295 | 149 | 100% | if (!target[part]) target[part] = {}; | |
296 | 149 | 100% | target = target[part]; | |
297 | not covered }); | |||
298 | not covered | |||
299 | 136 | 100% | target[last] = fn; | |
300 | not covered | |||
301 | 136 | 100% | 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 | |||
311 | 1 | 100% | Object.defineProperty(List.prototype, 'defaultSort', { get: function() { | |
312 | 0 | not covered var ds = this.get('defaultSort'); | ||
313 | not covered | |||
314 | 0 | not covered return (ds === '__default__') ? (this.get('sortable') ? 'sortOrder' : this.namePath) : ds; | ||
315 | not covered | |||
316 | not covered }, set: function(value) { | |||
317 | 0 | 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 | |||
339 | 1 | 100% | List.prototype.expandColumns = function(cols) { | |
340 | 0 | not covered if (typeof cols === 'string') { | ||
341 | 0 | not covered cols = cols.split(','); | ||
342 | not covered } | |||
343 | 0 | not covered if (!Array.isArray(cols)) { | ||
344 | 0 | not covered throw new Error('List.expandColumns: cols must be an array.'); | ||
345 | not covered } | |||
346 | 0 | not covered var list = this, | ||
347 | not covered | |||
348 | 0 | not covered var getCol = function(def) { | ||
349 | 0 | not covered if (def.path === '__name__') { | ||
350 | 0 | not covered def.path = list.namePath; | ||
351 | not covered } | |||
352 | 0 | not covered var field = list.fields[def.path], | ||
353 | not covered | |||
354 | 0 | not covered if (field) { | ||
355 | not covered | |||
356 | 0 | not covered col = { | ||
357 | 0 | not covered label: def.label || field.label | ||
358 | not covered }; | |||
359 | not covered | |||
360 | 0 | not covered if (col.type === 'relationship') { | ||
361 | not covered | |||
362 | 0 | not covered col.refList = col.field.refList; | ||
363 | not covered | |||
364 | not covered if (col.refList) { | |||
365 | 0 | not covered col.refPath = def.subpath || col.refList.namePath; | ||
366 | 0 | not covered col.subField = col.refList.fields[col.refPath]; | ||
367 | 0 | not covered col.populate = { path: col.field.path, subpath: col.refPath }; | ||
368 | not covered } | |||
369 | 0 | not covered if (!def.label && def.subpath) { | ||
370 | 0 | not covered col.label = field.label + ': ' + (col.subField ? col.subField.label : utils.keyToLabel(def.subpath)); | ||
371 | not covered } | |||
372 | 0 | 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 | |||
374 | 0 | not covered col = { | ||
375 | 0 | not covered label: def.label || utils.keyToLabel(def.path) | ||
376 | not covered }; | |||
377 | not covered } | |||
378 | 0 | not covered if (col) { | ||
379 | not covered | |||
380 | 0 | not covered col.width = def.width; | ||
381 | not covered | |||
382 | 0 | not covered if (col.path === list.namePath) { | ||
383 | 0 | not covered col.isName = true; | ||
384 | 0 | not covered nameCol = col; | ||
385 | not covered } | |||
386 | 0 | not covered if (field && field.col) { | ||
387 | 0 | not covered _.extend(col, field.col); | ||
388 | not covered } | |||
389 | 0 | not covered return col; | ||
390 | not covered }; | |||
391 | not covered | |||
392 | 0 | not covered for (var i = 0; i < cols.length; i++) { | ||
393 | not covered | |||
394 | 0 | not covered var def = {}; | ||
395 | not covered | |||
396 | 0 | not covered if (typeof cols[i] === 'string') { | ||
397 | not covered | |||
398 | 0 | not covered var parts = cols[i].trim().split('|'); | ||
399 | not covered | |||
400 | 0 | not covered def.width = parts[1] || false; | ||
401 | not covered | |||
402 | 0 | not covered parts = parts[0].split(':'); | ||
403 | not covered | |||
404 | 0 | not covered def.path = parts[0]; | ||
405 | 0 | not covered def.subpath = parts[1]; | ||
406 | not covered | |||
407 | not covered } | |||
408 | not covered | |||
409 | 0 | not covered if (!utils.isObject(def) || !def.path) { | ||
410 | 0 | not covered throw new Error('List.expandColumns: column definition must contain a path.'); | ||
411 | not covered } | |||
412 | 0 | not covered var col = getCol(def); | ||
413 | not covered | |||
414 | 0 | not covered if (col) { | ||
415 | 0 | not covered expanded.push(col); | ||
416 | not covered } | |||
417 | 0 | not covered if (!nameCol) { | ||
418 | 0 | not covered nameCol = getCol({ path: list.namePath }); | ||
419 | 0 | not covered if (nameCol) { | ||
420 | 0 | not covered expanded.unshift(nameCol); | ||
421 | not covered } | |||
422 | 0 | 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 | |||
434 | 1 | 100% | List.prototype.selectColumns = function(q, cols) { // Populate relationship columns | |
435 | 0 | not covered var select = [], | ||
436 | not covered | |||
437 | 0 | not covered cols.forEach(function(col) { | ||
438 | 0 | not covered select.push(col.path); | ||
439 | not covered if (col.populate) { | |||
440 | 0 | not covered if (!populate[col.populate.path]) { | ||
441 | 0 | not covered populate[col.populate.path] = []; | ||
442 | not covered } | |||
443 | 0 | not covered populate[col.populate.path].push(col.populate.subpath); | ||
444 | not covered } | |||
445 | not covered }); | |||
446 | not covered | |||
447 | 0 | not covered q.select(select.join(' ')); | ||
448 | not covered | |||
449 | not covered for (path in populate) { | |||
450 | 0 | not covered if ( populate.hasOwnProperty(path) ) { | ||
451 | 0 | 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 | |||
461 | 1 | 100% | Object.defineProperty(List.prototype, 'defaultColumns', { get: function() { | |
462 | 0 | not covered if (!this._defaultColumns) { | ||
463 | 0 | not covered this._defaultColumns = this.expandColumns(this.get('defaultColumns')); | ||
464 | not covered } | |||
465 | not covered | |||
466 | 0 | not covered return this._defaultColumns; | ||
467 | not covered | |||
468 | not covered }, set: function(value) { | |||
469 | not covered | |||
470 | 0 | not covered this.set('defaultColumns', value); | ||
471 | 0 | 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 | |||
481 | 1 | 100% | List.prototype.relationship = function(def) { | |
482 | 0 | not covered if (arguments.length > 1) { | ||
483 | 0 | not covered _.map(arguments, function(def) { this.relationship(def); }, this); | ||
484 | 0 | not covered return this; | ||
485 | not covered } | |||
486 | 0 | not covered if ('string' === typeof def) { | ||
487 | 0 | not covered def = { ref: def }; | ||
488 | not covered } | |||
489 | 0 | not covered if (!def.ref) { | ||
490 | 0 | not covered throw new Error('List Relationships must be specified with an object containing ref (' + this.key + ')'); | ||
491 | not covered } | |||
492 | 0 | not covered if (!def.refPath) { | ||
493 | 0 | not covered def.refPath = utils.downcase(this.key); | ||
494 | not covered } | |||
495 | 0 | not covered if (!def.path) { | ||
496 | 0 | not covered def.path = utils.keyToProperty(def.ref, true); | ||
497 | not covered } | |||
498 | 0 | not covered Object.defineProperty(def, 'refList', { | ||
499 | 0 | not covered return keystone.list(def.ref); | ||
500 | not covered } | |||
501 | not covered | |||
502 | 0 | not covered Object.defineProperty(def, 'isValid', { | ||
503 | 0 | not covered return keystone.list(def.ref) ? true : false; | ||
504 | not covered } | |||
505 | not covered | |||
506 | 0 | not covered this.relationships[def.path] = def; | ||
507 | not covered | |||
508 | 0 | 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 | |||
520 | 1 | 100% | List.prototype.register = function() { | |
521 | 15 | 100% | var list = this; | |
522 | not covered | |||
523 | 15 | 100% | this.schema.virtual('list').get(function () { | |
524 | 0 | not covered return list; | ||
525 | not covered }); | |||
526 | not covered | |||
527 | 15 | 100% | if (this.get('sortable')) { | |
528 | 0 | not covered schemaPlugins.sortable.apply(this); | ||
529 | not covered } | |||
530 | 15 | 100% | if (this.get('autokey')) { | |
531 | 0 | not covered schemaPlugins.autokey.apply(this); | ||
532 | not covered } | |||
533 | 15 | 100% | if (this.get('track')) { | |
534 | 12 | 100% | schemaPlugins.track.apply(this); | |
535 | not covered } | |||
536 | 12 | 100% | if (!_.isEmpty(this.relationships)) { | |
537 | 0 | not covered this.schema.methods.getRelated = schemaPlugins.methods.getRelated; | ||
538 | 0 | not covered this.schema.methods.populateRelated = schemaPlugins.methods.populateRelated; | ||
539 | 0 | not covered if (!this.schema.options.toObject) this.schema.options.toObject = {}; | ||
540 | 0 | not covered this.schema.options.toObject.transform = schemaPlugins.options.transform; | ||
541 | not covered } | |||
542 | 12 | 100% | this.schema.virtual('_').get(function() { | |
543 | 4 | 100% | if (!this.__methods) { | |
544 | 1 | 100% | this.__methods = utils.bindMethods(list.underscoreMethods, this); | |
545 | not covered } | |||
546 | 4 | 100% | return this.__methods; | |
547 | not covered }); | |||
548 | not covered | |||
549 | 12 | 100% | this.schema.method('getUpdateHandler', function(req, res, ops) { | |
550 | 9 | 100% | return new UpdateHandler(list, this, req, res, ops); | |
551 | not covered }); | |||
552 | not covered | |||
553 | 12 | 100% | this.model = keystone.mongoose.model(this.key, this.schema); | |
554 | not covered | |||
555 | 12 | 100% | require('../').list(this); | |
556 | not covered | |||
557 | 12 | 100% | 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 | |||
573 | 1 | 100% | List.prototype.getDocumentName = function(doc, escape) { | |
574 | 0 | not covered var name = String(this.nameField ? this.nameField.format(doc) : doc.get(this.namePath)); | ||
575 | 0 | 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 | |||
586 | 1 | 100% | List.prototype.processFilters = function(q) { | |
587 | 0 | not covered var me = this; | ||
588 | 0 | not covered var filters = {}; | ||
589 | 0 | not covered queryfilterlib.QueryFilters.create(q).getFilters().forEach(function(filter){ | ||
590 | 0 | not covered filter.path = filter.key; // alias for b/c | ||
591 | 0 | not covered filter.field = me.fields[filter.key]; | ||
592 | 0 | not covered filters[filter.path] = filter; | ||
593 | not covered }); | |||
594 | 0 | 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 | |||
614 | 1 | 100% | List.prototype.getSearchFilters = function(search, add) { | |
615 | 0 | not covered var filters = {}, | ||
616 | not covered | |||
617 | 0 | not covered search = String(search || '').trim(); | ||
618 | not covered | |||
619 | not covered if (search.length) { | |||
620 | 0 | not covered var searchFilter, | ||
621 | 0 | not covered searchParts = search.split(' '), | ||
622 | 0 | not covered searchRx = new RegExp(utils.escapeRegExp(search), 'i'), | ||
623 | 0 | not covered splitSearchRx = new RegExp((searchParts.length > 1) ? _.map(searchParts, utils.escapeRegExp).join('|') : search, 'i'), | ||
624 | 0 | not covered searchFields = this.get('searchFields'), | ||
625 | not covered searchFilters = [], | |||
626 | 0 | not covered searchIdField = utils.isValidObjectId(search); | ||
627 | not covered | |||
628 | 0 | not covered if ('string' === typeof searchFields) { | ||
629 | 0 | not covered searchFields = searchFields.split(','); | ||
630 | not covered } | |||
631 | 0 | not covered searchFields.forEach(function(path) { | ||
632 | 0 | not covered path = path.trim(); | ||
633 | not covered | |||
634 | 0 | not covered if (path === '__name__') { | ||
635 | 0 | not covered path = list.mappings.name; | ||
636 | not covered } | |||
637 | not covered | |||
638 | 0 | not covered var field = list.fields[path]; | ||
639 | not covered | |||
640 | 0 | not covered if (field && field.type === 'name') { | ||
641 | not covered | |||
642 | 0 | not covered var first = {}; | ||
643 | 0 | not covered first[field.paths.first] = splitSearchRx; | ||
644 | not covered | |||
645 | 0 | not covered var last = {}; | ||
646 | 0 | not covered last[field.paths.last] = splitSearchRx; | ||
647 | not covered | |||
648 | 0 | not covered searchFilter = {}; | ||
649 | 0 | not covered searchFilter.$or = [first, last]; | ||
650 | 0 | not covered searchFilters.push(searchFilter); | ||
651 | not covered | |||
652 | not covered } else { | |||
653 | not covered | |||
654 | 0 | not covered searchFilter = {}; | ||
655 | 0 | not covered searchFilter[path] = searchRx; | ||
656 | 0 | not covered searchFilters.push(searchFilter); | ||
657 | not covered } | |||
658 | not covered | |||
659 | not covered if (list.autokey) { | |||
660 | 0 | not covered searchFilter = {}; | ||
661 | 0 | not covered searchFilter[list.autokey.path] = searchRx; | ||
662 | 0 | not covered searchFilters.push(searchFilter); | ||
663 | not covered } | |||
664 | 0 | not covered if (searchIdField) { | ||
665 | 0 | not covered searchFilter = {}; | ||
666 | 0 | not covered searchFilter._id = search; | ||
667 | 0 | not covered searchFilters.push(searchFilter); | ||
668 | not covered } | |||
669 | 0 | not covered if (searchFilters.length > 1) { | ||
670 | 0 | not covered filters.$or = searchFilters; | ||
671 | not covered } else if (searchFilters.length) { | |||
672 | 0 | not covered filters = searchFilters[0]; | ||
673 | not covered } | |||
674 | 0 | not covered if (add) { | ||
675 | 0 | not covered _.each(add, function(filter) { | ||
676 | 0 | 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': | |||
681 | 0 | not covered if (value) { | ||
682 | 0 | not covered filters[path] = true; | ||
683 | not covered } else { | |||
684 | 0 | not covered filters[path] = { $ne: true }; | ||
685 | not covered } | |||
686 | 0 | not covered _.each({address:'street1', suburb:'suburb', state:'state', postcode:'postcode', country:'country'}, function(pathKey, valueKey){ | ||
687 | 0 | not covered var value = filter[valueKey]; | ||
688 | 0 | not covered if ( value ) { | ||
689 | 0 | not covered filters[filter.field.paths[pathKey]] = new RegExp(utils.escapeRegExp(value), 'i'); | ||
690 | not covered } | |||
691 | not covered break; | |||
692 | 0 | not covered if (value) { | ||
693 | not covered if (filter.field.many) { | |||
694 | 0 | not covered filters[path] = (filter.inverse) ? { $nin: [value] } : { $in: [value] }; | ||
695 | not covered } else { | |||
696 | 0 | not covered filters[path] = (filter.inverse) ? { $ne: value } : value; | ||
697 | not covered } | |||
698 | 0 | not covered filters[path] = (filter.inverse) ? { $not: { $size: 0 } } : { $size: 0 }; | ||
699 | not covered } else { | |||
700 | 0 | not covered filters[path] = (filter.inverse) ? { $ne: null } : null; | ||
701 | not covered } | |||
702 | 0 | not covered filters[path] = (filter.inverse) ? { $ne: value } : value; | ||
703 | not covered } else { | |||
704 | 0 | not covered filters[path] = (filter.inverse) ? { $nin: ['', null] } : { $in: ['', null] }; | ||
705 | not covered } | |||
706 | 0 | not covered if (filter.operator === 'bt') { | ||
707 | 0 | not covered value = [ | ||
708 | 0 | not covered utils.number(value[0]), | ||
709 | 0 | not covered utils.number(value[1]) | ||
710 | not covered ]; | |||
711 | 0 | not covered if ( !isNaN(value[0]) && !isNaN(value[1]) ) { | ||
712 | 0 | not covered filters[path] = { | ||
713 | not covered $gte: value[0], | |||
714 | 0 | not covered filters[path] = null; | ||
715 | not covered } | |||
716 | 0 | not covered value = utils.number(value); | ||
717 | 0 | not covered if ( !isNaN(value) ) { | ||
718 | 0 | not covered if (filter.operator === 'gt') { | ||
719 | 0 | not covered filters[path] = { $gt: value}; | ||
720 | not covered } | |||
721 | 0 | not covered else if (filter.operator === 'lt') { | ||
722 | 0 | not covered filters[path] = { $lt: value}; | ||
723 | not covered } | |||
724 | 0 | not covered filters[path] = value; | ||
725 | not covered } | |||
726 | 0 | not covered filters[path] = null; | ||
727 | not covered } | |||
728 | 0 | not covered if (filter.operator === 'bt') { | ||
729 | 0 | not covered value = [ | ||
730 | 0 | not covered moment(value[0]), | ||
731 | 0 | not covered moment(value[1]) | ||
732 | not covered ]; | |||
733 | 0 | not covered if ( (value[0] && value[0].isValid()) && (value[1] && value[0].isValid()) ) { | ||
734 | 0 | not covered filters[path] = { | ||
735 | 0 | not covered $gte: moment(value[0]).startOf('day').toDate(), | ||
736 | 0 | not covered $lte: moment(value[1]).endOf('day').toDate() | ||
737 | not covered }; | |||
738 | not covered } | |||
739 | 0 | not covered value = moment(value); | ||
740 | 0 | not covered if (value && value.isValid()) { | ||
741 | 0 | not covered var start = moment(value).startOf('day').toDate(); | ||
742 | 0 | not covered var end = moment(value).endOf('day').toDate(); | ||
743 | 0 | not covered if (filter.operator === 'gt') { | ||
744 | 0 | not covered filters[path] = { $gt: end }; | ||
745 | 0 | not covered } else if (filter.operator === 'lt') { | ||
746 | 0 | not covered filters[path] = { $lt: start }; | ||
747 | not covered } else { | |||
748 | 0 | not covered filters[path] = { $lte: end, $gte: start }; | ||
749 | not covered } | |||
750 | 0 | not covered if (value) { | ||
751 | 0 | not covered cond = new RegExp('^' + utils.escapeRegExp(value) + '$', 'i'); | ||
752 | 0 | not covered filters[path] = filter.inverse ? { $not: cond } : cond; | ||
753 | not covered } else { | |||
754 | 0 | not covered filters[path] = { $nin: ['', null] }; | ||
755 | not covered } else { | |||
756 | 0 | not covered filters[path] = { $in: ['', null] }; | ||
757 | not covered } | |||
758 | 0 | not covered } else if (value) { | ||
759 | 0 | not covered cond = new RegExp(utils.escapeRegExp(value), 'i'); | ||
760 | 0 | not covered filters[path] = filter.inverse ? { $not: cond } : cond; | ||
761 | not covered } | |||
762 | not covered } | |||
763 | 0 | 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 | |||
776 | 1 | 100% | List.prototype.updateAll = function(data, callback) { | |
777 | 0 | not covered if ('function' === typeof data) { | ||
778 | 0 | not covered callback = data; | ||
779 | 0 | not covered data = null; | ||
780 | not covered } | |||
781 | 0 | not covered callback = callback || function() {}; | ||
782 | not covered | |||
783 | 0 | not covered this.model.find(function(err, results) { | ||
784 | 0 | not covered if (err) return callback(err); | ||
785 | not covered | |||
786 | 0 | not covered async.eachSeries(results, function(doc, next) { | ||
787 | 0 | not covered if (data) { | ||
788 | 0 | not covered doc.set(data); | ||
789 | not covered } | |||
790 | not covered | |||
791 | 0 | not covered doc.save(next); | ||
792 | not covered | |||
793 | not covered }, function(err) { | |||
794 | 0 | 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 | |||
821 | 1 | 100% | List.prototype.getUniqueValue = function(path, generator, limit, callback) { | |
822 | 0 | not covered var model = this.model, | ||
823 | not covered | |||
824 | 0 | not covered if (utils.isFunction(limit)) { | ||
825 | 0 | not covered callback = limit; | ||
826 | 0 | not covered limit = 10; | ||
827 | not covered } | |||
828 | 0 | not covered if (utils.isArray(generator)) { | ||
829 | not covered | |||
830 | 0 | not covered var fn = generator[0], | ||
831 | not covered | |||
832 | 0 | not covered generator = function() { | ||
833 | 0 | not covered return fn.apply(this, args); | ||
834 | not covered }; | |||
835 | not covered | |||
836 | not covered } | |||
837 | not covered | |||
838 | 0 | not covered var check = function() { | ||
839 | 0 | not covered if (count++ > 10) { | ||
840 | 0 | not covered return callback(undefined, undefined); | ||
841 | not covered } | |||
842 | 0 | not covered value = generator(); | ||
843 | not covered | |||
844 | 0 | not covered model.count().where(path, value).exec(function(err, matches) { | ||
845 | 0 | not covered if (err) return callback(err); | ||
846 | 0 | not covered if (matches) return check(); | ||
847 | 0 | not covered callback(undefined, value); | ||
848 | not covered }); | |||
849 | not covered | |||
850 | not covered | |||
851 | 0 | 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 */ | |||
861 | 1 | 100% | List.prototype.getPages = function(options, maxPages) { | |
862 | 0 | not covered var surround = Math.floor(maxPages / 2), | ||
863 | 0 | not covered firstPage = maxPages ? Math.max(1, options.currentPage - surround) : 1, | ||
864 | 0 | not covered padRight = Math.max(((options.currentPage - surround) - 1) * -1, 0), | ||
865 | 0 | not covered lastPage = maxPages ? Math.min(options.totalPages, options.currentPage + surround + padRight) : options.totalPages, | ||
866 | 0 | not covered padLeft = Math.max(((options.currentPage + surround) - lastPage), 0); | ||
867 | not covered | |||
868 | 0 | not covered options.pages = []; | ||
869 | not covered | |||
870 | 0 | not covered firstPage = Math.max(Math.min(firstPage, firstPage - padLeft), 1); | ||
871 | not covered | |||
872 | 0 | not covered for (var i = firstPage; i <= lastPage; i++) { | ||
873 | 0 | not covered options.pages.push(i); | ||
874 | not covered } | |||
875 | 0 | not covered if (firstPage !== 1) { | ||
876 | 0 | not covered options.pages.shift(); | ||
877 | 0 | not covered options.pages.unshift('...'); | ||
878 | not covered } | |||
879 | 0 | not covered if (lastPage !== Number(options.totalPages)) { | ||
880 | 0 | not covered options.pages.pop(); | ||
881 | 0 | 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 | |||
902 | 1 | 100% | List.prototype.paginate = function(options, callback) { | |
903 | 0 | not covered var list = this, model = this.model; | ||
904 | not covered | |||
905 | 0 | not covered options = options || {}; | ||
906 | not covered | |||
907 | 0 | not covered var query = model.find(options.filters); | ||
908 | not covered | |||
909 | 0 | not covered query._original_exec = query.exec; | ||
910 | 0 | not covered query._original_sort = query.sort; | ||
911 | 0 | not covered query._original_select = query.select; | ||
912 | not covered | |||
913 | 0 | not covered var currentPage = Number(options.page) || 1, | ||
914 | 0 | not covered resultsPerPage = Number(options.perPage) || 50, | ||
915 | 0 | not covered maxPages = Number(options.maxPages) || 10, | ||
916 | 0 | not covered skip = (currentPage - 1) * resultsPerPage; | ||
917 | not covered | |||
918 | 0 | 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 | |||
923 | 0 | not covered query.select = function() { | ||
924 | 0 | not covered options.select = arguments[0]; | ||
925 | 0 | not covered return query; | ||
926 | not covered }; | |||
927 | not covered | |||
928 | 0 | not covered query.sort = function() { | ||
929 | 0 | not covered options.sort = arguments[0]; | ||
930 | 0 | not covered return query; | ||
931 | not covered }; | |||
932 | not covered | |||
933 | 0 | not covered query.exec = function(callback) { | ||
934 | 0 | not covered query.count(function(err, count) { | ||
935 | 0 | not covered if (err) return callback(err); | ||
936 | not covered | |||
937 | 0 | not covered query.find().limit(resultsPerPage).skip(skip); | ||
938 | not covered | |||
939 | not covered // apply the select and sort options before calling exec | |||
940 | 0 | not covered query._original_select(options.select); | ||
941 | not covered } | |||
942 | 0 | not covered query._original_sort(options.sort); | ||
943 | not covered } | |||
944 | 0 | not covered query._original_exec(function(err, results) { | ||
945 | 0 | not covered if (err) return callback(err); | ||
946 | not covered | |||
947 | 0 | not covered var totalPages = Math.ceil(count / resultsPerPage); | ||
948 | not covered | |||
949 | 0 | not covered var rtn = { | ||
950 | not covered total: count, | |||
951 | 0 | not covered previous: (currentPage > 1) ? (currentPage - 1) : false, | ||
952 | 0 | not covered next: (currentPage < totalPages) ? (currentPage + 1) : false, | ||
953 | 0 | not covered first: skip + 1, | ||
954 | 0 | not covered last: skip + results.length | ||
955 | not covered }; | |||
956 | not covered | |||
957 | 0 | not covered list.getPages(rtn, maxPages); | ||
958 | not covered | |||
959 | 0 | not covered callback(err, rtn); | ||
960 | not covered | |||
961 | not covered }); | |||
962 | not covered | |||
963 | not covered | |||
964 | 0 | not covered if (callback) { | ||
965 | 0 | not covered return query(callback); | ||
966 | not covered } else { | |||
967 | 0 | 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 | |||
975 | 1 | 100% | exports = module.exports = List; |
lib/schemaPlugins.js
34% line coverage go jump to first missed line
30% statement coverage go jump to first missed statement
24% block coverage
222 SLOC
Line | Hits | Statements | Source | Action |
---|---|---|---|---|
1 | 1 | 100% | var keystone = require('../'), | |
2 | 1 | 100% | Types = require('./fieldTypes'), | |
3 | 1 | 100% | _ = require('underscore'), | |
4 | 1 | 100% | async = require('async'), | |
5 | 1 | 100% | utils = require('keystone-utils'); | |
6 | not covered | |||
7 | 1 | 100% | var methods = module.exports.methods = {}; | |
8 | 1 | 100% | var options = module.exports.options = {}; | |
9 | not covered | |||
10 | 1 | 100% | exports.sortable = function() { | |
11 | 0 | not covered var list = this; | ||
12 | not covered | |||
13 | 0 | not covered this.schema.add({ | ||
14 | not covered | |||
15 | 0 | not covered this.schema.pre('save', function(next) { | ||
16 | 0 | not covered if (typeof this.sortOrder === 'number') { | ||
17 | 0 | not covered return next(); | ||
18 | not covered } | |||
19 | not covered | |||
20 | 0 | not covered var item = this; | ||
21 | not covered | |||
22 | 0 | not covered list.model.findOne().sort('-sortOrder').exec(function(err, max) { | ||
23 | 0 | not covered item.sortOrder = (max && max.sortOrder) ? max.sortOrder + 1 : 0; | ||
24 | 0 | not covered next(); | ||
25 | not covered }); | |||
26 | not covered | |||
27 | not covered | |||
28 | not covered | |||
29 | 1 | 100% | exports.autokey = function() { | |
30 | 0 | not covered var autokey = this.autokey = this.get('autokey'), | ||
31 | not covered | |||
32 | 0 | not covered if (!autokey.from) { | ||
33 | 0 | not covered var fromMsg = 'Invalid List Option (autokey) for ' + list.key + ' (from is required)\n'; | ||
34 | 0 | not covered throw new Error(fromMsg); | ||
35 | not covered } | |||
36 | 0 | not covered if (!autokey.path) { | ||
37 | 0 | not covered var pathMsg = 'Invalid List Option (autokey) for ' + list.key + ' (path is required)\n'; | ||
38 | 0 | not covered throw new Error(pathMsg); | ||
39 | not covered } | |||
40 | 0 | not covered if ('string' === typeof autokey.from) { | ||
41 | 0 | not covered autokey.from = autokey.from.split(' '); | ||
42 | not covered } | |||
43 | 0 | not covered autokey.from = autokey.from.map(function(i) { | ||
44 | 0 | not covered i = i.split(':'); | ||
45 | 0 | not covered return { path: i[0], format: i[1] }; | ||
46 | not covered }); | |||
47 | not covered | |||
48 | 0 | not covered def[autokey.path] = { | ||
49 | not covered | |||
50 | 0 | not covered def[autokey.path].index = { unique: true }; | ||
51 | not covered } | |||
52 | 0 | not covered this.schema.add(def); | ||
53 | not covered | |||
54 | 0 | not covered var getUniqueKey = function(doc, src, callback) { | ||
55 | 0 | not covered var q = list.model.find().where(autokey.path, src); | ||
56 | not covered | |||
57 | 0 | not covered if (_.isObject(autokey.unique)) { | ||
58 | 0 | not covered _.each(autokey.unique, function(k, v) { | ||
59 | 0 | not covered if (_.isString(v) && v.charAt(0) === ':') { | ||
60 | 0 | not covered q.where(k, doc.get(v.substr(1))); | ||
61 | not covered } else { | |||
62 | 0 | not covered q.where(k, v); | ||
63 | not covered } | |||
64 | not covered }); | |||
65 | 0 | not covered q.exec(function(err, results) { | ||
66 | 0 | not covered if (err) { | ||
67 | 0 | not covered callback(err); | ||
68 | 0 | not covered } else if (results.length && (results.length > 1 || results[0].id != doc.id)) { | ||
69 | 0 | not covered var inc = src.match(/^(.+)\-(\d+)$/); | ||
70 | 0 | not covered if (inc && inc.length === 3) { | ||
71 | 0 | not covered src = inc[1]; | ||
72 | 0 | not covered inc = '-' + ((inc[2] * 1) + 1); | ||
73 | not covered } else { | |||
74 | 0 | not covered inc = '-1'; | ||
75 | not covered } | |||
76 | 0 | not covered return getUniqueKey(doc, src + inc, callback); | ||
77 | not covered } else { | |||
78 | 0 | not covered doc.set(autokey.path, src); | ||
79 | 0 | not covered return callback(); | ||
80 | not covered } | |||
81 | not covered }); | |||
82 | not covered }; | |||
83 | not covered | |||
84 | 0 | not covered this.schema.pre('save', function(next) { | ||
85 | 0 | not covered var modified = false, | ||
86 | not covered | |||
87 | 0 | not covered autokey.from.forEach(function(ops) { | ||
88 | not covered if (list.fields[ops.path]) { | |||
89 | 0 | not covered values.push(list.fields[ops.path].format(this, ops.format)); | ||
90 | 0 | not covered if (list.fields[ops.path].isModified(this)) { | ||
91 | 0 | not covered modified = true; | ||
92 | not covered } | |||
93 | not covered } else { | |||
94 | 0 | not covered values.push(this.get(ops.path)); | ||
95 | not covered // virtual paths are always assumed to have changed, except 'id' | |||
96 | 0 | not covered if (ops.path !== 'id' && list.schema.pathType(ops.path) === 'virtual' || this.isModified(ops.path)) { | ||
97 | 0 | 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 | |||
102 | 0 | not covered if ((!modified || autokey.fixed) && this.get(autokey.path)) { | ||
103 | 0 | not covered return next(); | ||
104 | not covered } | |||
105 | 0 | not covered var newKey = utils.slug(values.join(' ') || this.id); | ||
106 | not covered | |||
107 | not covered if (autokey.unique) { | |||
108 | 0 | not covered return getUniqueKey(this, newKey, next); | ||
109 | not covered } else { | |||
110 | 0 | not covered this.set(autokey.path, newKey); | ||
111 | 0 | 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 */ | |||
121 | 1 | 100% | exports.track = function() { | |
122 | 12 | 100% | var list = this, | |
123 | 12 | 100% | options = list.get('track'), | |
124 | 12 | 100% | userModel = keystone.get('user model'), | |
125 | not covered defaultOptions = { | |||
126 | not covered | |||
127 | not covered // ensure track is a boolean or an object | |||
128 | 12 | 100% | if (!_.isBoolean(options) && !_.isObject(options) ) { | |
129 | not covered throw new Error('Invalid List "track" option for ' + list.key + '\n' + | |||
130 | 1 | 100% | '"track" must be a boolean or an object.\n\n' + | |
131 | 1 | 100% | 'See http://keystonejs.com/docs/database/#lists-options for more information.'); | |
132 | not covered } | |||
133 | 11 | 100% | if (_.isBoolean(options)) { | |
134 | not covered // if { track: true } set all track fields to true | |||
135 | 4 | 100% | if (options) { | |
136 | 4 | 100% | options = { createdAt: true, createdBy: true, updatedAt: true, updatedBy: true }; | |
137 | not covered createdAt: true, | |||
138 | 0 | not covered return; | ||
139 | not covered } | |||
140 | 11 | 100% | if (!options.createdAt && !options.createdBy && !options.updatedAt && !options.updatedBy) { | |
141 | 1 | 100% | return; | |
142 | not covered } | |||
143 | 10 | 100% | if (list.get('track simulate standard meta')) { | |
144 | 2 | 100% | if (!options.createdAt) { | |
145 | 0 | not covered options.createdAt = true; | ||
146 | not covered } | |||
147 | 2 | 100% | if (!options.updatedAt) { | |
148 | 0 | not covered options.updatedAt = true; | ||
149 | not covered } | |||
150 | 10 | 100% | options = _.extend({}, defaultOptions, options); | |
151 | not covered | |||
152 | not covered // validate option fields | |||
153 | 10 | 100% | _.each(options, function(value, key) { | |
154 | 39 | 100% | var fieldName; | |
155 | not covered | |||
156 | not covered // make sure it's a valid track option field | |||
157 | 39 | 100% | if (_.has(defaultOptions, key)) { | |
158 | not covered // make sure the option field value is either a boolean or a string | |||
159 | 38 | 100% | if (!_.isBoolean(value) && !_.isString(value)) { | |
160 | not covered throw new Error('Invalid List "track" option for ' + list.key + '\n' + | |||
161 | 1 | 100% | '"' + key + '" must be a boolean or a string.\n\n' + | |
162 | 1 | 100% | 'See http://keystonejs.com/docs/database/#lists-options for more information.'); | |
163 | not covered } | |||
164 | 37 | 100% | if (value) { | |
165 | not covered // determine | |||
166 | 29 | 100% | fieldName = value === true ? key : value; | |
167 | 29 | 100% | options[key] = fieldName; | |
168 | not covered | |||
169 | 29 | 100% | switch(key) { | |
170 | not covered case 'createdAt': | |||
171 | 15 | 100% | fields[fieldName] = { type: Date, hidden: true, index: true }; | |
172 | not covered break; | |||
173 | 14 | 100% | 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' + | |||
176 | 1 | 100% | 'valid field options are "createdAt", "createdBy", "updatedAt", an "updatedBy".\n\n' + | |
177 | 1 | 100% | '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 | |||
181 | 8 | 100% | if (list.get('track simulate standard meta')) { | |
182 | 2 | 100% | list.map('createdOn', options.createdAt); | |
183 | 2 | 100% | list.schema.virtual('createdOn') .get(function () { | |
184 | not covered .get(function () { | |||
185 | 4 | 100% | return this.get('createdAt'); | |
186 | not covered }); | |||
187 | 2 | 100% | list.map('updatedOn', options.updatedAt); | |
188 | 2 | 100% | list.schema.virtual('updatedOn') .get(function () { | |
189 | not covered .get(function () { | |||
190 | 4 | 100% | return this.get('updatedAt'); | |
191 | not covered }); | |||
192 | 8 | 100% | if (_.isEmpty(fields)) { | |
193 | 0 | not covered return; | ||
194 | not covered } | |||
195 | 8 | 100% | list.add(fields); | |
196 | not covered | |||
197 | not covered // add the pre-save schema plugin | |||
198 | 8 | 100% | list.schema.pre('save', function (next) { | |
199 | 16 | 100% | var now = new Date(); | |
200 | not covered | |||
201 | not covered // set createdAt/createdBy on new docs | |||
202 | 6 | 100% | this.set(options.createdAt, now); | |
203 | not covered } | |||
204 | 8 | 100% | if (options.createdBy && this._req_user) { | |
205 | 6 | 100% | this.set(options.createdBy, this._req_user._id); | |
206 | not covered } | |||
207 | 16 | 100% | if (this.isNew || this.isModified()) { | |
208 | not covered if (options.updatedAt) { | |||
209 | 16 | 100% | this.set(options.updatedAt, now); | |
210 | not covered } | |||
211 | 16 | 100% | if (options.updatedBy && this._req_user) { | |
212 | 16 | 100% | this.set(options.updatedBy, this._req_user._id); | |
213 | not covered } | |||
214 | 16 | 100% | next(); | |
215 | not covered }); | |||
216 | not covered | |||
217 | not covered | |||
218 | 1 | 100% | methods.getRelated = function(paths, callback, nocollapse) { | |
219 | 0 | not covered var item = this, | ||
220 | not covered | |||
221 | 0 | not covered if ('function' !== typeof callback) { | ||
222 | 0 | not covered throw new Error('List.getRelated(paths, callback, nocollapse) requires a callback function.'); | ||
223 | not covered } | |||
224 | 0 | not covered if ('string' === typeof paths) { | ||
225 | 0 | not covered var pathsArr = paths.split(' '); | ||
226 | 0 | not covered var lastPath = ''; | ||
227 | 0 | not covered paths = []; | ||
228 | 0 | not covered for (var i = 0; i < pathsArr.length; i++) { | ||
229 | 0 | not covered lastPath += (lastPath.length ? ' ' : '') + pathsArr[i]; | ||
230 | 0 | not covered if (lastPath.indexOf('[') < 0 || lastPath.charAt(lastPath.length - 1) === ']') { | ||
231 | 0 | not covered paths.push(lastPath); | ||
232 | 0 | not covered lastPath = ''; | ||
233 | not covered } | |||
234 | 0 | not covered _.each(paths, function(options) { | ||
235 | 0 | not covered var populateString = ''; | ||
236 | not covered | |||
237 | 0 | not covered if ('string' === typeof options) { | ||
238 | 0 | not covered if (options.indexOf('[') > 0) { | ||
239 | 0 | not covered populateString = options.substring(options.indexOf('[') + 1, options.indexOf(']')); | ||
240 | 0 | not covered options = options.substr(0,options.indexOf('[')); | ||
241 | not covered } | |||
242 | 0 | not covered options = { path: options }; | ||
243 | not covered } | |||
244 | 0 | not covered options.populate = options.populate || []; | ||
245 | 0 | not covered options.related = options.related || []; | ||
246 | not covered | |||
247 | 0 | not covered var relationship = list.relationships[options.path]; | ||
248 | 0 | not covered if (!relationship) throw new Error('List.getRelated: list ' + list.key + ' does not have a relationship ' + options.path + '.'); | ||
249 | not covered | |||
250 | 0 | not covered var refList = keystone.list(relationship.ref); | ||
251 | 0 | not covered if (!refList) throw new Error('List.getRelated: list ' + relationship.ref + ' does not exist.'); | ||
252 | not covered | |||
253 | 0 | not covered var relField = refList.fields[relationship.refPath]; | ||
254 | 0 | 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) { | |||
257 | 0 | not covered _.each(populateString.split(' '), function(key) { | ||
258 | 0 | not covered options.related.push(key); | ||
259 | not covered else | |||
260 | 0 | not covered options.populate.push(key); | ||
261 | not covered }); | |||
262 | not covered | |||
263 | not covered } | |||
264 | not covered | |||
265 | 0 | not covered queue[relationship.path] = function(done) { | ||
266 | 0 | not covered var query = refList.model.find().where(relField.path); | ||
267 | not covered | |||
268 | not covered if (options.populate) | |||
269 | 0 | not covered query.populate(options.populate); | ||
270 | not covered | |||
271 | not covered if (relField.many) { | |||
272 | 0 | not covered query.in([item.id]); | ||
273 | not covered } else { | |||
274 | 0 | not covered query.equals(item.id); | ||
275 | not covered } | |||
276 | 0 | not covered query.sort(options.sort || relationship.sort || refList.defaultSort); | ||
277 | not covered | |||
278 | not covered if (options.related.length) { | |||
279 | 0 | not covered query.exec(function(err, results) { | ||
280 | 0 | not covered if (err || !results.length) { | ||
281 | 0 | not covered return done(err, results); | ||
282 | not covered } | |||
283 | 0 | not covered async.parallel(results.map(function(item) { | ||
284 | 0 | not covered return function(done) { | ||
285 | 0 | not covered item.populateRelated(options.related, done); | ||
286 | not covered }; | |||
287 | not covered }), | |||
288 | not covered function(err) { | |||
289 | 0 | not covered done(err, results); | ||
290 | not covered } | |||
291 | not covered ); | |||
292 | not covered }); | |||
293 | not covered } else { | |||
294 | 0 | not covered query.exec(done); | ||
295 | not covered } | |||
296 | not covered | |||
297 | 0 | not covered if (!item._populatedRelationships) item._populatedRelationships = {}; | ||
298 | 0 | not covered item._populatedRelationships[relationship.path] = true; | ||
299 | not covered | |||
300 | not covered | |||
301 | 0 | not covered async.parallel(queue, function(err, results) { | ||
302 | 0 | not covered if (!nocollapse && results && paths.length === 1) { | ||
303 | 0 | not covered results = results[paths[0]]; | ||
304 | not covered } | |||
305 | 0 | not covered callback(err, results); | ||
306 | not covered }); | |||
307 | not covered | |||
308 | not covered | |||
309 | 1 | 100% | methods.populateRelated = function(rel, callback) { | |
310 | 0 | not covered var item = this; | ||
311 | not covered | |||
312 | 0 | not covered if ('function' !== typeof callback) { | ||
313 | 0 | not covered throw new Error('List.populateRelated(rel, callback) requires a callback function.'); | ||
314 | not covered } | |||
315 | 0 | not covered this.getRelated(rel, function(err, results) { | ||
316 | 0 | not covered _.each(results, function(data, key) { | ||
317 | 0 | not covered item[key] = data; | ||
318 | not covered }); | |||
319 | 0 | not covered callback(err, results); | ||
320 | not covered }, true); | |||
321 | not covered | |||
322 | not covered | |||
323 | 1 | 100% | options.transform = function(doc, ret) { if (doc._populatedRelationships) { | |
324 | 0 | not covered _.each(doc._populatedRelationships, function(on, key) { | ||
325 | 0 | not covered if (!on) return; | ||
326 | 0 | 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
Line | Hits | Statements | Source | Action |
---|---|---|---|---|
1 | 1 | 100% | exports.AzureFile = require('./azurefile'); | |
2 | 1 | 100% | exports.Boolean = require('./boolean'); | |
3 | 1 | 100% | exports.CloudinaryImage = require('./cloudinaryimage'); | |
4 | 1 | 100% | exports.CloudinaryImages = require('./cloudinaryimages'); | |
5 | 1 | 100% | exports.Code = require('./code'); | |
6 | 1 | 100% | exports.Color = require('./color'); | |
7 | 1 | 100% | exports.Date = require('./date'); | |
8 | 1 | 100% | exports.Datetime = require('./datetime'); | |
9 | 1 | 100% | exports.Email = require('./email'); | |
10 | 1 | 100% | exports.Embedly = require('./embedly'); | |
11 | 1 | 100% | exports.Html = require('./html'); | |
12 | 1 | 100% | exports.Key = require('./key'); | |
13 | 1 | 100% | exports.LocalFile = require('./localfile'); | |
14 | 1 | 100% | exports.Location = require('./location'); | |
15 | 1 | 100% | exports.Markdown = require('./markdown'); | |
16 | 1 | 100% | exports.Money = require('./money'); | |
17 | 1 | 100% | exports.Name = require('./name'); | |
18 | 1 | 100% | exports.Number = require('./number'); | |
19 | 1 | 100% | exports.Password = require('./password'); | |
20 | 1 | 100% | exports.Relationship = require('./relationship'); | |
21 | 1 | 100% | exports.S3File = require('./s3file'); | |
22 | 1 | 100% | exports.Select = require('./select'); | |
23 | 1 | 100% | exports.Text = require('./text'); | |
24 | 1 | 100% | exports.Textarea = require('./textarea'); | |
25 | 1 | 100% | exports.Url = require('./url'); | |
26 | 1 | 100% | exports.LocalFiles = require('./localfiles'); |
lib/fieldTypes/azurefile.js
20% line coverage go jump to first missed line
13% statement coverage go jump to first missed statement
0% block coverage
149 SLOC
Line | Hits | Statements | Source | Action |
---|---|---|---|---|
1 | not covered /*! | |||
2 | not covered * Module dependencies. | |||
3 | not covered */ | |||
4 | not covered | |||
5 | 1 | 100% | var _ = require('underscore'), | |
6 | 1 | 100% | moment = require('moment'), | |
7 | 1 | 100% | keystone = require('../../'), | |
8 | 1 | 100% | async = require('async'), | |
9 | 1 | 100% | util = require('util'), | |
10 | 1 | 100% | azure = require('azure'), | |
11 | 1 | 100% | utils = require('keystone-utils'), | |
12 | 1 | 100% | 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 | |||
23 | 0 | not covered this._underscoreMethods = ['format', 'uploadFile']; | ||
24 | not covered | |||
25 | not covered // event queues | |||
26 | 0 | not covered this._pre = { | ||
27 | not covered | |||
28 | not covered // TODO: implement filtering, usage disabled for now | |||
29 | 0 | 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) { | |||
33 | 0 | 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 | |||
36 | 0 | not covered azurefile.super_.call(this, list, path, options); | ||
37 | not covered | |||
38 | not covered // validate azurefile config (has to happen after super_.call) | |||
39 | 0 | not covered if (!this.azurefileconfig) { | ||
40 | not covered throw new Error('Invalid Configuration\n\n' + | |||
41 | 0 | not covered 'AzureFile fields (' + list.key + '.' + path + ') require the "azurefile config" option to be set.\n\n' + | ||
42 | 0 | not covered 'See http://keystonejs.com/docs/configuration#services-azure for more information.\n'); | ||
43 | not covered } | |||
44 | not covered | |||
45 | 0 | not covered process.env.AZURE_STORAGE_ACCOUNT = this.azurefileconfig.account; | ||
46 | 0 | not covered process.env.AZURE_STORAGE_ACCESS_KEY = this.azurefileconfig.key; | ||
47 | not covered | |||
48 | 0 | not covered this.azurefileconfig.container = this.azurefileconfig.container ||Â 'keystone'; | ||
49 | not covered | |||
50 | 0 | not covered var self = this; | ||
51 | 0 | not covered options.filenameFormatter = options.filenameFormatter || function(item, filename) { return filename; }; | ||
52 | 0 | 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 | |||
55 | 0 | not covered if (options.pre && options.pre.upload) { | ||
56 | 0 | 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 | |||
65 | 1 | 100% | 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 | |||
71 | 1 | 100% | Object.defineProperty(azurefile.prototype, 'azurefileconfig', { get: function() { | |
72 | 0 | 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 | |||
82 | 1 | 100% | azurefile.prototype.pre = function(event, fn) { | |
83 | 0 | not covered if (!this._pre[event]) { | ||
84 | 0 | not covered throw new Error('AzureFile (' + this.list.key + '.' + this.path + ') error: azurefile.pre()\n\n' + | ||
85 | not covered } | |||
86 | 0 | not covered this._pre[event].push(fn); | ||
87 | 0 | 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 | |||
97 | 1 | 100% | azurefile.prototype.addToSchema = function() { | |
98 | 0 | not covered var field = this, | ||
99 | not covered | |||
100 | 0 | not covered var paths = this.paths = { | ||
101 | 0 | not covered filename: this._path.append('.filename'), | ||
102 | 0 | not covered path: this._path.append('.path'), | ||
103 | 0 | not covered size: this._path.append('.size'), | ||
104 | 0 | not covered filetype: this._path.append('.filetype'), | ||
105 | 0 | not covered url: this._path.append('.url'), | ||
106 | 0 | not covered etag: this._path.append('.etag'), | ||
107 | 0 | not covered container: this._path.append('.container'), | ||
108 | not covered // virtuals | |||
109 | 0 | not covered exists: this._path.append('.exists'), | ||
110 | 0 | not covered upload: this._path.append('_upload'), | ||
111 | 0 | not covered action: this._path.append('_action') | ||
112 | not covered }; | |||
113 | not covered | |||
114 | 0 | not covered var schemaPaths = this._path.addTo({}, { | ||
115 | not covered | |||
116 | 0 | not covered schema.add(schemaPaths); | ||
117 | not covered | |||
118 | 0 | not covered var exists = function(item) { | ||
119 | 0 | 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 | |||
123 | 0 | not covered schema.virtual(paths.exists).get(function() { | ||
124 | 0 | not covered return schemaMethods.exists.apply(this); | ||
125 | not covered }); | |||
126 | not covered | |||
127 | 0 | not covered var reset = function(item) { | ||
128 | 0 | not covered item.set(field.path, { | ||
129 | not covered }; | |||
130 | not covered | |||
131 | 0 | not covered var schemaMethods = { | ||
132 | 0 | not covered return exists(this); | ||
133 | not covered }, | |||
134 | 0 | not covered var self = this; | ||
135 | not covered | |||
136 | not covered try { | |||
137 | 0 | not covered azure.createBlobService().deleteBlob(this.get(paths.container), this.get(paths.filename), function(error) {}); | ||
138 | not covered } catch(e) {} | |||
139 | 0 | not covered reset(self); | ||
140 | not covered }, | |||
141 | 0 | not covered var self = this; | ||
142 | not covered try { | |||
143 | 0 | not covered azure.createBlobService().blobService.deleteBlob(this.get(paths.container), this.get(paths.filename), function(error) { | ||
144 | 0 | not covered if(!error){} | ||
145 | not covered }); | |||
146 | not covered } catch(e) {} | |||
147 | 0 | not covered reset(self); | ||
148 | not covered } | |||
149 | not covered | |||
150 | 0 | not covered _.each(schemaMethods, function(fn, key) { | ||
151 | 0 | 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 | |||
155 | 0 | not covered this.apply = function(item, method) { | ||
156 | 0 | not covered return schemaMethods[method].apply(item, Array.prototype.slice.call(arguments, 2)); | ||
157 | not covered }; | |||
158 | not covered | |||
159 | 0 | 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 | |||
169 | 1 | 100% | azurefile.prototype.format = function(item) { | |
170 | 0 | 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 | |||
180 | 1 | 100% | azurefile.prototype.isModified = function(item) { | |
181 | 0 | 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 | |||
191 | 1 | 100% | azurefile.prototype.validateInput = function(data) { // TODO - how should file field input be validated? | |
192 | 0 | 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 | |||
202 | 1 | 100% | 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 | |||
211 | 1 | 100% | azurefile.prototype.uploadFile = function(item, file, update, callback) { | |
212 | 0 | not covered var field = this, | ||
213 | 0 | not covered prefix = field.options.datePrefix ? moment().format(field.options.datePrefix) + '-' : '', | ||
214 | 0 | not covered name = prefix + file.name; | ||
215 | not covered | |||
216 | 0 | not covered if (field.options.allowedTypes && !_.contains(field.options.allowedTypes, file.type)){ | ||
217 | 0 | not covered return callback(new Error('Unsupported File Type: '+file.type)); | ||
218 | not covered } | |||
219 | 0 | not covered if ('function' == typeof update) { | ||
220 | 0 | not covered callback = update; | ||
221 | 0 | not covered update = false; | ||
222 | not covered } | |||
223 | 0 | not covered var doUpload = function() { | ||
224 | 0 | not covered var blobService = azure.createBlobService(); | ||
225 | 0 | not covered var container = field.options.containerFormatter(item, file.name); | ||
226 | not covered | |||
227 | 0 | not covered blobService.createContainerIfNotExists(container, {publicAccessLevel : 'blob'}, function(error){ | ||
228 | 0 | not covered if(error){ | ||
229 | 0 | not covered return callback(error); | ||
230 | not covered } | |||
231 | not covered | |||
232 | 0 | not covered blobService.createBlockBlobFromFile(container, field.options.filenameFormatter(item, file.name), file.path, function(error, blob, res){ | ||
233 | 0 | not covered if(error){ | ||
234 | 0 | not covered return callback(error); | ||
235 | not covered } else { | |||
236 | 0 | not covered var fileData = { | ||
237 | not covered filename: blob.blob, | |||
238 | 0 | not covered url: 'http://' + field.azurefileconfig.account + '.blob.core.windows.net/' + container + '/' + blob.blob | ||
239 | not covered }; | |||
240 | not covered | |||
241 | 0 | not covered if (update) { | ||
242 | 0 | not covered item.set(field.path, fileData); | ||
243 | not covered } | |||
244 | 0 | not covered callback(null, fileData); | ||
245 | not covered } | |||
246 | not covered }); | |||
247 | not covered }; | |||
248 | not covered | |||
249 | 0 | not covered async.eachSeries(this._pre.upload, function(fn, next) { | ||
250 | 0 | not covered fn(item, file, next); | ||
251 | not covered }, function(err) { | |||
252 | 0 | not covered if (err) return callback(err); | ||
253 | 0 | 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 | |||
268 | 1 | 100% | azurefile.prototype.getRequestHandler = function(item, req, paths, callback) { | |
269 | 0 | not covered var field = this; | ||
270 | not covered | |||
271 | 0 | not covered if (utils.isFunction(paths)) { | ||
272 | 0 | not covered callback = paths; | ||
273 | 0 | not covered paths = field.paths; | ||
274 | 0 | not covered } else if (!paths) { | ||
275 | 0 | not covered paths = field.paths; | ||
276 | not covered } | |||
277 | 0 | not covered callback = callback || function() {}; | ||
278 | not covered | |||
279 | 0 | not covered return function() { | ||
280 | 0 | not covered var action = req.body[paths.action]; | ||
281 | not covered | |||
282 | 0 | not covered if (/^(delete|reset)$/.test(action)) | ||
283 | 0 | not covered field.apply(item, action); | ||
284 | not covered } | |||
285 | 0 | not covered if (req.files && req.files[paths.upload] && req.files[paths.upload].size) { | ||
286 | 0 | not covered return field.uploadFile(item, req.files[paths.upload], true, callback); | ||
287 | not covered } | |||
288 | 0 | 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 | |||
299 | 1 | 100% | azurefile.prototype.handleRequest = function(item, req, paths, callback) { | |
300 | 0 | 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 | |||
308 | 1 | 100% | exports = module.exports = azurefile; |
lib/field.js
55% line coverage go jump to first missed line
44% statement coverage go jump to first missed statement
25% block coverage
158 SLOC
Line | Hits | Statements | Source | Action |
---|---|---|---|---|
1 | not covered /*! | |||
2 | not covered * Module dependencies. | |||
3 | not covered */ | |||
4 | not covered | |||
5 | 1 | 100% | var _ = require('underscore'), | |
6 | 1 | 100% | marked = require('marked'), | |
7 | 1 | 100% | Path = require('./path'), | |
8 | 1 | 100% | fspath = require('path'), | |
9 | 1 | 100% | jade = require('jade'), | |
10 | 1 | 100% | fs = require('fs'), | |
11 | 1 | 100% | keystone = require('../'), | |
12 | 1 | 100% | 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 | |||
28 | 50 | 100% | this.list = list; | |
29 | 50 | 100% | this._path = new Path(path); | |
30 | 50 | 100% | this.path = path; | |
31 | not covered | |||
32 | 50 | 100% | this.type = this.constructor.name; | |
33 | 50 | 100% | this.options = utils.options(this.defaults, options); | |
34 | 50 | 100% | this.label = options.label || utils.keyToLabel(this.path); | |
35 | 50 | 100% | this.typeDescription = options.typeDescription || this.typeDescription || this.type; | |
36 | not covered | |||
37 | not covered // Add the field to the schema | |||
38 | 50 | 100% | this.list.automap(this); | |
39 | 50 | 100% | this.addToSchema(); | |
40 | not covered | |||
41 | not covered // Warn on required fields that aren't initial | |||
42 | not covered if (this.options.required && | |||
43 | 50 | 100% | this.options.initial === undefined && | |
44 | 50 | 100% | this.options.default === undefined && | |
45 | 50 | 100% | !this.options.value && | |
46 | 50 | 100% | !this.list.get('nocreate') && | |
47 | 50 | 100% | this.path !== this.list.mappings.name | |
48 | not covered ) { | |||
49 | not covered console.error('\nError: Invalid Configuration\n\n' + | |||
50 | 0 | not covered 'Field (' + list.key + '.' + path + ') is required but not initial, and has no default or generated value.\n' + | ||
51 | 0 | not covered 'Please provide a default, remove the required setting, or set initial: false to override this error.\n'); | ||
52 | 0 | not covered process.exit(1); | ||
53 | not covered } | |||
54 | not covered | |||
55 | not covered // Set up templates | |||
56 | 50 | 100% | this.templateDir = fspath.normalize(options.templateDir || (__dirname + '../../templates/fields/' + this.type)); | |
57 | not covered | |||
58 | 50 | 100% | var defaultTemplates = { | |
59 | 50 | 100% | form: this.templateDir + '/' + 'form.jade', | |
60 | 50 | 100% | initial: this.templateDir + '/' + 'initial.jade' | |
61 | not covered }; | |||
62 | not covered | |||
63 | 50 | 100% | 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) { | |||
67 | 0 | not covered this.list.schema.pre('save', this.getPreSaveWatcher()); | ||
68 | not covered } | |||
69 | not covered | |||
70 | not covered // Convert notes from markdown to html | |||
71 | 50 | 100% | var note = null; | |
72 | 50 | 100% | Object.defineProperty(this, 'note', { get: function() { | |
73 | 0 | 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 | |||
78 | 1 | 100% | Field.prototype.getPreSaveWatcher = function() { | |
79 | 0 | not covered var field = this, | ||
80 | not covered | |||
81 | 0 | not covered if (this.options.watch === true) { | ||
82 | not covered // watch == true means always apply the value method | |||
83 | 0 | not covered applyValue = function() { return true; }; | ||
84 | not covered } else { | |||
85 | 0 | not covered if (_.isString(this.options.watch)) { | ||
86 | 0 | not covered this.options.watch = this.options.watch.split(' '); | ||
87 | not covered } | |||
88 | 0 | not covered if (_.isFunction(this.options.watch)) { | ||
89 | 0 | not covered applyValue = this.options.watch; | ||
90 | 0 | not covered } else if (_.isArray(this.options.watch)) { | ||
91 | 0 | not covered applyValue = function(item) { | ||
92 | 0 | not covered var pass = false; | ||
93 | 0 | not covered field.options.watch.forEach(function(path) { | ||
94 | 0 | not covered if (item.isModified(path)) pass = true; | ||
95 | not covered }); | |||
96 | 0 | not covered return pass; | ||
97 | not covered }; | |||
98 | 0 | not covered } else if (_.isObject(this.options.watch)) { | ||
99 | 0 | not covered applyValue = function(item) { | ||
100 | 0 | not covered var pass = false; | ||
101 | 0 | not covered _.each(field.options.watch, function(value, path) { | ||
102 | 0 | not covered if (item.isModified(path) && item.get(path) === value) pass = true; | ||
103 | not covered }); | |||
104 | 0 | not covered return pass; | ||
105 | not covered }; | |||
106 | not covered } | |||
107 | 0 | not covered if (!applyValue) { | ||
108 | 0 | not covered console.error('\nError: Invalid Configuration\n\n' + | ||
109 | 0 | not covered process.exit(1); | ||
110 | not covered } | |||
111 | 0 | not covered if (!_.isFunction(this.options.value)) { | ||
112 | 0 | not covered console.error('\nError: Invalid Configuration\n\n' + | ||
113 | 0 | not covered process.exit(1); | ||
114 | not covered } | |||
115 | 0 | not covered return function(next) { | ||
116 | 0 | not covered if (!applyValue(this)) { | ||
117 | 0 | not covered return next(); | ||
118 | not covered } | |||
119 | 0 | not covered this.set(field.path, field.options.value.call(this)); | ||
120 | 0 | not covered next(); | ||
121 | not covered }; | |||
122 | not covered | |||
123 | not covered | |||
124 | 1 | 100% | exports = module.exports = Field; | |
125 | not covered | |||
126 | not covered | |||
127 | not covered /** Getter properties for the Field prototype */ | |||
128 | not covered | |||
129 | 1 | 50% | Object.defineProperty(Field.prototype, 'width', { get: function() { not covered return this.options.width || 'full';} }); // !! field width is, for certain types, overridden by css | |
130 | 1 | 50% | Object.defineProperty(Field.prototype, 'initial', { get: function() { not covered return this.options.initial || false;} }); | |
131 | 37 | 100% | Object.defineProperty(Field.prototype, 'required', { get: function() { return this.options.required || false; } }) | |
132 | 1 | 50% | Object.defineProperty(Field.prototype, 'col', { get: function() { not covered return this.options.col || false;} }); | |
133 | 37 | 100% | Object.defineProperty(Field.prototype, 'noedit', { get: function() { return this.options.noedit || false; } }) | |
134 | 1 | 50% | Object.defineProperty(Field.prototype, 'nocol', { get: function() { not covered return this.options.nocol || false;} }); | |
135 | 1 | 50% | Object.defineProperty(Field.prototype, 'nosort', { get: function() { not covered return this.options.nosort || false;} }); | |
136 | 1 | 50% | Object.defineProperty(Field.prototype, 'nofilter', { get: function() { not covered return this.options.nofilter || false;} }); | |
137 | 1 | 50% | Object.defineProperty(Field.prototype, 'collapse', { get: function() { not covered return this.options.collapse || false;} }); | |
138 | 1 | 50% | Object.defineProperty(Field.prototype, 'hidden', { get: function() { not covered return this.options.hidden || false;} }); | |
139 | 1 | 50% | 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 | |||
150 | 1 | 100% | Field.prototype.addToSchema = function() { | |
151 | 34 | 67% | var ops = (this._nativeType) ? _.defaults({ type: this._nativeType }, this.options) : not covered this.options | |
152 | not covered | |||
153 | 34 | 100% | this.list.schema.path(this.path, ops); | |
154 | not covered | |||
155 | 34 | 100% | this.bindUnderscoreMethods(); | |
156 | not covered | |||
157 | not covered | |||
158 | 1 | 100% | Field.prototype.bindUnderscoreMethods = function(methods) { | |
159 | 50 | 100% | 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 | |||
164 | 50 | 100% | (this._underscoreMethods || []).concat({ fn: 'updateItem', as: 'update' }, (methods || [])).forEach(function(method) { | |
165 | 136 | 100% | if ('string' === typeof method) { | |
166 | 86 | 100% | method = { fn: method, as: method }; | |
167 | not covered } | |||
168 | 136 | 100% | if ('function' !== typeof field[method.fn]) { | |
169 | 0 | not covered throw new Error('Invalid underscore method (' + method.fn + ') applied to ' + field.list.key + '.' + field.path + ' (' + field.type + ')'); | ||
170 | not covered } | |||
171 | 136 | 100% | field.underscoreMethod(method.as, function() { | |
172 | 4 | 100% | var args = [this].concat(Array.prototype.slice.call(arguments)); | |
173 | 4 | 100% | 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 | |||
186 | 1 | 100% | Field.prototype.underscoreMethod = function(path, fn) { | |
187 | 136 | 100% | this.list.underscoreMethod(this.path + '.' + path, function() { | |
188 | 4 | 100% | 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 | |||
200 | 1 | 100% | Field.prototype.format = function(item) { | |
201 | 0 | 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 | |||
212 | 1 | 100% | Field.prototype.isModified = function(item) { | |
213 | 0 | 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 | |||
224 | 1 | 100% | Field.prototype.validateInput = function(data, required, item) { | |
225 | 9 | 100% | if (!required) return true; | |
226 | 0 | not covered if (!(this.path in data) && item && item.get(this.path)) return true; | ||
227 | 0 | not covered if ('string' === typeof data[this.path]) { | ||
228 | 0 | not covered return (data[this.path].trim()) ? true : false; | ||
229 | not covered } else { | |||
230 | 0 | 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 | |||
241 | 1 | 100% | Field.prototype.updateItem = function(item, data) { | |
242 | 12 | 100% | var value = this.getValueFromData(data); | |
243 | not covered | |||
244 | 12 | 100% | if (value !== undefined && value != item.get(this.path)) { | |
245 | 12 | 100% | 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 | |||
254 | 1 | 100% | Field.prototype.getValueFromData = function(data) { | |
255 | 17 | 100% | 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 | |||
264 | 1 | 100% | Field.prototype.compile = function(type, callback) { | |
265 | 0 | not covered var templatePath = this.templates[type]; | ||
266 | not covered | |||
267 | 0 | not covered if (!compiledTemplates[templatePath]) { | ||
268 | 0 | not covered fs.readFile(templatePath, 'utf8', function(err, file) { | ||
269 | 0 | not covered if (!err){ | ||
270 | 0 | not covered compiledTemplates[templatePath] = jade.compile(file, { | ||
271 | not covered filename: templatePath, | |||
272 | 0 | not covered pretty: keystone.get('env') !== 'production' | ||
273 | not covered }); | |||
274 | not covered } | |||
275 | 0 | not covered if (callback) return callback(); | ||
276 | not covered }); | |||
277 | 0 | not covered } else if (callback) { | ||
278 | 0 | 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 | |||
287 | 1 | 100% | Field.prototype.render = function(type, item, locals) { | |
288 | 0 | not covered var templatePath = this.templates[type]; | ||
289 | not covered | |||
290 | not covered // Compile the template synchronously if it hasn't already been compiled | |||
291 | 0 | not covered if (!compiledTemplates[templatePath]) { | ||
292 | not covered | |||
293 | 0 | not covered var file = fs.readFileSync(templatePath, 'utf8'); | ||
294 | not covered | |||
295 | 0 | not covered compiledTemplates[templatePath] = jade.compile(file, { | ||
296 | 0 | not covered pretty: keystone.get('env') !== 'production' | ||
297 | not covered }); | |||
298 | not covered | |||
299 | not covered } | |||
300 | not covered | |||
301 | 0 | not covered return compiledTemplates[templatePath](_.extend(locals || {}, { | ||
302 | not covered |
lib/path.js
93% line coverage go jump to first missed line
81% statement coverage go jump to first missed statement
80% block coverage
47 SLOC
Line | Hits | Statements | Source | Action |
---|---|---|---|---|
1 | 1 | 100% | 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 | |||
9 | 1 | 100% | exports = module.exports = function Path(str) { | |
10 | 63 | 100% | if (!(this instanceof Path)) { | |
11 | 0 | not covered return new Path(str); | ||
12 | not covered } | |||
13 | 63 | 100% | this.original = str; | |
14 | not covered | |||
15 | 63 | 100% | var parts = this.parts = str.split('.'); | |
16 | 63 | 100% | var last = this.last = this.parts[this.parts.length-1]; | |
17 | 63 | 100% | var exceptLast = []; | |
18 | not covered | |||
19 | 93 | 100% | for (var i = 0; i < parts.length-1; i++) { | |
20 | 30 | 100% | exceptLast.push(parts[i]); | |
21 | not covered } | |||
22 | 63 | 100% | this.exceptLast = exceptLast.join('.'); | |
23 | not covered | |||
24 | 63 | 100% | this.addTo = function(obj, val) { | |
25 | 2 | 100% | var o = obj; | |
26 | 8 | 100% | for (var i = 0; i < parts.length - 1; i++) { | |
27 | 6 | 100% | if (!utils.isObject(o[parts[i]])) | |
28 | 4 | 100% | o[parts[i]] = {}; | |
29 | 6 | 100% | o = o[parts[i]]; | |
30 | not covered } | |||
31 | 2 | 100% | o[last] = val; | |
32 | 2 | 100% | return obj; | |
33 | not covered }; | |||
34 | not covered | |||
35 | 63 | 100% | this.get = function(obj) { | |
36 | 14 | 100% | var o = obj; | |
37 | 34 | 100% | for (var i = 0; i < parts.length; i++) { | |
38 | 29 | 100% | if (typeof o !== 'object') return undefined; | |
39 | 20 | 100% | o = o[parts[i]]; | |
40 | not covered } | |||
41 | 5 | 100% | return o; | |
42 | not covered }; | |||
43 | not covered | |||
44 | 63 | 100% | this.prependToLast = function(prepend, titlecase) { | |
45 | 3 | 100% | var rtn = ''; | |
46 | 9 | 100% | for (var i = 0; i < parts.length - 1; i++) { | |
47 | 6 | 100% | rtn += parts[i] + '.'; | |
48 | not covered } | |||
49 | 3 | 100% | return rtn + (prepend || '') + (titlecase ? utils.upcase(last) : last); | |
50 | not covered }; | |||
51 | not covered | |||
52 | 63 | 100% | this.append = function(append) { | |
53 | 73 | 100% | return str + append; | |
54 | not covered }; | |||
55 | not covered | |||
56 | 63 | 100% | this.flatten = function(titlecase) { | |
57 | 2 | 100% | return utils.camelcase(parts.join('_'), titlecase ? true : false); | |
58 | not covered }; | |||
59 | not covered | |||
60 | 63 | 100% | this.flattenplural = function(titlecase) { | |
61 | 0 | not covered return utils.camelcase([].concat(exceptLast).concat(utils.plural(last)).join('_'), titlecase ? true : false); | ||
62 | not covered }; | |||
63 | not covered | |||
64 | 63 | 100% | this.flattensingular = function(titlecase) { | |
65 | 0 | not covered return utils.camelcase([].concat(exceptLast).concat(utils.singular(last)).join('_'), titlecase ? true : false); | ||
66 | not covered }; | |||
67 | not covered | |||
68 | 63 | 100% | return this; | |
69 | not covered |
lib/fieldTypes/boolean.js
85% line coverage go jump to first missed line
77% statement coverage go jump to first missed statement
62% block coverage
20 SLOC
Line | Hits | Statements | Source | Action |
---|---|---|---|---|
1 | not covered /*! | |||
2 | not covered * Module dependencies. | |||
3 | not covered */ | |||
4 | not covered | |||
5 | 1 | 100% | 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) { | |||
14 | 2 | 100% | this._nativeType = Boolean; | |
15 | 2 | 67% | this.indent = (options.indent) ? not covered true: false; | |
16 | 2 | 100% | 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 | |||
23 | 1 | 100% | 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 | |||
34 | 1 | 100% | boolean.prototype.validateInput = function(data, required) { | |
35 | 0 | not covered if (required) { | ||
36 | 0 | not covered return (data[this.path] === true || data[this.path] === 'true') ? true : false; | ||
37 | not covered } else { | |||
38 | 0 | 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 | |||
50 | 1 | 100% | boolean.prototype.updateItem = function(item, data) { | |
51 | 5 | 100% | var value = this.getValueFromData(data); | |
52 | not covered | |||
53 | 5 | 100% | if (value === true || value === 'true') { | |
54 | 4 | 100% | if (!item.get(this.path)) { | |
55 | 4 | 100% | item.set(this.path, true); | |
56 | not covered } | |||
57 | 1 | 100% | } else if (item.get(this.path) !== false) { | |
58 | 1 | 100% | 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 | |||
66 | 1 | 100% | exports = module.exports = boolean; |
lib/fieldTypes/cloudinaryimage.js
12% line coverage go jump to first missed line
7% statement coverage go jump to first missed statement
0% block coverage
176 SLOC
Line | Hits | Statements | Source | Action |
---|---|---|---|---|
1 | not covered /*! | |||
2 | not covered * Module dependencies. | |||
3 | not covered */ | |||
4 | not covered | |||
5 | 1 | 100% | var _ = require('underscore'), | |
6 | 1 | 100% | keystone = require('../../'), | |
7 | 1 | 100% | util = require('util'), | |
8 | 1 | 100% | cloudinary = require('cloudinary'), | |
9 | 1 | 100% | utils = require('keystone-utils'), | |
10 | 1 | 100% | 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 | |||
20 | 0 | not covered this._underscoreMethods = ['format']; | ||
21 | not covered | |||
22 | not covered // TODO: implement filtering, usage disabled for now | |||
23 | 0 | 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) { | |||
27 | 0 | not covered throw new Error( | ||
28 | 0 | not covered 'Invalid Configuration\n\n' + | ||
29 | not covered ); | |||
30 | not covered } | |||
31 | not covered | |||
32 | 0 | not covered cloudinaryimage.super_.call(this, list, path, options); | ||
33 | not covered | |||
34 | not covered // validate cloudinary config | |||
35 | 0 | not covered if (!keystone.get('cloudinary config')) { | ||
36 | 0 | not covered throw new Error( | ||
37 | not covered 'Invalid Configuration\n\n' + | |||
38 | 0 | not covered 'CloudinaryImage fields (' + list.key + '.' + this.path + ') require the "cloudinary config" option to be set.\n\n' + | ||
39 | 0 | 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 | |||
49 | 1 | 100% | 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 | |||
58 | 1 | 100% | cloudinaryimage.prototype.addToSchema = function() { | |
59 | 0 | not covered var field = this, | ||
60 | not covered | |||
61 | 0 | not covered var paths = this.paths = { | ||
62 | 0 | not covered public_id: this._path.append('.public_id'), | ||
63 | 0 | not covered version: this._path.append('.version'), | ||
64 | 0 | not covered signature: this._path.append('.signature'), | ||
65 | 0 | not covered format: this._path.append('.format'), | ||
66 | 0 | not covered resource_type: this._path.append('.resource_type'), | ||
67 | 0 | not covered url: this._path.append('.url'), | ||
68 | 0 | not covered width: this._path.append('.width'), | ||
69 | 0 | not covered height: this._path.append('.height'), | ||
70 | 0 | not covered secure_url: this._path.append('.secure_url'), | ||
71 | not covered // virtuals | |||
72 | 0 | not covered exists: this._path.append('.exists'), | ||
73 | 0 | not covered folder: this._path.append('.folder'), | ||
74 | not covered // form paths | |||
75 | 0 | not covered upload: this._path.append('_upload'), | ||
76 | 0 | not covered action: this._path.append('_action'), | ||
77 | 0 | not covered select: this._path.append('_select') | ||
78 | not covered }; | |||
79 | not covered | |||
80 | 0 | not covered var schemaPaths = this._path.addTo({}, { | ||
81 | not covered | |||
82 | 0 | not covered schema.add(schemaPaths); | ||
83 | not covered | |||
84 | 0 | not covered var exists = function(item) { | ||
85 | 0 | 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 | |||
89 | 0 | not covered schema.virtual(paths.exists).get(function() { | ||
90 | 0 | not covered return schemaMethods.exists.apply(this); | ||
91 | not covered }); | |||
92 | not covered | |||
93 | 0 | not covered var folder = function(item) { | ||
94 | 0 | not covered var folderValue = null; | ||
95 | not covered | |||
96 | 0 | not covered if (keystone.get('cloudinary folders')) { | ||
97 | not covered if (field.options.folder) { | |||
98 | 0 | not covered folderValue = field.options.folder; | ||
99 | not covered } else { | |||
100 | 0 | not covered var folderList = keystone.get('cloudinary prefix') ? [keystone.get('cloudinary prefix')] : []; | ||
101 | 0 | not covered folderList.push(field.list.path); | ||
102 | 0 | not covered folderList.push(field.path); | ||
103 | 0 | not covered folderValue = folderList.join('/'); | ||
104 | not covered } | |||
105 | 0 | 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 | |||
109 | 0 | not covered schema.virtual(paths.folder).get(function() { | ||
110 | 0 | not covered return schemaMethods.folder.apply(this); | ||
111 | not covered }); | |||
112 | not covered | |||
113 | 0 | not covered var src = function(item, options) { | ||
114 | 0 | not covered if (!exists(item)) { | ||
115 | 0 | not covered return ''; | ||
116 | not covered } | |||
117 | 0 | not covered options = ('object' === typeof options) ? options : {}; | ||
118 | not covered | |||
119 | 0 | not covered if (!('fetch_format' in options) && keystone.get('cloudinary webp') !== false) { | ||
120 | 0 | not covered options.fetch_format = "auto"; | ||
121 | not covered } | |||
122 | 0 | not covered if (!('progressive' in options) && keystone.get('cloudinary progressive') !== false) { | ||
123 | 0 | not covered options.progressive = true; | ||
124 | not covered } | |||
125 | 0 | not covered if (!('secure' in options) && keystone.get('cloudinary secure')) { | ||
126 | 0 | not covered options.secure = true; | ||
127 | not covered } | |||
128 | 0 | not covered return cloudinary.url(item.get(paths.public_id) + '.' + item.get(paths.format), options); | ||
129 | not covered | |||
130 | not covered | |||
131 | 0 | not covered var reset = function(item) { | ||
132 | 0 | not covered item.set(field.path, { | ||
133 | not covered }; | |||
134 | not covered | |||
135 | 0 | not covered var addSize = function(options, width, height, other) { | ||
136 | 0 | not covered if (width) options.width = width; | ||
137 | 0 | not covered if (height) options.height = height; | ||
138 | 0 | not covered if ('object' === typeof other) { | ||
139 | 0 | not covered _.extend(options, other); | ||
140 | not covered } | |||
141 | 0 | not covered return options; | ||
142 | not covered }; | |||
143 | not covered | |||
144 | 0 | not covered var schemaMethods = { | ||
145 | 0 | not covered return exists(this); | ||
146 | not covered }, | |||
147 | 0 | not covered return folder(this); | ||
148 | not covered }, | |||
149 | 0 | not covered return src(this, options); | ||
150 | not covered }, | |||
151 | 0 | not covered return exists(this) ? cloudinary.image(this.get(field.path), options) : ''; | ||
152 | not covered }, | |||
153 | 0 | not covered return src(this, addSize({ crop: 'scale' }, width, height, options)); | ||
154 | not covered }, | |||
155 | 0 | not covered return src(this, addSize({ crop: 'fill', gravity: 'faces' }, width, height, options)); | ||
156 | not covered }, | |||
157 | 0 | not covered return src(this, addSize({ crop: 'lfill', gravity: 'faces' }, width, height, options)); | ||
158 | not covered }, | |||
159 | 0 | not covered return src(this, addSize({ crop: 'fit' }, width, height, options)); | ||
160 | not covered }, | |||
161 | 0 | not covered return src(this, addSize({ crop: 'limit' }, width, height, options)); | ||
162 | not covered }, | |||
163 | 0 | not covered return src(this, addSize({ crop: 'pad' }, width, height, options)); | ||
164 | not covered }, | |||
165 | 0 | not covered return src(this, addSize({ crop: 'lpad' }, width, height, options)); | ||
166 | not covered }, | |||
167 | 0 | not covered return src(this, addSize({ crop: 'crop', gravity: 'faces' }, width, height, options)); | ||
168 | not covered }, | |||
169 | 0 | not covered return src(this, addSize({ crop: 'thumb', gravity: 'faces' }, width, height, options)); | ||
170 | not covered }, | |||
171 | 0 | not covered reset(this); | ||
172 | not covered }, | |||
173 | 0 | not covered cloudinary.uploader.destroy(this.get(paths.public_id), function() {}); | ||
174 | 0 | not covered reset(this); | ||
175 | not covered } | |||
176 | not covered | |||
177 | 0 | not covered _.each(schemaMethods, function(fn, key) { | ||
178 | 0 | 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 | |||
182 | 0 | not covered this.apply = function(item, method) { | ||
183 | 0 | not covered return schemaMethods[method].apply(item, Array.prototype.slice.call(arguments, 2)); | ||
184 | not covered }; | |||
185 | not covered | |||
186 | 0 | 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 | |||
196 | 1 | 100% | cloudinaryimage.prototype.format = function(item) { | |
197 | 0 | 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 | |||
207 | 1 | 100% | cloudinaryimage.prototype.isModified = function(item) { | |
208 | 0 | 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 | |||
218 | 1 | 100% | cloudinaryimage.prototype.validateInput = function(data) { // TODO - how should image field input be validated? | |
219 | 0 | 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 | |||
229 | 1 | 100% | cloudinaryimage.prototype.updateItem = function(item, data) { | |
230 | 0 | not covered var paths = this.paths; | ||
231 | not covered | |||
232 | 0 | not covered var setValue = function(key) { | ||
233 | 0 | not covered var index = paths[key].indexOf("."); | ||
234 | 0 | 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 | |||
236 | 0 | not covered if (data[field] && data[field][key] && data[field][key] != item.get(paths[key])) { | ||
237 | 0 | not covered item.set(paths[key], data[field][key] || null); | ||
238 | not covered } | |||
239 | not covered | |||
240 | 0 | 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 | |||
254 | 1 | 100% | cloudinaryimage.prototype.getRequestHandler = function(item, req, paths, callback) { | |
255 | 0 | not covered var field = this; | ||
256 | not covered | |||
257 | 0 | not covered if (utils.isFunction(paths)) { | ||
258 | 0 | not covered callback = paths; | ||
259 | 0 | not covered paths = field.paths; | ||
260 | 0 | not covered } else if (!paths) { | ||
261 | 0 | not covered paths = field.paths; | ||
262 | not covered } | |||
263 | 0 | not covered callback = callback || function() {}; | ||
264 | not covered | |||
265 | 0 | not covered return function() { | ||
266 | 0 | not covered var action = req.body[paths.action]; | ||
267 | not covered | |||
268 | 0 | not covered if (/^(delete|reset)$/.test(action)) { | ||
269 | 0 | not covered field.apply(item, action); | ||
270 | not covered } | |||
271 | 0 | not covered if (req.body && req.body[paths.select]) { | ||
272 | not covered | |||
273 | 0 | not covered cloudinary.api.resource(req.body[paths.select], function(result) { | ||
274 | 0 | not covered callback(result.error); | ||
275 | not covered } else { | |||
276 | 0 | not covered item.set(field.path, result); | ||
277 | 0 | not covered callback(); | ||
278 | not covered } | |||
279 | not covered }); | |||
280 | not covered | |||
281 | 0 | not covered } else if (req.files && req.files[paths.upload] && req.files[paths.upload].size) { | ||
282 | not covered | |||
283 | 0 | not covered var tp = keystone.get('cloudinary prefix') || ''; | ||
284 | not covered | |||
285 | not covered if (tp.length) { | |||
286 | 0 | not covered tp += '_'; | ||
287 | not covered } | |||
288 | 0 | not covered var uploadOptions = { | ||
289 | 0 | not covered tags: [tp + field.list.path + '_' + field.path, tp + field.list.path + '_' + field.path + '_' + item.id] | ||
290 | not covered }; | |||
291 | not covered | |||
292 | 0 | not covered if (item.get(field.paths.folder)) { | ||
293 | 0 | not covered uploadOptions.folder = item.get(field.paths.folder); | ||
294 | not covered } | |||
295 | 0 | not covered if (keystone.get('cloudinary prefix')) { | ||
296 | 0 | not covered uploadOptions.tags.push(keystone.get('cloudinary prefix')); | ||
297 | not covered } | |||
298 | 0 | not covered if (keystone.get('env') !== 'production') { | ||
299 | 0 | not covered uploadOptions.tags.push(tp + 'dev'); | ||
300 | not covered } | |||
301 | 0 | not covered var publicIdValue = item.get(field.options.publicID); | ||
302 | 0 | not covered if (publicIdValue) { | ||
303 | 0 | not covered uploadOptions.public_id = publicIdValue; | ||
304 | not covered } | |||
305 | 0 | not covered if (field.options.autoCleanup && item.get(field.paths.exists)) { | ||
306 | 0 | not covered field.apply(item, 'delete'); | ||
307 | not covered } | |||
308 | 0 | not covered cloudinary.uploader.upload(req.files[paths.upload].path, function(result) { | ||
309 | 0 | not covered callback(result.error); | ||
310 | not covered } else { | |||
311 | 0 | not covered item.set(field.path, result); | ||
312 | 0 | not covered callback(); | ||
313 | not covered } | |||
314 | not covered }, uploadOptions); | |||
315 | not covered | |||
316 | not covered } else { | |||
317 | 0 | 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 | |||
328 | 1 | 100% | cloudinaryimage.prototype.handleRequest = function(item, req, paths, callback) { | |
329 | 0 | 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 | |||
337 | 1 | 100% | exports = module.exports = cloudinaryimage; |
lib/fieldTypes/cloudinaryimages.js
14% line coverage go jump to first missed line
7% statement coverage go jump to first missed statement
0% block coverage
162 SLOC
Line | Hits | Statements | Source | Action |
---|---|---|---|---|
1 | not covered /*! | |||
2 | not covered * Module dependencies. | |||
3 | not covered */ | |||
4 | not covered | |||
5 | 1 | 100% | var _ = require('underscore'), | |
6 | 1 | 100% | keystone = require('../../'), | |
7 | 1 | 100% | util = require('util'), | |
8 | 1 | 100% | cloudinary = require('cloudinary'), | |
9 | 1 | 100% | utils = require('keystone-utils'), | |
10 | 1 | 100% | super_ = require('../field'), | |
11 | 1 | 100% | 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 | |||
21 | 0 | not covered this._underscoreMethods = ['format']; | ||
22 | not covered | |||
23 | not covered // TODO: implement filtering, usage disabled for now | |||
24 | 0 | not covered options.nofilter = true; | ||
25 | not covered // TODO: implement initial form, usage disabled for now | |||
26 | not covered if (options.initial) { | |||
27 | 0 | not covered throw new Error('Invalid Configuration\n\n' + | ||
28 | not covered } | |||
29 | not covered | |||
30 | 0 | not covered cloudinaryimages.super_.call(this, list, path, options); | ||
31 | not covered | |||
32 | not covered // validate cloudinary config | |||
33 | 0 | not covered if (!keystone.get('cloudinary config')) { | ||
34 | not covered throw new Error('Invalid Configuration\n\n' + | |||
35 | 0 | not covered 'CloudinaryImages fields (' + list.key + '.' + this.path + ') require the "cloudinary config" option to be set.\n\n' + | ||
36 | 0 | 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 | |||
45 | 1 | 100% | 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 | |||
54 | 1 | 100% | cloudinaryimages.prototype.addToSchema = function() { | |
55 | 0 | not covered var mongoose = keystone.mongoose; | ||
56 | not covered | |||
57 | 0 | not covered var field = this, | ||
58 | not covered | |||
59 | 0 | not covered var paths = this.paths = { | ||
60 | 0 | not covered upload: this._path.append('_upload'), | ||
61 | 0 | not covered uploads: this._path.append('_uploads'), | ||
62 | 0 | not covered action: this._path.append('_action'), | ||
63 | 0 | not covered order: this._path.append('_order') | ||
64 | not covered }; | |||
65 | not covered | |||
66 | 0 | not covered var ImageSchema = new mongoose.Schema({ | ||
67 | not covered | |||
68 | 0 | not covered var src = function(img, options) { | ||
69 | 0 | not covered if (keystone.get('cloudinary secure')) { | ||
70 | 0 | not covered options = options || {}; | ||
71 | 0 | not covered options.secure = true; | ||
72 | not covered } | |||
73 | 0 | not covered return img.public_id ? cloudinary.url(img.public_id + '.' + img.format, options) : ''; | ||
74 | not covered }; | |||
75 | not covered | |||
76 | 0 | not covered var addSize = function(options, width, height) { | ||
77 | 0 | not covered if (width) options.width = width; | ||
78 | 0 | not covered if (height) options.height = height; | ||
79 | 0 | not covered return options; | ||
80 | not covered }; | |||
81 | not covered | |||
82 | 0 | not covered ImageSchema.method('src', function(options) { | ||
83 | 0 | not covered return src(this, options); | ||
84 | not covered }); | |||
85 | not covered | |||
86 | 0 | not covered ImageSchema.method('scale', function(width, height) { | ||
87 | 0 | not covered return src(this, addSize({ crop: 'scale' }, width, height)); | ||
88 | not covered }); | |||
89 | not covered | |||
90 | 0 | not covered ImageSchema.method('fill', function(width, height) { | ||
91 | 0 | not covered return src(this, addSize({ crop: 'fill', gravity: 'faces' }, width, height)); | ||
92 | not covered }); | |||
93 | not covered | |||
94 | 0 | not covered ImageSchema.method('lfill', function(width, height) { | ||
95 | 0 | not covered return src(this, addSize({ crop: 'lfill', gravity: 'faces' }, width, height)); | ||
96 | not covered }); | |||
97 | not covered | |||
98 | 0 | not covered ImageSchema.method('fit', function(width, height) { | ||
99 | 0 | not covered return src(this, addSize({ crop: 'fit' }, width, height)); | ||
100 | not covered }); | |||
101 | not covered | |||
102 | 0 | not covered ImageSchema.method('limit', function(width, height) { | ||
103 | 0 | not covered return src(this, addSize({ crop: 'limit' }, width, height)); | ||
104 | not covered }); | |||
105 | not covered | |||
106 | 0 | not covered ImageSchema.method('pad', function(width, height) { | ||
107 | 0 | not covered return src(this, addSize({ crop: 'pad' }, width, height)); | ||
108 | not covered }); | |||
109 | not covered | |||
110 | 0 | not covered ImageSchema.method('lpad', function(width, height) { | ||
111 | 0 | not covered return src(this, addSize({ crop: 'lpad' }, width, height)); | ||
112 | not covered }); | |||
113 | not covered | |||
114 | 0 | not covered ImageSchema.method('crop', function(width, height) { | ||
115 | 0 | not covered return src(this, addSize({ crop: 'crop', gravity: 'faces' }, width, height)); | ||
116 | not covered }); | |||
117 | not covered | |||
118 | 0 | not covered ImageSchema.method('thumbnail', function(width, height) { | ||
119 | 0 | not covered return src(this, addSize({ crop: 'thumb', gravity: 'faces' }, width, height)); | ||
120 | not covered }); | |||
121 | not covered | |||
122 | 0 | not covered schema.add(this._path.addTo({}, [ImageSchema])); | ||
123 | not covered | |||
124 | 0 | not covered this.removeImage = function(item, id, method, callback) { | ||
125 | 0 | not covered var images = item.get(field.path); | ||
126 | 0 | not covered if ('number' != typeof id) { | ||
127 | 0 | not covered for (var i = 0; i < images.length; i++) { | ||
128 | 0 | not covered if (images[i].public_id == id) { | ||
129 | 0 | not covered id = i; | ||
130 | not covered break; | |||
131 | 0 | not covered var img = images[id]; | ||
132 | 0 | not covered if (!img) return; | ||
133 | 0 | not covered if (method == 'delete') { | ||
134 | 0 | not covered cloudinary.uploader.destroy(img.public_id, function() {}); | ||
135 | not covered } | |||
136 | 0 | not covered images.splice(id, 1); | ||
137 | 0 | not covered if (callback) { | ||
138 | 0 | not covered item.save(('function' != typeof callback) ? callback : undefined); | ||
139 | not covered } | |||
140 | not covered | |||
141 | 0 | not covered this.underscoreMethod('remove', function(id, callback) { | ||
142 | 0 | not covered field.removeImage(this, id, 'remove', callback); | ||
143 | not covered }); | |||
144 | not covered | |||
145 | 0 | not covered this.underscoreMethod('delete', function(id, callback) { | ||
146 | 0 | not covered field.removeImage(this, id, 'delete', callback); | ||
147 | not covered }); | |||
148 | not covered | |||
149 | 0 | 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 | |||
159 | 1 | 100% | cloudinaryimages.prototype.format = function(item) { | |
160 | 0 | not covered return _.map(item.get(this.path), function(img) { | ||
161 | 0 | 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 | |||
172 | 1 | 100% | cloudinaryimages.prototype.isModified = function(item) { // TODO - how should this be detected? | |
173 | 0 | 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 | |||
183 | 1 | 100% | cloudinaryimages.prototype.validateInput = function(data) { // TODO - how should image field input be validated? | |
184 | 0 | 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 | |||
194 | 1 | 100% | 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 | |||
207 | 1 | 100% | cloudinaryimages.prototype.getRequestHandler = function(item, req, paths, callback) { | |
208 | 0 | not covered var field = this; | ||
209 | not covered | |||
210 | 0 | not covered if (utils.isFunction(paths)) { | ||
211 | 0 | not covered callback = paths; | ||
212 | 0 | not covered paths = field.paths; | ||
213 | 0 | not covered } else if (!paths) { | ||
214 | 0 | not covered paths = field.paths; | ||
215 | not covered } | |||
216 | 0 | not covered callback = callback || function() {}; | ||
217 | not covered | |||
218 | 0 | not covered return function() { | ||
219 | 0 | not covered var images = item.get(field.path), | ||
220 | not covered | |||
221 | 0 | not covered images.sort(function(a, b) { | ||
222 | 0 | not covered return (newOrder.indexOf(a.public_id) > newOrder.indexOf(b.public_id)) ? 1 : -1; | ||
223 | not covered }); | |||
224 | not covered } | |||
225 | 0 | not covered if (req.body && req.body[paths.action]) { | ||
226 | 0 | not covered var actions = req.body[paths.action].split('|'); | ||
227 | not covered | |||
228 | 0 | not covered actions.forEach(function(action) { | ||
229 | 0 | not covered action = action.split(':'); | ||
230 | 0 | not covered var method = action[0], | ||
231 | not covered | |||
232 | 0 | not covered if (!method.match(/^(remove|delete)$/) || !ids) return; | ||
233 | not covered | |||
234 | 0 | not covered ids.split(',').forEach(function(id) { | ||
235 | 0 | not covered field.removeImage(item, id, method); | ||
236 | not covered }); | |||
237 | not covered }); | |||
238 | not covered } | |||
239 | 0 | not covered var uploads = JSON.parse(req.body[paths.uploads]); | ||
240 | not covered | |||
241 | 0 | not covered uploads.forEach(function(file) { | ||
242 | 0 | not covered item.get(field.path).push(file); | ||
243 | not covered }); | |||
244 | not covered } | |||
245 | 0 | not covered if (req.files && req.files[paths.upload]) { | ||
246 | 0 | not covered var files = _.flatten(req.files[paths.upload]); | ||
247 | not covered | |||
248 | 0 | not covered var tp = keystone.get('cloudinary prefix') || ''; | ||
249 | not covered | |||
250 | not covered if (tp.length) | |||
251 | 0 | not covered tp += '_'; | ||
252 | not covered | |||
253 | 0 | not covered var uploadOptions = { | ||
254 | 0 | not covered tags: [tp + field.list.path + '_' + field.path, tp + field.list.path + '_' + field.path + '_' + item.id] | ||
255 | not covered }; | |||
256 | not covered | |||
257 | 0 | not covered if (keystone.get('cloudinary prefix')) | ||
258 | 0 | not covered uploadOptions.tags.push(keystone.get('cloudinary prefix')); | ||
259 | not covered | |||
260 | 0 | not covered if (keystone.get('env') != 'production') | ||
261 | 0 | not covered uploadOptions.tags.push(tp + 'dev'); | ||
262 | not covered | |||
263 | 0 | not covered async.each(files, function(file, next) { | ||
264 | 0 | not covered if (!file.size) return next(); | ||
265 | not covered | |||
266 | 0 | not covered cloudinary.uploader.upload(file.path, function(result) { | ||
267 | not covered if (result.error) { | |||
268 | 0 | not covered return next(result.error); | ||
269 | not covered } else { | |||
270 | 0 | not covered item.get(field.path).push(result); | ||
271 | 0 | not covered return next(); | ||
272 | not covered } | |||
273 | not covered }, uploadOptions); | |||
274 | not covered | |||
275 | not covered }, function(err) { | |||
276 | 0 | not covered return callback(err); | ||
277 | not covered }); | |||
278 | not covered } else { | |||
279 | 0 | 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 | |||
290 | 1 | 100% | cloudinaryimages.prototype.handleRequest = function(item, req, paths, callback) { | |
291 | 0 | 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 | |||
299 | 1 | 100% | exports = module.exports = cloudinaryimages; |
lib/fieldTypes/code.js
10% line coverage go jump to first missed line
12% statement coverage go jump to first missed statement
0% block coverage
39 SLOC
Line | Hits | Statements | Source | Action |
---|---|---|---|---|
1 | not covered /*! | |||
2 | not covered * Module dependencies. | |||
3 | not covered */ | |||
4 | not covered | |||
5 | 1 | 100% | 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 | |||
15 | 0 | not covered this._nativeType = String; | ||
16 | not covered | |||
17 | 0 | not covered this.height = options.height || 180; | ||
18 | 0 | not covered this.lang = options.lang; | ||
19 | 0 | 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) { | |||
23 | 0 | not covered throw new Error('Invalid Configuration\n\n' + | ||
24 | not covered } | |||
25 | not covered | |||
26 | 0 | 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 | |||
34 | 1 | 100% | 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 | |||
44 | 0 | not covered var mime; | ||
45 | not covered | |||
46 | 0 | not covered switch (lang) { | ||
47 | not covered case 'c': | |||
48 | 0 | not covered mime = 'text/x-csrc'; break; | ||
49 | not covered case 'c++': | |||
50 | not covered case 'objetivec': | |||
51 | 0 | not covered mime = 'text/x-c++src'; break; | ||
52 | not covered case 'css': | |||
53 | 0 | not covered mime = 'text/css'; break; | ||
54 | not covered case 'asp': | |||
55 | 0 | not covered mime = 'application/x-aspx'; break; | ||
56 | not covered case 'c#': | |||
57 | 0 | not covered mime = 'text/x-csharp'; break; | ||
58 | not covered case 'vb': | |||
59 | 0 | not covered mime = 'text/x-vb'; break; | ||
60 | not covered case 'xml': | |||
61 | 0 | not covered mime = 'text/xml'; break; | ||
62 | not covered case 'php': | |||
63 | 0 | not covered mime = 'application/x-httpd-php'; break; | ||
64 | not covered case 'html': | |||
65 | 0 | not covered mime = 'text/html'; break; | ||
66 | not covered case 'ini': | |||
67 | 0 | not covered mime = 'text/x-properties'; break; | ||
68 | not covered case 'js': | |||
69 | 0 | not covered mime = 'text/javascript'; break; | ||
70 | not covered case 'java': | |||
71 | 0 | not covered mime = 'text/x-java'; break; | ||
72 | not covered case 'coffee': | |||
73 | 0 | not covered mime = 'text/x-coffeescript'; break; | ||
74 | not covered case 'lisp': | |||
75 | 0 | not covered mime = 'text/x-common-lisp'; break; | ||
76 | not covered case 'perl': | |||
77 | 0 | not covered mime = 'text/x-perl'; break; | ||
78 | not covered case 'python': | |||
79 | 0 | not covered mime = 'text/x-python'; break; | ||
80 | not covered case 'sql': | |||
81 | 0 | not covered mime = 'text/x-sql'; break; | ||
82 | not covered case 'json': | |||
83 | 0 | not covered mime = 'application/json'; break; | ||
84 | not covered case 'less': | |||
85 | 0 | not covered mime = 'text/x-less'; break; | ||
86 | not covered case 'sass': | |||
87 | 0 | not covered mime = 'text/x-sass'; break; | ||
88 | not covered case 'sh': | |||
89 | 0 | not covered mime = 'text/x-sh'; break; | ||
90 | not covered case 'ruby': | |||
91 | 0 | not covered mime = 'text/x-ruby'; break; | ||
92 | not covered case 'jsp': | |||
93 | 0 | not covered mime = 'application/x-jsp'; break; | ||
94 | not covered case 'tpl': | |||
95 | 0 | not covered mime = 'text/x-smarty'; break; | ||
96 | not covered case 'jade': | |||
97 | 0 | not covered mime = 'text/x-jade'; break; | ||
98 | not covered } | |||
99 | not covered | |||
100 | 0 | 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 | |||
109 | 1 | 100% | exports = module.exports = code; |
lib/fieldTypes/color.js
57% line coverage go jump to first missed line
63% statement coverage go jump to first missed statement
0% block coverage
7 SLOC
Line | Hits | Statements | Source | Action |
---|---|---|---|---|
1 | not covered /*! | |||
2 | not covered * Module dependencies. | |||
3 | not covered */ | |||
4 | not covered | |||
5 | 1 | 100% | 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) { | |||
14 | 0 | not covered this._nativeType = String; | ||
15 | 0 | not covered this._underscoreMethods = []; | ||
16 | 0 | 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 | |||
23 | 1 | 100% | util.inherits(color, super_); | |
24 | not covered | |||
25 | not covered | |||
26 | not covered /*! | |||
27 | not covered * Export class | |||
28 | not covered */ | |||
29 | not covered | |||
30 | 1 | 100% | exports = module.exports = color; |
lib/fieldTypes/date.js
60% line coverage go jump to first missed line
48% statement coverage go jump to first missed statement
29% block coverage
43 SLOC
Line | Hits | Statements | Source | Action |
---|---|---|---|---|
1 | not covered /*! | |||
2 | not covered * Module dependencies. | |||
3 | not covered */ | |||
4 | not covered | |||
5 | 1 | 100% | var util = require('util'), | |
6 | 1 | 100% | moment = require('moment'), | |
7 | 1 | 100% | 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) { | |||
16 | 1 | 100% | this._nativeType = Date; | |
17 | 1 | 100% | this._underscoreMethods = ['format', 'moment', 'parse']; | |
18 | not covered | |||
19 | 1 | 75% | this._formatString = (options.format === false) ? not covered false: (options.format || 'Do MMM YYYY'); | |
20 | 1 | 100% | if (this._formatString && 'string' !== typeof this._formatString) { | |
21 | 0 | not covered throw new Error('FieldType.Date: options.format must be a string.'); | ||
22 | not covered } | |||
23 | not covered | |||
24 | 1 | 100% | 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 | |||
31 | 1 | 100% | 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 | |||
40 | 1 | 100% | date.prototype.format = function(item, format) { | |
41 | 2 | 100% | if (format || this._formatString) { | |
42 | 2 | 88% | return item.get(this.path) ? moment(item.get(this.path)).format(format || this._formatString) : not covered '' | |
43 | not covered } else { | |||
44 | 0 | 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 | |||
54 | 1 | 100% | date.prototype.moment = function(item) { | |
55 | 1 | 100% | 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 | |||
65 | 1 | 100% | date.prototype.parse = function(item) { | |
66 | 1 | 100% | var newValue = moment.apply(moment, Array.prototype.slice.call(arguments, 1)); | |
67 | 1 | 86% | item.set(this.path, (newValue && newValue.isValid()) ? newValue.toDate() : not covered null; | |
68 | 1 | 100% | 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 | |||
79 | 1 | 100% | date.prototype.validateInput = function(data, required, item) { | |
80 | 0 | not covered if (!(this.path in data) && item && item.get(this.path)) return true; | ||
81 | not covered | |||
82 | 0 | not covered var newValue = moment(data[this.path]); | ||
83 | not covered | |||
84 | 0 | not covered if (required && (!newValue || !newValue.isValid())) { | ||
85 | 0 | not covered return false; | ||
86 | 0 | not covered } else if (data[this.path] && newValue && !newValue.isValid()) { | ||
87 | 0 | not covered return false; | ||
88 | not covered } else { | |||
89 | 0 | 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 | |||
99 | 1 | 100% | date.prototype.updateItem = function(item, data) { | |
100 | 0 | not covered if (!(this.path in data)) | ||
101 | 0 | not covered return; | ||
102 | not covered | |||
103 | 0 | not covered var newValue = moment(data[this.path]); | ||
104 | not covered | |||
105 | 0 | not covered if (newValue && newValue.isValid()) { | ||
106 | 0 | not covered if (!item.get(this.path) || !newValue.isSame(item.get(this.path))) { | ||
107 | 0 | not covered item.set(this.path, newValue.toDate()); | ||
108 | not covered } | |||
109 | 0 | not covered } else if (item.get(this.path)) { | ||
110 | 0 | 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 | |||
118 | 1 | 100% | exports = module.exports = date; |
lib/fieldTypes/datetime.js
68% line coverage go jump to first missed line
46% statement coverage go jump to first missed statement
35% block coverage
54 SLOC
Line | Hits | Statements | Source | Action |
---|---|---|---|---|
1 | not covered /*! | |||
2 | not covered * Module dependencies. | |||
3 | not covered */ | |||
4 | not covered | |||
5 | 1 | 100% | var util = require('util'), | |
6 | 1 | 100% | moment = require('moment'), | |
7 | 1 | 100% | super_ = require('../field'); | |
8 | not covered | |||
9 | 1 | 100% | 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 | |||
19 | 15 | 100% | this._nativeType = Date; | |
20 | 15 | 100% | this._underscoreMethods = ['format', 'moment', 'parse']; | |
21 | 15 | 100% | this.typeDescription = 'date and time'; | |
22 | not covered | |||
23 | 15 | 75% | this._formatString = (options.format === false) ? not covered false: (options.format || 'Do MMM YYYY hh:mm:ss a'); | |
24 | 15 | 100% | if (this._formatString && 'string' !== typeof this._formatString) { | |
25 | 0 | not covered throw new Error('FieldType.DateTime: options.format must be a string.'); | ||
26 | not covered } | |||
27 | not covered | |||
28 | 15 | 100% | datetime.super_.call(this, list, path, options); | |
29 | not covered | |||
30 | 15 | 100% | this.paths = { | |
31 | 15 | 100% | date: this._path.append('_date'), | |
32 | 15 | 100% | 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 | |||
41 | 1 | 100% | 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 */ | |||
49 | 1 | 100% | datetime.prototype.format = function(item, format) { | |
50 | 0 | not covered if (format || this._formatString) { | ||
51 | 0 | not covered return item.get(this.path) ? moment(item.get(this.path)).format(format || this._formatString) : ''; | ||
52 | not covered } else { | |||
53 | 0 | 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 | |||
63 | 1 | 100% | datetime.prototype.moment = function(item) { | |
64 | 0 | 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 | |||
74 | 1 | 100% | datetime.prototype.parse = function(item) { | |
75 | 0 | not covered var newValue = moment.apply(moment, Array.prototype.slice.call(arguments, 1)); | ||
76 | 0 | not covered item.set(this.path, (newValue && newValue.isValid()) ? newValue.toDate() : null); | ||
77 | 0 | 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 | |||
87 | 1 | 100% | datetime.prototype.getInputFromData = function(data) { | |
88 | 28 | 60% | if (this.paths.date in data && not covered this.paths.time in data { | |
89 | 0 | not covered return (data[this.paths.date] + ' ' + data[this.paths.time]).trim(); | ||
90 | not covered } else { | |||
91 | 28 | 100% | 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 | |||
103 | 1 | 100% | datetime.prototype.validateInput = function(data, required, item) { | |
104 | 14 | 47% | 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 | |||
106 | 14 | 100% | var newValue = moment(this.getInputFromData(data), parseFormats); | |
107 | not covered | |||
108 | 14 | 29% | if (required && not covered !newValue || !newValue.isValid()) { | |
109 | 0 | not covered return false; | ||
110 | 14 | 50% | } else if (this.getInputFromData(data) && not covered newValue&& not covered !newValue.isValid() { | |
111 | 0 | not covered return false; | ||
112 | not covered } else { | |||
113 | 14 | 100% | 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 | |||
123 | 1 | 100% | datetime.prototype.updateItem = function(item, data) { | |
124 | 14 | 78% | if (!(this.path in data || (this.paths.date in data && not covered this.paths.time in data)) | |
125 | 14 | 100% | return; | |
126 | not covered | |||
127 | 0 | not covered var newValue = moment(this.getInputFromData(data), parseFormats); | ||
128 | not covered | |||
129 | 0 | not covered if (newValue && newValue.isValid()) { | ||
130 | 0 | not covered if (!item.get(this.path) || !newValue.isSame(item.get(this.path))) { | ||
131 | 0 | not covered item.set(this.path, newValue.toDate()); | ||
132 | not covered } | |||
133 | 0 | not covered } else if (item.get(this.path)) { | ||
134 | 0 | 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 | |||
142 | 1 | 100% | exports = module.exports = datetime; |
lib/fieldTypes/email.js
37% line coverage go jump to first missed line
20% statement coverage go jump to first missed statement
0% block coverage
32 SLOC
Line | Hits | Statements | Source | Action |
---|---|---|---|---|
1 | not covered /*! | |||
2 | not covered * Module dependencies. | |||
3 | not covered */ | |||
4 | not covered | |||
5 | 1 | 100% | var util = require('util'), | |
6 | 1 | 100% | utils = require('keystone-utils'), | |
7 | 1 | 100% | super_ = require('../field'), | |
8 | 1 | 100% | 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) { | |||
17 | 0 | not covered this._nativeType = String; | ||
18 | 0 | not covered this._underscoreMethods = ['gravatarUrl']; | ||
19 | 0 | not covered this.typeDescription = 'email address'; | ||
20 | 0 | 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 | |||
27 | 1 | 100% | 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 | |||
36 | 1 | 100% | email.prototype.validateInput = function(data, required, item) { if (data[this.path]) { | |
37 | 0 | not covered return utils.isEmail(data[this.path]); | ||
38 | not covered } else { | |||
39 | 0 | 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 | |||
50 | 1 | 100% | email.prototype.updateItem = function(item, data) { | |
51 | 0 | not covered var newValue = data[this.path]; | ||
52 | not covered | |||
53 | 0 | not covered if ('string' === typeof newValue) { | ||
54 | 0 | not covered newValue = newValue.toLowerCase(); | ||
55 | not covered } | |||
56 | 0 | not covered if (this.path in data && data[this.path] !== item.get(this.path)) { | ||
57 | 0 | 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 */ | |||
66 | 1 | 100% | email.prototype.gravatarUrl = function(item, size, defaultImage, rating) { | |
67 | 0 | not covered var value = item.get(this.path); | ||
68 | not covered | |||
69 | 0 | not covered if ('string' !== typeof value) { | ||
70 | 0 | not covered return ''; | ||
71 | not covered } | |||
72 | 0 | not covered return [ | ||
73 | 0 | 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 | |||
76 | 0 | 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 | |||
79 | 0 | not covered '&d=' + (defaultImage ? encodeURIComponent(defaultImage) : 'identicon'), | ||
80 | not covered | |||
81 | not covered // rating, g, pg, r or x | |||
82 | 0 | 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 | |||
90 | 1 | 100% | exports = module.exports = email; |
lib/fieldTypes/embedly.js
20% line coverage go jump to first missed line
12% statement coverage go jump to first missed statement
0% block coverage
94 SLOC
Line | Hits | Statements | Source | Action |
---|---|---|---|---|
1 | not covered /*! | |||
2 | not covered * Module dependencies. | |||
3 | not covered */ | |||
4 | not covered | |||
5 | 1 | 100% | var _ = require('underscore'), | |
6 | 1 | 100% | keystone = require('../../'), | |
7 | 1 | 100% | util = require('util'), | |
8 | 1 | 100% | EmbedlyAPI = require('embedly'), | |
9 | 1 | 100% | 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 | |||
23 | 0 | not covered this._underscoreMethods = ['reset']; | ||
24 | 0 | not covered this.fromPath = options.from; | ||
25 | 0 | not covered this.embedlyOptions = options.options || {}; | ||
26 | not covered | |||
27 | not covered // TODO: implement filtering, usage disabled for now | |||
28 | 0 | not covered options.nofilter = true; | ||
29 | not covered | |||
30 | not covered // check and api key has been set, or bail. | |||
31 | 0 | not covered if (!keystone.get('embedly api key')) { | ||
32 | not covered throw new Error('Invalid Configuration\n\n' + | |||
33 | 0 | not covered 'Embedly fields (' + list.key + '.' + this.path + ') require the "embedly api key" option to be set.\n\n' + | ||
34 | 0 | 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 | |||
38 | 0 | not covered if (!options.from) { | ||
39 | not covered throw new Error('Invalid Configuration\n\n' + | |||
40 | 0 | not covered 'Embedly fields (' + list.key + '.' + path + ') require a fromPath option to be set.\n' + | ||
41 | 0 | 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) { | |||
46 | 0 | not covered throw new Error('Invalid Configuration\n\n' + | ||
47 | not covered } | |||
48 | not covered | |||
49 | 0 | 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 | |||
57 | 1 | 100% | 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 | |||
66 | 1 | 100% | embedly.prototype.addToSchema = function() { | |
67 | 0 | not covered var field = this, | ||
68 | not covered | |||
69 | 0 | not covered this.paths = { | ||
70 | 0 | not covered exists: this._path.append('.exists'), | ||
71 | 0 | not covered type: this._path.append('.type'), | ||
72 | 0 | not covered title: this._path.append('.title'), | ||
73 | 0 | not covered url: this._path.append('.url'), | ||
74 | 0 | not covered width: this._path.append('.width'), | ||
75 | 0 | not covered height: this._path.append('.height'), | ||
76 | 0 | not covered version: this._path.append('.version'), | ||
77 | 0 | not covered description: this._path.append('.description'), | ||
78 | 0 | not covered html: this._path.append('.html'), | ||
79 | 0 | not covered authorName: this._path.append('.authorName'), | ||
80 | 0 | not covered authorUrl: this._path.append('.authorUrl'), | ||
81 | 0 | not covered providerName: this._path.append('.providerName'), | ||
82 | 0 | not covered providerUrl: this._path.append('.providerUrl'), | ||
83 | 0 | not covered thumbnailUrl: this._path.append('.thumbnailUrl'), | ||
84 | 0 | not covered thumbnailWidth: this._path.append('.thumbnailWidth'), | ||
85 | 0 | not covered thumbnailHeight: this._path.append('.thumbnailHeight') | ||
86 | not covered }; | |||
87 | not covered | |||
88 | 0 | not covered schema.nested[this.path] = true; | ||
89 | 0 | not covered schema.add({ | ||
90 | not covered exists: Boolean, | |||
91 | 0 | not covered schema.pre('save', function(next) { | ||
92 | 0 | not covered if (!this.isModified(field.fromPath)) { | ||
93 | 0 | not covered return next(); | ||
94 | not covered } | |||
95 | not covered | |||
96 | 0 | not covered var fromValue = this.get(field.fromPath); | ||
97 | not covered | |||
98 | 0 | not covered if (!fromValue) { | ||
99 | 0 | not covered field.reset(this); | ||
100 | 0 | not covered return next(); | ||
101 | not covered } | |||
102 | 0 | not covered var post = this; | ||
103 | not covered | |||
104 | 0 | not covered new EmbedlyAPI({ key: keystone.get('embedly api key') }, function(err, api) { | ||
105 | 0 | not covered if (err) { | ||
106 | 0 | not covered console.error('Error creating Embedly api:'); | ||
107 | 0 | not covered console.error(err, api); | ||
108 | 0 | not covered field.reset(this); | ||
109 | 0 | not covered return next(); | ||
110 | not covered } | |||
111 | 0 | not covered var opts = _.defaults({ url: fromValue }, field.embedlyOptions); | ||
112 | not covered | |||
113 | 0 | not covered api.oembed(opts, function(err, objs) { | ||
114 | 0 | not covered if (err) { | ||
115 | 0 | not covered console.error('Embedly API Error:'); | ||
116 | 0 | not covered console.error(err, objs); | ||
117 | 0 | not covered field.reset(post); | ||
118 | not covered } else { | |||
119 | 0 | not covered var data = objs[0]; | ||
120 | 0 | not covered if (data && data.type !== 'error') { | ||
121 | 0 | not covered post.set(field.path, { | ||
122 | not covered exists: true, | |||
123 | not covered type: data.type, | |||
124 | 0 | not covered field.reset(post); | ||
125 | not covered } | |||
126 | 0 | not covered return next(); | ||
127 | not covered | |||
128 | not covered }); | |||
129 | not covered | |||
130 | not covered | |||
131 | 0 | 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 | |||
141 | 1 | 100% | embedly.prototype.reset = function(item) { | |
142 | 0 | 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 | |||
152 | 1 | 100% | embedly.prototype.format = function(item) { | |
153 | 0 | 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 | |||
163 | 1 | 100% | embedly.prototype.isModified = function(item) { // Assume that it has changed if the url is different | |
164 | 0 | 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 | |||
174 | 1 | 100% | embedly.prototype.validateInput = function(data) { // TODO: I don't think embedly fields need to be validated... | |
175 | 0 | 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 | |||
185 | 1 | 100% | 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 | |
186 | 0 | 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 | |||
194 | 1 | 100% | exports = module.exports = embedly; |
lib/fieldTypes/html.js
44% line coverage go jump to first missed line
43% statement coverage go jump to first missed statement
0% block coverage
9 SLOC
Line | Hits | Statements | Source | Action |
---|---|---|---|---|
1 | not covered /*! | |||
2 | not covered * Module dependencies. | |||
3 | not covered */ | |||
4 | not covered | |||
5 | 1 | 100% | 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 | |||
15 | 0 | not covered this._nativeType = String; | ||
16 | not covered | |||
17 | not covered // TODO: implement filtering, usage disabled for now | |||
18 | 0 | not covered options.nofilter = true; | ||
19 | not covered | |||
20 | 0 | not covered this.wysiwyg = (options.wysiwyg) ? true : false; | ||
21 | 0 | not covered this.height = options.height || 180; | ||
22 | not covered | |||
23 | 0 | 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 | |||
31 | 1 | 100% | util.inherits(html, super_); | |
32 | not covered | |||
33 | not covered | |||
34 | not covered /*! | |||
35 | not covered * Export class | |||
36 | not covered */ | |||
37 | not covered | |||
38 | 1 | 100% | exports = module.exports = html; |
lib/fieldTypes/key.js
47% line coverage go jump to first missed line
29% statement coverage go jump to first missed statement
0% block coverage
23 SLOC
Line | Hits | Statements | Source | Action |
---|---|---|---|---|
1 | not covered /*! | |||
2 | not covered * Module dependencies. | |||
3 | not covered */ | |||
4 | not covered | |||
5 | 1 | 100% | var util = require('util'), | |
6 | 1 | 100% | utils = require('keystone-utils'), | |
7 | 1 | 100% | 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) { | |||
16 | 0 | not covered this._nativeType = String; | ||
17 | 0 | not covered this.separator = options.separator || '-'; | ||
18 | 0 | 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 | |||
25 | 1 | 100% | 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 | |||
34 | 1 | 100% | key.prototype.generateKey = function(str) { | |
35 | 0 | 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 | |||
45 | 1 | 100% | key.prototype.validateInput = function(data, required, item) { | |
46 | 0 | not covered if (!(this.path in data) && item && item.get(this.path)) return true; | ||
47 | not covered | |||
48 | 0 | not covered var newValue = this.generateKey(data[this.path]); | ||
49 | not covered | |||
50 | 0 | 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 | |||
60 | 1 | 100% | key.prototype.updateItem = function(item, data) { | |
61 | 0 | not covered if (!(this.path in data)) { | ||
62 | 0 | not covered return; | ||
63 | not covered } | |||
64 | 0 | not covered var newValue = this.generateKey(data[this.path]); | ||
65 | not covered | |||
66 | 0 | not covered if (item.get(this.path) !== newValue) { | ||
67 | 0 | 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 | |||
75 | 1 | 100% | exports = module.exports = key; |
lib/fieldTypes/localfile.js
21% line coverage go jump to first missed line
12% statement coverage go jump to first missed statement
0% block coverage
158 SLOC
Line | Hits | Statements | Source | Action |
---|---|---|---|---|
1 | not covered /*! | |||
2 | not covered * Module dependencies. | |||
3 | not covered */ | |||
4 | not covered | |||
5 | 1 | 100% | var fs = require('fs'), | |
6 | 1 | 100% | path = require('path'), | |
7 | 1 | 100% | _ = require('underscore'), | |
8 | 1 | 100% | moment = require('moment'), | |
9 | 1 | 100% | async = require('async'), | |
10 | 1 | 100% | util = require('util'), | |
11 | 1 | 100% | utils = require('keystone-utils'), | |
12 | 1 | 100% | 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) { | |||
21 | 0 | not covered this._underscoreMethods = ['format', 'uploadFile']; | ||
22 | not covered | |||
23 | not covered // event queues | |||
24 | 0 | not covered this._pre = { | ||
25 | not covered | |||
26 | 0 | not covered this._post = { | ||
27 | not covered | |||
28 | not covered // TODO: implement filtering, usage disabled for now | |||
29 | 0 | 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) { | |||
33 | 0 | not covered throw new Error('Invalid Configuration\n\n' + | ||
34 | not covered } | |||
35 | not covered | |||
36 | 0 | not covered localfile.super_.call(this, list, path, options); | ||
37 | not covered | |||
38 | not covered // validate destination dir | |||
39 | 0 | not covered if (!options.dest) { | ||
40 | 0 | not covered throw new Error('Invalid Configuration\n\n' + | ||
41 | not covered } | |||
42 | not covered | |||
43 | not covered // Allow hook into before and after | |||
44 | 0 | not covered if (options.pre && options.pre.move) { | ||
45 | 0 | not covered this._pre.move = this._pre.move.concat(options.pre.move); | ||
46 | not covered } | |||
47 | not covered | |||
48 | 0 | not covered if (options.post && options.post.move) { | ||
49 | 0 | 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 | |||
57 | 1 | 100% | 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 | |||
66 | 1 | 100% | localfile.prototype.pre = function(event, fn) { | |
67 | 0 | not covered if (!this._pre[event]) { | ||
68 | 0 | not covered throw new Error('localfile (' + this.list.key + '.' + this.path + ') error: localfile.pre()\n\n' + | ||
69 | not covered } | |||
70 | 0 | not covered this._pre[event].push(fn); | ||
71 | 0 | 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 | |||
81 | 1 | 100% | localfile.prototype.post = function(event, fn) { | |
82 | 0 | not covered if (!this._post[event]) { | ||
83 | 0 | not covered throw new Error('localfile (' + this.list.key + '.' + this.path + ') error: localfile.post()\n\n' + | ||
84 | not covered } | |||
85 | 0 | not covered this._post[event].push(fn); | ||
86 | 0 | 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 | |||
96 | 1 | 100% | localfile.prototype.addToSchema = function() { | |
97 | 0 | not covered var field = this, | ||
98 | not covered | |||
99 | 0 | not covered var paths = this.paths = { | ||
100 | 0 | not covered filename: this._path.append('.filename'), | ||
101 | 0 | not covered path: this._path.append('.path'), | ||
102 | 0 | not covered size: this._path.append('.size'), | ||
103 | 0 | not covered filetype: this._path.append('.filetype'), | ||
104 | not covered // virtuals | |||
105 | 0 | not covered exists: this._path.append('.exists'), | ||
106 | 0 | not covered upload: this._path.append('_upload'), | ||
107 | 0 | not covered action: this._path.append('_action') | ||
108 | not covered }; | |||
109 | not covered | |||
110 | 0 | not covered var schemaPaths = this._path.addTo({}, { | ||
111 | not covered | |||
112 | 0 | not covered schema.add(schemaPaths); | ||
113 | not covered | |||
114 | 0 | not covered var exists = function(item) { | ||
115 | 0 | not covered var filepath = item.get(paths.path), | ||
116 | not covered | |||
117 | 0 | not covered if (!filepath || !filename) { | ||
118 | 0 | not covered return false; | ||
119 | not covered } | |||
120 | 0 | 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 | |||
124 | 0 | not covered schema.virtual(paths.exists).get(function() { | ||
125 | 0 | not covered return schemaMethods.exists.apply(this); | ||
126 | not covered }); | |||
127 | not covered | |||
128 | 0 | not covered var reset = function(item) { | ||
129 | 0 | not covered item.set(field.path, { | ||
130 | not covered }; | |||
131 | not covered | |||
132 | 0 | not covered var schemaMethods = { | ||
133 | 0 | not covered return exists(this); | ||
134 | not covered }, | |||
135 | 0 | not covered reset(this); | ||
136 | not covered }, | |||
137 | 0 | not covered if (exists(this)) { | ||
138 | 0 | not covered fs.unlinkSync(path.join(this.get(paths.path), this.get(paths.filename))); | ||
139 | not covered } | |||
140 | 0 | not covered reset(this); | ||
141 | not covered } | |||
142 | not covered | |||
143 | 0 | not covered _.each(schemaMethods, function(fn, key) { | ||
144 | 0 | 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 | |||
148 | 0 | not covered this.apply = function(item, method) { | ||
149 | 0 | not covered return schemaMethods[method].apply(item, Array.prototype.slice.call(arguments, 2)); | ||
150 | not covered }; | |||
151 | not covered | |||
152 | 0 | 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 | |||
163 | 1 | 100% | localfile.prototype.format = function(item) { | |
164 | 0 | not covered if (this.hasFormatter()) { | ||
165 | 0 | not covered return this.options.format(item, item[this.path]); | ||
166 | not covered } | |||
167 | 0 | 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 | |||
177 | 1 | 100% | localfile.prototype.hasFormatter = function() { | |
178 | 0 | 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 | |||
188 | 1 | 100% | localfile.prototype.href = function(item) { if (this.options.prefix) { | |
189 | 0 | not covered return this.options.prefix + '/' + item.get(this.paths.filename); | ||
190 | not covered } | |||
191 | 0 | 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 | |||
201 | 1 | 100% | localfile.prototype.isModified = function(item) { | |
202 | 0 | 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 | |||
212 | 1 | 100% | localfile.prototype.validateInput = function(data) { // TODO - how should file field input be validated? | |
213 | 0 | 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 | |||
223 | 1 | 100% | 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 | |||
232 | 1 | 100% | localfile.prototype.uploadFile = function(item, file, update, callback) { | |
233 | 0 | not covered var field = this, | ||
234 | 0 | not covered prefix = field.options.datePrefix ? moment().format(field.options.datePrefix) + '-' : '', | ||
235 | 0 | not covered name = prefix + file.name; | ||
236 | not covered | |||
237 | 0 | not covered if (field.options.allowedTypes && !_.contains(field.options.allowedTypes, file.type)){ | ||
238 | 0 | not covered return callback(new Error('Unsupported File Type: '+file.type)); | ||
239 | not covered } | |||
240 | 0 | not covered if ('function' === typeof update) { | ||
241 | 0 | not covered callback = update; | ||
242 | 0 | not covered update = false; | ||
243 | not covered } | |||
244 | 0 | not covered var doMove = function(callback) { | ||
245 | 0 | not covered if ('function' === typeof field.options.filename) { | ||
246 | 0 | not covered name = field.options.filename(item, name); | ||
247 | not covered } | |||
248 | 0 | not covered fs.rename(file.path, path.join(field.options.dest, name), function(err) { | ||
249 | 0 | not covered if (err) return callback(err); | ||
250 | not covered | |||
251 | 0 | not covered var fileData = { | ||
252 | not covered filename: name, | |||
253 | not covered | |||
254 | 0 | not covered if (update) { | ||
255 | 0 | not covered item.set(field.path, fileData); | ||
256 | not covered } | |||
257 | 0 | not covered callback(null, fileData); | ||
258 | not covered | |||
259 | not covered }; | |||
260 | not covered | |||
261 | 0 | not covered async.eachSeries(this._pre.move, function(fn, next) { | ||
262 | 0 | not covered fn(item, file, next); | ||
263 | not covered }, function(err) { | |||
264 | not covered | |||
265 | 0 | not covered if (err) return callback(err); | ||
266 | not covered | |||
267 | 0 | not covered doMove(function(err, fileData) { | ||
268 | 0 | not covered if (err) return callback(err); | ||
269 | not covered | |||
270 | 0 | not covered async.eachSeries(field._post.move, function(fn, next) { | ||
271 | 0 | not covered fn(item, file, fileData, next); | ||
272 | not covered }, function(err) { | |||
273 | 0 | not covered if (err) return callback(err); | ||
274 | 0 | 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 | |||
291 | 1 | 100% | localfile.prototype.getRequestHandler = function(item, req, paths, callback) { | |
292 | 0 | not covered var field = this; | ||
293 | not covered | |||
294 | 0 | not covered if (utils.isFunction(paths)) { | ||
295 | 0 | not covered callback = paths; | ||
296 | 0 | not covered paths = field.paths; | ||
297 | 0 | not covered } else if (!paths) { | ||
298 | 0 | not covered paths = field.paths; | ||
299 | not covered } | |||
300 | 0 | not covered callback = callback || function() {}; | ||
301 | not covered | |||
302 | 0 | not covered return function() { | ||
303 | 0 | not covered var action = req.body[paths.action]; | ||
304 | not covered | |||
305 | 0 | not covered if (/^(delete|reset)$/.test(action)) { | ||
306 | 0 | not covered field.apply(item, action); | ||
307 | not covered } | |||
308 | 0 | not covered if (req.files && req.files[paths.upload] && req.files[paths.upload].size) { | ||
309 | 0 | not covered return field.uploadFile(item, req.files[paths.upload], true, callback); | ||
310 | not covered } | |||
311 | 0 | 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 | |||
322 | 1 | 100% | localfile.prototype.handleRequest = function(item, req, paths, callback) { | |
323 | 0 | 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 | |||
331 | 1 | 100% | exports = module.exports = localfile; |
lib/fieldTypes/location.js
47% line coverage go jump to first missed line
38% statement coverage go jump to first missed statement
27% block coverage
249 SLOC
Line | Hits | Statements | Source | Action |
---|---|---|---|---|
1 | not covered /*! | |||
2 | not covered * Module dependencies. | |||
3 | not covered */ | |||
4 | not covered | |||
5 | 1 | 100% | var _ = require('underscore'), | |
6 | 1 | 100% | keystone = require('../../'), | |
7 | 1 | 100% | querystring = require('querystring'), | |
8 | 1 | 100% | https = require('https'), | |
9 | 1 | 100% | util = require('util'), | |
10 | 1 | 100% | utils = require('keystone-utils'), | |
11 | 1 | 100% | super_ = require('../field'); | |
12 | not covered | |||
13 | 1 | 100% | 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 | |||
23 | 2 | 100% | this._underscoreMethods = ['format', 'googleLookup', 'kmFrom', 'milesFrom']; | |
24 | not covered | |||
25 | 2 | 75% | this.enableMapsAPI = keystone.get('google api key') ? not covered true: false; | |
26 | not covered | |||
27 | 2 | 100% | if (!options.defaults) { | |
28 | 2 | 100% | options.defaults = {}; | |
29 | not covered } | |||
30 | not covered | |||
31 | not covered if (options.required) { | |||
32 | 1 | 100% | if (Array.isArray(options.required)) { | |
33 | not covered // required can be specified as an array of paths | |||
34 | 1 | 100% | this.requiredPaths = options.required; | |
35 | 0 | not covered } else if ('string' === typeof options.required) { | ||
36 | not covered // or it can be specified as a comma-delimited list | |||
37 | 0 | 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 | |||
40 | 1 | 100% | options.required = true; | |
41 | not covered } | |||
42 | not covered // default this.requiredPaths | |||
43 | 2 | 100% | if (!this.requiredPaths) { | |
44 | 1 | 100% | this.requiredPaths = ['street1', 'suburb']; | |
45 | not covered } | |||
46 | not covered | |||
47 | 2 | 100% | 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 | |||
55 | 1 | 100% | 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 | |||
64 | 1 | 100% | location.prototype.addToSchema = function() { | |
65 | 2 | 100% | var field = this, schema = this.list.schema, options = this.options; | |
66 | not covered | |||
67 | 2 | 100% | var paths = this.paths = { | |
68 | 2 | 100% | number: this._path.append('.number'), | |
69 | 2 | 100% | name: this._path.append('.name'), | |
70 | 2 | 100% | street1: this._path.append('.street1'), | |
71 | 2 | 100% | street2: this._path.append('.street2'), | |
72 | 2 | 100% | suburb: this._path.append('.suburb'), | |
73 | 2 | 100% | state: this._path.append('.state'), | |
74 | 2 | 100% | postcode: this._path.append('.postcode'), | |
75 | 2 | 100% | country: this._path.append('.country'), | |
76 | 2 | 100% | geo: this._path.append('.geo'), | |
77 | 2 | 100% | geo_lat: this._path.append('.geo_lat'), | |
78 | 2 | 100% | geo_lng: this._path.append('.geo_lng'), | |
79 | 2 | 100% | serialised: this._path.append('.serialised'), | |
80 | 2 | 100% | improve: this._path.append('_improve'), | |
81 | 2 | 100% | overwrite: this._path.append('_improve_overwrite') | |
82 | not covered }; | |||
83 | not covered | |||
84 | 2 | 100% | var getFieldDef = function(type, key) { | |
85 | 18 | 100% | var def = { type: type }; | |
86 | not covered if (options.defaults[key]) { | |||
87 | 0 | not covered def.default = options.defaults[key]; | ||
88 | not covered } | |||
89 | 18 | 100% | return def; | |
90 | not covered }; | |||
91 | not covered | |||
92 | 2 | 100% | schema.nested[this.path] = true; | |
93 | 2 | 100% | schema.add({ | |
94 | 2 | 100% | number: getFieldDef(String, 'number'), | |
95 | 2 | 100% | name: getFieldDef(String, 'name'), | |
96 | 2 | 100% | street1: getFieldDef(String, 'street1'), | |
97 | 2 | 100% | street2: getFieldDef(String, 'street2'), | |
98 | 2 | 100% | street3: getFieldDef(String, 'street3'), | |
99 | 2 | 100% | suburb: getFieldDef(String, 'suburb'), | |
100 | 2 | 100% | state: getFieldDef(String, 'state'), | |
101 | 2 | 100% | postcode: getFieldDef(String, 'postcode'), | |
102 | 2 | 100% | country: getFieldDef(String, 'country'), | |
103 | not covered geo: { type: [Number], index: '2dsphere' } | |||
104 | 2 | 100% | }, this.path + '.'); | |
105 | 2 | 100% | schema.virtual(paths.serialised).get(function() { | |
106 | 0 | not covered return _.compact([ | ||
107 | 0 | not covered this.get(paths.number), | ||
108 | 0 | not covered this.get(paths.name), | ||
109 | 0 | not covered this.get(paths.street1), | ||
110 | 0 | not covered this.get(paths.street2), | ||
111 | 0 | not covered this.get(paths.suburb), | ||
112 | 0 | not covered this.get(paths.state), | ||
113 | 0 | not covered this.get(paths.postcode), | ||
114 | 0 | 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 | |||
120 | 2 | 100% | schema.pre('save', function(next) { | |
121 | 0 | not covered var obj = field._path.get(this); | ||
122 | 0 | not covered if (Array.isArray(obj.geo) && (obj.geo.length !== 2 || (obj.geo[0] === null && obj.geo[1] === null))) { | ||
123 | 0 | not covered obj.geo = undefined; | ||
124 | not covered } | |||
125 | 0 | not covered next(); | ||
126 | not covered }); | |||
127 | not covered | |||
128 | 2 | 100% | 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 | |||
143 | 1 | 100% | location.prototype.format = function(item, values, delimiter) { | |
144 | 0 | not covered if (!values) { | ||
145 | 0 | not covered return item.get(this.paths.serialised); | ||
146 | not covered } | |||
147 | 0 | not covered var paths = this.paths; | ||
148 | not covered | |||
149 | 0 | not covered values = values.split(' ').map(function(i) { | ||
150 | 0 | not covered return item.get(paths[i]); | ||
151 | not covered }); | |||
152 | not covered | |||
153 | 0 | 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 | |||
163 | 1 | 100% | location.prototype.isModified = function(item) { | |
164 | not covered return item.isModified(this.paths.number) || | |||
165 | 0 | not covered item.isModified(this.paths.name) || | ||
166 | 0 | not covered item.isModified(this.paths.street1) || | ||
167 | 0 | not covered item.isModified(this.paths.street2) || | ||
168 | 0 | not covered item.isModified(this.paths.suburb) || | ||
169 | 0 | not covered item.isModified(this.paths.state) || | ||
170 | 0 | not covered item.isModified(this.paths.postcode) || | ||
171 | 0 | not covered item.isModified(this.paths.country) || | ||
172 | 0 | 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 | |||
185 | 1 | 100% | location.prototype.validateInput = function(data, required, item) { | |
186 | 6 | 100% | if (!required) { | |
187 | 0 | not covered return true; | ||
188 | not covered } | |||
189 | 6 | 100% | var paths = this.paths, | |
190 | 6 | 100% | nested = this._path.get(data), | |
191 | 6 | 100% | values = nested || data, | |
192 | not covered valid = true; | |||
193 | not covered | |||
194 | 6 | 100% | this.requiredPaths.forEach(function(path) { | |
195 | 12 | 100% | if (nested) { | |
196 | 2 | 75% | if (!(path in values) && not covered item&& not covered item.get(paths[path]) { | |
197 | 0 | not covered return; | ||
198 | not covered } | |||
199 | 2 | 100% | if (!values[path]) { | |
200 | 0 | not covered valid = false; | ||
201 | not covered } | |||
202 | not covered } else { | |||
203 | 10 | 100% | if (!(paths[path] in values) && item && item.get(paths[path])) { | |
204 | 0 | not covered return; | ||
205 | not covered } | |||
206 | 10 | 100% | if (!values[paths[path]]) { | |
207 | 5 | 100% | valid = false; | |
208 | not covered } | |||
209 | not covered | |||
210 | 6 | 100% | 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 | |||
220 | 1 | 100% | location.prototype.updateItem = function(item, data) { | |
221 | 4 | 100% | var paths = this.paths, fieldKeys = ['number', 'name', 'street1', 'street2', 'suburb', 'state', 'postcode', 'country'], geoKeys = ['geo', 'geo_lat', 'geo_lng'], | |
222 | 4 | 100% | valueKeys = fieldKeys.concat(geoKeys), | |
223 | not covered valuePaths = valueKeys, | |||
224 | 4 | 100% | values = this._path.get(data); | |
225 | not covered | |||
226 | 4 | 100% | if (!values) { | |
227 | not covered // Handle flattened values | |||
228 | 3 | 100% | valuePaths = valueKeys.map(function(i) { | |
229 | 33 | 100% | return paths[i]; | |
230 | not covered }); | |||
231 | 3 | 100% | values = _.pick(data, valuePaths); | |
232 | not covered } | |||
233 | 4 | 100% | valuePaths = _.object(valueKeys, valuePaths); | |
234 | not covered | |||
235 | 4 | 100% | var setValue = function(key) { | |
236 | 32 | 100% | if (valuePaths[key] in values && values[valuePaths[key]] !== item.get(paths[key])) { | |
237 | 16 | 100% | item.set(paths[key], values[valuePaths[key]] || null); | |
238 | not covered } | |||
239 | not covered | |||
240 | 4 | 100% | _.each(fieldKeys, setValue); | |
241 | not covered | |||
242 | 4 | 100% | if (valuePaths.geo in values) { | |
243 | not covered | |||
244 | 2 | 100% | var oldGeo = item.get(paths.geo) || [], newGeo = values[valuePaths.geo]; | |
245 | not covered | |||
246 | 2 | 100% | if (!Array.isArray(newGeo) || newGeo.length !== 2) { | |
247 | 0 | not covered newGeo = []; | ||
248 | not covered } | |||
249 | 2 | 67% | if (newGeo[0] !== oldGeo[0] || not covered newGeo[1] !== oldGeo[1] { | |
250 | 2 | 100% | item.set(paths.geo, newGeo); | |
251 | not covered } | |||
252 | 2 | 100% | } else if (valuePaths.geo_lat in values && valuePaths.geo_lng in values) { | |
253 | not covered | |||
254 | 2 | 100% | var lat = utils.number(values[valuePaths.geo_lat]), lng = utils.number(values[valuePaths.geo_lng]); | |
255 | not covered | |||
256 | 2 | 100% | 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 | |||
270 | 1 | 100% | location.prototype.getRequestHandler = function(item, req, paths, callback) { | |
271 | 0 | not covered var field = this; | ||
272 | not covered | |||
273 | 0 | not covered if (utils.isFunction(paths)) { | ||
274 | 0 | not covered callback = paths; | ||
275 | 0 | not covered paths = field.paths; | ||
276 | 0 | not covered } else if (!paths) { | ||
277 | 0 | not covered paths = field.paths; | ||
278 | not covered } | |||
279 | 0 | not covered callback = callback || function() {}; | ||
280 | not covered | |||
281 | 0 | not covered return function() { | ||
282 | 0 | not covered var update = req.body[paths.overwrite] ? 'overwrite' : true; | ||
283 | not covered | |||
284 | 0 | not covered if (req.body && req.body[paths.improve]) { | ||
285 | 0 | not covered field.googleLookup(item, false, update, function() { | ||
286 | 0 | not covered callback(); | ||
287 | not covered }); | |||
288 | not covered } else { | |||
289 | 0 | 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 | |||
300 | 1 | 100% | location.prototype.handleRequest = function(item, req, paths, callback) { | |
301 | 0 | 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 | |||
318 | 0 | not covered var options = { | ||
319 | not covered | |||
320 | 0 | not covered if (arguments.length === 2 && _.isFunction(region)) { | ||
321 | 0 | not covered callback = region; | ||
322 | 0 | not covered region = null; | ||
323 | not covered } | |||
324 | not covered | |||
325 | 0 | not covered if (region) { | ||
326 | 0 | not covered options.region = region; | ||
327 | not covered } | |||
328 | not covered | |||
329 | 0 | not covered var endpoint = 'https://maps.googleapis.com/maps/api/geocode/json?' + querystring.stringify(options); | ||
330 | not covered | |||
331 | 0 | not covered https.get(endpoint, function(res) { | ||
332 | 0 | not covered var data = []; | ||
333 | 0 | not covered res.on('data', function(chunk) { | ||
334 | 0 | not covered data.push(chunk); | ||
335 | not covered }) | |||
336 | not covered .on('end', function() { | |||
337 | 0 | not covered var dataBuff = data.join('').trim(); | ||
338 | 0 | not covered var result; | ||
339 | not covered try { | |||
340 | 0 | not covered result = JSON.parse(dataBuff); | ||
341 | not covered } | |||
342 | not covered catch (exp) { | |||
343 | 0 | not covered result = {'status_code': 500, 'status_text': 'JSON Parse Failed', 'status': 'UNKNOWN_ERROR'}; | ||
344 | not covered } | |||
345 | 0 | not covered callback(null, result); | ||
346 | not covered }); | |||
347 | not covered }) | |||
348 | 0 | 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 | |||
365 | 1 | 100% | location.prototype.googleLookup = function(item, region, update, callback) { | |
366 | 0 | not covered if (_.isFunction(update)) { | ||
367 | 0 | not covered callback = update; | ||
368 | 0 | not covered update = false; | ||
369 | not covered } | |||
370 | 0 | not covered var field = this, | ||
371 | 0 | not covered stored = item.get(this.path), | ||
372 | 0 | not covered address = item.get(this.paths.serialised); | ||
373 | not covered | |||
374 | 0 | not covered if (address.length === 0) { | ||
375 | 0 | not covered return callback({'status_code': 500, 'status_text': 'No address to geocode', 'status': 'NO_ADDRESS'}); | ||
376 | not covered } | |||
377 | 0 | not covered doGoogleGeocodeRequest(address, region || keystone.get('default region'), function(err, geocode){ | ||
378 | 0 | not covered if (err || geocode.status !== 'OK') { | ||
379 | 0 | 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 | |||
384 | 0 | not covered var result = geocode.results[0]; | ||
385 | not covered | |||
386 | not covered // parse the address components into a location object | |||
387 | not covered | |||
388 | 0 | not covered var location = {}; | ||
389 | not covered | |||
390 | 0 | not covered _.each(result.address_components, function(val){ | ||
391 | 0 | not covered if ( _.indexOf(val.types,'street_number') >= 0 ) { | ||
392 | 0 | not covered location.street1 = location.street1 || []; | ||
393 | 0 | not covered location.street1.push(val.long_name); | ||
394 | not covered } | |||
395 | 0 | not covered if ( _.indexOf(val.types,'route') >= 0 ) { | ||
396 | 0 | not covered location.street1 = location.street1 || []; | ||
397 | 0 | 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 | |||
400 | 0 | not covered if ( _.indexOf(val.types,'locality') >= 0 && !location.suburb) { | ||
401 | 0 | not covered location.suburb = val.long_name; | ||
402 | not covered } | |||
403 | 0 | not covered if ( _.indexOf(val.types,'administrative_area_level_1') >= 0 ) { | ||
404 | 0 | not covered location.state = val.short_name; | ||
405 | not covered } | |||
406 | 0 | not covered if ( _.indexOf(val.types,'country') >= 0 ) { | ||
407 | 0 | not covered location.country = val.long_name; | ||
408 | not covered } | |||
409 | 0 | not covered if ( _.indexOf(val.types,'postal_code') >= 0 ) { | ||
410 | 0 | not covered location.postcode = val.short_name; | ||
411 | not covered } | |||
412 | not covered | |||
413 | 0 | not covered if (Array.isArray(location.street1)) { | ||
414 | 0 | not covered location.street1 = location.street1.join(' '); | ||
415 | not covered } | |||
416 | 0 | 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 | |||
423 | 0 | not covered if (update === 'overwrite') { | ||
424 | 0 | not covered item.set(field.path, location); | ||
425 | 0 | not covered } else if (update) { | ||
426 | 0 | not covered _.each(location, function(value, key) { | ||
427 | 0 | not covered if (key === 'geo') { | ||
428 | 0 | not covered return; | ||
429 | not covered } | |||
430 | 0 | not covered if (!stored[key]) { | ||
431 | 0 | not covered item.set(field.paths[key], value); | ||
432 | not covered } | |||
433 | not covered }); | |||
434 | 0 | not covered if (!Array.isArray(stored.geo) || !stored.geo[0] || !stored.geo[1]) { | ||
435 | 0 | not covered item.set(field.paths.geo, location.geo); | ||
436 | not covered } | |||
437 | 0 | 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 | |||
452 | 0 | not covered var dLng = (point2[0] - point1[0]) * Math.PI / 180; | ||
453 | 0 | not covered var dLat = (point2[1] - point1[1]) * Math.PI / 180; | ||
454 | 0 | not covered var lat1 = (point1[1]) * Math.PI / 180; | ||
455 | 0 | not covered var lat2 = (point2[1]) * Math.PI / 180; | ||
456 | not covered | |||
457 | 0 | 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); | ||
458 | 0 | not covered var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a)); | ||
459 | not covered | |||
460 | 0 | 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 | |||
471 | 1 | 100% | location.prototype.kmFrom = function(item, point) { | |
472 | 0 | 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 | |||
482 | 1 | 100% | location.prototype.milesFrom = function(item, point) { | |
483 | 0 | 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 | |||
491 | 1 | 100% | exports = module.exports = location; |
lib/fieldTypes/markdown.js
34% line coverage go jump to first missed line
22% statement coverage go jump to first missed statement
0% block coverage
44 SLOC
Line | Hits | Statements | Source | Action |
---|---|---|---|---|
1 | not covered /*! | |||
2 | not covered * Module dependencies. | |||
3 | not covered */ | |||
4 | not covered | |||
5 | 1 | 100% | var util = require('util'), | |
6 | 1 | 100% | marked = require('marked'), | |
7 | 1 | 100% | 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 | |||
18 | 0 | not covered options.nofilter = true; | ||
19 | not covered | |||
20 | 0 | not covered this.height = options.height || 90; | ||
21 | 0 | 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 | |||
29 | 1 | 100% | 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 | |||
41 | 1 | 100% | markdown.prototype.addToSchema = function() { | |
42 | 0 | not covered var schema = this.list.schema; | ||
43 | not covered | |||
44 | 0 | not covered var paths = this.paths = { | ||
45 | 0 | not covered md: this._path.append('.md'), | ||
46 | 0 | not covered html: this._path.append('.html') | ||
47 | not covered }; | |||
48 | not covered | |||
49 | 0 | not covered var setMarkdown = function(value) { | ||
50 | 0 | not covered if (value === this.get(paths.md)) { | ||
51 | 0 | not covered return value; | ||
52 | not covered } | |||
53 | 0 | not covered if (typeof value === 'string') { | ||
54 | 0 | not covered this.set(paths.html, marked(value)); | ||
55 | 0 | not covered return value; | ||
56 | not covered } else { | |||
57 | 0 | not covered this.set(paths.html, undefined); | ||
58 | 0 | not covered return undefined; | ||
59 | not covered } | |||
60 | not covered | |||
61 | 0 | not covered schema.nested[this.path] = true; | ||
62 | 0 | not covered schema.add({ | ||
63 | not covered html: { type: String }, | |||
64 | 0 | 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 | |||
74 | 1 | 100% | markdown.prototype.format = function(item) { | |
75 | 0 | 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 | |||
87 | 1 | 100% | markdown.prototype.validateInput = function(data, required, item) { | |
88 | 0 | not covered if (!(this.path in data || this.paths.md in data) && item && item.get(this.paths.md)) return true; | ||
89 | not covered | |||
90 | 0 | 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 | |||
100 | 1 | 100% | markdown.prototype.isModified = function(item) { | |
101 | 0 | 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 | |||
113 | 1 | 100% | markdown.prototype.updateItem = function(item, data) { | |
114 | 0 | not covered if (this.path in data) { | ||
115 | 0 | not covered item.set(this.paths.md, data[this.path]); | ||
116 | 0 | not covered } else if (this.paths.md in data) { | ||
117 | 0 | 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 | |||
125 | 1 | 100% | exports = module.exports = markdown; |
lib/fieldTypes/money.js
36% line coverage go jump to first missed line
18% statement coverage go jump to first missed statement
0% block coverage
33 SLOC
Line | Hits | Statements | Source | Action |
---|---|---|---|---|
1 | not covered /*! | |||
2 | not covered * Module dependencies. | |||
3 | not covered */ | |||
4 | not covered | |||
5 | 1 | 100% | var util = require('util'), | |
6 | 1 | 100% | numeral = require('numeral'), | |
7 | 1 | 100% | utils = require('keystone-utils'), | |
8 | 1 | 100% | 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) { | |||
17 | 0 | not covered this._nativeType = Number; | ||
18 | 0 | not covered this._underscoreMethods = ['format']; | ||
19 | 0 | not covered this._formatString = (options.format === false) ? false : (options.format || '$0,0.00'); | ||
20 | 0 | not covered if (this._formatString && 'string' !== typeof this._formatString) { | ||
21 | 0 | not covered throw new Error('FieldType.Money: options.format must be a string.'); | ||
22 | not covered } | |||
23 | 0 | 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 | |||
30 | 1 | 100% | 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 | |||
39 | 1 | 100% | money.prototype.format = function(item, format) { | |
40 | 0 | not covered if (format || this._formatString) { | ||
41 | 0 | not covered return ('number' === typeof item.get(this.path)) ? numeral(item.get(this.path)).format(format || this._formatString) : ''; | ||
42 | not covered } else { | |||
43 | 0 | 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 | |||
55 | 1 | 100% | money.prototype.validateInput = function(data, required, item) { | |
56 | 0 | 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]) { | |||
59 | 0 | not covered var newValue = utils.number(data[this.path]); | ||
60 | 0 | not covered return (!isNaN(newValue)); | ||
61 | not covered } else { | |||
62 | 0 | 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 | |||
72 | 1 | 100% | money.prototype.updateItem = function(item, data) { | |
73 | 0 | not covered if (!(this.path in data)) | ||
74 | 0 | not covered return; | ||
75 | not covered | |||
76 | 0 | not covered var newValue = utils.number(data[this.path]); | ||
77 | not covered | |||
78 | 0 | not covered if (!isNaN(newValue)) { | ||
79 | 0 | not covered if (newValue !== item.get(this.path)) { | ||
80 | 0 | not covered item.set(this.path, newValue); | ||
81 | not covered } | |||
82 | 0 | not covered } else if ('number' === typeof item.get(this.path)) { | ||
83 | 0 | 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 | |||
91 | 1 | 100% | exports = module.exports = money; |
lib/fieldTypes/name.js
23% line coverage go jump to first missed line
12% statement coverage go jump to first missed statement
0% block coverage
63 SLOC
Line | Hits | Statements | Source | Action |
---|---|---|---|---|
1 | not covered /*! | |||
2 | not covered * Module dependencies. | |||
3 | not covered */ | |||
4 | not covered | |||
5 | 1 | 100% | var _ = require('underscore'), | |
6 | 1 | 100% | util = require('util'), | |
7 | 1 | 100% | 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 | |||
17 | 0 | not covered options.nofilter = true; | ||
18 | 0 | 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 | |||
25 | 1 | 100% | 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 | |||
37 | 1 | 100% | name.prototype.addToSchema = function() { | |
38 | 0 | not covered var schema = this.list.schema; | ||
39 | not covered | |||
40 | 0 | not covered var paths = this.paths = { | ||
41 | 0 | not covered first: this._path.append('.first'), | ||
42 | 0 | not covered last: this._path.append('.last'), | ||
43 | 0 | not covered full: this._path.append('.full') | ||
44 | not covered }; | |||
45 | not covered | |||
46 | 0 | not covered schema.nested[this.path] = true; | ||
47 | 0 | not covered schema.add({ | ||
48 | not covered first: String, | |||
49 | 0 | not covered schema.virtual(paths.full).get(function () { | ||
50 | 0 | not covered return _.compact([this.get(paths.first), this.get(paths.last)]).join(' '); | ||
51 | not covered }); | |||
52 | not covered | |||
53 | 0 | not covered schema.virtual(paths.full).set(function(value) { | ||
54 | 0 | not covered if (typeof value !== 'string') { | ||
55 | 0 | not covered this.set(paths.first, undefined); | ||
56 | 0 | not covered this.set(paths.last, undefined); | ||
57 | 0 | not covered return; | ||
58 | not covered } | |||
59 | not covered | |||
60 | 0 | not covered var split = value.split(' '); | ||
61 | 0 | not covered this.set(paths.first, split.shift()); | ||
62 | 0 | not covered this.set(paths.last, split.join(' ') || undefined); | ||
63 | not covered | |||
64 | not covered | |||
65 | 0 | 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 | |||
75 | 1 | 100% | name.prototype.format = function(item) { | |
76 | 0 | 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 | |||
86 | 1 | 100% | name.prototype.validateInput = function(data, required, item) { // Input is valid if none was provided, but the item has data | |
87 | 0 | 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 | |||
89 | 0 | not covered if (!required) return true; | ||
90 | not covered | |||
91 | 0 | not covered if (_.isObject(data[this.path])) { | ||
92 | 0 | not covered return (data[this.path].full || data[this.path].first || data[this.path].last) ? true : false; | ||
93 | not covered } else { | |||
94 | 0 | 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 | |||
104 | 1 | 100% | name.prototype.isModified = function(item) { | |
105 | 0 | 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 | |||
115 | 1 | 100% | name.prototype.updateItem = function(item, data) { | |
116 | 0 | not covered if (!_.isObject(data)) return; | ||
117 | not covered | |||
118 | 0 | not covered var paths = this.paths, | ||
119 | not covered | |||
120 | 0 | not covered if (this.path in data && _.isString(data[this.path])) { | ||
121 | not covered | |||
122 | 0 | not covered item.set(paths.full, data[this.path]); | ||
123 | not covered | |||
124 | 0 | not covered } else if (this.path in data && _.isObject(data[this.path])) { | ||
125 | not covered | |||
126 | 0 | not covered var valueObj = data[this.path]; | ||
127 | not covered | |||
128 | 0 | not covered setValue = function(key) { | ||
129 | 0 | not covered if (key in valueObj && valueObj[key] !== item.get(paths[key])) { | ||
130 | 0 | not covered item.set(paths[key], valueObj[key]); | ||
131 | not covered } | |||
132 | not covered | |||
133 | 0 | not covered setValue = function(key) { | ||
134 | 0 | not covered if (paths[key] in data && data[paths[key]] !== item.get(paths[key])) { | ||
135 | 0 | not covered item.set(paths[key], data[paths[key]]); | ||
136 | not covered } | |||
137 | not covered | |||
138 | 0 | not covered if (setValue) { | ||
139 | 0 | 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 | |||
147 | 1 | 100% | exports = module.exports = name; |
lib/fieldTypes/number.js
36% line coverage go jump to first missed line
18% statement coverage go jump to first missed statement
0% block coverage
33 SLOC
Line | Hits | Statements | Source | Action |
---|---|---|---|---|
1 | not covered /*! | |||
2 | not covered * Module dependencies. | |||
3 | not covered */ | |||
4 | not covered | |||
5 | 1 | 100% | var util = require('util'), | |
6 | 1 | 100% | numeral = require('numeral'), | |
7 | 1 | 100% | utils = require('keystone-utils'), | |
8 | 1 | 100% | 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) { | |||
17 | 0 | not covered this._nativeType = Number; | ||
18 | 0 | not covered this._underscoreMethods = ['format']; | ||
19 | 0 | not covered this._formatString = (options.format === false) ? false : (options.format || '0,0[.][000000000000]'); | ||
20 | 0 | not covered if (this._formatString && 'string' !== typeof this._formatString) { | ||
21 | 0 | not covered throw new Error('FieldType.Number: options.format must be a string.'); | ||
22 | not covered } | |||
23 | 0 | 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 | |||
30 | 1 | 100% | 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 | |||
39 | 1 | 100% | number.prototype.format = function(item, format) { | |
40 | 0 | not covered if (format || this._formatString) { | ||
41 | 0 | not covered return ('number' === typeof item.get(this.path)) ? numeral(item.get(this.path)).format(format || this._formatString) : ''; | ||
42 | not covered } else { | |||
43 | 0 | 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 | |||
55 | 1 | 100% | number.prototype.validateInput = function(data, required, item) { | |
56 | 0 | 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]) { | |||
59 | 0 | not covered var newValue = utils.number(data[this.path]); | ||
60 | 0 | not covered return (!isNaN(newValue)); | ||
61 | not covered } else { | |||
62 | 0 | 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 | |||
72 | 1 | 100% | number.prototype.updateItem = function(item, data) { | |
73 | 0 | not covered if (!(this.path in data)) | ||
74 | 0 | not covered return; | ||
75 | not covered | |||
76 | 0 | not covered var newValue = utils.number(data[this.path]); | ||
77 | not covered | |||
78 | 0 | not covered if (!isNaN(newValue)) { | ||
79 | 0 | not covered if (newValue !== item.get(this.path)) { | ||
80 | 0 | not covered item.set(this.path, newValue); | ||
81 | not covered } | |||
82 | 0 | not covered } else if ('number' === typeof item.get(this.path)) { | ||
83 | 0 | 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 | |||
91 | 1 | 100% | exports = module.exports = number; |
lib/fieldTypes/password.js
23% line coverage go jump to first missed line
15% statement coverage go jump to first missed statement
0% block coverage
59 SLOC
Line | Hits | Statements | Source | Action |
---|---|---|---|---|
1 | not covered /*! | |||
2 | not covered * Module dependencies. | |||
3 | not covered */ | |||
4 | not covered | |||
5 | 1 | 100% | var _ = require('underscore'), | |
6 | 1 | 100% | util = require('util'), | |
7 | 1 | 100% | bcrypt = require('bcrypt-nodejs'), | |
8 | 1 | 100% | 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) { | |||
17 | 0 | not covered this.workFactor = options.workFactor || 10; | ||
18 | 0 | not covered this._nativeType = String; | ||
19 | 0 | not covered this._underscoreMethods = ['format', 'compare']; | ||
20 | 0 | not covered options.nosort = true; // You can't sort on password fields | ||
21 | not covered // TODO: implement filtering, hard-coded as disabled for now | |||
22 | 0 | not covered options.nofilter = true; | ||
23 | 0 | 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 | |||
30 | 1 | 100% | 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 */ | |||
39 | 1 | 100% | password.prototype.addToSchema = function() { | |
40 | 0 | not covered var field = this, | ||
41 | not covered | |||
42 | 0 | not covered this.paths = { | ||
43 | 0 | not covered confirm: this.options.confirmPath || this._path.append('_confirm') | ||
44 | not covered }; | |||
45 | not covered | |||
46 | 0 | not covered schema.path(this.path, _.defaults({ type: String }, this.options)); | ||
47 | not covered | |||
48 | 0 | not covered schema.pre('save', function(next) { | ||
49 | 0 | not covered if (!this.isModified(field.path)) | ||
50 | 0 | not covered return next(); | ||
51 | not covered | |||
52 | 0 | not covered if (!this.get(field.path)) { | ||
53 | 0 | not covered this.set(field.path, undefined); | ||
54 | 0 | not covered return next(); | ||
55 | not covered } | |||
56 | not covered | |||
57 | 0 | not covered var item = this; | ||
58 | not covered | |||
59 | 0 | not covered bcrypt.genSalt(field.workFactor, function(err, salt) { | ||
60 | 0 | not covered if (err) | ||
61 | 0 | not covered return next(err); | ||
62 | not covered | |||
63 | 0 | not covered bcrypt.hash(item.get(field.path), salt, function () {}, function(err, hash) { | ||
64 | 0 | not covered if (err) | ||
65 | 0 | not covered return next(err); | ||
66 | not covered | |||
67 | not covered // override the cleartext password with the hashed one | |||
68 | 0 | not covered item.set(field.path, hash); | ||
69 | 0 | not covered next(); | ||
70 | not covered }); | |||
71 | not covered }); | |||
72 | not covered | |||
73 | not covered | |||
74 | 0 | 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 | |||
88 | 1 | 100% | password.prototype.format = function(item) { | |
89 | 0 | not covered if (!item.get(this.path)) return ''; | ||
90 | 0 | not covered var len = Math.round(Math.random() * 4) + 6; | ||
91 | 0 | not covered var stars = ''; | ||
92 | 0 | not covered for (var i = 0; i < len; i++) stars += '*'; | ||
93 | 0 | 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 | |||
103 | 1 | 100% | password.prototype.compare = function(item, candidate, callback) { | |
104 | 0 | not covered if ('function' !== typeof callback) throw new Error('Password.compare() requires a callback function.'); | ||
105 | 0 | not covered var value = item.get(this.path); | ||
106 | 0 | not covered if (!value) return callback(null, false); | ||
107 | 0 | 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 | |||
121 | 1 | 100% | password.prototype.validateInput = function(data, required, item) { | |
122 | 0 | not covered if (!required) { | ||
123 | 0 | not covered return true; | ||
124 | not covered } | |||
125 | 0 | not covered if (item) { | ||
126 | 0 | not covered return (data[this.path] || item.get(this.path)) ? true : false; | ||
127 | not covered } else { | |||
128 | 0 | 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 | |||
136 | 1 | 100% | exports = module.exports = password; |
lib/fieldTypes/relationship.js
46% line coverage go jump to first missed line
33% statement coverage go jump to first missed statement
17% block coverage
91 SLOC
Line | Hits | Statements | Source | Action |
---|---|---|---|---|
1 | not covered /*! | |||
2 | not covered * Module dependencies. | |||
3 | not covered */ | |||
4 | not covered | |||
5 | 1 | 100% | var _ = require('underscore'), | |
6 | 1 | 100% | keystone = require('../../'), | |
7 | 1 | 100% | util = require('util'), | |
8 | 1 | 100% | utils = require('keystone-utils'), | |
9 | 1 | 100% | 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 | |||
19 | 14 | 67% | this.many = (options.many) ? not covered true: false; | |
20 | 14 | 100% | this.filters = options.filters; | |
21 | 14 | 100% | this._nativeType = keystone.mongoose.Schema.Types.ObjectId; | |
22 | 14 | 100% | this._underscoreMethods = ['format']; | |
23 | not covered | |||
24 | 14 | 100% | 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 | |||
32 | 1 | 100% | 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 | |||
41 | 1 | 100% | relationship.prototype.addToSchema = function() { | |
42 | 14 | 100% | var field = this, schema = this.list.schema; | |
43 | not covered | |||
44 | 14 | 100% | this.paths = { | |
45 | 14 | 100% | refList: this.options.refListPath || this._path.append('RefList') | |
46 | not covered }; | |||
47 | not covered | |||
48 | 14 | 100% | var def = { type: this._nativeType, ref: this.options.ref }; | |
49 | not covered | |||
50 | 14 | 75% | schema.path(this.path, this.many ? not covered [def]: def); | |
51 | not covered | |||
52 | 14 | 100% | schema.virtual(this.paths.refList).get(function () { | |
53 | 0 | not covered return keystone.list(field.options.ref); | ||
54 | not covered }); | |||
55 | not covered | |||
56 | not covered if (this.many) { | |||
57 | 0 | not covered this.underscoreMethod('contains', function(find) { | ||
58 | 0 | not covered var value = this.populated(field.path) || this.get(field.path); | ||
59 | 0 | not covered if ('object' === typeof find) { | ||
60 | 0 | not covered find = find.id; | ||
61 | not covered } | |||
62 | 0 | not covered var result = _.some(value, function(value) { | ||
63 | 0 | not covered return (value + '' === find); | ||
64 | not covered }); | |||
65 | 0 | not covered return result; | ||
66 | not covered }); | |||
67 | not covered } | |||
68 | 14 | 100% | 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 */ | |||
76 | 1 | 100% | relationship.prototype.format = function(item) { | |
77 | 0 | not covered var value = item.get(this.path); | ||
78 | not covered // force the formatted value to be a - unexpected things happen with ObjectIds. | |||
79 | 0 | 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 | |||
89 | 1 | 100% | relationship.prototype.validateInput = function(data, required, item) { | |
90 | 14 | 100% | if (!required) return true; | |
91 | 0 | not covered if (!(this.path in data) && item && ((this.many && item.get(this.path).length) || item.get(this.path))) return true; | ||
92 | not covered | |||
93 | 0 | not covered if ('string' === typeof data[this.path]) { | ||
94 | 0 | not covered return (data[this.path].trim()) ? true : false; | ||
95 | not covered } else { | |||
96 | 0 | 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 | |||
108 | 1 | 100% | relationship.prototype.updateItem = function(item, data) { | |
109 | 14 | 100% | if (!(this.path in data)) { | |
110 | 14 | 100% | return; | |
111 | not covered } | |||
112 | 0 | not covered if (item.populated(this.path)) { | ||
113 | 0 | not covered throw new Error('fieldTypes.relationship.updateItem() Error - You cannot update populated relationships.'); | ||
114 | not covered } | |||
115 | 0 | not covered var arr = item.get(this.path), | ||
116 | 0 | not covered _old = arr.map(function(i) { return String(i); }), | ||
117 | not covered _new = data[this.path]; | |||
118 | not covered | |||
119 | 0 | not covered if (!utils.isArray(_new)) { | ||
120 | 0 | not covered _new = String(_new || '').split(','); | ||
121 | not covered } | |||
122 | 0 | not covered _new = _.compact(_new); | ||
123 | not covered | |||
124 | not covered // remove ids | |||
125 | 0 | not covered _.difference(_old, _new).forEach(function(val) { | ||
126 | 0 | not covered arr.pull(val); | ||
127 | not covered }); | |||
128 | not covered // add new ids | |||
129 | 0 | not covered _.difference(_new, _old).forEach(function(val) { | ||
130 | 0 | not covered arr.push(val); | ||
131 | not covered }); | |||
132 | not covered | |||
133 | not covered } else { | |||
134 | not covered if (data[this.path]) { | |||
135 | 0 | not covered if (data[this.path] !== item.get(this.path)) { | ||
136 | 0 | not covered item.set(this.path, data[this.path]); | ||
137 | not covered } | |||
138 | 0 | not covered } else if (item.get(this.path)) { | ||
139 | 0 | 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 | |||
149 | 1 | 100% | Object.defineProperty(relationship.prototype, 'isValid', { get: function() { | |
150 | 0 | 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 | |||
160 | 1 | 100% | Object.defineProperty(relationship.prototype, 'refList', { get: function() { | |
161 | 0 | 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 | |||
171 | 1 | 100% | Object.defineProperty(relationship.prototype, 'hasFilters', { get: function() { | |
172 | 0 | 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 | |||
182 | 1 | 100% | relationship.prototype.addFilters = function(query, item) { | |
183 | 0 | not covered _.each(this.filters, function(filters, path) { | ||
184 | 0 | not covered if (!utils.isObject(filters)) { | ||
185 | 0 | not covered filters = { equals: filters }; | ||
186 | not covered } | |||
187 | 0 | not covered query.where(path); | ||
188 | 0 | not covered _.each(filters, function(value, method) { | ||
189 | 0 | not covered if ('string' === typeof value && value.substr(0,1) === ':') { | ||
190 | 0 | not covered if (!item) { | ||
191 | 0 | not covered return; | ||
192 | not covered } | |||
193 | 0 | not covered value = item.get(value.substr(1)); | ||
194 | not covered } | |||
195 | 0 | 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 | |||
205 | 1 | 100% | exports = module.exports = relationship; |
lib/fieldTypes/s3file.js
22% line coverage go jump to first missed line
13% statement coverage go jump to first missed statement
0% block coverage
134 SLOC
Line | Hits | Statements | Source | Action |
---|---|---|---|---|
1 | not covered /*! | |||
2 | not covered * Module dependencies. | |||
3 | not covered */ | |||
4 | not covered | |||
5 | 1 | 100% | var _ = require('underscore'), | |
6 | 1 | 100% | moment = require('moment'), | |
7 | 1 | 100% | keystone = require('../../'), | |
8 | 1 | 100% | async = require('async'), | |
9 | 1 | 100% | util = require('util'), | |
10 | 1 | 100% | knox = require('knox'), | |
11 | not covered // s3 = require('s3'), | |||
12 | 1 | 100% | utils = require('keystone-utils'), | |
13 | 1 | 100% | 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 | |||
23 | 0 | not covered this._underscoreMethods = ['format', 'uploadFile']; | ||
24 | not covered | |||
25 | not covered // event queues | |||
26 | 0 | not covered this._pre = { | ||
27 | not covered | |||
28 | not covered // TODO: implement filtering, usage disabled for now | |||
29 | 0 | 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) { | |||
33 | 0 | not covered throw new Error('Invalid Configuration\n\n' + | ||
34 | not covered } | |||
35 | not covered | |||
36 | 0 | not covered s3file.super_.call(this, list, path, options); | ||
37 | not covered | |||
38 | not covered // validate s3 config (has to happen after super_.call) | |||
39 | 0 | not covered if (!this.s3config) { | ||
40 | not covered throw new Error('Invalid Configuration\n\n' + | |||
41 | 0 | not covered 'S3File fields (' + list.key + '.' + path + ') require the "s3 config" option to be set.\n\n' + | ||
42 | 0 | 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 | |||
46 | 0 | not covered if (options.pre && options.pre.upload) { | ||
47 | 0 | 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 | |||
56 | 1 | 100% | 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 | |||
62 | 1 | 100% | Object.defineProperty(s3file.prototype, 's3config', { get: function() { | |
63 | 0 | 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 | |||
73 | 1 | 100% | s3file.prototype.pre = function(event, fn) { | |
74 | 0 | not covered if (!this._pre[event]) { | ||
75 | 0 | not covered throw new Error('S3File (' + this.list.key + '.' + this.path + ') error: s3field.pre()\n\n' + | ||
76 | not covered } | |||
77 | 0 | not covered this._pre[event].push(fn); | ||
78 | 0 | 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 | |||
88 | 1 | 100% | s3file.prototype.addToSchema = function() { | |
89 | 0 | not covered var field = this, | ||
90 | not covered | |||
91 | 0 | not covered var paths = this.paths = { | ||
92 | 0 | not covered filename: this._path.append('.filename'), | ||
93 | 0 | not covered path: this._path.append('.path'), | ||
94 | 0 | not covered size: this._path.append('.size'), | ||
95 | 0 | not covered filetype: this._path.append('.filetype'), | ||
96 | 0 | not covered url: this._path.append('.url'), | ||
97 | not covered // virtuals | |||
98 | 0 | not covered exists: this._path.append('.exists'), | ||
99 | 0 | not covered upload: this._path.append('_upload'), | ||
100 | 0 | not covered action: this._path.append('_action') | ||
101 | not covered }; | |||
102 | not covered | |||
103 | 0 | not covered var schemaPaths = this._path.addTo({}, { | ||
104 | not covered | |||
105 | 0 | not covered schema.add(schemaPaths); | ||
106 | not covered | |||
107 | 0 | not covered var exists = function(item) { | ||
108 | 0 | 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 | |||
112 | 0 | not covered schema.virtual(paths.exists).get(function() { | ||
113 | 0 | not covered return schemaMethods.exists.apply(this); | ||
114 | not covered }); | |||
115 | not covered | |||
116 | 0 | not covered var reset = function(item) { | ||
117 | 0 | not covered item.set(field.path, { | ||
118 | not covered }; | |||
119 | not covered | |||
120 | 0 | not covered var schemaMethods = { | ||
121 | 0 | not covered return exists(this); | ||
122 | not covered }, | |||
123 | 0 | not covered reset(this); | ||
124 | not covered }, | |||
125 | 0 | not covered var client = knox.createClient(field.s3config); | ||
126 | 0 | not covered client.deleteFile(this.get(paths.path) + this.get(paths.filename), function(err, res){ res ? res.resume() : false; }); | ||
127 | not covered } catch(e) {} | |||
128 | 0 | not covered reset(this); | ||
129 | not covered } | |||
130 | not covered | |||
131 | 0 | not covered _.each(schemaMethods, function(fn, key) { | ||
132 | 0 | 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 | |||
136 | 0 | not covered this.apply = function(item, method) { | ||
137 | 0 | not covered return schemaMethods[method].apply(item, Array.prototype.slice.call(arguments, 2)); | ||
138 | not covered }; | |||
139 | not covered | |||
140 | 0 | 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 | |||
150 | 1 | 100% | s3file.prototype.format = function(item) { | |
151 | 0 | 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 | |||
161 | 1 | 100% | s3file.prototype.isModified = function(item) { | |
162 | 0 | 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 | |||
172 | 1 | 100% | s3file.prototype.validateInput = function(data) { // TODO - how should file field input be validated? | |
173 | 0 | 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 | |||
183 | 1 | 100% | 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 | |||
192 | 1 | 100% | s3file.prototype.uploadFile = function(item, file, update, callback) { | |
193 | 0 | not covered var field = this, | ||
194 | 0 | not covered path = field.options.s3path ? field.options.s3path + '/' : '', | ||
195 | 0 | not covered prefix = field.options.datePrefix ? moment().format(field.options.datePrefix) + '-' : '', | ||
196 | 0 | not covered name = prefix + file.name; | ||
197 | not covered | |||
198 | 0 | not covered if (field.options.allowedTypes && !_.contains(field.options.allowedTypes, file.type)){ | ||
199 | 0 | not covered return callback(new Error('Unsupported File Type: '+file.type)); | ||
200 | not covered } | |||
201 | 0 | not covered if ('function' == typeof update) { | ||
202 | 0 | not covered callback = update; | ||
203 | 0 | not covered update = false; | ||
204 | not covered } | |||
205 | 0 | not covered var doUpload = function() { | ||
206 | 0 | not covered knox.createClient(field.s3config).putFile(file.path, path + name, { | ||
207 | 0 | not covered if (res) res.resume(); | ||
208 | 0 | not covered if (err) return callback(err); | ||
209 | not covered | |||
210 | 0 | not covered var protocol = (field.s3config.protocol && field.s3config.protocol + ':') || '', | ||
211 | not covered | |||
212 | 0 | not covered var fileData = { | ||
213 | not covered filename: name, | |||
214 | not covered | |||
215 | 0 | not covered if (update) { | ||
216 | 0 | not covered item.set(field.path, fileData); | ||
217 | not covered } | |||
218 | 0 | not covered callback(null, fileData); | ||
219 | not covered | |||
220 | not covered }; | |||
221 | not covered | |||
222 | 0 | not covered async.eachSeries(this._pre.upload, function(fn, next) { | ||
223 | 0 | not covered fn(item, file, next); | ||
224 | not covered }, function(err) { | |||
225 | 0 | not covered if (err) return callback(err); | ||
226 | 0 | 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 | |||
241 | 1 | 100% | s3file.prototype.getRequestHandler = function(item, req, paths, callback) { | |
242 | 0 | not covered var field = this; | ||
243 | not covered | |||
244 | 0 | not covered if (utils.isFunction(paths)) { | ||
245 | 0 | not covered callback = paths; | ||
246 | 0 | not covered paths = field.paths; | ||
247 | 0 | not covered } else if (!paths) { | ||
248 | 0 | not covered paths = field.paths; | ||
249 | not covered } | |||
250 | 0 | not covered callback = callback || function() {}; | ||
251 | not covered | |||
252 | 0 | not covered return function() { | ||
253 | 0 | not covered var action = req.body[paths.action]; | ||
254 | not covered | |||
255 | 0 | not covered if (/^(delete|reset)$/.test(action)) | ||
256 | 0 | not covered field.apply(item, action); | ||
257 | not covered } | |||
258 | 0 | not covered if (req.files && req.files[paths.upload] && req.files[paths.upload].size) { | ||
259 | 0 | not covered return field.uploadFile(item, req.files[paths.upload], true, callback); | ||
260 | not covered } | |||
261 | 0 | 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 | |||
272 | 1 | 100% | s3file.prototype.handleRequest = function(item, req, paths, callback) { | |
273 | 0 | 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 | |||
281 | 1 | 100% | exports = module.exports = s3file; |
lib/fieldTypes/select.js
22% line coverage go jump to first missed line
12% statement coverage go jump to first missed statement
0% block coverage
71 SLOC
Line | Hits | Statements | Source | Action |
---|---|---|---|---|
1 | not covered /*! | |||
2 | not covered * Module dependencies. | |||
3 | not covered */ | |||
4 | not covered | |||
5 | 1 | 100% | var _ = require('underscore'), | |
6 | 1 | 100% | util = require('util'), | |
7 | 1 | 100% | utils = require('keystone-utils'), | |
8 | 1 | 100% | 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 | |||
18 | 0 | not covered this._nativeType = (options.numeric) ? Number : String; | ||
19 | 0 | not covered this._underscoreMethods = ['format']; | ||
20 | not covered | |||
21 | 0 | not covered this.ui = options.ui || 'select'; | ||
22 | not covered | |||
23 | 0 | not covered if (typeof options.options === 'string') { | ||
24 | 0 | not covered options.options = options.options.split(','); | ||
25 | not covered } | |||
26 | not covered | |||
27 | 0 | not covered if (!Array.isArray(options.options)) { | ||
28 | 0 | not covered throw new Error('Select fields require an options array.'); | ||
29 | not covered } | |||
30 | not covered | |||
31 | 0 | not covered this.ops = options.options.map(function(i) { | ||
32 | 0 | not covered var op = _.isString(i) ? { value: i.trim(), label: utils.keyToLabel(i) } : i; | ||
33 | 0 | not covered if (!_.isObject(op)) { | ||
34 | 0 | not covered op = { label: ''+i, value: ''+i }; | ||
35 | not covered } | |||
36 | 0 | not covered if (options.numeric && !_.isNumber(op.value)) { | ||
37 | 0 | not covered op.value = Number(op.value); | ||
38 | not covered } | |||
39 | 0 | not covered return op; | ||
40 | not covered }); | |||
41 | not covered | |||
42 | not covered // undefined options.emptyOption defaults to true | |||
43 | 0 | not covered if (options.emptyOption === undefined) { | ||
44 | 0 | not covered options.emptyOption = true; | ||
45 | not covered } | |||
46 | not covered | |||
47 | not covered // ensure this.emptyOption is a boolean | |||
48 | 0 | not covered this.emptyOption = options.emptyOption ? true : false; | ||
49 | not covered | |||
50 | not covered // cached maps for options, labels and values | |||
51 | 0 | not covered this.map = utils.optionsMap(this.ops); | ||
52 | 0 | not covered this.labels = utils.optionsMap(this.ops, 'label'); | ||
53 | 0 | not covered this.values = _.pluck(this.ops, 'value'); | ||
54 | not covered | |||
55 | 0 | 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 | |||
62 | 1 | 100% | 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 | |||
74 | 1 | 100% | select.prototype.addToSchema = function() { | |
75 | 0 | not covered var field = this, | ||
76 | not covered | |||
77 | 0 | not covered this.paths = { | ||
78 | 0 | not covered data: this.options.dataPath || this._path.append('Data'), | ||
79 | 0 | not covered label: this.options.labelPath || this._path.append('Label'), | ||
80 | 0 | not covered options: this.options.optionsPath || this._path.append('Options'), | ||
81 | 0 | not covered map: this.options.optionsMapPath || this._path.append('OptionsMap') | ||
82 | not covered }; | |||
83 | not covered | |||
84 | 0 | not covered schema.path(this.path, _.defaults({ | ||
85 | 0 | not covered return (val === '') ? undefined : val; | ||
86 | not covered } | |||
87 | not covered | |||
88 | 0 | not covered schema.virtual(this.paths.data).get(function () { | ||
89 | 0 | not covered return field.map[this.get(field.path)]; | ||
90 | not covered }); | |||
91 | not covered | |||
92 | 0 | not covered schema.virtual(this.paths.label).get(function () { | ||
93 | 0 | not covered return field.labels[this.get(field.path)]; | ||
94 | not covered }); | |||
95 | not covered | |||
96 | 0 | not covered schema.virtual(this.paths.options).get(function() { | ||
97 | 0 | not covered return field.ops; | ||
98 | not covered }); | |||
99 | not covered | |||
100 | 0 | not covered schema.virtual(this.paths.map).get(function() { | ||
101 | 0 | not covered return field.map; | ||
102 | not covered }); | |||
103 | not covered | |||
104 | 0 | not covered this.underscoreMethod('pluck', function(property, d) { | ||
105 | 0 | not covered var option = this.get(field.paths.data); | ||
106 | 0 | not covered return (option) ? option[property] : d; | ||
107 | not covered }); | |||
108 | not covered | |||
109 | 0 | 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 | |||
118 | 1 | 100% | select.prototype.cloneOps = function() { | |
119 | 0 | 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 | |||
129 | 1 | 100% | select.prototype.cloneMap = function() { | |
130 | 0 | 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 | |||
140 | 1 | 100% | select.prototype.validateInput = function(data, required, item) { if (data[this.path]) { | |
141 | 0 | not covered return (data[this.path] in this.map) ? true : false; | ||
142 | not covered } else { | |||
143 | 0 | 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 | |||
153 | 1 | 100% | select.prototype.format = function(item) { | |
154 | 0 | 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 | |||
162 | 1 | 100% | exports = module.exports = select; |
lib/fieldTypes/text.js
90% line coverage go jump to first missed line
82% statement coverage go jump to first missed statement
50% block coverage
11 SLOC
Line | Hits | Statements | Source | Action |
---|---|---|---|---|
1 | not covered /*! | |||
2 | not covered * Module dependencies. | |||
3 | not covered */ | |||
4 | not covered | |||
5 | 1 | 100% | var util = require('util'), | |
6 | 1 | 100% | utils = require('keystone-utils'), | |
7 | 1 | 100% | 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) { | |||
16 | 16 | 100% | this._nativeType = String; | |
17 | 16 | 100% | this._underscoreMethods = ['crop']; | |
18 | 16 | 100% | 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 | |||
25 | 1 | 100% | 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 | |||
34 | 1 | 100% | text.prototype.crop = function(item, length, append, preserveWords) { | |
35 | 0 | 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 | |||
43 | 1 | 100% | exports = module.exports = text; |
lib/fieldTypes/textarea.js
60% line coverage go jump to first missed line
50% statement coverage go jump to first missed statement
0% block coverage
15 SLOC
Line | Hits | Statements | Source | Action |
---|---|---|---|---|
1 | not covered /*! | |||
2 | not covered * Module dependencies. | |||
3 | not covered */ | |||
4 | not covered | |||
5 | 1 | 100% | var util = require('util'), | |
6 | 1 | 100% | utils = require('keystone-utils'), | |
7 | 1 | 100% | 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) { | |||
16 | 0 | not covered this._nativeType = String; | ||
17 | 0 | not covered this._underscoreMethods = ['format', 'crop']; | ||
18 | 0 | not covered this.height = options.height || 90; | ||
19 | 0 | 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 | |||
26 | 1 | 100% | 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 | |||
35 | 1 | 100% | textarea.prototype.format = function(item) { | |
36 | 0 | 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 | |||
46 | 1 | 100% | textarea.prototype.crop = function(item, length, append, preserveWords) { | |
47 | 0 | 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 | |||
55 | 1 | 100% | exports = module.exports = textarea; |
lib/fieldTypes/url.js
60% line coverage go jump to first missed line
52% statement coverage go jump to first missed statement
0% block coverage
10 SLOC
Line | Hits | Statements | Source | Action |
---|---|---|---|---|
1 | not covered /*! | |||
2 | not covered * Module dependencies. | |||
3 | not covered */ | |||
4 | not covered | |||
5 | 1 | 100% | 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) { | |||
14 | 0 | not covered this._nativeType = String; | ||
15 | 0 | not covered this._underscoreMethods = ['format']; | ||
16 | 0 | 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 | |||
23 | 1 | 100% | 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 | |||
34 | 1 | 100% | url.prototype.format = function(item) { | |
35 | 0 | 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 | |||
46 | 1 | 100% | exports = module.exports = url; |
lib/fieldTypes/localfiles.js
18% line coverage go jump to first missed line
10% statement coverage go jump to first missed statement
0% block coverage
172 SLOC
Line | Hits | Statements | Source | Action |
---|---|---|---|---|
1 | not covered /*! | |||
2 | not covered * Module dependencies. | |||
3 | not covered */ | |||
4 | not covered | |||
5 | 1 | 100% | var fs = require('fs'), | |
6 | 1 | 100% | path = require('path'), | |
7 | 1 | 100% | _ = require('underscore'), | |
8 | 1 | 100% | moment = require('moment'), | |
9 | 1 | 100% | keystone = require('../../'), | |
10 | 1 | 100% | async = require('async'), | |
11 | 1 | 100% | util = require('util'), | |
12 | 1 | 100% | utils = require('keystone-utils'), | |
13 | 1 | 100% | super_ = require('../field'), | |
14 | 1 | 100% | 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) { | |||
23 | 0 | not covered this._underscoreMethods = ['format', 'uploadFiles']; | ||
24 | not covered | |||
25 | not covered // event queues | |||
26 | 0 | not covered this._pre = { | ||
27 | not covered | |||
28 | 0 | not covered this._post = { | ||
29 | not covered | |||
30 | not covered // TODO: implement filtering, usage disabled for now | |||
31 | 0 | 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) { | |||
35 | 0 | not covered throw new Error('Invalid Configuration\n\n' + | ||
36 | not covered } | |||
37 | not covered | |||
38 | 0 | not covered localfiles.super_.call(this, list, path, options); | ||
39 | not covered | |||
40 | not covered // validate destination dir | |||
41 | 0 | not covered if (!options.dest) { | ||
42 | 0 | not covered throw new Error('Invalid Configuration\n\n' + | ||
43 | not covered } | |||
44 | not covered | |||
45 | not covered // Allow hook into before and after | |||
46 | 0 | not covered if (options.pre && options.pre.move) { | ||
47 | 0 | not covered this._pre.move = this._pre.move.concat(options.pre.move); | ||
48 | not covered } | |||
49 | not covered | |||
50 | 0 | not covered if (options.post && options.post.move) { | ||
51 | 0 | 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 | |||
59 | 1 | 100% | 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 | |||
68 | 1 | 100% | localfiles.prototype.pre = function(event, fn) { | |
69 | 0 | not covered if (!this._pre[event]) { | ||
70 | 0 | not covered throw new Error('localfiles (' + this.list.key + '.' + this.path + ') error: localfiles.pre()\n\n' + | ||
71 | not covered } | |||
72 | 0 | not covered this._pre[event].push(fn); | ||
73 | 0 | 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 | |||
83 | 1 | 100% | localfiles.prototype.post = function(event, fn) { | |
84 | 0 | not covered if (!this._post[event]) { | ||
85 | 0 | not covered throw new Error('localfiles (' + this.list.key + '.' + this.path + ') error: localfiles.post()\n\n' + | ||
86 | not covered } | |||
87 | 0 | not covered this._post[event].push(fn); | ||
88 | 0 | 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 | |||
98 | 1 | 100% | localfiles.prototype.addToSchema = function() { | |
99 | 0 | not covered var field = this, | ||
100 | 0 | not covered var mongoose = keystone.mongoose; | ||
101 | not covered | |||
102 | 0 | not covered var paths = this.paths = { | ||
103 | 0 | not covered filename: this._path.append('.filename'), | ||
104 | 0 | not covered path: this._path.append('.path'), | ||
105 | 0 | not covered size: this._path.append('.size'), | ||
106 | 0 | not covered filetype: this._path.append('.filetype'), | ||
107 | not covered // virtuals | |||
108 | 0 | not covered exists: this._path.append('.exists'), | ||
109 | 0 | not covered upload: this._path.append('_upload'), | ||
110 | 0 | not covered action: this._path.append('_action'), | ||
111 | 0 | not covered order: this._path.append('_order'), | ||
112 | not covered }; | |||
113 | not covered | |||
114 | 0 | 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 | |||
123 | 0 | not covered schema.add(this._path.addTo({}, [schemaPaths])); | ||
124 | not covered | |||
125 | 0 | not covered var exists = function(item) { | ||
126 | 0 | not covered var filepaths = item.get(paths.path), | ||
127 | not covered | |||
128 | 0 | not covered if (!filepaths || !filename) { | ||
129 | 0 | not covered return false; | ||
130 | not covered } | |||
131 | 0 | 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 | |||
135 | 0 | not covered schema.virtual(paths.exists).get(function() { | ||
136 | 0 | not covered return schemaMethods.exists.apply(this); | ||
137 | not covered }); | |||
138 | not covered | |||
139 | 0 | not covered var reset = function(item) { | ||
140 | 0 | not covered item.set(field.path, { | ||
141 | not covered }; | |||
142 | not covered | |||
143 | 0 | not covered var schemaMethods = { | ||
144 | 0 | not covered return exists(this); | ||
145 | not covered }, | |||
146 | 0 | not covered reset(this); | ||
147 | not covered }, | |||
148 | 0 | not covered if (exists(this)) { | ||
149 | 0 | not covered fs.unlinkSync(path.join(this.get(paths.path), this.get(paths.filename))); | ||
150 | not covered } | |||
151 | 0 | not covered reset(this); | ||
152 | not covered } | |||
153 | not covered | |||
154 | 0 | not covered _.each(schemaMethods, function(fn, key) { | ||
155 | 0 | 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 | |||
159 | 0 | not covered this.apply = function(item, method) { | ||
160 | 0 | not covered return schemaMethods[method].apply(item, Array.prototype.slice.call(arguments, 2)); | ||
161 | not covered }; | |||
162 | not covered | |||
163 | 0 | 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 | |||
173 | 1 | 100% | localfiles.prototype.format = function(item) { | |
174 | 0 | 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 | |||
184 | 1 | 100% | localfiles.prototype.isModified = function(item) { | |
185 | 0 | 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 | |||
195 | 1 | 100% | localfiles.prototype.validateInput = function(data) { // TODO - how should file field input be validated? | |
196 | 0 | 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 | |||
206 | 1 | 100% | 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 | |||
215 | 1 | 100% | localfiles.prototype.uploadFiles = function(item, files, update, callback) { | |
216 | 0 | not covered var field = this; | ||
217 | 0 | not covered var fileDatas = []; | ||
218 | not covered | |||
219 | 0 | not covered _.each(files, function(file){ | ||
220 | 0 | not covered var prefix = field.options.datePrefix ? moment().format(field.options.datePrefix) + '-' : '', | ||
221 | not covered | |||
222 | 0 | not covered if (field.options.allowedTypes && !_.contains(field.options.allowedTypes, file.type)){ | ||
223 | 0 | not covered return callback(new Error('Unsupported File Type: '+file.type)); | ||
224 | not covered } | |||
225 | not covered | |||
226 | 0 | not covered if ('function' === typeof update) { | ||
227 | 0 | not covered callback = update; | ||
228 | 0 | not covered update = false; | ||
229 | not covered } | |||
230 | 0 | not covered var doMove = function(callback) { | ||
231 | 0 | not covered if ('function' === typeof field.options.filename) { | ||
232 | 0 | not covered name = field.options.filename(item, name); | ||
233 | not covered } | |||
234 | 0 | not covered fs.rename(file.path, path.join(field.options.dest, name), function(err) { | ||
235 | 0 | not covered if (err) return callback(err); | ||
236 | not covered | |||
237 | 0 | not covered var fileData = { | ||
238 | not covered filename: name, | |||
239 | not covered | |||
240 | 0 | not covered if (update) { | ||
241 | 0 | not covered item.set(field.path, fileData); | ||
242 | not covered } else { | |||
243 | 0 | not covered item.get(field.path).push(fileData); | ||
244 | not covered } | |||
245 | 0 | not covered fileDatas.push(fileData); | ||
246 | 0 | not covered callback(null, fileData); | ||
247 | not covered }); | |||
248 | not covered | |||
249 | not covered | |||
250 | 0 | not covered async.eachSeries(this._pre.move, function(fn, next) { | ||
251 | 0 | not covered fn(item, file, next); | ||
252 | not covered }, function(err) { | |||
253 | 0 | not covered if (err) return callback(err); | ||
254 | not covered | |||
255 | 0 | not covered doMove(function(err, fileData) { | ||
256 | 0 | not covered if (err) return callback(err); | ||
257 | not covered | |||
258 | 0 | not covered async.eachSeries(field._post.move, function(fn, next) { | ||
259 | 0 | not covered fn(item, file, fileData, next); | ||
260 | not covered }, function(err) { | |||
261 | 0 | not covered if (err) return callback(err); | ||
262 | 0 | not covered if (fileDatas.length === files.length) { | ||
263 | 0 | 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 | |||
282 | 1 | 100% | localfiles.prototype.getRequestHandler = function(item, req, paths, callback) { | |
283 | 0 | not covered var field = this; | ||
284 | not covered | |||
285 | 0 | not covered if (utils.isFunction(paths)) { | ||
286 | 0 | not covered callback = paths; | ||
287 | 0 | not covered paths = field.paths; | ||
288 | 0 | not covered } else if (!paths) { | ||
289 | 0 | not covered paths = field.paths; | ||
290 | not covered } | |||
291 | 0 | not covered callback = callback || function() {}; | ||
292 | not covered | |||
293 | 0 | not covered return function() { | ||
294 | 0 | not covered var files = item.get(field.path), | ||
295 | not covered | |||
296 | 0 | not covered files.sort(function(a, b) { | ||
297 | 0 | not covered return (newOrder.indexOf(a._id.toString()) > newOrder.indexOf(b._id.toString())) ? 1 : -1; | ||
298 | not covered }); | |||
299 | not covered } | |||
300 | 0 | not covered if (req.body && req.body[paths.action]) { | ||
301 | 0 | not covered var actions = req.body[paths.action].split('|'); | ||
302 | not covered | |||
303 | 0 | not covered actions.forEach(function(action) { | ||
304 | 0 | not covered action = action.split(':'); | ||
305 | not covered | |||
306 | 0 | not covered var method = action[0], | ||
307 | not covered | |||
308 | 0 | not covered if (!(/^(delete|reset)$/.test(method)) || !ids) return; | ||
309 | not covered | |||
310 | 0 | not covered ids.split(',').forEach(function(id) { | ||
311 | 0 | not covered field.apply(item, action); | ||
312 | not covered }); | |||
313 | not covered | |||
314 | not covered }); | |||
315 | not covered } | |||
316 | 0 | not covered if (req.files && req.files[paths.upload] && (req.files[paths.upload].length > 0)) { | ||
317 | 0 | not covered return field.uploadFiles(item, req.files[paths.upload], false, callback); | ||
318 | not covered } | |||
319 | 0 | 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 | |||
330 | 1 | 100% | localfiles.prototype.handleRequest = function(item, req, paths, callback) { | |
331 | 0 | 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 | |||
339 | 1 | 100% | exports = module.exports = localfiles; |
lib/updateHandler.js
51% line coverage go jump to first missed line
42% statement coverage go jump to first missed statement
24% block coverage
126 SLOC
Line | Hits | Statements | Source | Action |
---|---|---|---|---|
1 | 1 | 100% | 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 | |||
12 | 9 | 100% | if (!(this instanceof UpdateHandler)) | |
13 | 0 | not covered return new UpdateHandler(list, item); | ||
14 | not covered | |||
15 | 9 | 100% | this.list = list; | |
16 | 9 | 100% | this.item = item; | |
17 | 9 | 100% | this.req = req; | |
18 | 9 | 100% | this.res = res; | |
19 | 9 | 100% | this.user = req.user; | |
20 | 9 | 100% | this.options = options || {}; | |
21 | not covered | |||
22 | 9 | 100% | if (!this.options.errorMessage) { | |
23 | 9 | 100% | this.options.errorMessage = 'There was a problem saving your changes:'; | |
24 | not covered } | |||
25 | not covered | |||
26 | not covered if (this.options.user) { | |||
27 | 0 | not covered this.user = this.options.user; | ||
28 | not covered } | |||
29 | not covered | |||
30 | 9 | 100% | this.validationMethods = {}; | |
31 | 9 | 100% | 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 | |||
44 | 1 | 100% | UpdateHandler.prototype.validate = function(path, fn) { | |
45 | 0 | not covered this.validationMethods[path] = fn; | ||
46 | 0 | 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 | |||
61 | 1 | 100% | UpdateHandler.prototype.addValidationError = function(path, msg, type) { | |
62 | 0 | not covered this.validationErrors[path] = { | ||
63 | 0 | not covered type: type || 'required' | ||
64 | not covered }; | |||
65 | 0 | 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 | |||
84 | 1 | 100% | UpdateHandler.prototype.process = function(data, options, callback) { | |
85 | 9 | 100% | var usingDefaultFields = false; | |
86 | not covered | |||
87 | 9 | 100% | if ('function' === typeof options) { | |
88 | 9 | 100% | callback = options; | |
89 | 9 | 100% | options = null; | |
90 | not covered } | |||
91 | 9 | 100% | if (!options) { | |
92 | 9 | 100% | options = {}; | |
93 | 0 | not covered } else if ('string' === typeof options) { | ||
94 | 0 | not covered options = { fields: options }; | ||
95 | not covered } | |||
96 | 9 | 100% | if (!options.fields) { | |
97 | 9 | 100% | options.fields = _.keys(this.list.fields); | |
98 | 9 | 100% | usingDefaultFields = true; | |
99 | 0 | not covered } else if ('string' === typeof options.fields) { | ||
100 | 0 | not covered options.fields = options.fields.split(',').map(function(i) { return i.trim(); }); | ||
101 | not covered } | |||
102 | 9 | 100% | options.required = options.required || {}; | |
103 | 9 | 100% | options.errorMessage = options.errorMessage || this.options.errorMessage; | |
104 | 9 | 100% | options.invalidMessages = options.invalidMessages || {}; | |
105 | 9 | 100% | options.requiredMessages = options.requiredMessages || {}; | |
106 | not covered | |||
107 | not covered // Parse a string of required fields into field paths | |||
108 | 9 | 100% | if ('string' === typeof options.required) { | |
109 | 0 | not covered var requiredFields = options.required.split(',').map(function(i) { return i.trim(); }); | ||
110 | 0 | not covered options.required = {}; | ||
111 | 0 | not covered requiredFields.forEach(function(path) { | ||
112 | 0 | not covered options.required[path] = true; | ||
113 | not covered }); | |||
114 | not covered } | |||
115 | 9 | 100% | options.fields.forEach(function(path) { | |
116 | 37 | 80% | var field = (path instanceof keystone.Field) ? not covered path: this.list.field(path); | |
117 | 37 | 100% | if (field && field.required) { | |
118 | 0 | 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 | |||
123 | 9 | 100% | var actionQueue = [], | |
124 | 9 | 100% | addValidationError = this.addValidationError.bind(this), | |
125 | not covered validationErrors = this.validationErrors; | |||
126 | not covered | |||
127 | 9 | 100% | var progress = function(err) { | |
128 | 9 | 100% | if (err) { | |
129 | not covered if (options.logErrors) { | |||
130 | 0 | not covered console.log('Error saving changes to ' + this.item.list.singular + ' ' + this.item.id + ':'); | ||
131 | 0 | not covered console.log(err); | ||
132 | not covered } | |||
133 | 0 | not covered callback(err, this); | ||
134 | 9 | 100% | } else if (_.size(validationErrors)) { | |
135 | not covered if (options.flashErrors) { | |||
136 | 0 | not covered this.req.flash('error', { | ||
137 | not covered type: 'ValidationError', | |||
138 | 0 | not covered list: _.pluck(validationErrors, 'message') | ||
139 | not covered }); | |||
140 | not covered } | |||
141 | 0 | not covered callback({ | ||
142 | not covered } else if (actionQueue.length) { | |||
143 | 0 | not covered actionQueue.pop()(); | ||
144 | not covered } else { | |||
145 | 9 | 100% | saveItem(); | |
146 | not covered } | |||
147 | not covered | |||
148 | 9 | 100% | var saveItem = function() { // Make current user available to pre/post save events | |
149 | 9 | 100% | this.item._req_user = this.user; | |
150 | not covered | |||
151 | 9 | 100% | this.item.save(function(err) { | |
152 | 9 | 100% | if (err) { | |
153 | 0 | not covered if (err.name === 'ValidationError') { | ||
154 | not covered // don't log simple validation errors | |||
155 | not covered if (options.flashErrors) { | |||
156 | 0 | not covered this.req.flash('error', { | ||
157 | not covered type: 'ValidationError', | |||
158 | not covered title: options.errorMessage, | |||
159 | 0 | not covered list: _.pluck(err.errors, 'message') | ||
160 | not covered }); | |||
161 | not covered } | |||
162 | 0 | not covered console.log('Error saving changes to ' + this.item.list.singular + ' ' + this.item.id + ':'); | ||
163 | 0 | not covered console.log(err); | ||
164 | not covered } | |||
165 | 0 | not covered this.req.flash('error', 'There was an error saving your changes: ' + err.message + ' (' + err.name + (err.type ? ': ' + err.type : '') + ')'); | ||
166 | not covered } | |||
167 | 9 | 100% | return callback(err, this); | |
168 | not covered }.bind(this)); | |||
169 | not covered }.bind(this); | |||
170 | not covered | |||
171 | 9 | 100% | options.fields.forEach(function(path) { // console.log('Processing field ' + path); | |
172 | 37 | 100% | var message; | |
173 | not covered | |||
174 | 37 | 80% | var field = (path instanceof keystone.Field) ? not covered path: this.list.field(path), invalidated = false; | |
175 | not covered | |||
176 | 37 | 100% | if (!field) { | |
177 | 0 | 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 | |||
181 | 37 | 75% | if (usingDefaultFields && field.noedit && not covered !options.ignoreNoedit { | |
182 | not covered // console.log('Skipping field ' + path + ' (noedit: true)'); | |||
183 | 0 | not covered return; | ||
184 | not covered } | |||
185 | 0 | not covered actionQueue.push(field.getRequestHandler(this.item, this.req, options.paths, function(err) { | ||
186 | 0 | not covered if (err && options.flashErrors) { | ||
187 | 0 | not covered this.req.flash('error', field.label + ' upload failed - ' + err.message); | ||
188 | not covered } | |||
189 | 0 | not covered progress(err); | ||
190 | not covered }.bind(this))); | |||
191 | not covered break; | |||
192 | 0 | not covered actionQueue.push(field.getRequestHandler(this.item, this.req, options.paths, function(err) { | ||
193 | 0 | not covered if (err && options.flashErrors) { | ||
194 | 0 | not covered this.req.flash('error', field.label + ' improve failed - ' + (err.status_text || err.status)); | ||
195 | not covered } | |||
196 | 0 | not covered progress(err); | ||
197 | not covered }.bind(this))); | |||
198 | not covered break; | |||
199 | 0 | not covered if (!data[field.path] && (!options.required[field.path] || this.item.get(field.path))) { | ||
200 | 0 | not covered return; | ||
201 | not covered } | |||
202 | 0 | not covered if (data[field.path] !== data[field.paths.confirm]) { | ||
203 | 0 | not covered message = options.invalidMessages[field.path + '_match'] || 'Passwords must match'; | ||
204 | 0 | not covered addValidationError(field.path, message); | ||
205 | 0 | not covered invalidated = true; | ||
206 | not covered } | |||
207 | 37 | 100% | if (!invalidated && !field.validateInput(data)) { | |
208 | not covered // console.log('Field ' + field.path + ' is invalid'); | |||
209 | 0 | not covered message = options.invalidMessages[field.path] || field.options.invalidMessage || 'Please enter a valid ' + field.typeDescription + ' in the ' + field.label + ' field'; | ||
210 | 0 | not covered addValidationError(field.path, message); | ||
211 | 0 | not covered invalidated = true; | ||
212 | not covered } | |||
213 | 37 | 67% | 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.'); | |||
215 | 0 | not covered message = options.requiredMessages[field.path] || field.options.requiredMessage || field.label + ' is required'; | ||
216 | 0 | not covered addValidationError(field.path, message); | ||
217 | 0 | not covered invalidated = true; | ||
218 | not covered } | |||
219 | 37 | 100% | if (!invalidated && this.validationMethods[field.path]) { | |
220 | 0 | not covered message = this.validationMethods[field.path](data); | ||
221 | 0 | not covered if (message) { | ||
222 | 0 | not covered addValidationError(field.path, message); | ||
223 | not covered } | |||
224 | 37 | 100% | field.updateItem(this.item, data); | |
225 | not covered | |||
226 | not covered | |||
227 | 9 | 100% | progress(); | |
228 | not covered | |||
229 | not covered | |||
230 | not covered | |||
231 | not covered /*! | |||
232 | not covered * Export class | |||
233 | not covered */ | |||
234 | not covered | |||
235 | 1 | 100% | exports = module.exports = UpdateHandler; |
lib/view.js
58% line coverage go jump to first missed line
58% statement coverage go jump to first missed statement
34% block coverage
127 SLOC
Line | Hits | Statements | Source | Action |
---|---|---|---|---|
1 | not covered /*! | |||
2 | not covered * Module dependencies. | |||
3 | not covered */ | |||
4 | not covered | |||
5 | 1 | 100% | var _ = require('underscore'), | |
6 | 1 | 100% | keystone = require('../'), | |
7 | 1 | 100% | async = require('async'), | |
8 | 1 | 100% | 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 | |||
21 | 13 | 100% | if (!req || req.constructor.name !== 'IncomingMessage') { | |
22 | 0 | not covered throw new Error('Keystone.View Error: Express request object is required.'); | ||
23 | not covered } | |||
24 | not covered | |||
25 | 13 | 100% | if (!res || res.constructor.name !== 'ServerResponse') { | |
26 | 0 | not covered throw new Error('Keystone.View Error: Express response object is required.'); | ||
27 | not covered } | |||
28 | not covered | |||
29 | 13 | 100% | this.req = req; | |
30 | 13 | 100% | this.res = res; | |
31 | not covered | |||
32 | 13 | 100% | this.initQueue = []; // executed first in series | |
33 | 13 | 100% | this.actionQueue = []; // executed second in parallel, if optional conditions are met | |
34 | 13 | 100% | this.queryQueue = []; // executed third in parallel | |
35 | 13 | 100% | this.renderQueue = []; // executed fourth in parallel | |
36 | not covered | |||
37 | not covered } | |||
38 | not covered | |||
39 | 1 | 100% | 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 | |||
49 | 1 | 100% | View.prototype.on = function(on) { | |
50 | 12 | 100% | var req = this.req, callback = arguments[1], values; | |
51 | not covered | |||
52 | 12 | 100% | if ('function' === typeof on) { | |
53 | not covered | |||
54 | 0 | not covered if (on()) { | ||
55 | 0 | not covered this.actionQueue.push(callback); | ||
56 | not covered } | |||
57 | 12 | 100% | } else if (utils.isObject(on)) { | |
58 | not covered | |||
59 | 1 | 100% | var check = function(value, path) { | |
60 | 1 | 100% | var ctx = req, parts = path.split('.'); | |
61 | not covered | |||
62 | 2 | 100% | for (var i = 0; i < parts.length - 1; i++) { | |
63 | 1 | 100% | if (!ctx[parts[i]]) { | |
64 | 0 | not covered return false; | ||
65 | not covered } | |||
66 | 1 | 100% | ctx = ctx[parts[i]]; | |
67 | not covered } | |||
68 | 1 | 100% | path = _.last(parts); | |
69 | not covered | |||
70 | 1 | 60% | return (value === true && not covered path in ctx ? not covered true: (ctx[path] === value); | |
71 | not covered | |||
72 | not covered | |||
73 | 1 | 100% | if (_.every(on, check)) { | |
74 | 1 | 100% | this.actionQueue.push(callback); | |
75 | not covered } | |||
76 | 11 | 100% | } else if (on === 'get' || on === 'post' || on === 'put' || on === 'delete') { | |
77 | not covered | |||
78 | 10 | 100% | if (req.method !== on.toUpperCase()) { | |
79 | 2 | 100% | return; | |
80 | not covered } | |||
81 | 8 | 100% | if (arguments.length === 3) { | |
82 | not covered | |||
83 | 6 | 100% | if (utils.isString(arguments[1])) { | |
84 | 0 | not covered values = {}; | ||
85 | 0 | not covered values[arguments[1]] = true; | ||
86 | not covered } else { | |||
87 | 6 | 100% | values = arguments[1]; | |
88 | not covered } | |||
89 | 6 | 100% | callback = arguments[2]; | |
90 | not covered | |||
91 | 6 | 100% | var ctx = (on === 'post' || on === 'put') ? req.body : req.query; | |
92 | not covered | |||
93 | 6 | 100% | if (_.every(values || {}, function(value, path) { | |
94 | 6 | 100% | return (value === true && path in ctx) ? true : (ctx[path] === value); | |
95 | not covered })) { | |||
96 | 3 | 100% | this.actionQueue.push(callback); | |
97 | not covered } | |||
98 | 2 | 100% | this.actionQueue.push(callback); | |
99 | not covered } | |||
100 | 1 | 100% | } else if (on === 'init') { | |
101 | not covered | |||
102 | 1 | 100% | this.initQueue.push(callback); | |
103 | not covered | |||
104 | 0 | not covered } else if (on === 'render') { | ||
105 | not covered | |||
106 | 0 | not covered this.renderQueue.push(callback); | ||
107 | not covered | |||
108 | 10 | 100% | return this; | |
109 | not covered | |||
110 | not covered | |||
111 | 1 | 100% | var QueryCallbacks = function(options) { | |
112 | 0 | not covered if (utils.isString(options)) { | ||
113 | 0 | not covered options = { then: options }; | ||
114 | not covered } else { | |||
115 | 0 | not covered options = options || {}; | ||
116 | not covered } | |||
117 | 0 | not covered this.callbacks = {}; | ||
118 | 0 | not covered if (options.err) this.callbacks.err = options.err; | ||
119 | 0 | not covered if (options.none) this.callbacks.none = options.none; | ||
120 | 0 | not covered if (options.then) this.callbacks.then = options.then; | ||
121 | 0 | not covered return this; | ||
122 | not covered }; | |||
123 | not covered | |||
124 | 1 | 25% | QueryCallbacks.prototype.has = function(fn) { not covered return (fn in this.callbacks);}; | |
125 | 1 | 33% | QueryCallbacks.prototype.err = function(fn) { not covered this.callbacks.err = fn; return this;}; | |
126 | 1 | 33% | QueryCallbacks.prototype.none = function(fn) { not covered this.callbacks.none = fn; return this;}; | |
127 | 1 | 33% | 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 | |||
165 | 1 | 100% | View.prototype.query = function(key, query, options) { | |
166 | 0 | not covered var locals = this.res.locals, | ||
167 | 0 | not covered parts = key.split('.'), | ||
168 | not covered chain = new QueryCallbacks(options); | |||
169 | 0 | not covered key = parts.pop(); | ||
170 | not covered | |||
171 | 0 | not covered for (var i = 0; i < parts.length; i++) { | ||
172 | 0 | not covered if (!locals[parts[i]]) { | ||
173 | 0 | not covered locals[parts[i]] = {}; | ||
174 | not covered } | |||
175 | 0 | not covered locals = locals[parts[i]]; | ||
176 | not covered } | |||
177 | 0 | not covered this.queryQueue.push(function(next) { | ||
178 | 0 | not covered query.exec(function(err, results) { | ||
179 | 0 | not covered locals[key] = results; | ||
180 | 0 | not covered var callbacks = chain.callbacks; | ||
181 | not covered | |||
182 | 0 | not covered if (err) { | ||
183 | 0 | not covered if ('err' in callbacks) { | ||
184 | not covered /* Will pass errors into the err callback | |||
185 | not covered * | |||
186 | 0 | not covered return callbacks.err(err, next); | ||
187 | not covered } | |||
188 | 0 | 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 | |||
190 | 0 | not covered return callbacks.none(next); | ||
191 | 0 | not covered } else if ('then' in callbacks) { | ||
192 | 0 | not covered if (utils.isFunction(callbacks.then)) { | ||
193 | 0 | not covered return callbacks.then(err, results, next); | ||
194 | not covered } else { | |||
195 | 0 | not covered return keystone.populateRelated(results, callbacks.then, next); | ||
196 | not covered } | |||
197 | 0 | not covered return next(err); | ||
198 | not covered | |||
199 | not covered }); | |||
200 | not covered | |||
201 | 0 | 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 */ | |||
218 | 1 | 100% | View.prototype.render = function(renderFn, locals, callback) { | |
219 | 12 | 100% | var req = this.req, res = this.res; | |
220 | not covered | |||
221 | 12 | 100% | if ('string' === typeof renderFn) { | |
222 | 0 | not covered var viewPath = renderFn; | ||
223 | 0 | not covered renderFn = function() { | ||
224 | 0 | not covered if ('function' === typeof locals) { | ||
225 | 0 | not covered locals = locals(); | ||
226 | not covered } | |||
227 | 0 | not covered this.res.render(viewPath, locals, callback); | ||
228 | not covered }.bind(this); | |||
229 | 12 | 100% | if ('function' !== typeof renderFn) { | |
230 | 0 | not covered throw new Error('Keystone.View.render() renderFn must be a templatePath (string) or a function.'); | ||
231 | not covered } | |||
232 | 12 | 100% | this.initQueue.push(this.actionQueue); | |
233 | 12 | 100% | this.initQueue.push(this.queryQueue); | |
234 | not covered | |||
235 | 12 | 100% | var preRenderQueue = []; | |
236 | not covered | |||
237 | not covered // Add Keystone's global pre('render') queue | |||
238 | 12 | 100% | keystone._pre.render.forEach(function(fn) { | |
239 | 0 | not covered preRenderQueue.push(function(next) { | ||
240 | 0 | not covered fn(req, res, next); | ||
241 | not covered }); | |||
242 | not covered }); | |||
243 | not covered | |||
244 | 12 | 100% | this.initQueue.push(preRenderQueue); | |
245 | 12 | 100% | this.initQueue.push(this.renderQueue); | |
246 | not covered | |||
247 | 12 | 100% | async.eachSeries(this.initQueue, function(i, next) { | |
248 | 49 | 100% | if (Array.isArray(i)) { | |
249 | not covered // process nested arrays in parallel | |||
250 | 48 | 100% | async.parallel(i, next); | |
251 | 1 | 100% | } else if ('function' === typeof i) { | |
252 | not covered // process single methods in series | |||
253 | 1 | 100% | i(next); | |
254 | not covered } else { | |||
255 | 0 | not covered throw new Error('Keystone.View.render() events must be functions.'); | ||
256 | not covered } | |||
257 | not covered }, function(err) { | |||
258 | 12 | 100% | renderFn(err, req, res); | |
259 | not covered }); | |||
260 | not covered |
lib/email.js
20% line coverage go jump to first missed line
11% statement coverage go jump to first missed statement
0% block coverage
196 SLOC
Line | Hits | Statements | Source | Action |
---|---|---|---|---|
1 | 1 | 100% | var _ = require('underscore'), | |
2 | 1 | 100% | keystone = require('../'), | |
3 | 1 | 100% | fs = require('fs'), | |
4 | 1 | 100% | util = require('util'), | |
5 | 1 | 100% | path = require('path'), | |
6 | 1 | 100% | moment = require('moment'), | |
7 | 1 | 100% | utils = require('keystone-utils'), | |
8 | 1 | 100% | mandrillapi = require('mandrill-api'); | |
9 | not covered | |||
10 | 1 | 100% | var templateCache = {}; | |
11 | not covered | |||
12 | 1 | 100% | var defaultConfig = { templateExt: 'jade', | |
13 | 1 | 100% | templateEngine: require('jade'), | |
14 | 1 | 100% | 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 | |||
20 | 1 | 100% | var ErrorNoEmailTemplateName = function() { | |
21 | 0 | not covered Error.apply(this, arguments); | ||
22 | 0 | not covered Error.captureStackTrace(this, arguments.callee); | ||
23 | 0 | not covered this.message = 'No email templateName specified.'; | ||
24 | 0 | not covered this.name = 'ErrorNoEmailTemplateName'; | ||
25 | not covered }; | |||
26 | 1 | 100% | util.inherits(ErrorNoEmailTemplateName, Error); | |
27 | not covered | |||
28 | 1 | 100% | var ErrorEmailsPathNotSet = function() { | |
29 | 0 | not covered Error.apply(this, arguments); | ||
30 | 0 | not covered Error.captureStackTrace(this, arguments.callee); | ||
31 | 0 | not covered this.message = 'Keystone has not been configured for email support. Set the `emails` option in your configuration.'; | ||
32 | 0 | not covered this.name = 'ErrorEmailsPathNotSet'; | ||
33 | not covered }; | |||
34 | 1 | 100% | util.inherits(ErrorEmailsPathNotSet, Error); | |
35 | not covered | |||
36 | 1 | 100% | var ErrorEmailOptionsRequired = function() { | |
37 | 0 | not covered Error.apply(this, arguments); | ||
38 | 0 | not covered Error.captureStackTrace(this, arguments.callee); | ||
39 | 0 | not covered this.message = 'The keystone.Email class requires a templateName or options argument to be provided.'; | ||
40 | 0 | not covered this.name = 'ErrorEmailOptionsRequired'; | ||
41 | not covered }; | |||
42 | 1 | 100% | util.inherits(ErrorEmailsPathNotSet, Error); | |
43 | not covered | |||
44 | not covered | |||
45 | not covered /** Helper methods */ | |||
46 | not covered | |||
47 | 1 | 100% | var getEmailsPath = function() { | |
48 | 0 | not covered var path = keystone.getPath('emails'); | ||
49 | 0 | not covered if (path) { | ||
50 | 0 | not covered return path; | ||
51 | not covered } | |||
52 | 0 | not covered throw new ErrorEmailsPathNotSet(); | ||
53 | not covered }; | |||
54 | not covered | |||
55 | not covered | |||
56 | not covered /** CSS Helper methods */ | |||
57 | not covered | |||
58 | 1 | 100% | var templateCSSMethods = { shadeColor: function(color, percent) { | |
59 | 0 | 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; | ||
60 | 0 | 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 | |||
82 | 1 | 100% | var Email = function(options) { // Passing a string will use Email.defaults for everything but template name | |
83 | 0 | not covered if ('string' === typeof(options)) { | ||
84 | 0 | not covered options = { | ||
85 | not covered templateName: options | |||
86 | 0 | not covered } else if (!utils.isObject(options)) { | ||
87 | 0 | not covered throw new ErrorEmailOptionsRequired(); | ||
88 | not covered } | |||
89 | 0 | not covered this.templateName = options.templateName; | ||
90 | 0 | not covered this.templateExt = options.templateExt || Email.defaults.templateExt; | ||
91 | 0 | not covered this.templateEngine = options.templateEngine || Email.defaults.templateEngine; | ||
92 | 0 | not covered this.templateBasePath = options.templateBasePath || Email.defaults.templateBasePath; | ||
93 | not covered | |||
94 | 0 | not covered if (!this.templateName) { | ||
95 | 0 | not covered throw new ErrorNoEmailTemplateName(); | ||
96 | not covered } | |||
97 | 0 | 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 | |||
110 | 1 | 100% | Email.prototype.render = function(locals, callback) { | |
111 | 0 | not covered if ('function' === typeof locals && !callback) { | ||
112 | 0 | not covered callback = locals; | ||
113 | 0 | not covered locals = {}; | ||
114 | not covered } | |||
115 | 0 | not covered locals = ('object' === typeof locals) ? locals : {}; | ||
116 | 0 | not covered callback = ('function' === typeof callback) ? callback : function() {}; | ||
117 | not covered | |||
118 | 0 | not covered if (keystone.get('email locals')) { | ||
119 | 0 | not covered _.defaults(locals, keystone.get('email locals')); | ||
120 | not covered } | |||
121 | 0 | not covered _.defaults(locals, { | ||
122 | 0 | not covered brand: keystone.get('brand'), | ||
123 | not covered theme: {}, | |||
124 | not covered css: templateCSSMethods | |||
125 | not covered | |||
126 | 0 | not covered if (!locals.theme.buttons) { | ||
127 | 0 | not covered locals.theme.buttons = {}; | ||
128 | not covered } | |||
129 | 0 | not covered this.compileTemplate(function(err) { | ||
130 | 0 | not covered if (err) { | ||
131 | 0 | not covered return callback(err); | ||
132 | not covered } | |||
133 | not covered | |||
134 | 0 | not covered var html = templateCache[this.templateName](locals); | ||
135 | not covered | |||
136 | not covered // ensure extended characters are replaced | |||
137 | 0 | not covered html = html.replace(/[\u007f-\uffff]/g, function(c) { | ||
138 | 0 | not covered return '&#x'+('0000'+c.charCodeAt(0).toString(16)).slice(-4)+';'; | ||
139 | not covered }); | |||
140 | not covered | |||
141 | not covered // process email rules | |||
142 | 0 | not covered var rules = keystone.get('email rules'); | ||
143 | not covered | |||
144 | 0 | not covered if (rules) { | ||
145 | not covered | |||
146 | 0 | not covered if (!Array.isArray(rules)) { | ||
147 | 0 | not covered rules = [rules]; | ||
148 | not covered } | |||
149 | 0 | not covered _.each(rules, function(rule) { | ||
150 | 0 | not covered if (rule.find && rule.replace) { | ||
151 | not covered | |||
152 | 0 | not covered var find = rule.find, | ||
153 | not covered | |||
154 | 0 | not covered if ('string' === typeof find) { | ||
155 | 0 | not covered find = new RegExp(find, 'gi'); | ||
156 | not covered } | |||
157 | not covered | |||
158 | 0 | not covered html = html.replace(find, replace); | ||
159 | not covered } | |||
160 | not covered | |||
161 | not covered } | |||
162 | not covered | |||
163 | 0 | 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 | |||
176 | 1 | 100% | Email.prototype.loadTemplate = function(callback) { | |
177 | 0 | not covered var fsTemplatePath = path.join(Email.getEmailsPath(), this.templateName + '.' + this.templateExt); | ||
178 | not covered | |||
179 | 0 | not covered fs.readFile(fsTemplatePath, function(err, contents) { | ||
180 | 0 | not covered if (err) { | ||
181 | 0 | not covered if (err.code === 'ENOENT') { | ||
182 | not covered | |||
183 | 0 | not covered fsTemplatePath = path.join(Email.getEmailsPath(), this.templateName, 'email.' + this.templateExt); | ||
184 | not covered | |||
185 | 0 | not covered fs.readFile(fsTemplatePath, function(err, contents) { | ||
186 | 0 | not covered callback(err, fsTemplatePath, contents); | ||
187 | not covered }); | |||
188 | not covered | |||
189 | not covered } else { | |||
190 | 0 | not covered return callback(err); | ||
191 | not covered } | |||
192 | not covered } else { | |||
193 | 0 | 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 | |||
205 | 1 | 100% | Email.prototype.compileTemplate = function(callback) { | |
206 | 0 | not covered if (keystone.get('env') === 'production' && templateCache[this.templateName]) { | ||
207 | 0 | not covered return process.nextTick(callback); | ||
208 | not covered } | |||
209 | 0 | not covered this.loadTemplate(function(err, filename, contents) { | ||
210 | 0 | not covered if (err) return callback(err); | ||
211 | not covered | |||
212 | 0 | not covered var template = this.templateEngine.compile(contents.toString(), Email.defaults.compilerOptions || { filename: fs.realpathSync(filename), basedir: this.templateBasePath }); | ||
213 | not covered | |||
214 | 0 | not covered templateCache[this.templateName] = template; | ||
215 | not covered | |||
216 | 0 | 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 | |||
254 | 1 | 100% | Email.prototype.send = function(options, callback) { | |
255 | 0 | not covered var locals = options; | ||
256 | not covered | |||
257 | 0 | not covered if (arguments.length === 3 || !utils.isFunction(callback)) { | ||
258 | 0 | not covered callback = arguments[2]; | ||
259 | 0 | not covered options = arguments[1] || arguments[0]; | ||
260 | not covered } | |||
261 | 0 | not covered this.render(locals, function(err, email) { | ||
262 | 0 | not covered callback = ('function' === typeof callback) ? callback : function() {}; | ||
263 | not covered | |||
264 | 0 | not covered if (err) { | ||
265 | 0 | not covered return callback(err); | ||
266 | not covered } | |||
267 | not covered | |||
268 | 0 | not covered if (!utils.isObject(options)) { | ||
269 | 0 | not covered return callback({ | ||
270 | not covered from: 'Email.send', | |||
271 | not covered key: 'invalid options', | |||
272 | 0 | not covered if ('string' === typeof options.from) { | ||
273 | 0 | not covered options.fromName = options.from; | ||
274 | 0 | not covered options.fromEmail = options.from; | ||
275 | 0 | not covered } else if (utils.isObject(options.from)) { | ||
276 | 0 | not covered options.fromName = utils.isObject(options.from.name) ? options.from.name.full : options.from.name; | ||
277 | 0 | not covered options.fromEmail = options.from.email; | ||
278 | not covered } | |||
279 | 0 | not covered if (!options.fromName || !options.fromEmail) { | ||
280 | 0 | not covered return callback({ | ||
281 | not covered from: 'Email.send', | |||
282 | 0 | not covered if (!options.mandrill) { | ||
283 | 0 | not covered if (!keystone.get('mandrill api key')) | ||
284 | 0 | not covered return callback({ | ||
285 | not covered from: 'Email.send', | |||
286 | 0 | not covered options.mandrill = new mandrillapi.Mandrill(keystone.get('mandrill api key')); | ||
287 | not covered } | |||
288 | 0 | not covered options.tags = utils.isArray(options.tags) ? options.tags : []; | ||
289 | 0 | not covered options.tags.push('sent:' + moment().format('YYYY-MM-DD')); | ||
290 | 0 | not covered options.tags.push(this.templateName); | ||
291 | not covered | |||
292 | 0 | not covered if (keystone.get('env') === 'development') { | ||
293 | 0 | not covered options.tags.push('development'); | ||
294 | not covered } | |||
295 | 0 | not covered var recipients = [], | ||
296 | not covered | |||
297 | 0 | not covered options.to = Array.isArray(options.to) ? options.to : [options.to]; | ||
298 | not covered | |||
299 | 0 | not covered for (var i = 0; i < options.to.length; i++) { | ||
300 | not covered | |||
301 | 0 | not covered if ('string' === typeof options.to[i]) { | ||
302 | 0 | not covered options.to[i] = { email: options.to[i] }; | ||
303 | 0 | not covered } else if ('object' === typeof options.to[i]) { | ||
304 | 0 | not covered if (!options.to[i].email) { | ||
305 | 0 | not covered return callback({ | ||
306 | not covered from: 'Email.send', | |||
307 | 0 | not covered message: 'Recipient ' + (i + 1) + ' does not have a valid email address.' | ||
308 | not covered }); | |||
309 | 0 | not covered return callback({ | ||
310 | 0 | not covered message: 'Recipient ' + (i + 1) + ' is not a string or an object.' | ||
311 | not covered }); | |||
312 | not covered } | |||
313 | 0 | not covered var recipient = { email: options.to[i].email }, | ||
314 | not covered | |||
315 | 0 | not covered if ('string' === typeof options.to[i].name) { | ||
316 | 0 | not covered recipient.name = options.to[i].name; | ||
317 | 0 | not covered vars.push({ name: 'name', content: options.to[i].name }); | ||
318 | 0 | not covered } else if ('object' === typeof options.to[i].name) { | ||
319 | 0 | not covered recipient.name = options.to[i].name.full || ''; | ||
320 | 0 | not covered vars.push({ name: 'name', content: options.to[i].name.full || '' }); | ||
321 | 0 | not covered vars.push({ name: 'first_name', content: options.to[i].name.first || '' }); | ||
322 | 0 | not covered vars.push({ name: 'last_name', content: options.to[i].name.last || '' }); | ||
323 | not covered } | |||
324 | 0 | not covered recipients.push(recipient); | ||
325 | not covered | |||
326 | 0 | not covered mergeVars.push({ | ||
327 | not covered } | |||
328 | 0 | not covered var onSuccess = function(info) { | ||
329 | 0 | not covered callback(null, info); | ||
330 | not covered }; | |||
331 | not covered | |||
332 | 0 | not covered var onFail = function(info) { | ||
333 | 0 | not covered callback({ | ||
334 | not covered }; | |||
335 | not covered | |||
336 | 0 | not covered var message = { | ||
337 | not covered | |||
338 | 0 | not covered _.defaults(message, options.mandrillOptions); | ||
339 | 0 | not covered _.defaults(message, Email.defaults.mandrill); | ||
340 | not covered | |||
341 | 0 | not covered return process.nextTick(function() { | ||
342 | 0 | not covered options.mandrill.messages.send({ message: message }, onSuccess, onFail); | ||
343 | not covered }); | |||
344 | not covered | |||
345 | not covered | |||
346 | not covered | |||
347 | 1 | 100% | Email.getEmailsPath = getEmailsPath; | |
348 | 1 | 100% | Email.templateCache = templateCache; | |
349 | 1 | 100% | Email.templateCSSMethods = templateCSSMethods; | |
350 | 1 | 100% | Email.defaults = defaultConfig; | |
351 | not covered | |||
352 | 1 | 100% | exports = module.exports = Email; |
lib/security/csrf.js
97% line coverage go jump to first missed line
98% statement coverage go jump to first missed statement
93% block coverage
45 SLOC
Line | Hits | Statements | Source | Action |
---|---|---|---|---|
1 | 1 | 100% | var crypto = require('crypto'), utils = require('keystone-utils'); | |
2 | not covered | |||
3 | 1 | 100% | exports.TOKEN_KEY = '_csrf'; | |
4 | 1 | 100% | exports.LOCAL_KEY = 'csrf_token_key'; | |
5 | 1 | 100% | exports.LOCAL_VALUE = 'csrf_token_value'; | |
6 | 1 | 100% | exports.SECRET_KEY = exports.TOKEN_KEY + '_secret'; | |
7 | 1 | 100% | exports.SECRET_LENGTH = 10; | |
8 | not covered | |||
9 | not covered function tokenize(salt, secret) { | |||
10 | 10 | 100% | return salt + crypto.createHash('sha1').update(salt + secret).digest('base64'); | |
11 | not covered } | |||
12 | not covered | |||
13 | 1 | 100% | exports.createSecret = function() { | |
14 | 5 | 100% | return crypto.pseudoRandomBytes(exports.SECRET_LENGTH).toString('base64'); | |
15 | not covered }; | |||
16 | not covered | |||
17 | 1 | 100% | exports.getSecret = function(req) { | |
18 | 7 | 100% | return req.session[exports.SECRET_KEY] || (req.session[exports.SECRET_KEY] = exports.createSecret()); | |
19 | not covered }; | |||
20 | not covered | |||
21 | 1 | 100% | exports.createToken = function(req) { | |
22 | 5 | 100% | return tokenize(utils.randomString(exports.SECRET_LENGTH), exports.getSecret(req)); | |
23 | not covered }; | |||
24 | not covered | |||
25 | 1 | 100% | exports.getToken = function(req, res) { | |
26 | 2 | 100% | return res.locals[exports.LOCAL_VALUE] || (res.locals[exports.LOCAL_VALUE] = exports.createToken(req)); | |
27 | not covered }; | |||
28 | not covered | |||
29 | 1 | 100% | exports.requestToken = function(req) { | |
30 | 6 | 100% | if (req.body && req.body[exports.TOKEN_KEY]) { | |
31 | 3 | 100% | return req.body[exports.TOKEN_KEY]; | |
32 | 3 | 100% | } else if (req.query && req.query[exports.TOKEN_KEY]) { | |
33 | 1 | 100% | return req.query[exports.TOKEN_KEY]; | |
34 | not covered } | |||
35 | 2 | 100% | return ''; | |
36 | not covered }; | |||
37 | not covered | |||
38 | 1 | 100% | exports.validate = function(req, token) { | |
39 | 5 | 100% | if (arguments.length === 1) { | |
40 | 3 | 100% | token = exports.requestToken(req); | |
41 | not covered } | |||
42 | 5 | 100% | if (typeof token !== 'string') { | |
43 | 0 | not covered return false; | ||
44 | not covered } | |||
45 | 5 | 100% | return token === tokenize(token.slice(0, exports.SECRET_LENGTH), req.session[exports.SECRET_KEY]); | |
46 | not covered }; | |||
47 | not covered | |||
48 | 1 | 100% | exports.middleware = { init: function(req, res, next) { | |
49 | 1 | 100% | res.locals[exports.LOCAL_KEY] = exports.LOCAL_VALUE; | |
50 | 1 | 100% | exports.getToken(req, res); | |
51 | 1 | 100% | next(); | |
52 | not covered }, | |||
53 | 3 | 100% | if (req.method === 'GET' || req.method === 'HEAD' || req.method === 'OPTIONS') { | |
54 | 1 | 100% | return next(); | |
55 | not covered } | |||
56 | 2 | 100% | if (exports.validate(req)) { | |
57 | 1 | 100% | next(); | |
58 | not covered } else { | |||
59 | 1 | 100% | res.statusCode = 403; | |
60 | 1 | 100% | next(new Error('CSRF token mismatch')); | |
61 | not covered } |
lib/session.js
17% line coverage go jump to first missed line
8% statement coverage go jump to first missed statement
0% block coverage
68 SLOC
Line | Hits | Statements | Source | Action |
---|---|---|---|---|
1 | 1 | 100% | 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 */ | |||
7 | 1 | 100% | var hash = function(str) { // force type | |
8 | 0 | not covered str = '' + str; | ||
9 | not covered // get the first half | |||
10 | 0 | not covered str = str.substr(0, Math.round(str.length / 2)); | ||
11 | not covered // hash using sha256 | |||
12 | not covered return crypto | |||
13 | 0 | not covered .createHmac('sha256', keystone.get('cookie secret')) | ||
14 | 0 | not covered .update(str) | ||
15 | 0 | not covered .digest('base64') | ||
16 | 0 | 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 | |||
29 | 1 | 100% | exports.signin = function(lookup, req, res, onSuccess, onFail) { | |
30 | 0 | not covered if (!lookup) { | ||
31 | 0 | not covered return onFail(new Error('session.signin requires a User ID or Object as the first argument')); | ||
32 | not covered } | |||
33 | 0 | not covered var User = keystone.list(keystone.get('user model')); | ||
34 | not covered | |||
35 | 0 | not covered var doSignin = function(user) { | ||
36 | 0 | not covered req.session.regenerate(function() { | ||
37 | 0 | not covered req.user = user; | ||
38 | 0 | 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 | |||
41 | 0 | not covered if (keystone.get('cookie signin') && user.password) { | ||
42 | 0 | not covered var userToken = user.id + ':' + hash(user.password); | ||
43 | 0 | not covered res.cookie('keystone.uid', userToken, { signed: true, httpOnly: true }); | ||
44 | not covered } | |||
45 | 0 | not covered onSuccess(user); | ||
46 | not covered | |||
47 | not covered }; | |||
48 | not covered | |||
49 | 0 | not covered if ('string' === typeof lookup.email && 'string' === typeof lookup.password) { | ||
50 | not covered | |||
51 | 0 | not covered User.model.findOne({ email: lookup.email }).exec(function(err, user) { | ||
52 | 0 | not covered if (user) { | ||
53 | 0 | not covered user._.password.compare(lookup.password, function(err, isMatch) { | ||
54 | 0 | not covered if (!err && isMatch) { | ||
55 | 0 | not covered doSignin(user); | ||
56 | not covered } | |||
57 | not covered else { | |||
58 | 0 | not covered onFail(err); | ||
59 | not covered } | |||
60 | not covered }); | |||
61 | 0 | not covered onFail(err); | ||
62 | not covered } | |||
63 | not covered | |||
64 | 0 | not covered lookup = '' + lookup; | ||
65 | not covered | |||
66 | 0 | not covered var userId = (lookup.indexOf(':') > 0) ? lookup.substr(0, lookup.indexOf(':')) : lookup, | ||
67 | not covered | |||
68 | 0 | not covered User.model.findById(userId).exec(function(err, user) { | ||
69 | 0 | not covered if (user && (!passwordCheck || passwordCheck === hash(user.password))) { | ||
70 | 0 | not covered doSignin(user); | ||
71 | not covered } else { | |||
72 | 0 | 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 | |||
86 | 1 | 100% | exports.signout = function(req, res, next) { | |
87 | 0 | not covered res.clearCookie('keystone.uid'); | ||
88 | 0 | not covered req.user = null; | ||
89 | not covered | |||
90 | 0 | 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 | |||
105 | 1 | 100% | exports.persist = function(req, res, next) { | |
106 | 0 | not covered var User = keystone.list(keystone.get('user model')); | ||
107 | not covered | |||
108 | 0 | not covered if (keystone.get('cookie signin') && !req.session.userId && req.signedCookies['keystone.uid'] && req.signedCookies['keystone.uid'].indexOf(':') > 0) { | ||
109 | not covered | |||
110 | 0 | not covered var _next = function() { next(); }; // otherwise the matching user is passed to next() which messes with the middleware signature | ||
111 | 0 | 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 | |||
115 | 0 | not covered User.model.findById(req.session.userId).exec(function(err, user) { | ||
116 | 0 | not covered if (err) { | ||
117 | 0 | not covered return next(err); | ||
118 | not covered } | |||
119 | not covered | |||
120 | 0 | not covered req.user = user; | ||
121 | 0 | not covered next(); | ||
122 | not covered | |||
123 | not covered | |||
124 | not covered } | |||
125 | not covered else { | |||
126 | 0 | 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 | |||
140 | 1 | 100% | exports.keystoneAuth = function(req, res, next) { | |
141 | 0 | not covered if (!req.user || !req.user.canAccessKeystone) { | ||
142 | 0 | not covered var from = new RegExp('^\/keystone\/?$', 'i').test(req.url) ? '' : '?from=' + req.url; | ||
143 | 0 | not covered return res.redirect(keystone.get('signin url') + from); | ||
144 | not covered } | |||
145 | 0 | not covered next(); | ||
146 | not covered |
lib/asyncdi.js
57% line coverage go jump to first missed line
56% statement coverage go jump to first missed statement
40% block coverage
57 SLOC
Line | Hits | Statements | Source | Action |
---|---|---|---|---|
1 | 1 | 100% | 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 | |||
9 | 1 | 100% | 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 */ | |||
18 | 1 | 100% | exports = module.exports = function(fn) { | |
19 | 8 | 100% | 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 */ | |||
28 | 1 | 100% | 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. | |
29 | 9 | 100% | if (!(this instanceof Wrapper)) { | |
30 | 0 | not covered return new Wrapper(fn, context); | ||
31 | not covered } | |||
32 | 9 | 100% | if (!_.isFunction(fn)) { | |
33 | 0 | not covered throw new Error('AsyncDI Wrapper must be initialised with a function'); | ||
34 | not covered } | |||
35 | 9 | 100% | this.fn = fn; | |
36 | not covered | |||
37 | 9 | 100% | this.deps = fn.toString().match(FN_ARGS)[1].split(',').map(function(i) { return i.trim(); }); | |
38 | not covered | |||
39 | 9 | 100% | this.requires = {}; | |
40 | 9 | 100% | this.deps.forEach(function(i) { | |
41 | 9 | 100% | this.requires[i] = true; | |
42 | not covered }, this); | |||
43 | not covered | |||
44 | 9 | 100% | if (_.last(this.deps) === 'callback') { | |
45 | 2 | 100% | this.isAsync = true; | |
46 | 2 | 100% | this.deps.pop(); | |
47 | not covered } | |||
48 | 9 | 100% | this._context = context; | |
49 | not covered | |||
50 | 9 | 100% | this._provides = {}; | |
51 | not covered | |||
52 | 9 | 100% | this._arguments = []; | |
53 | not covered | |||
54 | not covered | |||
55 | 1 | 100% | _.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) { | |
56 | 0 | not covered _.extend(this._provides, provides); | ||
57 | 0 | not covered this._arguments = _.map(this.deps, function(key) { | ||
58 | 0 | not covered return this._provides[key]; | ||
59 | not covered }, this); | |||
60 | 0 | not covered return this; | ||
61 | not covered }, | |||
62 | not covered | |||
63 | 0 | not covered this._context = context; | ||
64 | 0 | not covered return this; | ||
65 | not covered }, | |||
66 | 3 | 100% | if (arguments.length === 1) { | |
67 | 2 | 100% | callback = context; | |
68 | 2 | 100% | context = this._context; | |
69 | not covered } | |||
70 | 1 | 100% | var asyncArgs = this._arguments.slice(); | |
71 | not covered // push the callback onto the new arguments array | |||
72 | 1 | 100% | asyncArgs.push(callback); | |
73 | not covered // call the function | |||
74 | 1 | 100% | this.fn.apply(context, asyncArgs); | |
75 | not covered } else { | |||
76 | 2 | 100% | if (callback) { | |
77 | not covered // If a callback is provided, it must use the error-first arguments pattern. | |||
78 | 1 | 100% | callback(null, this.fn.apply(context, this._arguments)); | |
79 | not covered } else { | |||
80 | 1 | 100% | return this.fn.apply(context, this._arguments); | |
81 | not covered } | |||
82 | 0 | not covered var wrapper = this; | ||
83 | not covered if (this.isAsync) { | |||
84 | 0 | not covered return async.each(arr, function(item, cb) { | ||
85 | 0 | not covered wrapper.call(item, cb); | ||
86 | not covered }, callback); | |||
87 | not covered } else { | |||
88 | 0 | not covered arr.each(function(item) { | ||
89 | 0 | not covered wrapper.call(item); | ||
90 | not covered }); | |||
91 | 0 | not covered if (callback) { callback(); } | ||
92 | not covered } | |||
93 | 0 | not covered var wrapper = this; | ||
94 | not covered if (this.isAsync) { | |||
95 | 0 | not covered async.map(arr, function(item, cb) { | ||
96 | 0 | not covered wrapper.call(item, cb); | ||
97 | not covered }, callback); | |||
98 | not covered } else { | |||
99 | 0 | not covered callback(null, arr.map(function(item) { | ||
100 | 0 | 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