1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 | var BooleanField, DateField, DateTimeField, EmailField, EncryptedString, EncryptedStringField, Field, FloatField, IntField, JSONField, NumberField, StringField,
|
10 | extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
|
11 | hasProp = {}.hasOwnProperty,
|
12 | bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
|
13 |
|
14 | Field = (function() {
|
15 | function Field(name, options) {
|
16 | if (options == null) {
|
17 | options = {};
|
18 | }
|
19 | if (!(this instanceof Field)) {
|
20 | return new Field(name);
|
21 | }
|
22 | this.name = name;
|
23 | this.options = options;
|
24 | }
|
25 |
|
26 | Field.prototype.pluginOption = function(name, defaultVal) {
|
27 | if (name in this.model.__bookshelf_schema_options) {
|
28 | return this.model.__bookshelf_schema_options[name];
|
29 | } else {
|
30 | return defaultVal;
|
31 | }
|
32 | };
|
33 |
|
34 | Field.prototype.option = function(name, pluginOptionName, defaultVal) {
|
35 | var value;
|
36 | if (arguments.length === 2) {
|
37 | defaultVal = pluginOptionName;
|
38 | pluginOptionName = name;
|
39 | }
|
40 | value = this.options[name];
|
41 | if (value == null) {
|
42 | value = this.pluginOption(pluginOptionName, defaultVal);
|
43 | }
|
44 | return value;
|
45 | };
|
46 |
|
47 | Field.prototype.contributeToSchema = function(schema) {
|
48 | return schema.push(this);
|
49 | };
|
50 |
|
51 | Field.prototype.contributeToModel = function(cls) {
|
52 | var base;
|
53 | this.model = cls;
|
54 | if ((base = this.model).__bookshelf_schema == null) {
|
55 | base.__bookshelf_schema = {
|
56 | validations: {},
|
57 | parsers: [],
|
58 | formatters: []
|
59 | };
|
60 | }
|
61 | if (this.option('createProperty', 'createProperties', true)) {
|
62 | this._createProperty(cls);
|
63 | }
|
64 | if (this.option('validation', true)) {
|
65 | this._appendValidations(cls);
|
66 | }
|
67 | this._appendFormatter();
|
68 | return this._appendParser();
|
69 | };
|
70 |
|
71 | Field.prototype.validations = function() {
|
72 | var result;
|
73 | result = this.options.validations ? this.options.validations.slice(0) : [];
|
74 | this.acceptsRule(result, ['required', 'accepted', 'exists']);
|
75 | return result;
|
76 | };
|
77 |
|
78 | Field.prototype.modelValidations = function() {};
|
79 |
|
80 | Field.prototype.createGetter = function() {
|
81 | var name;
|
82 | name = this.name;
|
83 | return function() {
|
84 | return this.get(name);
|
85 | };
|
86 | };
|
87 |
|
88 | Field.prototype.createSetter = function() {
|
89 | var name;
|
90 | name = this.name;
|
91 | return function(value) {
|
92 | return this.set(name, value);
|
93 | };
|
94 | };
|
95 |
|
96 | Field.prototype.acceptsRule = function(validations, names, rule) {
|
97 | var i, len, name;
|
98 | if (!(names instanceof Array)) {
|
99 | names = [names];
|
100 | }
|
101 | if (rule == null) {
|
102 | rule = names[0];
|
103 | }
|
104 | for (i = 0, len = names.length; i < len; i++) {
|
105 | name = names[i];
|
106 | if (!(name in this.options)) {
|
107 | continue;
|
108 | }
|
109 | validations.push(this._normalizeRule(rule, this.options[name]));
|
110 | return;
|
111 | }
|
112 | };
|
113 |
|
114 | Field.prototype._createProperty = function(cls) {
|
115 | var getter, setter, spec;
|
116 | if (this.name === 'id' || this.name in cls.prototype) {
|
117 | return;
|
118 | }
|
119 | spec = {};
|
120 | getter = this.createGetter();
|
121 | setter = this.createSetter();
|
122 | if (getter) {
|
123 | spec.get = getter;
|
124 | }
|
125 | if (setter) {
|
126 | spec.set = setter;
|
127 | }
|
128 | return Object.defineProperty(cls.prototype, this.name, spec);
|
129 | };
|
130 |
|
131 | Field.prototype._appendValidations = function(model) {
|
132 | var meta, modelValidations, validations;
|
133 | meta = model.__bookshelf_schema;
|
134 | validations = this.validations();
|
135 | if (validations && validations.length > 0) {
|
136 | if (this.name in meta.validations) {
|
137 | if (!(meta.validations[this.name] instanceof Array)) {
|
138 | meta.validations[this.name] = [meta.validations[this.name]];
|
139 | }
|
140 | } else {
|
141 | meta.validations[this.name] = [];
|
142 | }
|
143 | meta.validations[this.name].push.apply(meta.validations[this.name], validations);
|
144 | }
|
145 | modelValidations = this.modelValidations();
|
146 | if (modelValidations && modelValidations.length > 0) {
|
147 | if (meta.modelValidations == null) {
|
148 | meta.modelValidations = [];
|
149 | }
|
150 | return meta.modelValidations.push.apply(meta.modelValidations, modelValidations);
|
151 | }
|
152 | };
|
153 |
|
154 | Field.prototype._normalizeRule = function(rule, value) {
|
155 | var k, result, v;
|
156 | return this._withMessage((function() {
|
157 | switch (false) {
|
158 | case !(typeof value === 'object' && !(value instanceof Array)):
|
159 | result = {
|
160 | rule: rule
|
161 | };
|
162 | for (k in value) {
|
163 | v = value[k];
|
164 | result[k] = v;
|
165 | }
|
166 | if ('value' in result) {
|
167 | if (typeof rule === 'string') {
|
168 | result.rule += ':' + result.value;
|
169 | } else {
|
170 | result.params = result.value;
|
171 | }
|
172 | delete result.value;
|
173 | }
|
174 | result.params || (result.params = []);
|
175 | return result;
|
176 | case typeof value !== 'boolean':
|
177 | return rule;
|
178 | case typeof rule !== 'string':
|
179 | return rule + ":" + value;
|
180 | default:
|
181 | return this._normalizeRule(rule, {
|
182 | value: value
|
183 | });
|
184 | }
|
185 | }).call(this));
|
186 | };
|
187 |
|
188 | Field.prototype._withMessage = function(rule) {
|
189 | if (!((this.options.message != null) || (this.options.label != null))) {
|
190 | return rule;
|
191 | }
|
192 | if (typeof rule !== 'object') {
|
193 | rule = {
|
194 | rule: rule
|
195 | };
|
196 | }
|
197 | if (this.options.message != null) {
|
198 | if (rule.message == null) {
|
199 | rule.message = this.options.message;
|
200 | }
|
201 | }
|
202 | if (this.options.label != null) {
|
203 | if (rule.label == null) {
|
204 | rule.label = this.options.label;
|
205 | }
|
206 | }
|
207 | return rule;
|
208 | };
|
209 |
|
210 | Field.prototype._appendFormatter = function() {
|
211 | if (typeof this.format === 'function') {
|
212 | return this.model.__bookshelf_schema.formatters.push(this.format.bind(this));
|
213 | }
|
214 | };
|
215 |
|
216 | Field.prototype._appendParser = function() {
|
217 | if (typeof this.parse === 'function') {
|
218 | return this.model.__bookshelf_schema.parsers.push(this.parse.bind(this));
|
219 | }
|
220 | };
|
221 |
|
222 | return Field;
|
223 |
|
224 | })();
|
225 |
|
226 | StringField = (function(superClass) {
|
227 | extend(StringField, superClass);
|
228 |
|
229 | function StringField(name, options) {
|
230 | if (!(this instanceof StringField)) {
|
231 | return new StringField(name, options);
|
232 | }
|
233 | StringField.__super__.constructor.call(this, name, options);
|
234 | }
|
235 |
|
236 | StringField.prototype.validations = function() {
|
237 | var result;
|
238 | result = StringField.__super__.validations.apply(this, arguments);
|
239 | this.acceptsRule(result, ['minLength', 'min_length']);
|
240 | this.acceptsRule(result, ['maxLength', 'max_length']);
|
241 | return result;
|
242 | };
|
243 |
|
244 | return StringField;
|
245 |
|
246 | })(Field);
|
247 |
|
248 | EmailField = (function(superClass) {
|
249 | extend(EmailField, superClass);
|
250 |
|
251 | function EmailField(name, options) {
|
252 | if (!(this instanceof EmailField)) {
|
253 | return new EmailField(name, options);
|
254 | }
|
255 | EmailField.__super__.constructor.call(this, name, options);
|
256 | }
|
257 |
|
258 | EmailField.prototype.validations = function() {
|
259 | var result;
|
260 | result = EmailField.__super__.validations.apply(this, arguments);
|
261 | result.push(this._withMessage('email'));
|
262 | return result;
|
263 | };
|
264 |
|
265 | return EmailField;
|
266 |
|
267 | })(StringField);
|
268 |
|
269 | EncryptedString = (function() {
|
270 | function EncryptedString(encrypted, plain1, options1) {
|
271 | var base;
|
272 | this.encrypted = encrypted;
|
273 | this.plain = plain1;
|
274 | this.options = options1 != null ? options1 : {};
|
275 | if ((base = this.options).saltLength == null) {
|
276 | base.saltLength = 16;
|
277 | }
|
278 | }
|
279 |
|
280 | EncryptedString.prototype.encrypt = function() {
|
281 | return this._genSalt(this.options.saltLength).then((function(_this) {
|
282 | return function(salt) {
|
283 | return _this._genHash(_this.plain, salt).then(function(hash) {
|
284 | return _this.encrypted = salt.toString('base64') + '$' + hash.toString('base64');
|
285 | });
|
286 | };
|
287 | })(this));
|
288 | };
|
289 |
|
290 | EncryptedString.prototype.verify = function(value) {
|
291 | var checked, salt;
|
292 | checked = this.encrypted.split('$');
|
293 | salt = new Buffer(checked[0], 'base64');
|
294 | return this._genHash(value, salt).then(function(hash) {
|
295 | return hash.toString('base64') === checked[1];
|
296 | });
|
297 | };
|
298 |
|
299 | EncryptedString.prototype._genSalt = function(length) {
|
300 | var crypto;
|
301 | if (this.options.saltAlgorithm) {
|
302 | return this.options.saltAlgorithm(length, callback);
|
303 | } else {
|
304 | crypto = require('crypto');
|
305 | return new Promise(function(resolve, reject) {
|
306 | return crypto.randomBytes(length, function(err, salt) {
|
307 | if (err) {
|
308 | return reject(err);
|
309 | } else {
|
310 | return resolve(salt);
|
311 | }
|
312 | });
|
313 | });
|
314 | }
|
315 | };
|
316 |
|
317 | EncryptedString.prototype._genHash = function(plain, salt) {
|
318 | var crypto, digest, iterations, keylen;
|
319 | iterations = this.options.iterations || 1000;
|
320 | keylen = this.options.length || 512;
|
321 | if (typeof this.options.algorithm === 'function') {
|
322 | return this.options.algorithm(plain, salt, iterations, keylen);
|
323 | } else {
|
324 | crypto = require('crypto');
|
325 | digest = typeof this.options.algorithm === 'string' ? this.options.algorithm : 'sha256';
|
326 | return new Promise(function(resolve, reject) {
|
327 | return crypto.pbkdf2(plain, salt, iterations, keylen, digest, function(err, hash) {
|
328 | if (err) {
|
329 | return reject(err);
|
330 | } else {
|
331 | return resolve(hash);
|
332 | }
|
333 | });
|
334 | });
|
335 | }
|
336 | };
|
337 |
|
338 | return EncryptedString;
|
339 |
|
340 | })();
|
341 |
|
342 | EncryptedStringField = (function(superClass) {
|
343 | extend(EncryptedStringField, superClass);
|
344 |
|
345 | function EncryptedStringField(name, options) {
|
346 | if (options == null) {
|
347 | options = {};
|
348 | }
|
349 | this._onSaving = bind(this._onSaving, this);
|
350 | if (!(this instanceof EncryptedStringField)) {
|
351 | return new EncryptedStringField(name, options);
|
352 | }
|
353 | EncryptedStringField.__super__.constructor.call(this, name, options);
|
354 | }
|
355 |
|
356 | EncryptedStringField.prototype.validations = function() {
|
357 | var result;
|
358 | result = EncryptedStringField.__super__.validations.call(this);
|
359 | this.acceptsRule(result, ['minLength', 'min_length'], this._validateMinLenghth);
|
360 | this.acceptsRule(result, ['maxLength', 'max_length'], this._validateMaxLenghth);
|
361 | return result;
|
362 | };
|
363 |
|
364 | EncryptedStringField.prototype.initialize = function(instance) {
|
365 | return instance.on('saving', this._onSaving);
|
366 | };
|
367 |
|
368 | EncryptedStringField.prototype.parse = function(attrs, options) {
|
369 | if (attrs[this.name] == null) {
|
370 | return attrs;
|
371 | }
|
372 | attrs[this.name] = new EncryptedString(attrs[this.name], null, this.options);
|
373 | return attrs;
|
374 | };
|
375 |
|
376 | EncryptedStringField.prototype.format = function(attrs, options) {
|
377 | var me;
|
378 | me = attrs[this.name];
|
379 | if (me == null) {
|
380 | return attrs;
|
381 | }
|
382 | if (!(me instanceof EncryptedString && me.encrypted)) {
|
383 | throw new Error("Field @name should be encryted first");
|
384 | }
|
385 | attrs[this.name] = me.encrypted;
|
386 | return attrs;
|
387 | };
|
388 |
|
389 | EncryptedStringField.prototype._validateMinLenghth = function(value, minLength) {
|
390 | if (value instanceof EncryptedString && (value.plain == null)) {
|
391 | return;
|
392 | }
|
393 | value = value instanceof EncryptedString ? value.plain : value;
|
394 | return value.length >= minLength;
|
395 | };
|
396 |
|
397 | EncryptedStringField.prototype._validateMaxLenghth = function(value, maxLength) {
|
398 | if (value instanceof EncryptedString && (value.plain == null)) {
|
399 | return;
|
400 | }
|
401 | value = value instanceof EncryptedString ? value.plain : value;
|
402 | return value.length <= maxLength;
|
403 | };
|
404 |
|
405 | EncryptedStringField.prototype._onSaving = function(instance, attrs, options) {
|
406 | var me;
|
407 | me = attrs[this.name] || instance.attributes[this.name];
|
408 | if (me == null) {
|
409 | return;
|
410 | }
|
411 | if (me instanceof EncryptedString && !me.plain) {
|
412 | return;
|
413 | }
|
414 | if (!(me instanceof EncryptedString)) {
|
415 | me = attrs[this.name] = instance.attributes[this.name] = new EncryptedString(null, me, this.options);
|
416 | }
|
417 | return me.encrypt();
|
418 | };
|
419 |
|
420 | return EncryptedStringField;
|
421 |
|
422 | })(Field);
|
423 |
|
424 | NumberField = (function(superClass) {
|
425 | extend(NumberField, superClass);
|
426 |
|
427 | function NumberField(name, options) {
|
428 | if (!(this instanceof NumberField)) {
|
429 | return new NumberField(name, options);
|
430 | }
|
431 | NumberField.__super__.constructor.call(this, name, options);
|
432 | }
|
433 |
|
434 | NumberField.prototype.validations = function() {
|
435 | var result;
|
436 | result = NumberField.__super__.validations.apply(this, arguments);
|
437 | this.acceptsRule(result, ['greaterThan', 'greater_than', 'gt']);
|
438 | this.acceptsRule(result, ['greaterThanEqualTo', 'greater_than_equal_to', 'gte', 'min']);
|
439 | this.acceptsRule(result, ['lessThan', 'less_than', 'lt']);
|
440 | this.acceptsRule(result, ['lessThanEqualTo', 'less_than_equal_to', 'lte', 'max']);
|
441 | return result;
|
442 | };
|
443 |
|
444 | return NumberField;
|
445 |
|
446 | })(Field);
|
447 |
|
448 | IntField = (function(superClass) {
|
449 | extend(IntField, superClass);
|
450 |
|
451 | function IntField(name, options) {
|
452 | if (!(this instanceof IntField)) {
|
453 | return new IntField(name, options);
|
454 | }
|
455 | IntField.__super__.constructor.call(this, name, options);
|
456 | }
|
457 |
|
458 | IntField.prototype.validations = function() {
|
459 | var result;
|
460 | result = IntField.__super__.validations.apply(this, arguments);
|
461 | this.acceptsRule(result, ['naturalNonZero', 'positive']);
|
462 | this.acceptsRule(result, 'natural');
|
463 | result.unshift(this._withMessage('integer'));
|
464 | return result;
|
465 | };
|
466 |
|
467 | IntField.prototype.parse = function(attrs) {
|
468 | if (attrs[this.name] != null) {
|
469 | return attrs[this.name] = parseInt(attrs[this.name]);
|
470 | }
|
471 | };
|
472 |
|
473 | return IntField;
|
474 |
|
475 | })(NumberField);
|
476 |
|
477 | FloatField = (function(superClass) {
|
478 | extend(FloatField, superClass);
|
479 |
|
480 | function FloatField(name, options) {
|
481 | if (!(this instanceof FloatField)) {
|
482 | return new FloatField(name, options);
|
483 | }
|
484 | FloatField.__super__.constructor.call(this, name, options);
|
485 | }
|
486 |
|
487 | FloatField.prototype.validations = function() {
|
488 | var result;
|
489 | result = FloatField.__super__.validations.apply(this, arguments);
|
490 | result.unshift(this._withMessage('numeric'));
|
491 | return result;
|
492 | };
|
493 |
|
494 | FloatField.prototype.parse = function(attrs) {
|
495 | if (attrs[this.name] != null) {
|
496 | return attrs[this.name] = parseFloat(attrs[this.name]);
|
497 | }
|
498 | };
|
499 |
|
500 | return FloatField;
|
501 |
|
502 | })(NumberField);
|
503 |
|
504 | BooleanField = (function(superClass) {
|
505 | extend(BooleanField, superClass);
|
506 |
|
507 | function BooleanField(name, options) {
|
508 | if (!(this instanceof BooleanField)) {
|
509 | return new BooleanField(name, options);
|
510 | }
|
511 | BooleanField.__super__.constructor.call(this, name, options);
|
512 | }
|
513 |
|
514 | BooleanField.prototype.parse = function(attrs) {
|
515 | if (this.name in attrs) {
|
516 | return attrs[this.name] = !!attrs[this.name];
|
517 | }
|
518 | };
|
519 |
|
520 | BooleanField.prototype.format = function(attrs) {
|
521 | if (this.name in attrs) {
|
522 | return attrs[this.name] = !!attrs[this.name];
|
523 | }
|
524 | };
|
525 |
|
526 | return BooleanField;
|
527 |
|
528 | })(Field);
|
529 |
|
530 | DateTimeField = (function(superClass) {
|
531 | extend(DateTimeField, superClass);
|
532 |
|
533 | function DateTimeField(name, options) {
|
534 | if (!(this instanceof DateTimeField)) {
|
535 | return new DateTimeField(name, options);
|
536 | }
|
537 | DateTimeField.__super__.constructor.call(this, name, options);
|
538 | }
|
539 |
|
540 | DateTimeField.prototype.validations = function() {
|
541 | var result;
|
542 | result = DateTimeField.__super__.validations.apply(this, arguments);
|
543 | result.push(this._withMessage(this._validateDatetime));
|
544 | return result;
|
545 | };
|
546 |
|
547 | DateTimeField.prototype.parse = function(attrs) {
|
548 | if (attrs[this.name] != null) {
|
549 | return attrs[this.name] = new Date(attrs[this.name]);
|
550 | }
|
551 | };
|
552 |
|
553 | DateTimeField.prototype.format = function(attrs) {
|
554 | if ((attrs[this.name] != null) && !(attrs[this.name] instanceof Date)) {
|
555 | return attrs[this.name] = new Date(attrs[this.name]);
|
556 | }
|
557 | };
|
558 |
|
559 | DateTimeField.prototype._validateDatetime = function(value) {
|
560 | if (value instanceof Date) {
|
561 | return true;
|
562 | }
|
563 | if (typeof value === 'string' && !isNaN(Date.parse(value))) {
|
564 | return true;
|
565 | }
|
566 | return false;
|
567 | };
|
568 |
|
569 | return DateTimeField;
|
570 |
|
571 | })(Field);
|
572 |
|
573 | DateField = (function(superClass) {
|
574 | extend(DateField, superClass);
|
575 |
|
576 | function DateField(name, options) {
|
577 | if (!(this instanceof DateField)) {
|
578 | return new DateField(name, options);
|
579 | }
|
580 | DateField.__super__.constructor.call(this, name, options);
|
581 | }
|
582 |
|
583 | DateField.prototype.parse = function(attrs) {
|
584 | var d;
|
585 | if (attrs[this.name] != null) {
|
586 | d = new Date(attrs[this.name]);
|
587 | return attrs[this.name] = new Date(d.getFullYear(), d.getMonth(), d.getDate());
|
588 | }
|
589 | };
|
590 |
|
591 | DateField.prototype.format = function(attrs) {
|
592 | var d;
|
593 | if (attrs[this.name] != null) {
|
594 | d = !(attrs[this.name] instanceof Date) ? new Date(attrs[this.name]) : attrs[this.name];
|
595 | return attrs[this.name] = new Date(d.getFullYear(), d.getMonth(), d.getDate());
|
596 | }
|
597 | };
|
598 |
|
599 | return DateField;
|
600 |
|
601 | })(DateTimeField);
|
602 |
|
603 | JSONField = (function(superClass) {
|
604 | extend(JSONField, superClass);
|
605 |
|
606 | function JSONField(name, options) {
|
607 | if (!(this instanceof JSONField)) {
|
608 | return new JSONField(name, options);
|
609 | }
|
610 | JSONField.__super__.constructor.call(this, name, options);
|
611 | }
|
612 |
|
613 | JSONField.prototype.validations = function() {
|
614 | var result;
|
615 | result = JSONField.__super__.validations.apply(this, arguments);
|
616 | result.push(this._withMessage(this._validateJSON));
|
617 | return result;
|
618 | };
|
619 |
|
620 | JSONField.prototype.format = function(attrs) {
|
621 | if (!(attrs[this.name] && typeof attrs[this.name] === 'object')) {
|
622 | return;
|
623 | }
|
624 | return attrs[this.name] = JSON.stringify(attrs[this.name]);
|
625 | };
|
626 |
|
627 | JSONField.prototype.parse = function(attrs) {
|
628 | if (!(attrs[this.name] && typeof attrs[this.name] === 'string')) {
|
629 | return;
|
630 | }
|
631 | return attrs[this.name] = JSON.parse(attrs[this.name]);
|
632 | };
|
633 |
|
634 | JSONField.prototype._validateJSON = function(value) {
|
635 | if (typeof value === 'object') {
|
636 | return true;
|
637 | }
|
638 | if (typeof value !== 'string') {
|
639 | return false;
|
640 | }
|
641 | JSON.parse(value);
|
642 | return true;
|
643 | };
|
644 |
|
645 | return JSONField;
|
646 |
|
647 | })(Field);
|
648 |
|
649 | module.exports = {
|
650 | Field: Field,
|
651 | StringField: StringField,
|
652 | EmailField: EmailField,
|
653 | EncryptedStringField: EncryptedStringField,
|
654 | NumberField: NumberField,
|
655 | IntField: IntField,
|
656 | FloatField: FloatField,
|
657 | BooleanField: BooleanField,
|
658 | DateTimeField: DateTimeField,
|
659 | DateField: DateField,
|
660 | JSONField: JSONField
|
661 | };
|