UNPKG

45.3 kBJavaScriptView Raw
1(function () {
2
3 ejs = require('ejs');
4 inflect = require('i')();
5 async = require('async');
6 _ = require('lodash');
7 when = require('when');
8 crypto = require('crypto');
9 toRegexp = require('path-to-regexp');
10
11 var MIN_MONGOOSE_VERSION = '3.6.15';
12
13 var mongoose, ObjectId, acreAdminModel;
14
15 var createVerb = 'put';
16 var updateVerb = 'post';
17
18 function UserCallback (operation, mode, path, callback)
19 {
20 this.operation = operation;
21 this.mode = mode;
22 this.path = path;
23 this.callback = callback;
24 }
25
26 function DynamicRouteData (viewPath, contentType, options, modelName)
27 {
28 this.viewPath = viewPath;
29 this.contentType = contentType;
30 this.options = _.cloneDeep(options);
31 this.modelName = modelName;
32 }
33
34 var userCallbacks = [];
35 var dynamicRouteData = {};
36
37 var getUserCallback = function(operation, mode, path){
38 for (var i in userCallbacks)
39 {
40 var userCallback = userCallbacks[i];
41 if (userCallback.operation === operation && userCallback.mode === mode && userCallback.path.test(path))
42 {
43 return userCallback.callback;
44 }
45 }
46 return null;
47 };
48
49 var setRESTPaths = function (app, path, Model)
50 {
51 var preCreate = getUserCallback(acre.CREATE, acre.PRE, path);
52 var preRetrieve = getUserCallback(acre.RETRIEVE, acre.PRE, path);
53 var preUpdate = getUserCallback(acre.UPDATE, acre.PRE, path);
54 var preDelete = getUserCallback(acre.REMOVE, acre.PRE, path);
55
56 var postCreate = getUserCallback(acre.CREATE, acre.POST, path);
57 var postRetrieve = getUserCallback(acre.RETRIEVE, acre.POST, path);
58 var postUpdate = getUserCallback(acre.UPDATE, acre.POST, path);
59 var postDelete = getUserCallback(acre.REMOVE, acre.POST, path);
60
61 var overrideCreate = getUserCallback(acre.CREATE, acre.OVERRIDE, path);
62 var overrideRetrieve = getUserCallback(acre.RETRIEVE, acre.OVERRIDE, path);
63 var overrideUpdate = getUserCallback(acre.UPDATE, acre.OVERRIDE, path);
64 var overrideDelete = getUserCallback(acre.REMOVE, acre.OVERRIDE, path);
65
66 var createPrivate = getUserCallback(acre.CREATE, acre.PRIVATE, path);
67 var retrievePrivate = getUserCallback(acre.RETRIEVE, acre.PRIVATE, path);
68 var updatePrivate = getUserCallback(acre.UPDATE, acre.PRIVATE, path);
69 var deletePrivate = getUserCallback(acre.REMOVE, acre.PRIVATE, path);
70
71 console.log("setting up CRUD REST paths for: " + path);
72
73 // Create
74 if (overrideCreate)
75 {
76 console.log(createVerb.toUpperCase() + " is user overriden for path: " + path);
77 }
78 else
79 {
80 var middleware = [];
81 if (createPrivate)
82 {
83 middleware.push(forcePrivateAccess);
84 }
85
86 if (preCreate)
87 {
88 middleware.push(preCreate);
89 }
90
91 if (middleware.length)
92 {
93 app[createVerb](path, middleware, function(request, response){
94 create(Model, request, response, postCreate);
95 });
96 }
97 else
98 {
99 app[createVerb](path, function(request, response){
100 create(Model, request, response, postCreate);
101 });
102 }
103 }
104
105 // Retrieve
106 if (overrideRetrieve)
107 {
108 console.log("GET is user overriden for path: " + path);
109 }
110 else
111 {
112 var middleware = [];
113 if (retrievePrivate)
114 {
115 middleware.push(forcePrivateAccess);
116 }
117
118 if (preRetrieve)
119 {
120 middleware.push(preRetrieve);
121 }
122
123 if (middleware.length)
124 {
125 app.get(path, middleware, function(request, response){
126 retrieve(Model, request, response, postRetrieve);
127 });
128 }
129 else
130 {
131 app.get(path, function(request, response){
132 retrieve(Model, request, response, postRetrieve);
133 });
134 }
135 }
136
137 // Update
138 if (overrideUpdate)
139 {
140 console.log(updateVerb.toUpperCase() + " is user overriden for path: " + path);
141 }
142 else
143 {
144 var middleware = [];
145 if (updatePrivate)
146 {
147 middleware.push(forcePrivateAccess);
148 }
149
150 if (preUpdate)
151 {
152 middleware.push(preUpdate);
153 }
154
155 if (middleware.length)
156 {
157 app[updateVerb](path, middleware, function(request, response){
158 update(Model, request, response, postUpdate);
159 });
160 }
161 else
162 {
163 app[updateVerb](path, function(request, response){
164 update(Model, request, response, postUpdate);
165 });
166 }
167 }
168
169 // Delete
170 if (overrideDelete)
171 {
172 console.log("DELETE is user overriden for path: " + path);
173 }
174 else
175 {
176 var middleware = [];
177 if (deletePrivate)
178 {
179 middleware.push(forcePrivateAccess);
180 }
181
182 if (preDelete)
183 {
184 middleware.push(preDelete);
185 }
186
187 if (middleware.length)
188 {
189 app["delete"](path, middleware, function(request, response){
190 remove(Model, request, response, postDelete);
191 });
192 }
193 else
194 {
195 app["delete"](path, function(request, response){
196 remove(Model, request, response, postDelete);
197 });
198 }
199 }
200 };
201
202 var findModel = function(modelName)
203 {
204 for (i in mongoose.models)
205 {
206 var Model = mongoose.models[i];
207 if (Model.modelName === modelName)
208 {
209 return when.resolve(Model);
210 }
211 }
212
213 return when.reject('failed to find model: ' + modelName);
214 };
215
216 var normalizePath = function(path)
217 {
218 var _path = path.replace(new RegExp('^' + acre.options.rootPath), '');
219 if (_path.charAt(0) !== '/')
220 {
221 _path = '/' + _path;
222 }
223
224 return _path;
225 };
226
227 var storeForeignKeys = function(Models)
228 {
229 var i, result = {};
230 var _storeForeignKeys = function(tree, path, Model)
231 {
232 var i;
233 for (i in tree)
234 {
235 if (i === 'id' || i === '_id')
236 {
237 continue;
238 }
239
240 var _path = (path === '') ? i : path + '.' + i;
241
242 if (
243 (_.isPlainObject(tree[i]) && !_.isUndefined(tree[i].ref)) ||
244 (_.isArray(tree[i]) && tree[i].length === 1 && !_.isUndefined(tree[i][0].ref))
245 )
246 {
247 if (_.isUndefined(acre.foreignKeys[Model.modelName]))
248 {
249 acre.foreignKeys[Model.modelName] = {};
250 }
251
252 acre.foreignKeys[Model.modelName][_path] = (_.isArray(tree[i])) ? tree[i][0].ref : tree[i].ref;
253 }
254 else if (_.isPlainObject(tree[i]))
255 {
256 _storeForeignKeys(tree[i], _path, Model);
257 }
258 else if (_.isArray(tree[i]))
259 {
260 _storeForeignKeys(tree[i][0], _path, Model);
261 }
262 }
263 };
264
265 for (i in Models)
266 {
267 _storeForeignKeys(Models[i].schema.tree, '', Models[i]);
268 }
269 };
270
271 var initializeAdminModel = function(app)
272 {
273 var AcreAdminSchema = new mongoose.Schema({
274 username: {
275 type: String,
276 required: true,
277 unique: true,
278 index: true,
279 validate: [/[\s\S]/, 'admin username cannot be empty']
280 },
281 password: {
282 type: String,
283 required: true,
284 validate: [/[\s\S]/, 'admin password cannot be empty']
285 }
286 });
287
288 acreAdminModel = mongoose.model('AcreAdmin', AcreAdminSchema);
289
290 acre.private(acre.CREATE, [
291 acre.options.rootPath + '/acreadmins/:id/username',
292 acre.options.rootPath + '/acreadmins/:id/password',
293 acre.options.rootPath + '/acreadmins/:id',
294 acre.options.rootPath + '/acreadmins'
295 ]);
296 acre.private(acre.RETRIEVE, [
297 acre.options.rootPath + '/acreadmins/:id/username',
298 acre.options.rootPath + '/acreadmins/:id/password',
299 acre.options.rootPath + '/acreadmins/:id',
300 acre.options.rootPath + '/acreadmins'
301 ]);
302 acre.private(acre.UPDATE, [
303 acre.options.rootPath + '/acreadmins/:id/username',
304 acre.options.rootPath + '/acreadmins/:id/password',
305 acre.options.rootPath + '/acreadmins/:id',
306 acre.options.rootPath + '/acreadmins'
307 ]);
308 acre.private(acre.REMOVE, [
309 acre.options.rootPath + '/acreadmins/:id/username',
310 acre.options.rootPath + '/acreadmins/:id/password',
311 acre.options.rootPath + '/acreadmins/:id',
312 acre.options.rootPath + '/acreadmins'
313 ]);
314 acre.pre(acre.CREATE, acre.options.rootPath + '/acreadmins', function(request, response, next){
315 if (request.body && request.body.password)
316 {
317 request.body.password = crypto.createHmac('sha256', acre.options.adminPasswordSalt).update(request.body.password).digest('hex');
318 }
319 next();
320 });
321
322 app.post(acre.options.rootPath + '/acreadmins/login', function(request, response){
323 adminLogin(request)
324 .then(function(){
325 respondSuccess(response, 'login successful');
326 }, function(err){
327 console.log('login err: ');
328 console.log(err);
329 respondError(response, 401, 'Unauthorized');
330 });
331 });
332
333 app.post(acre.options.rootPath + '/acreadmins/logout', function(request, response){
334 adminLogout(request);
335 respondSuccess(response, 'logout successful');
336 });
337
338 app.get(acre.options.rootPath + '/acreadmins/test', forcePrivateAccess, function(request, response){
339 respondSuccess(response, '200 OK');
340 });
341 };
342
343 var adminLogin = function(request)
344 {
345 var deferred = when.defer();
346
347 if (_.isUndefined(request.session))
348 {
349 deferred.reject('acre admin portal requires express session middleware');
350 }
351 else
352 {
353 adminLogout(request);
354 acreAdminModel.find(function(error, admins){
355 if (error)
356 {
357 deferred.reject(error);
358 }
359 else
360 {
361 // if there are no admins, permit anyone to see the admin portal
362 // this way admins can be configured
363 if (_.isArray(admins))
364 {
365 var i = 0, found = false;
366 for (i in admins)
367 {
368 if (admins[i].username === request.body.username)
369 {
370 if (admins[i].password === crypto.createHmac('sha256', acre.options.adminPasswordSalt).update(request.body.password).digest('hex'))
371 {
372 request.session.acre_admin_id = admins[i]._id.toString();
373 found = true;
374 }
375 break;
376 }
377 }
378
379 if (found)
380 {
381 deferred.resolve();
382 }
383 else
384 {
385 deferred.reject('login failed');
386 }
387 }
388 else
389 {
390 deferred.reject('login failed');
391 }
392 }
393 });
394 }
395
396 return deferred.promise;
397 };
398
399 var adminLogout = function(request)
400 {
401 if (!_.isUndefined(request.session))
402 {
403 delete request.session.acre_admin_id;
404 }
405 };
406
407 var forcePrivateAccess = function(request, response, next)
408 {
409 if (_.isUndefined(request.session))
410 {
411 respondError(response, 400, 'acre admin portal requires express session middleware');
412 }
413 else
414 {
415 acreAdminModel.find(function(error, admins){
416 if (error)
417 {
418 respondError(response, 500, error.message);
419 }
420 else
421 {
422 // if there are no admins, permit anyone to see the admin portal
423 // this way admins can be configured
424 if (!_.isArray(admins) || admins.length == 0)
425 {
426 next();
427 }
428 else
429 {
430 if (!_.isUndefined(request.session.acre_admin_id))
431 {
432 var i = 0, found = false;
433 for (i in admins)
434 {
435 if (admins[i]._id.toString() === request.session.acre_admin_id)
436 {
437 found = true;
438 break;
439 }
440 }
441
442 if (found)
443 {
444 next();
445 }
446 else
447 {
448 respondError(response, 401, 'Unauthorized');
449 }
450 }
451 else
452 {
453 respondError(response, 401, 'Unauthorized');
454 }
455 }
456 }
457 });
458 }
459 };
460
461 /**
462 * Given a model, composes a default instantiation of a mongoose model
463 * @param Model - mongoose model descriptor
464 * @returns - an array containing default instantiated model, and a version of the default instantiated model for form generation
465 */
466 var composeDefaultInstance = function(Model)
467 {
468 var modelInstance, formInstance, i, j, k;
469
470 /*
471 * strips the tree down to bare data, leaf elements will either be
472 * empty strings ("") or a list of options in the case of a ref
473 * field
474 */
475 var stripTree = function(tree, path)
476 {
477 var i, result = {};
478 for (i in tree)
479 {
480 if (i === 'id' || i === '_id')
481 {
482 continue;
483 }
484
485 var _path = (path === '') ? i : path + '.' + i;
486
487 if (
488 (_.isPlainObject(tree[i]) && !_.isUndefined(tree[i].ref)) ||
489 (_.isArray(tree[i]) && tree[i].length === 1 && !_.isUndefined(tree[i][0].ref))
490 )
491 {
492 result[i] = (_.isArray(tree[i])) ? [] : '';
493 }
494 else if (_.isPlainObject(tree[i]))
495 {
496 result[i] = stripTree(tree[i], _path);
497 }
498 else if (_.isArray(tree[i]))
499 {
500 if (i === 'validate')
501 {
502 continue;
503 }
504 else
505 {
506 result[i] = [stripTree(tree[i][0], _path)];
507 }
508 }
509 }
510
511 if (Object.keys(result).length === 0)
512 {
513 return "";
514 }
515
516 return result;
517 };
518
519 var tree = _.cloneDeep(Model.schema.tree);
520 modelInstance = stripTree(tree, '');
521 formInstance = _.cloneDeep(modelInstance);
522
523 if (_.isUndefined(acre.foreignKeys) ||
524 Object.keys(acre.foreignKeys).length === 0 ||
525 _.isUndefined(acre.foreignKeys[Model.modelName]) ||
526 Object.keys(acre.foreignKeys[Model.modelName]).length === 0)
527 {
528 return when.resolve([modelInstance, formInstance]);
529 }
530 else
531 {
532 var foreignKeys = Object.keys(acre.foreignKeys[Model.modelName]), defereds = {}, defered = when.defer();
533 _.each(foreignKeys, function(key){
534 var defered = when.defer();
535 defereds[key] = defered;
536 findModel(acre.foreignKeys[Model.modelName][key])
537 .then(
538 function(_Model){
539 _Model.find(function(error, models){
540 if (error)
541 {
542 defered.reject('failed to fetch models for ref: ' + _Model.modelName);
543 }
544 else
545 {
546 if (!models)
547 {
548 models = [];
549 }
550
551 var crumbs = key.split('.');
552 var ref = formInstance, obj, field;
553
554 for (j = 0; j < crumbs.length; j++)
555 {
556 var crumb = crumbs[j];
557 if (j === crumbs.length - 1)
558 {
559 if (_.isArray(ref[crumb]))
560 {
561 obj = ref[crumb];
562 field = 0;
563 }
564 else
565 {
566 obj = ref;
567 field = crumb;
568 }
569 break;
570 }
571 else
572 {
573 if (_.isPlainObject(ref[crumb]))
574 {
575 ref = ref[crumb];
576 }
577 else if (_.isArray(ref[crumb]))
578 {
579 ref = ref[crumb][0];
580 }
581 else
582 {
583 defered.reject('cannot handle object type: ' + typeof(ref[crumb]));
584 break;
585 }
586 }
587 }
588
589 if (_.isUndefined(obj) || _.isUndefined(field))
590 {
591 defered.reject('failed to find ref for ' + _Model.modelName + ' in: ' + JSON.stringify(formInstance));
592 }
593 else
594 {
595 if (_.isArray(obj[field]))
596 {
597 obj[field] = [{
598 type: 'select',
599 options: models
600 }];
601 }
602 else
603 {
604 obj[field] = {
605 type: 'select',
606 options: models
607 };
608 }
609
610 defered.resolve();
611 }
612 }
613 });
614 },
615 function(err){
616 defered.reject(err);
617 }
618 );
619 });
620
621 var promises = _.values(defereds).map(function(defered){
622 return defered.promise;
623 });
624
625 when.all(promises)
626 .then(
627 function(){
628 defered.resolve([modelInstance, formInstance]);
629 },
630 function(err){
631 defered.reject(err);
632 }
633 );
634
635 return defered.promise;
636 }
637 };
638
639 var heirarchy = {
640 ROOT: "HEIRARCHY_ROOT",
641 BRANCH: "HEIRARCHY_BRANCH",
642 LEAF: "HEIRARCHY_LEAF"
643 };
644
645 var traverseObjectHeirarchy = function(model, request, response, callback){
646 if (!model || !model._id)
647 {
648 respondError(response, 500, "invalid model");
649 callback("invalid model", null, null);
650 }
651 else
652 {
653 var path = normalizePath(request.route.path);
654 var keys = path.split("/");
655
656 //remove first three elements
657 //these correspond to blank(""), model and its id
658 keys.shift();
659 keys.shift();
660 keys.shift();
661
662 var traverseSubObjectHeirarchy = function(_object, keys, callback)
663 {
664 var key, param, i, type, found;
665
666 if (keys.length === 0)
667 {
668 // return root resource
669 console.log("returning root resource");
670 callback(null, heirarchy.ROOT, _object, null);
671 }
672 else if (keys.length === 1)
673 {
674 // returning nested array or object
675 if (keys[0].indexOf(":") === 0)
676 {
677 key = keys[0].replace(":", "");
678 param = request.params[key];
679 if (Object.prototype.toString.call(_object) === '[object Array]')
680 {
681 found = false;
682 for (i in _object)
683 {
684 if ((_object[i] instanceof ObjectId && _object[i] == param) || _object[i]._id == param)
685 {
686 console.log("returning nested array object");
687 type = Object.prototype.toString.call(_object[i]);
688 switch (type)
689 {
690 case '[object Object]':
691 if (_object[i] instanceof ObjectId)
692 {
693 callback(null, heirarchy.LEAF, _object, i);
694 }
695 else
696 {
697 callback(null, heirarchy.BRANCH, _object, i);
698 }
699 break;
700
701 case '[object String]':
702 case '[object Date]':
703 case '[object Number]':
704 case '[object Boolean]':
705 callback(null, heirarchy.LEAF, _object, i);
706 break;
707
708 default:
709 respondError(response, 400, "nested property of type: " + type + " not supported");
710 callback("nested property of type: " + type + " not supported", null, null);
711 break;
712 }
713
714 found = true;
715 break;
716 }
717 }
718
719 if (!found)
720 {
721 console.log("object with id: " + param + " doesnt exist in array");
722 respondError(response, 404, "target object not found");
723 callback("object with id: " + param + " doesnt exist in array", null, null);
724 }
725 }
726 else
727 {
728 console.log("current item isnt an array, 400");
729 respondError(response, 400, "requested array operation on non array resource");
730 callback("requested array operation on non array resource, 400", null, null);
731 }
732 }
733 // fetching nested property
734 else
735 {
736 if (Object.prototype.toString.call(_object) === '[object Object]')
737 {
738 type = Object.prototype.toString.call(_object[keys[0]]);
739 switch (type)
740 {
741 case '[object Object]':
742 console.log("returning object property");
743 if(_object[keys[0]] instanceof ObjectId)
744 {
745 // represents an id, so actually a leaf
746 callback(null, heirarchy.LEAF, _object, keys[0]);
747 }
748 else
749 {
750 callback(null, heirarchy.BRANCH, _object, keys[0]);
751 }
752 break;
753
754 case '[object Array]':
755 console.log("returning object property");
756 callback(null, heirarchy.BRANCH, _object, keys[0]);
757 break;
758
759 case '[object String]':
760 case '[object Date]':
761 case '[object Number]':
762 case '[object Boolean]':
763 // If this is a legacy object in the database which doesnt have the new property
764 // that is being updated, we will get 'Undefined' here. In this case, allow client
765 // to update the undefined property
766 case '[object Undefined]':
767 console.log("returning object property");
768 callback(null, heirarchy.LEAF, _object, keys[0]);
769 break;
770
771 default:
772 console.log("nested property of type: " + type + " not supported");
773 respondError(response, 400, "nested property of type: " + type + " not supported");
774 callback("nested property of type: " + type + " not supported", null, null);
775 break;
776 }
777 }
778 else
779 {
780 console.log("asked for property but no object found");
781 respondError(response, 400, "bad request");
782 callback("asked for property but no object found", null, null);
783 }
784 }
785 }
786 else
787 {
788 if (keys[0].indexOf(":") === 0)
789 {
790 key = keys[0].replace(":", "");
791 param = request.params[key];
792 if (Object.prototype.toString.call(_object) === '[object Array]')
793 {
794 found = false;
795 for (i in _object)
796 {
797 if (_object[i]._id == param)
798 {
799 console.log("recursing on nested array object");
800 traverseSubObjectHeirarchy(_object[i], keys.slice(1), callback);
801 found = true;
802 break;
803 }
804 }
805
806 if (!found)
807 {
808 console.log("uri specifies id, but array has no object with that id");
809 respondError(response, 404, "target object not found");
810 callback("uri specifies id, but array has no object with that id", null, null);
811 }
812 }
813 else if (_.isPlainObject(_object))
814 {
815 if (_object._id == param)
816 {
817 console.log("recursing on nested object");
818 traverseSubObjectHeirarchy(_object, keys.slice(1), callback);
819 }
820 else
821 {
822 console.log("asked for object with id that doesnt match");
823 respondError(response, 400, "bad request");
824 callback("asked for object with id that doesnt match", null, null);
825 }
826 }
827 else
828 {
829 console.log("uri specifies id, but object is neither array nor object");
830 respondError(response, 400, "bad request");
831 callback("uri specifies id, but object is neither array nor object", null, null);
832 }
833 }
834 else
835 {
836 if (!_object[keys[0]])
837 {
838 console.log("failed to find key: " + keys[0] + " in object");
839 respondError(response, 400, "bad request");
840 callback("failed to find key: " + keys[0] + " in object", null, null);
841 }
842 else
843 {
844 console.log("recursing with object at key: " + keys[0]);
845 traverseSubObjectHeirarchy(_object[keys[0]], keys.slice(1), callback);
846 }
847 }
848 }
849 };
850
851 traverseSubObjectHeirarchy(model, keys, callback);
852 }
853 };
854
855 var acre = {};
856
857 acre.options = {
858 rootPath: '',
859 putIsCreate: true,
860 adminPortal: true,
861 adminRoute: '/admin',
862 appName: 'Acre App',
863 adminPasswordSalt: '87fc9f12e96ad14e1899ff5e429d5b9066cb3aecb80bc62d52967662590dacd7'
864 };
865
866 acre.foreignKeys = {};
867
868 acre.CREATE = "acre.CREATE";
869 acre.RETRIEVE = "acre.RETRIEVE";
870 acre.UPDATE = "acre.UPDATE";
871 acre.REMOVE = "acre.REMOVE";
872
873 acre.PRE = "acre.PRE";
874 acre.POST = "acre.POST";
875 acre.OVERRIDE = "acre.OVERRIDE";
876 acre.PRIVATE = "acre.PRIVATE";
877
878 acre.bodyParser = function(app)
879 {
880 app.use(function(req, res, next){
881 if (req.is('text/*'))
882 {
883 req.text = '';
884 req.setEncoding('utf8');
885 req.on('data', function(chunk){
886 req.text += chunk;
887 });
888 req.on('end', next);
889 }
890 else
891 {
892 next();
893 }
894 });
895 };
896
897 acre.init = function(_mongoose, app, options)
898 {
899 /*
900 * initialize mongoose
901 */
902 mongoose = _mongoose;
903 ObjectId = mongoose.Types.ObjectId;
904
905 if (mongoose.version !== MIN_MONGOOSE_VERSION)
906 {
907 return when.reject('using incompatible mongoose version: ' + mongoose.version + ', acre requires: ' + MIN_MONGOOSE_VERSION);
908 }
909
910 /*
911 * initialize acre options
912 */
913 _.merge(this.options, options);
914
915 if (!this.options.putIsCreate)
916 {
917 createVerb = "post";
918 updateVerb = "put";
919 }
920
921 /*
922 * setup acre admin model
923 */
924 initializeAdminModel(app);
925
926
927 var promises = [], models = _.values(mongoose.models);
928
929 /*
930 * store foreign keys
931 */
932 storeForeignKeys(models);
933
934 /*
935 * setup REST CRUD paths for models
936 */
937 promises.push(serveModels(app, models));
938
939 /*
940 * setup admin portal
941 */
942 if (this.options.adminPortal)
943 {
944 promises.push(bindAdminRoutes(app, models));
945 }
946
947 return when.all(promises);
948 };
949
950 acre.pre = function(operation, path, callback)
951 {
952 var _path = toRegexp(path);
953 if ([acre.CREATE, acre.RETRIEVE, acre.UPDATE, acre.REMOVE].indexOf(operation) !== -1)
954 {
955 var userCallback = new UserCallback(operation, acre.PRE, _path, callback);
956 userCallbacks.push(userCallback);
957 }
958 else
959 {
960 throw operation + " is not a valid acre operation, must use one of: acre.CREATE, acre.RETRIEVE, acre.UPDATE, or acre.REMOVE";
961 }
962 };
963
964 acre.post = function(operation, path, callback)
965 {
966 var _path = toRegexp(path);
967 if ([acre.CREATE, acre.RETRIEVE, acre.UPDATE, acre.REMOVE].indexOf(operation) !== -1)
968 {
969 var userCallback = new UserCallback(operation, acre.POST, _path, callback);
970 userCallbacks.push(userCallback);
971 }
972 else
973 {
974 throw operation + " is not a valid acre operation, must use one of: acre.CREATE, acre.RETRIEVE, acre.UPDATE, or acre.REMOVE";
975 }
976 };
977
978 acre.override = function(operation, paths)
979 {
980 if (Object.prototype.toString.call(paths) !== '[object Array]')
981 {
982 paths = [paths];
983 }
984
985 for (var i in paths)
986 {
987 var path = toRegexp(paths[i]);
988 if ([acre.CREATE, acre.RETRIEVE, acre.UPDATE, acre.REMOVE].indexOf(operation) !== -1)
989 {
990 var userCallback = new UserCallback(operation, acre.OVERRIDE, path, true);
991 userCallbacks.push(userCallback);
992 }
993 else
994 {
995 throw operation + " is not a valid acre operation, must use one of: acre.CREATE, acre.RETRIEVE, acre.UPDATE, or acre.REMOVE";
996 }
997 }
998 };
999
1000 acre.private = function(operation, paths)
1001 {
1002 if (Object.prototype.toString.call(paths) !== '[object Array]')
1003 {
1004 paths = [paths];
1005 }
1006
1007 for (var i in paths)
1008 {
1009 var path = toRegexp(paths[i]);
1010 if ([acre.CREATE, acre.RETRIEVE, acre.UPDATE, acre.REMOVE].indexOf(operation) !== -1)
1011 {
1012 var userCallback = new UserCallback(operation, acre.PRIVATE, path, true);
1013 userCallbacks.push(userCallback);
1014 }
1015 else
1016 {
1017 throw operation + " is not a valid acre operation, must use one of: acre.CREATE, acre.RETRIEVE, acre.UPDATE, or acre.REMOVE";
1018 }
1019 }
1020 };
1021
1022 var serveModels = function(app, Models)
1023 {
1024 var bindRoutes = function (uriPath, schemaPaths, resourceIsArray, Model) {
1025 var processedObjects = [];
1026 var resource = uriPath.split("/").pop();
1027 var nestedUriPath;
1028 if (resourceIsArray)
1029 {
1030 var modelName = (isModelRootPath(Model, uriPath)) ? Model.modelName : inflect.singularize(resource).toLowerCase();
1031 nestedUriPath = uriPath + "/:" + modelName + "_id";
1032 }
1033 else
1034 {
1035 nestedUriPath = uriPath;
1036 }
1037
1038 for (var path in schemaPaths)
1039 {
1040 var propertyDesc = Model.schema.paths[path];
1041 if (String(path).indexOf(".") !== -1)
1042 {
1043 // nested object detected
1044 var objName = path.split(".")[0];
1045 if (processedObjects.indexOf(objName) === -1)
1046 {
1047 var newPaths = {};
1048 for (var _path in schemaPaths)
1049 {
1050 if (_path.indexOf(objName+".") === 0)
1051 {
1052 newPaths[_path.replace(objName+".", "")] = schemaPaths[_path];
1053 }
1054 }
1055 processedObjects.push(objName);
1056 bindRoutes(nestedUriPath + "/" + objName, newPaths, false, Model);
1057 }
1058 }
1059 else if (schemaPaths[path].schema)
1060 {
1061 // nested array of objects detected
1062 bindRoutes(nestedUriPath + "/" + path, schemaPaths[path].schema.paths, true, Model);
1063 }
1064 else
1065 {
1066 // we do not want to expose any mods to _id attribute
1067 if (path !== '_id' && path !== '__v')
1068 {
1069 // property detected
1070 setRESTPaths(app, nestedUriPath + "/" + path, Model);
1071
1072 if (!_.isUndefined(propertyDesc) && !_.isUndefined(propertyDesc.options))
1073 {
1074 if (_.isArray(propertyDesc.options.type) && propertyDesc.options.type.length > 0)
1075 {
1076 if (!_.isUndefined(propertyDesc.options.type[0].ref))
1077 {
1078 // add REST paths for refs
1079 setRESTPaths(app, nestedUriPath + "/" + path + "/:" + propertyDesc.options.type[0].ref + "_id", Model);
1080 }
1081 else
1082 {
1083 // add REST paths for array of plain strings (this cld be ObjectIds, or strings)
1084 setRESTPaths(app, nestedUriPath + "/" + path + "/:" + inflect.singularize(path) + "_id", Model);
1085 }
1086 }
1087 }
1088 }
1089 }
1090 }
1091
1092 /**
1093 * CRUD for root resource
1094 */
1095 setRESTPaths(app, nestedUriPath, Model);
1096
1097 if (nestedUriPath != uriPath)
1098 {
1099 /**
1100 * CRUD for root
1101 */
1102 setRESTPaths(app, uriPath, Model);
1103 }
1104 };
1105
1106 for (var i in Models)
1107 {
1108 var Model = Models[i];
1109 console.log('serving model: ' + Model.modelName);
1110 var path = acre.options.rootPath + "/" + Model.collection.name;
1111 bindRoutes(path, Model.schema.paths, true, Model);
1112 }
1113
1114 return when.resolve();
1115 };
1116
1117 var bindAdminRoutes = function(app, models)
1118 {
1119 var i, j, k, routePath, viewPath;
1120 var jsPaths = [], cssPaths = [], _models = [];
1121
1122 var serveRoute = function()
1123 {
1124 return function(request, response){
1125 var routeData = dynamicRouteData[request.path];
1126 if (_.isUndefined(routeData))
1127 {
1128 respondError(response, 500, 'failed to find data for route: ' + request.originalUrl);
1129 }
1130 else
1131 {
1132 var viewPath = routeData.viewPath,
1133 contentType = routeData.contentType,
1134 options = routeData.options,
1135 modelName = routeData.modelName;
1136 function renderFile(options)
1137 {
1138 ejs.renderFile(viewPath, options, function(error, page){
1139 if (error)
1140 {
1141 respondError(response, 500, 'ejs failed to render page: " + viewPath + ", error: ' + error);
1142 }
1143 else
1144 {
1145 response.set('Content-Type', contentType);
1146 response.send(page);
1147 }
1148 });
1149 }
1150
1151 if (modelName)
1152 {
1153 // we have to load this everytime
1154 findModel(modelName)
1155 .then(
1156 function(Model){
1157 composeDefaultInstance(Model)
1158 .then(
1159 function(instances){
1160 options.instance = instances[0];
1161 options.formDesc = instances[1];
1162 renderFile(options);
1163 },
1164 function(err){
1165 respondError(response, 500, 'failed to compose default instance for: ' + modelName + ', err: ' + err);
1166 }
1167 );
1168 },
1169 function(err){
1170 respondError(response, 500, 'failed to find model: ' + modelName + ', err: ' + err);
1171 }
1172 );
1173 }
1174 else
1175 {
1176 renderFile(options);
1177 }
1178 }
1179 };
1180 };
1181
1182 var serveFile = function(path)
1183 {
1184 app.get(acre.options.adminRoute + path, function(request, response){
1185 response.sendfile(__dirname + '/portal/public' + path);
1186 });
1187
1188 return acre.options.adminRoute + path;
1189 };
1190
1191 cssPaths.push(serveFile('/css/bootstrap-combined.min.css'));
1192 cssPaths.push(serveFile('/css/acre.css'));
1193 jsPaths.push(serveFile('/js/angular.min.js'));
1194 jsPaths.push(serveFile('/js/angular-resource.min.js'));
1195 jsPaths.push(serveFile('/js/ui-bootstrap-tpls-0.3.0.min.js'));
1196 jsPaths.push(serveFile('/js/lodash.min.js'));
1197 jsPaths.push(serveFile('/js/restangular.min.js'));
1198 jsPaths.push(serveFile('/js/ng-form-direct.js'));
1199 jsPaths.push(serveFile('/js/ng-object-view.js'));
1200 jsPaths.push(serveFile('/js/inflect.min.js'));
1201 jsPaths.push(serveFile('/js/controller-utils.js'));
1202
1203 for (i in models)
1204 {
1205 var modelName = models[i].modelName;
1206 var createRoutePath = acre.options.adminRoute + "/" + modelName + "-create.html";
1207 var retrieveRoutePath = acre.options.adminRoute + "/" + modelName + "-retrieve.html";
1208 var updateRoutePath = acre.options.adminRoute + "/" + modelName + "-update.html";
1209 var controllersPath = acre.options.adminRoute + "/js/" + modelName + "-controllers.js";
1210
1211 var model = {
1212 name: modelName,
1213 module: {
1214 name: modelName
1215 },
1216 collection: models[i].collection.name,
1217 controllers: {
1218 create: modelName + "CreateController",
1219 retrieve: modelName + "RetrieveController",
1220 update: modelName + "UpdateController"
1221 },
1222 views: {
1223 create: createRoutePath,
1224 retrieve: retrieveRoutePath,
1225 update: updateRoutePath
1226 }
1227 };
1228
1229 dynamicRouteData[createRoutePath] = new DynamicRouteData(__dirname + '/portal/views/admin-model-create-view.ejs', 'text/html', {model: model}, modelName);
1230 dynamicRouteData[retrieveRoutePath] = new DynamicRouteData(__dirname + '/portal/views/admin-model-retrieve-view.ejs', 'text/html', {model: model}, null);
1231 dynamicRouteData[updateRoutePath] = new DynamicRouteData(__dirname + '/portal/views/admin-model-update-view.ejs', 'text/html', {model: model}, modelName);
1232 dynamicRouteData[controllersPath] = new DynamicRouteData(__dirname + '/portal/views/js/admin-model-controllers.ejs', 'text/javascript', {model: model, createVerb: createVerb.toUpperCase(), updateVerb: updateVerb.toUpperCase()}, modelName);
1233
1234 app.get(createRoutePath, serveRoute());
1235 app.get(retrieveRoutePath, serveRoute());
1236 app.get(updateRoutePath, serveRoute());
1237 app.get(controllersPath, serveRoute());
1238
1239 jsPaths.push(controllersPath);
1240 _models.push(model);
1241 }
1242
1243 routePath = acre.options.adminRoute + '/js/admin-controllers.js';
1244 dynamicRouteData[_.clone(routePath)] = new DynamicRouteData(__dirname + '/portal/views/js/admin-controllers.ejs', 'text/javascript', {models: _models}, null);
1245 app.get(_.clone(routePath), serveRoute());
1246 jsPaths.push(_.clone(routePath));
1247
1248 routePath = acre.options.adminRoute + "/js/admin-module.js";
1249 dynamicRouteData[_.clone(routePath)] = new DynamicRouteData(__dirname + '/portal/views/js/admin-module.ejs', 'text/javascript', {models: _models, apiRoute: acre.options.rootPath}, null);
1250 app.get(_.clone(routePath), serveRoute());
1251 jsPaths.push(_.clone(routePath));
1252
1253 routePath = acre.options.adminRoute;
1254 dynamicRouteData[_.clone(routePath)] = new DynamicRouteData(__dirname + '/portal/views/admin-home.ejs', 'text/html', {
1255 models: _models,
1256 js: jsPaths,
1257 css: cssPaths,
1258 apiRoute: acre.options.rootPath,
1259 appName: acre.options.appName
1260 }, null);
1261 app.get(_.clone(routePath), serveRoute());
1262
1263 return when.resolve();
1264 };
1265
1266 var create = function(Model, request, response, callback)
1267 {
1268 var path = request.route.path;
1269 console.log("create path: " + path);
1270
1271 if (isModelRootPath(Model, path))
1272 {
1273 Model.create(request.body, function(error, model){
1274 if (error)
1275 {
1276 respondMongooseError(response, error);
1277 }
1278 else
1279 {
1280 if (callback)
1281 {
1282 callback(request, response, model);
1283 }
1284 else
1285 {
1286 respondSuccess(response, "created " + Model.modelName);
1287 }
1288 }
1289 });
1290 }
1291 else
1292 {
1293 // permit addition of objects to arrays
1294 var modelId = request.params[Model.modelName + "_id"];
1295 Model.findById(modelId, function(error, model) {
1296 if (error)
1297 {
1298 respondMongooseError(response, error);
1299 }
1300 else
1301 {
1302 if (!model)
1303 {
1304 respondError(response, 404, "failed to find " + Model.modelName + " with id: " + modelId);
1305 }
1306 else
1307 {
1308 traverseObjectHeirarchy(model, request, response, function(error, object_heirarchy, parent, key){
1309 if (error)
1310 {
1311 console.log("error occured : " + error);
1312 }
1313 else
1314 {
1315 if (_.isArray(parent[key]))
1316 {
1317 if (request.is('text/*'))
1318 {
1319 if (!_.isUndefined(request.text))
1320 {
1321 // adding to array of refs
1322 parent[key].push(request.text);
1323 }
1324 else
1325 {
1326 console.log("request.text undefined, acre bodyParser probably not set up");
1327 respondError(response, 500, "request.text undefined, acre bodyParser not setup");
1328 }
1329 }
1330 else
1331 {
1332 // adding nested resource to nested array
1333 parent[key].push(request.body);
1334 }
1335
1336 model.save(function(error, model){
1337 if (error)
1338 {
1339 console.log("error saving object to: " + request.originalUrl + " error: " + error.message);
1340 respondMongooseError(response, error);
1341 }
1342 else
1343 {
1344 if (callback)
1345 {
1346 callback(request, response, model);
1347 }
1348 else
1349 {
1350 respondSuccess(response, "create done");
1351 }
1352 }
1353 });
1354 }
1355 else
1356 {
1357 console.log("cannot " + createVerb + " to " + request.originalUrl);
1358 respondError(response, 405, "cannot " + createVerb + " to " + request.originalUrl);
1359 }
1360 }
1361 });
1362 }
1363 }
1364 });
1365 }
1366 };
1367
1368 var retrieve = function(Model, request, response, callback)
1369 {
1370 var path = request.route.path;
1371 console.log("retrieve path: " + path);
1372 var handleFind, hndl = null;
1373
1374 if (isModelRootPath(Model, path))
1375 {
1376 handleFind = function(error, models)
1377 {
1378 if (error)
1379 {
1380 respondMongooseError(response, error);
1381 }
1382 else
1383 {
1384 if (!models)
1385 {
1386 models = [];
1387 }
1388
1389 if (callback)
1390 {
1391 callback(request, response, models);
1392 }
1393 else
1394 {
1395 respondJson(response, models);
1396 }
1397 }
1398 };
1399
1400 try
1401 {
1402 if (request.query.q)
1403 {
1404 var query = new Buffer(request.query.q, 'base64').toString('ascii');
1405 query = JSON.parse(query);
1406 console.log('query: ');
1407 console.log(query);
1408 hndl = Model.find(query);
1409 }
1410 else
1411 {
1412 hndl = Model.find();
1413 }
1414
1415 if (request.query.l)
1416 {
1417 var limit = parseInt(request.query.l);
1418 console.log('limit: ' + limit);
1419 hndl.limit(limit);
1420 }
1421
1422 if (request.query.srt)
1423 {
1424 var sort = request.query.srt;
1425 console.log('sort: ' + sort);
1426 hndl.sort(sort);
1427 }
1428 }
1429 catch(e)
1430 {
1431 hndl = null;
1432 respondError(response, 400, 'Bad Request');
1433 }
1434 }
1435 else
1436 {
1437 var modelId = request.params[Model.modelName + "_id"];
1438
1439 handleFind = function(error, model)
1440 {
1441 if (error)
1442 {
1443 console.log("failed to find model with id: " + modelId);
1444 respondMongooseError(response, error);
1445 }
1446 else
1447 {
1448 if (!model)
1449 {
1450 console.log("failed to find model with id: " + modelId);
1451 respondError(response, 404, "failed to find " + Model.modelName + " with id: " + modelId);
1452 }
1453 else
1454 {
1455 traverseObjectHeirarchy(model, request, response, function(error, object_heirarchy, parent, key){
1456 if (error)
1457 {
1458 console.log("error occred: " + error);
1459 }
1460 else
1461 {
1462 var object = (key) ? parent[key] : parent;
1463 if (callback)
1464 {
1465 callback(request, response, object);
1466 }
1467 else
1468 {
1469 //TODO for some reason _.isPlainObject and _.isArray always return false here
1470 switch (Object.prototype.toString.call(object))
1471 {
1472 case '[object Array]':
1473 case '[object Object]':
1474 respondJson(response, object);
1475 break;
1476
1477 default:
1478 respondSuccess(response, object);
1479 break;
1480 }
1481 }
1482 }
1483 });
1484 }
1485 }
1486 };
1487
1488 var hndl = Model.findById(modelId);
1489 }
1490
1491 var refs = [];
1492 var addNestedRefs = function(prefix, foreignKeys)
1493 {
1494 var i, key, keys;
1495 if (_.isPlainObject(foreignKeys))
1496 {
1497 keys = _.keys(foreignKeys)
1498 for (i in keys)
1499 {
1500 key = keys[i];
1501 refs.push({
1502 path: prefix ? prefix + '.' + key : key,
1503 model: foreignKeys[key]
1504 });
1505 }
1506
1507 var nested = [];
1508 for (i in foreignKeys)
1509 {
1510 key = i;
1511 var nestedModelName = foreignKeys[i];
1512
1513 // Block out cyclic refs for now
1514 if (nestedModelName !== Model.modelName && _.keys(acre.foreignKeys).indexOf(nestedModelName) !== -1)
1515 {
1516 nested.push({
1517 pfx: key,
1518 model: nestedModelName
1519 });
1520 }
1521 }
1522
1523 if (nested.length > 0)
1524 {
1525 for(i in nested)
1526 {
1527 addNestedRefs(nested[i].pfx, acre.foreignKeys[nested[i].model]);
1528 }
1529 }
1530 }
1531 };
1532
1533 addNestedRefs(null, acre.foreignKeys[Model.modelName]);
1534
1535 if (hndl)
1536 {
1537 hndl.exec(function(err, data){
1538 if (err)
1539 {
1540 handleFind(err, data);
1541 }
1542 else
1543 {
1544 var finalData = data;
1545 async.eachSeries(
1546 refs,
1547 function(ref, done){
1548 mongoose.models[ref.model].populate(data, ref.path, function(err, data){
1549 finalData = data;
1550 done(err);
1551 });
1552 },
1553 function(err){
1554 handleFind(err, finalData);
1555 }
1556 );
1557 }
1558 });
1559 }
1560 };
1561
1562 var update = function(Model, request, response, callback)
1563 {
1564 var path = request.route.path;
1565 console.log("update path: " + path);
1566
1567 if (isModelRootPath(Model, path))
1568 {
1569 respondError(response, 405, "not allowed to " + updateVerb + " to " + Model.collection.name + ", must only update leaf elements");
1570 }
1571 else
1572 {
1573 var modelId = request.params[Model.modelName + "_id"];
1574 Model.findById(modelId, function(error, model) {
1575 if (error)
1576 {
1577 respondMongooseError(response, error);
1578 }
1579 else
1580 {
1581 if (!model)
1582 {
1583 respondError(response, 404, "failed to find " + Model.modelName + " with id: " + modelId);
1584 }
1585 else
1586 {
1587 traverseObjectHeirarchy(model, request, response, function(error, object_heirarchy, parent, key){
1588 if (error)
1589 {
1590 console.log("error occured: " + error);
1591 }
1592 else
1593 {
1594 if (object_heirarchy === heirarchy.LEAF && !_.isArray(parent))
1595 {
1596 if (!_.isUndefined(request.text))
1597 {
1598 parent[key] = request.text;
1599 model.save(function(error, model){
1600 if (error)
1601 {
1602 console.log("error occured updating object at path: " + request.originalUrl + " error: " + error.message);
1603 respondMongooseError(response, error);
1604 }
1605 else
1606 {
1607 console.log("update done");
1608 if (callback)
1609 {
1610 callback(request, response, model);
1611 }
1612 else
1613 {
1614 respondSuccess(response, "update sucessful");
1615 }
1616 }
1617 });
1618 }
1619 else
1620 {
1621 console.log("request.text undefined, acre bodyParser probably not set up");
1622 respondError(response, 500, "request.text undefined, acre bodyParser not setup");
1623 }
1624 }
1625 else
1626 {
1627 // not permitted to update a string in an array (eg a ref)
1628 console.log("update not allowed to: " + request.originalUrl);
1629 respondError(response, 405, "update not allowed to: " + request.originalUrl);
1630 }
1631 }
1632 });
1633 }
1634 }
1635 });
1636 }
1637 };
1638
1639 var remove = function(Model, request, response, callback)
1640 {
1641 var path = request.route.path;
1642 console.log("delete path: " + path);
1643
1644 if (isModelRootPath(Model, path))
1645 {
1646 // delete all resources
1647 Model.remove(function(error, models){
1648 if (error)
1649 {
1650 respondMongooseError(response, error);
1651 }
1652 else
1653 {
1654 if (callback)
1655 {
1656 callback(request, response, models);
1657 }
1658 else
1659 {
1660 respondSuccess(response, "deleted all " + Model.collection.name);
1661 }
1662 }
1663 });
1664 }
1665 else
1666 {
1667 var modelId = request.params[Model.modelName + "_id"];
1668 if (isModelRootResourcePath(Model, path))
1669 {
1670 // delete single resource
1671 Model.findByIdAndRemove(modelId, function(error, model){
1672 if (error)
1673 {
1674 console.log('failed to delete: ' + Model.modelName + ' with id: ' + modelId + ', error: ');
1675 console.log(error);
1676 respondMongooseError(response, error);
1677 }
1678 else
1679 {
1680 if (callback)
1681 {
1682 callback(request, response, model);
1683 }
1684 else
1685 {
1686 respondSuccess(response, "deleted " + Model.modelName + " at id: " + modelId);
1687 }
1688 }
1689 });
1690 }
1691 else
1692 {
1693 // permit deletion of objects from arrays
1694 Model.findById(modelId, function(error, model) {
1695 if (error)
1696 {
1697 console.log("failed to find object with id: " + modelId + ", error: " + error.message);
1698 respondMongooseError(response, error);
1699 }
1700 else
1701 {
1702 if (!model)
1703 {
1704 respondError(response, 404, "failed to find " + Model.modelName + " with id: " + modelId);
1705 }
1706 else
1707 {
1708 traverseObjectHeirarchy(model, request, response, function(error, object_heirarchy, parent, key){
1709 if (error)
1710 {
1711 console.log("error occured: " + error);
1712 }
1713 else
1714 {
1715 if (_.isArray(parent))
1716 {
1717 parent.splice(key, 1);
1718 model.save(function(error, model){
1719 if (error)
1720 {
1721 console.log("failed to delete array item, error: " + error.message);
1722 respondMongooseError(response, error);
1723 }
1724 else
1725 {
1726 console.log("delete done");
1727 if (callback)
1728 {
1729 callback(request, response, model);
1730 }
1731 else
1732 {
1733 respondSuccess(response, "delete done");
1734 }
1735 }
1736 });
1737 }
1738 else
1739 {
1740 console.log("unsupported delete operation");
1741 respondError(response, 405, "cannot delete " + request.originalUrl);
1742 }
1743 }
1744 });
1745 }
1746 }
1747 });
1748 }
1749 }
1750 };
1751
1752 var isModelRootPath = function(Model, path)
1753 {
1754 return acre.options.rootPath + "/" + Model.collection.name === path;
1755 };
1756
1757 var isModelRootResourcePath = function(Model, path)
1758 {
1759 return acre.options.rootPath + "/" + Model.collection.name + "/:" + Model.modelName + "_id" === path;
1760 };
1761
1762 var respondSuccess = function(response, str) {
1763 response.send(200, String(str));
1764 };
1765
1766 var respondJson = function(response, json) {
1767 response.json(json);
1768 };
1769
1770 var respondMongooseError = function(response, err)
1771 {
1772 var regexps = [
1773 /validation failed/gi,
1774 /duplicate key error/gi
1775 ];
1776 var code = 500;
1777
1778 for (i in regexps)
1779 {
1780 if (regexps[i].test(err.message))
1781 {
1782 code = 400;
1783 break;
1784 }
1785 }
1786
1787 response.send(code, err);
1788 };
1789
1790 var respondError = function(response, code, err)
1791 {
1792 response.send(code, err);
1793 };
1794
1795 module.exports = acre;
1796}());
\No newline at end of file