diff --git a/handlers.js b/handlers.js new file mode 100644 index 0000000..8f7f96b --- /dev/null +++ b/handlers.js @@ -0,0 +1,25 @@ +module.exports = function (mongoDataInstance, previewInstance, importerInstance) { + + function getAttribute(options, callback) { + mongoDataInstance.getMongoAttribute(options, callback); + } + + function getContent(options, callback) { + mongoDataInstance.getMongoContent(options, callback); + } + + function getPreviewHTML(html, options, callback) { + previewInstance.getPreviewHTML(html, options, callback); + } + + function importer(doc, options, callback) { + importerInstance.importer(doc, options, callback); + } + + return { + getAttribute: getAttribute, + getContent: getContent, + getPreviewHTML: getPreviewHTML, + importer: importer + } +}; diff --git a/mongodata.js b/mongodata.js index 39c91b3..bf6470a 100644 --- a/mongodata.js +++ b/mongodata.js @@ -2,7 +2,7 @@ var ObjectID = require('mongodb').ObjectID; var _ = require('underscore'); -module.exports = function (db, shareModel, config) { +module.exports = function (config, db, shareModel) { /* options: diff --git a/routes.js b/routes.js index 6667e5d..3c84a02 100644 --- a/routes.js +++ b/routes.js @@ -1,16 +1,14 @@ var responder = require('./responder.js'); -var preview = require('./preview.js'); -var importer = require('./importer.js'); var path = require('path'); var fs = require('fs'); -module.exports = function (app, mongoDataInstance, model, config) { +module.exports = function (app, handlers, config) { var route; route = config.api.data + '/:collection/:guid/:attribute.:ext(css|less|js|html)'; app.get(route, - function getMongoAttribute(req, res, next) { + function getAttributeByGUID(req, res, next) { config.debug && console.log('/data/:collection/:guid/:attribute.:ext(less|js|html)'); var options = { collection: req.params.collection, @@ -18,7 +16,7 @@ module.exports = function (app, mongoDataInstance, model, config) { ext: req.params.ext, query: {_id: req.params.guid} }; - mongoDataInstance.getMongoAttribute(options, + handlers.getAttribute(options, responder(options, res, next) ); } @@ -26,14 +24,14 @@ module.exports = function (app, mongoDataInstance, model, config) { route = config.api.data + '/:collection/:guid.:ext(json)'; app.get(route, - function getMongoContent(req, res, next) { + function getContentByGUID(req, res, next) { config.debug && console.log('/data/:collection/:guid.:ext(json)'); var options = { collection: req.params.collection, ext: req.params.ext, query: {_id: req.params.guid} }; - mongoDataInstance.getMongoContent(options, + handlers.getContent(options, responder(options, res, next) ); } @@ -41,7 +39,7 @@ module.exports = function (app, mongoDataInstance, model, config) { route = config.api.content + '/:collection/:name/:attribute.:ext(css|less|js|html)'; app.get(route, - function getMongoAttribute(req, res, next) { + function getAttributeByName(req, res, next) { config.debug && console.log('/content/:collection/:name/:attribute.:ext(less|js|html)'); var options = { collection: req.params.collection, @@ -49,7 +47,7 @@ module.exports = function (app, mongoDataInstance, model, config) { ext: req.params.ext, query: {name: req.params.name} }; - mongoDataInstance.getMongoAttribute(options, + handlers.getAttribute(options, responder(options, res, next) ); } @@ -57,219 +55,19 @@ module.exports = function (app, mongoDataInstance, model, config) { route = config.api.content + '/content/:collection/:name.:ext(json)'; app.get(route, - function getMongoContent(req, res, next) { + function getContentByName(req, res, next) { config.debug && console.log('/content/:collection/:name.:ext(json)'); var options = { collection: req.params.collection, ext: req.params.ext, query: {name: req.params.name} }; - mongoDataInstance.getMongoContent(options, + handlers.getContent(options, responder(options, res, next) ); } ); - function handleMongoGetResult(options) { - function handleResult(err, result) { - var notFound = false; - if (err) { - if (/Data not found*/.test(err.message)) { - config.debug && console.log('handleMongoGetResult.handleResult Document/Attribute not found, It will be created on first OT'); - result = {}; - if (options.attribute) { - if (options.type == 'json') { - result[options.attribute] = {}; - } else { - result[options.attribute] = ""; - } - } - notFound = true; - } else { - config.errors && console.log('ERR1 handleMongoGetResult.handleResult Error retrieving document ', options.collection, JSON.stringify(options.query), options.attribute || "", err); - } - } - if (result || notFound) { - var operation = null; - config.debug && console.log('handleMongoGetResult options', options, result); - var data = result; - if (options.attribute) { - data = result[options.attribute]; - } - var version = 0; - if (options.type == 'json') { - if (data instanceof String) { - data = JSON.parse(data); - } - operation = { op: [ - { p: [], oi: data, od: null } - ], v: version }; - } else if (options.type == 'text') { - operation = { op: [ - {i: data, p: 0} - ], v: version }; - } - if (operation) { - model.applyOp(options.documentId, operation, function appliedOp(error, version) { - options.debug && console.log('getResult applyOp version', version); - if (error) { - options.error && console.log('ERR2 handleMongoGetResult', error); - } - }); - } - } - } - - return handleResult; - } - - model.on('create', function populateDocument(documentId, data) { - config.debug && console.log('Populating a doc in channel', documentId, data); - var splitId = documentId.split(':'); - var options = { - documentId: documentId, - type: splitId[0], - collection: splitId[1], - attribute: null - }; - if (splitId.length == 4) { -// options.query = {_id: splitId[2]}; - options.query = {name: splitId[2]}; - options.attribute = splitId[3]; - mongoDataInstance.getMongoAttribute(options, handleMongoGetResult(options)); - } else { - options.query = {name: splitId[2]}; - mongoDataInstance.getMongoContent(options, handleMongoGetResult(options)); - } - }); - - - function handleMongoSetResult(options, current, callback) { - function handleResult(err, result) { - if (err) { - config.errors && console.log('ERR1 handleMongoSetResult Error while saving document ', options.collection, JSON.stringify(options.query), options.attribute || "", err); - return callback && callback(err); - } - config.debug && console.log('current', current, 'result', result, 'options', options); - if ((!current || !current.name) && (result.name || options.name)) { - var name = result.name || options.name; - var operation = { op: [ - { p: ['name'], oi: name, od: null } - ], v: options.operation.v }; - return model.applyOp(options.documentId, operation, function appliedOp(error, version) { - config.debug && console.log('setResult applyOp version', version); - if (error) { - config.error && console.log('ERR2 handleMongoSetResult', error); - return callback && callback(error); - } - return callback && callback(null, version); - }); - } else { - return callback(null, null); - } - } - - return handleResult; - } - - function handleMongoAttributeSetResult(options, current, callback) { - function handleResult(err, result) { - if (err) { - config.errors && console.log('ERR1 handleMongoAttributeSetResult Error while saving document ', options.collection, JSON.stringify(options.query), options.attribute || "", err); - return callback(err); - } - config.debug && console.log('current', current, 'result', result); - return callback(null, null); - } - - return handleResult; - } - - var timers = {}; - - function handleSetTimeout(documentId) { - return function saveContent() { - var args = timers[documentId]; - delete timers[documentId]; - config.debug && console.log('running timer', documentId); - mongoDataInstance.setMongoContent(args.current, args.options, - handleMongoSetResult(args.options, args.current, - function handleApplyOpResult(err, version) { - if (err) { - config.errors && console.log('ERR2 applyOp', version, err); - } - })); - }; - } - - function handleSetAttributeTimeout(documentId) { - return function saveAttribute() { - var args = timers[documentId]; - delete timers[documentId]; - config.debug && console.log('running timer', documentId); - var data = args.current; - if (args.options.type == 'json') { - data = JSON.parse(args.current); - } - mongoDataInstance.setMongoAttribute(data, args.options, - handleMongoAttributeSetResult(args.options, data, - function handleApplyOpResult(err, version) { - if (err) { - config.errors && console.log('ERR1 applyOp', documentId, version, err); - } - })); - }; - } - - // 'applyOp' event is fired when an operational transform is applied to to a shareDoc - // a shareDoc has changed and needs to be saved to mongo - model.on('applyOp', function persistDocument(documentId, operation, current) { - config.debug && console.log('applyOp', documentId, operation, current); - if (operation.v == 0) return; - - var splitId = documentId.split(':'); - var options = { - documentId: documentId, - type: splitId[0], - collection: splitId[1], - name: splitId[2], - attribute: null, - operation: operation, - no_share: true // prevent circular updates. - }; - var timer = { - current: current, - options: options - }; - var attribute = false; - if (splitId.length == 4) { -// options.query = {_id: splitId[2]}; - options.query = {name: splitId[2]}; - options.attribute = splitId[3]; - attribute = true; - } else { - options.query = {name: splitId[2]}; - } - if (timers[documentId]) { - timer.timer_id = timers[documentId].timer_id; - timers[documentId] = timer; - config.debug && console.log('resetting timer', documentId); - } else { - timers[documentId] = timer; - if (attribute) { - timer.timer_id = setTimeout( - handleSetAttributeTimeout(documentId), - config.savedelay); - } else { - timer.timer_id = setTimeout( - handleSetTimeout(documentId), - config.savedelay); - } - config.debug && console.log('setting timer', documentId); - } - }); - - var previewInstance = preview(config, mongoDataInstance); route = config.api.preview + '/:collection/:name.:ext(html|md)'; app.get(route, @@ -289,11 +87,11 @@ module.exports = function (app, mongoDataInstance, model, config) { //var markdownDocument=helpers.marker_prefix + markdownTag + helpers.marker_postfix; // TODO: remove hardcoded marker var markdownDocument = ''; - return previewInstance.getPreviewHTML(markdownDocument, { req: options.req }, + return handlers.getPreviewHTML(markdownDocument, { req: options.req }, responder(options, res, next) ); } - return mongoDataInstance.getMongoContent(options, function handleResult(err, result) { + return handlers.getContent(options, function handleResult(err, result) { if (err) { return responder(options, res, next)(err, result); } @@ -312,7 +110,7 @@ module.exports = function (app, mongoDataInstance, model, config) { }; config.debug && console.log('getPreviewContent content', attribute_value); - return previewInstance.getPreviewHTML(attribute_value, preview_options, + return handlers.getPreviewHTML(attribute_value, preview_options, responder(options, res, next) ); } else { @@ -325,7 +123,6 @@ module.exports = function (app, mongoDataInstance, model, config) { } ); - var importerInstance = importer(config, mongoDataInstance); route = config.api.importer + '/:filename'; app.get(route, function importFile(req, res, next) { @@ -338,7 +135,7 @@ module.exports = function (app, mongoDataInstance, model, config) { } // process with leftover marker support var options = {}; - importerInstance.importer(sub_doc, options, + handlers.importer(sub_doc, options, responder(options, res, next) ); }); diff --git a/server.js b/server.js index 091c431..4712f0a 100644 --- a/server.js +++ b/server.js @@ -1,11 +1,16 @@ +process.title = "Prototyper"; + var connect = require('connect'); var express = require('express'); var MongoClient = require('mongodb').MongoClient; var addRoutes = require('./routes.js'); -var addShare = require('./share.js'); +var shareServer = require('./share.js'); +var shareHandlers = require('./shareHandlers.js'); var mongoData = require('./mongodata.js'); +var preview = require('./preview.js'); +var importer = require('./importer.js'); +var handlers = require('./handlers.js'); -process.title = "Prototyper"; var config = { errors: true, @@ -52,10 +57,12 @@ var config = { } }; +config.debug && console.log('config loaded'); + var app = express(); config.debug && app.use(connect.logger()); if (!process.env.NODE_ENV) { - app.get('/favicon.ico', function (req, res, next) { + app.get('/favicon.ico', function (req, res) { res.sendfile(config.statics.dev_favicon_path); }); } @@ -68,26 +75,52 @@ app.use('/lib/ace', express.static(config.statics.ace_client)); //noinspection JSUnresolvedFunction app.use('/lib/async', express.static(config.statics.async_client)); +config.debug && console.log('static routes set'); + MongoClient.connect(config.mongo.server, config.mongo.options, function connection(err, db) { if (err) { config.errors && console.log('ERR connection to database', err); return process.exit(1); } - var share = addShare(app, db, config); + config.debug && console.log('database connected'); + + var share = shareServer(app, db, config); var model = share.model; var server = share.server; - var mongoDataInstance = mongoData(db, model, config); + config.debug && console.log('share attached'); - app = addRoutes(app, mongoDataInstance, model, config); + var mongoDataInstance = mongoData(config, db, model); + + config.debug && console.log('mongodata initialized'); + + shareHandlers(config, model, mongoDataInstance); + + config.debug && console.log('sharehandlers attached'); + + var previewInstance = preview(config, mongoDataInstance); + + config.debug && console.log('previews initialized'); + + var importerInstance = importer(config, mongoDataInstance); + + config.debug && console.log('importer initialized'); + + var handlerInstance = handlers(mongoDataInstance, previewInstance, importerInstance); + + config.debug && console.log('handlers initialized'); + + app = addRoutes(app, handlerInstance, config); + + config.debug && console.log('routes added'); server.on('error', function (err) { - config.error && console.log('server error',err); + config.error && console.log('server error', err); }); return server.listen(config.port, function handleServerResult(err) { if (err) { app.stop(); - console.log('Server error', err); + console.error('Server error', err); return process.exit(1); } config.debug && console.log('routes', app.routes); diff --git a/shareHandlers.js b/shareHandlers.js new file mode 100644 index 0000000..0be530b --- /dev/null +++ b/shareHandlers.js @@ -0,0 +1,201 @@ +module.exports = function (config, model, mongoDataInstance) { + var timers = {}; + + function handleMongoGetResult(options) { + function handleResult(err, result) { + var notFound = false; + if (err) { + if (/Data not found*/.test(err.message)) { + config.debug && console.log('handleMongoGetResult.handleResult Document/Attribute not found, It will be created on first OT'); + result = {}; + if (options.attribute) { + if (options.type == 'json') { + result[options.attribute] = {}; + } else { + result[options.attribute] = ""; + } + } + notFound = true; + } else { + config.errors && console.log('ERR1 handleMongoGetResult.handleResult Error retrieving document ', options.collection, JSON.stringify(options.query), options.attribute || "", err); + } + } + if (result || notFound) { + var operation = null; + config.debug && console.log('handleMongoGetResult options', options, result); + var data = result; + if (options.attribute) { + data = result[options.attribute]; + } + var version = 0; + if (options.type == 'json') { + if (data instanceof String) { + data = JSON.parse(data); + } + operation = { op: [ + { p: [], oi: data, od: null } + ], v: version }; + } else if (options.type == 'text') { + operation = { op: [ + {i: data, p: 0} + ], v: version }; + } + if (operation) { + model.applyOp(options.documentId, operation, function appliedOp(error, version) { + options.debug && console.log('getResult applyOp version', version); + if (error) { + options.error && console.log('ERR2 handleMongoGetResult', error); + } + }); + } + } + } + + return handleResult; + } + + model.on('create', function populateDocument(documentId, data) { + config.debug && console.log('Populating a doc in channel', documentId, data); + var splitId = documentId.split(':'); + var options = { + documentId: documentId, + type: splitId[0], + collection: splitId[1], + attribute: null + }; + if (splitId.length == 4) { +// options.query = {_id: splitId[2]}; + options.query = {name: splitId[2]}; + options.attribute = splitId[3]; + mongoDataInstance.getMongoAttribute(options, handleMongoGetResult(options)); + } else { + options.query = {name: splitId[2]}; + mongoDataInstance.getMongoContent(options, handleMongoGetResult(options)); + } + }); + + + function handleMongoSetResult(options, current, callback) { + function handleResult(err, result) { + if (err) { + config.errors && console.log('ERR1 handleMongoSetResult Error while saving document ', options.collection, JSON.stringify(options.query), options.attribute || "", err); + return callback && callback(err); + } + config.debug && console.log('current', current, 'result', result, 'options', options); + if ((!current || !current.name) && (result.name || options.name)) { + var name = result.name || options.name; + var operation = { op: [ + { p: ['name'], oi: name, od: null } + ], v: options.operation.v }; + return model.applyOp(options.documentId, operation, function appliedOp(error, version) { + config.debug && console.log('setResult applyOp version', version); + if (error) { + config.error && console.log('ERR2 handleMongoSetResult', error); + return callback && callback(error); + } + return callback && callback(null, version); + }); + } else { + return callback(null, null); + } + } + + return handleResult; + } + + function handleMongoAttributeSetResult(options, current, callback) { + function handleResult(err, result) { + if (err) { + config.errors && console.log('ERR1 handleMongoAttributeSetResult Error while saving document ', options.collection, JSON.stringify(options.query), options.attribute || "", err); + return callback(err); + } + config.debug && console.log('current', current, 'result', result); + return callback(null, null); + } + + return handleResult; + } + + + function handleSetTimeout(documentId) { + return function saveContent() { + var args = timers[documentId]; + delete timers[documentId]; + config.debug && console.log('running timer', documentId); + mongoDataInstance.setMongoContent(args.current, args.options, + handleMongoSetResult(args.options, args.current, + function handleApplyOpResult(err, version) { + if (err) { + config.errors && console.log('ERR2 applyOp', version, err); + } + })); + }; + } + + function handleSetAttributeTimeout(documentId) { + return function saveAttribute() { + var args = timers[documentId]; + delete timers[documentId]; + config.debug && console.log('running timer', documentId); + var data = args.current; + if (args.options.type == 'json') { + data = JSON.parse(args.current); + } + mongoDataInstance.setMongoAttribute(data, args.options, + handleMongoAttributeSetResult(args.options, data, + function handleApplyOpResult(err, version) { + if (err) { + config.errors && console.log('ERR1 applyOp', documentId, version, err); + } + })); + }; + } + + // 'applyOp' event is fired when an operational transform is applied to to a shareDoc + // a shareDoc has changed and needs to be saved to mongo + model.on('applyOp', function persistDocument(documentId, operation, current) { + config.debug && console.log('applyOp', documentId, operation, current); + if (operation.v == 0) return; + + var splitId = documentId.split(':'); + var options = { + documentId: documentId, + type: splitId[0], + collection: splitId[1], + name: splitId[2], + attribute: null, + operation: operation, + no_share: true // prevent circular updates. + }; + var timer = { + current: current, + options: options + }; + var attribute = false; + if (splitId.length == 4) { +// options.query = {_id: splitId[2]}; + options.query = {name: splitId[2]}; + options.attribute = splitId[3]; + attribute = true; + } else { + options.query = {name: splitId[2]}; + } + if (timers[documentId]) { + timer.timer_id = timers[documentId].timer_id; + timers[documentId] = timer; + config.debug && console.log('resetting timer', documentId); + } else { + timers[documentId] = timer; + if (attribute) { + timer.timer_id = setTimeout( + handleSetAttributeTimeout(documentId), + config.savedelay); + } else { + timer.timer_id = setTimeout( + handleSetTimeout(documentId), + config.savedelay); + } + config.debug && console.log('setting timer', documentId); + } + }); +};