UNPKG

18.7 kBJavaScriptView Raw
1/*jshint node:true, laxcomma:true*/
2"use strict";
3
4var path = require('path')
5 , basename = path.basename(__filename, '.js')
6 , debug = require('debug')('castor:' + basename)
7 , fs = require('fs')
8 , kuler = require('kuler')
9 , mime = require('mime')
10 , express = require('express')
11 , Loader = require('castor-load')
12 , Computer = require('./helpers/compute.js')
13 , Download = require('./helpers/download.js')
14 , serveStatic = require('serve-static')
15 , I18n = require('i18n-2')
16 , Hook = require('./helpers/hook.js')
17 , async = require('async')
18 , MongoClient = require('mongodb').MongoClient
19 , JBJ = require('jbj')
20 , ACL = require('./helpers/acl.js')
21 , passport = require('passport')
22 , Errors = require('./helpers/errors.js')
23 , Agent = require('./helpers/agent.js')
24 , querystring = require('querystring')
25 , datamodel = require('datamodel')
26 , objectPath = require("object-path")
27 ;
28
29module.exports = function(config, online) {
30
31 var core = {
32 server : undefined,
33 app : express(),
34 jbj: JBJ,
35 states : { },
36 config : config,
37 models : {},
38 connect : undefined,
39 computer : undefined,
40 loaders : undefined,
41 downloaders : undefined,
42 heart: undefined,
43 agent : new Agent(config.get('port')),
44 passport : passport,
45 acl : new ACL(),
46 Errors : Errors
47 };
48
49 //
50 // Passport
51 //
52 core.passport.serializeUser(function(user, done) {
53 done(null, JSON.stringify(user));
54 });
55 core.passport.deserializeUser(function(user, done) {
56 done(null, JSON.parse(user));
57 });
58
59
60
61 //
62 // Find & Detect extensionPath & viewPath & publicPath
63 //
64 var extensionPath, viewPath;
65 try {
66 extensionPath = require('./helpers/view.js')(config);
67 console.info(kuler('Set extension directory. ', 'olive'), kuler(extensionPath, 'limegreen'));
68 viewPath = path.resolve(extensionPath, './views')
69 if (!fs.existsSync(viewPath)) {
70 viewPath = extensionPath;
71 }
72 }
73 catch(e) {
74 return online(e);
75 }
76
77 //
78 // viewPath can impove or change config
79 //
80 config.fix('connectionURI', 'mongodb://' + config.get('databaseHost') + '/' + config.get('databaseName'));
81
82
83 var publicPath = [
84 path.resolve(extensionPath, './public'),
85 path.resolve(viewPath, './public'),
86 path.resolve(__dirname, './public')
87 ].filter(fs.existsSync).shift();
88 if (publicPath === undefined) {
89 return online(new Error('publicPath is not defined'));
90 }
91
92
93 //
94 // JBJ
95 // to load ./filters/
96 //
97 var filters = new Hook('filters')
98 filters.from(extensionPath, __dirname)
99 filters.over(config.get('filters'))
100 filters.apply(function(hash, func) {
101 JBJ.use(func);
102 });
103
104 // JBJ
105 // to load ./protocols/
106 //
107 var protocols = new Hook('protocols')
108 protocols.from(extensionPath, __dirname)
109 protocols.over(config.get('protocols'))
110 protocols.apply(function(hash, func, item) {
111 JBJ.register(item.pattern || hash, func(item.options || {}, core));
112 });
113
114 JBJ.register('local:', require('./protocols/local.js')({}, core));
115 JBJ.register('http:', require('./protocols/http.js')({}, core));
116 JBJ.register('https:', require('./protocols/https.js')({}, core));
117
118
119
120
121
122 //
123 // create an heart & set heartrate
124 // load ./heartbeats/
125 //
126 var pulse;
127 try {
128 core.heart = require('./helpers/heart.js')(config.get('heartrate'));
129 pulse = core.heart.newPulse();
130 }
131 catch(e) {
132 return online(e);
133 }
134 var heartbeats = new Hook('heartbeats');
135 heartbeats.from(extensionPath, __dirname)
136 heartbeats.over(config.get('heartbeats'))
137 heartbeats.apply(function(hash, func, item) {
138 item.repeat = Number(item.repeat);
139 item.beat = Number(item.beat);
140 core.heart.createEvent(Number.isNaN(item.beat) ? 1 : item.beat, {repeat: Number.isNaN(item.repeat) ? 0 : item.repeat}, func(item.options, core));
141 });
142 core.heart.createEvent(2, {repeat: 1}, require('./heartbeats/compute.js')({}, core));
143
144
145 //
146 // Models
147 // load ./models/
148 //
149 core.models.init = require('./models/init.js')
150 core.models.mongo = require('./models/mongo.js')
151 // core.models.reduceTable = require('./models/reduce-table.js')
152 core.models.getTable = require('./models/get-table.js')
153 core.models.getDocument = require('./models/get-document.js')
154 core.models.getDocuments = require('./models/get-documents.js')
155 core.models.computeDocuments = require('./models/compute-documents.js')
156
157 var models = new Hook('models');
158 models.from(extensionPath, __dirname)
159 models.over(config.get('models'))
160 models.apply(function(hash, func, item) {
161 core.models[hash] = func;
162 });
163
164
165 //
166 // Check Database & Collections Index
167 //
168 datamodel([core.models.mongo, core.models.init])
169 .apply(core)
170 .then(function(res) {
171 if (res.initState) {
172 console.info(kuler('Collections index initialized.', 'olive'), kuler(core.config.get('collectionNameIndex'), 'limegreen'));
173 }
174 }).catch(online);
175
176
177 //
178 // Loaders for insert into mongodb
179 // load ./loaders/
180 //
181 core.loaders = [];
182 core.loaders.push(['**/*', require('./loaders/prepend.js'), {}]);
183
184 var loaders = new Hook('loaders');
185 loaders.from(extensionPath, __dirname)
186 loaders.over(config.get('loaders'))
187 loaders.apply(function(hash, func, item) {
188 core.loaders.push([item.pattern || '**/*', func, item.options]);
189 });
190 core.loaders.push(['**/*', require('./loaders/document.js'), { stylesheet: config.get('documentFields') }]);
191 core.loaders.push(['**/*', require('./loaders/append.js'), {}]);
192
193
194 //
195 // HOT folder
196 //
197 var ldr, ldropts;
198 if (config.has('dataPath')) {
199 objectPath.set(core.states, 'hotfolder.first.syncOver', false);
200 try {
201 ldropts = {
202 // "dateConfig" : config.get('dateConfig'),
203 "connexionURI" : config.get('connectionURI'),
204 "collectionName": config.get('collectionNameHotFolder'),
205 "concurrency" : config.get('concurrency'),
206 "delay" : config.get('delay'),
207 "maxFileSize" : config.get('maxFileSize'),
208 "writeConcern" : config.get('writeConcern'),
209 "ignore" : config.get('filesToIgnore')
210 }
211 ldr = new Loader(config.get('dataPath'), ldropts);
212
213 if (fs.existsSync(config.get('dataPath'))) {
214 console.info(kuler('Watching hot directory. ', 'olive'), kuler(config.get('dataPath'), 'limegreen'));
215 core.loaders.forEach(function(loader) {
216 ldr.use(loader[0], loader[1](loader[2], core));
217 });
218 ldr.sync(function(err) {
219 if (err instanceof Error) {
220 console.error(kuler("Loader synchronization failed.", "red"), kuler(err.toString(), 'orangered'));
221 }
222 else {
223 console.info(kuler('Files and Database are synchronised.', 'olive'));
224 }
225 });
226 config.set('collectionName', ldr.options.collectionName);
227 }
228 }
229 catch(e) {
230 return online(e);
231 }
232 }
233
234 //
235 // Add Mongo indexes
236 //
237 core.indexes = [];
238 core.indexes.push({ '_wid': 1 }); // wid = fid + number (which are unique)
239 core.indexes.push({ '_text': 'text' });
240 core.indexes.push({ 'state': 1 });
241 core.connect = function() {
242 return MongoClient.connect(config.get('connectionURI'));
243 }
244 try {
245 core.connect()
246 .then(function(db) {
247 db.collection(config.get('collectionName'))
248 .then(function(coll) {
249 debug('indexes', coll);
250 var usfs = config.get('documentFields');
251 core.indexes = Object.keys(usfs)
252 .filter(function(i) {
253 return (i !== '$text') && (usfs[i].noindex !== true);
254 })
255 .map(function(i) {
256 var j = {}; j[i.replace('$','')] = 1; return j;
257 });
258 async.map(core.indexes, function(i, cb) {
259 coll.ensureIndex(i, { w: config.get('writeConcern')}, function(err, indexName) {
260 if (err instanceof Error) {
261 console.error(kuler("Unable to create the index.", "red"), kuler(err.toString(), 'orangered'));
262 }
263 else {
264 console.info(kuler('Index added.', 'olive'), kuler(Object.keys(i)[0] + '/' + indexName, 'limegreen'));
265 }
266 cb(err, indexName);
267 });
268 }, function(e, ret) {
269 if (e) {
270 throw e;
271 }
272 db.close();
273 });
274 }).catch(function(e) {
275 throw e;
276 });
277 }).catch(function(e) {
278 throw e;
279 });
280 }
281 catch(e) {
282 return online(e);
283 }
284
285
286 //
287 // Map/Reduce features
288 // load ./operators
289 //
290 var cptlock, cptopts;
291 try {
292 cptopts = {
293 "port": config.get('port'),
294 "connectionURI" : config.get('connectionURI'),
295 "collectionName": config.get('collectionNameHotFields'),
296 "concurrency" : config.get('concurrency')
297 }
298 core.computer = new Computer(config.get('computedFields'), cptopts) ;
299
300 core.computer.use('count', require('./operators/count.js'));
301 core.computer.use('catalog', require('./operators/catalog.js'));
302 core.computer.use('distinct', require('./operators/distinct.js'));
303 core.computer.use('ventilate', require('./operators/ventilate.js'));
304 core.computer.use('total', require('./operators/total.js'));
305 core.computer.use('graph', require('./operators/graph.js'));
306 core.computer.use('groupby', require('./operators/groupby.js'));
307 core.computer.use('merge', require('./operators/merge.js'));
308 core.computer.use('labelize', require('./operators/labelize.js'));
309 core.computer.use('max', require('./operators/max.js'));
310 core.computer.use('stats', require('./operators/stats.js'));
311
312 var operators = new Hook('operators')
313 operators.from(extensionPath, __dirname)
314 operators.over(config.get('operators'))
315 operators.apply(function(hash, func) {
316 core.computer.use(hash, func);
317 });
318 var cptfunc = function() {
319 if (cptlock === undefined || cptlock === false) {
320 cptlock = true;
321 core.heart.createEvent(2, {repeat: 1}, function() {
322 cptlock = false; // évite d'oublier un evenement pendant le calcul
323 core.computer.run(function(err) {
324 if (err instanceof Error) {
325 console.error(kuler("Unable to compute some fields.", "red"), kuler(err.toString(), 'orangered'));
326 }
327 else {
328 console.info(kuler('Corpus fields computed.', 'olive'));
329 objectPath.set(core.states, 'hotfolder.first.syncOver', true);
330 }
331 });
332 });
333 }
334 };
335 if (ldr !== undefined) {
336 ldr.on('browseOver', function (found) {
337 objectPath.set(core.states, 'hotfolder.last.browseOver', found);
338 });
339 ldr.on('watching', function (err, doc) {
340 objectPath.set(core.states, 'hotfolder.last.watching', doc);
341 cptfunc();
342 });
343 ldr.on('checked', function (err, doc) {
344 objectPath.set(core.states, 'hotfolder.last.checked', doc);
345 cptfunc();
346 });
347 ldr.on('cancelled', function (err, doc) {
348 objectPath.set(core.states, 'hotfolder.last.cancelled', doc);
349 cptfunc();
350 });
351 ldr.on('added', function (err, doc) {
352 objectPath.set(core.states, 'hotfolder.last.added', doc);
353 cptfunc();
354 });
355 ldr.on('changed', function (err, doc) {
356 objectPath.set(core.states, 'hotfolder.last.changed', doc);
357 cptfunc();
358 });
359 ldr.on('dropped', function (err, doc) {
360 objectPath.set(core.states, 'hotfolder.last.dropped', doc);
361 cptfunc();
362 });
363 ldr.on('saved', function (doc) {
364 objectPath.set(core.states, 'hotfolder.last.saved', doc);
365 cptfunc();
366 });
367
368 }
369 }
370 catch(e) {
371 return online(e);
372 }
373
374 //
375 // MimeTypes
376 var mimeTypes = config.get('mimeTypes');
377 debug('mimeTypes', mimeTypes);
378 mime.define(mimeTypes);
379
380
381 //
382 // Downloaders
383 // load ./downloaders/
384 //
385
386 var dwllock, dwlopts;
387 try {
388
389 dwlopts = {
390 "maxAge": config.get('maxAge'),
391 "concurrency" : config.get('concurrency')
392 }
393 core.downloader = new Download(dwlopts) ;
394 core.downloader.use('*', require('./downloaders/columns.js')({}, core));
395 core.downloader.use('*', require('./downloaders/uri.js')({}, core));
396 core.downloader.use('min', require('./downloaders/min.js')({
397 uniqueValueWith : config.get('uniqueValueWith')
398 }, core));
399 core.downloader.use('dry', require('./downloaders/dry.js')({}, core));
400 // not by default core.downloader.use('csv', require('./downloaders/csv.js')({}, core));
401 core.downloader.use('tsv', require('./downloaders/tsv.js')({}, core));
402 // not by default core.downloader.use('+(xlsx|xls)', require('./downloaders/excel.js')({}, core));
403
404 var downloaders = new Hook('downloaders')
405 downloaders.from(extensionPath, __dirname)
406 downloaders.over(config.get('downloaders'))
407 downloaders.apply(function(hash, func, item) {
408 core.downloader.use(item.pattern || '*', func(item.options, core));
409 });
410
411 core.downloader.use('*', require('./downloaders/json.js')({}, core));
412 }
413 catch(e) {
414 return online(e);
415 }
416
417
418
419
420 //
421 // Strategies for PassportJS
422 // load ./strategies/
423 //
424 var strategies = new Hook('strategies')
425 strategies.from(extensionPath, __dirname)
426 strategies.over(config.get('strategies'))
427 strategies.apply(function(hash, func, item) {
428 core.passport.use(func(item.options, core));
429 });
430
431
432 //
433 // Authorizations
434 // load ./authorizations/
435 //
436 var authorizations = new Hook('authorizations')
437 authorizations.from(extensionPath, __dirname)
438 authorizations.over(config.get('authorizations'))
439 authorizations.apply(function(hash, func, item) {
440 core.acl.use(item.pattern, func(item.options, core));
441 });
442 core.acl.use('* /**', require('./authorizations/recall.js')());
443
444
445
446 //
447 // define WEB Server
448 //
449
450 // report config vars
451 if (config.get('trustProxy') === true) {
452 core.app.enable('trust proxy');
453 }
454
455
456
457 //
458 // Middlewares for Express
459 // load ./middlewares/
460 //
461 try {
462 core.app.use(function (req, res, next) {
463 req.routeParams = {};
464 req.config = config;
465 req.core = core;
466 next();
467 });
468 core.app.use(require('express-cache-response-directive')());
469 core.app.use(require('morgan')(config.get('logFormat'), { stream : process.stderr }));
470 core.app.use(require('serve-favicon')(path.resolve(publicPath, './favicon.ico')));
471 core.app.use(require('cookie-parser')());
472 core.app.use(require('express-session')({ secret: __dirname, resave: false, saveUninitialized: false }));
473 core.app.use(passport.initialize());
474 core.app.use(passport.session());
475 I18n.expressBind(core.app, {
476 locales: ['en', 'fr'],
477 directory: path.resolve(extensionPath, './locales')
478 });
479 core.app.use(require('./middlewares/i18n.js')());
480
481 var middlewares = new Hook('middlewares')
482 middlewares.from(extensionPath, __dirname)
483 middlewares.over(config.get('middlewares'))
484 middlewares.apply(function(hash, func, item) {
485 core.app.use(item.path || hash, func(item.options, core));
486 });
487 core.app.use(function (req, res, next) {
488 // shared function
489 res.resolveTemplate = function(templateName, depth) {
490 return [
491 path.resolve(viewPath, templateName),
492 path.resolve(__dirname, './views/', templateName)
493 ].slice(0, depth).filter(fs.existsSync).shift()
494 }
495 next();
496 });
497 }
498 catch(e) {
499 return online(e);
500 }
501
502
503 //
504 // View
505 // to load ./engines/
506 // for use ./views/
507 //
508 core.app.set('views', [viewPath, path.resolve(__dirname, './views/')]);
509
510 core.app.engine('jbj', require('./engines/jbj.js')({}, core));
511 core.app.engine('html', require('./engines/nunjucks.js')({views: core.app.get('views')}, core));
512 core.app.engine('txt', require('./engines/rawfile.js')({}, core));
513
514 var engines = new Hook('engines')
515 engines.from(extensionPath, __dirname)
516 engines.over(config.get('engines'))
517 engines.apply(function(hash, func, item) {
518 core.app.engine(item.pattern || hash, func(item.options || {}, core));
519 });
520 core.app.set('view engine', config.get('defaultEngine'));
521
522
523
524
525 if (config.get('rootURL') !== undefined && config.get('rootURL') !== '/') {
526 core.app.route('/').all(function(req, res) {
527 res.redirect(config.get('rootURL'));
528 });
529 }
530
531
532 //
533 // Access for route
534 //
535 //core.app.use(core.acl.route());
536 core.app.route('/*').all(core.acl.route());
537
538
539 //
540 // Static server
541 //
542 core.app.use(serveStatic(publicPath, {
543 maxAge: config.get('maxAge')
544 }))
545
546 //
547 // API routes
548 //
549
550 var pageRouter = express.Router();
551
552 //
553 // Optionals routes
554 // load ./routes/
555 //
556 var routes = new Hook('routes')
557 routes.from(extensionPath, __dirname)
558 routes.over(config.get('routes'))
559 routes.apply(function(hash, func, item) {
560 var router = express.Router();
561 func(router, core)
562 core.app.use(router);
563 });
564
565 // Mandatory route
566 var restRouter = express.Router();
567 require('./routes/rest.js')(restRouter, core)
568 core.app.use(restRouter);
569
570
571 //
572 // catch 404 and forward to error handler
573 //
574 core.app.use(function(req, res, next) {
575 debug('Error handler');
576 next(new Errors.PageNotFound('Not Found'));
577 });
578
579
580
581 //
582 // Route Errors handler
583 //
584 core.app.use(function errorsHandler(err, req, res, next) {
585 var statusCode;
586 if (res.headersSent === false) {
587 if (err instanceof Errors.PageNotFound || err instanceof Errors.TableNotFound) {
588 statusCode = 404;
589 }
590 else if (err instanceof Errors.InvalidParameters) {
591 statusCode = 400;
592 }
593 else if (err instanceof Errors.Forbidden) {
594 statusCode = 403;
595 }
596 else {
597 statusCode = 500;
598 }
599 }
600 if (req.user === undefined && statusCode === 403 && config.get('loginURL')) {
601 res.redirect(config.get('loginURL') + '?' + querystring.stringify({ 'url' : req.originalUrl }));
602 return;
603 }
604 res.status(statusCode);
605 console.error(kuler("Route error for", "red"), req.originalUrl, kuler(statusCode + ' - ' + err.toString(), 'orangered'), ' from ', req.get('referer'));
606 if (req.accepts('html')) {
607 res.render('error.html', {
608 code: statusCode,
609 name: err.name,
610 message: err.message,
611 error: err
612 });
613 return;
614 }
615 if (req.accepts('json')) {
616 res.send({
617 code: statusCode,
618 name: err.name,
619 message: err.message,
620 });
621 return;
622
623 }
624 res.type('text').send(err.toString());
625 });
626
627
628 //
629 // Create HTTP server
630 //
631 //
632 core.server = require('http').createServer(core.app)
633 core.server.timeout = config.get('timeout');
634 core.server.listen(config.get('port'), function() {
635 online(null, core);
636 });
637
638 return core.server;
639 }