diff options
Diffstat (limited to 'app/index.js')
| -rw-r--r-- | app/index.js | 233 |
1 files changed, 170 insertions, 63 deletions
diff --git a/app/index.js b/app/index.js index b312eb1..c0d2ff3 100644 --- a/app/index.js +++ b/app/index.js @@ -13,7 +13,9 @@ var OKResource = require('okresource') var OKTemplate = require('oktemplate'); var OKServer = require('okserver'); var OKSchema = require('okschema'); -var OKImageService = require('okservices').OKImageService; +var OKS3Service = require('okservices/oks3'); +var OKTwitterService = require('okservices/oktwitter') +var OKWebhookService = require('okservices/okwebhook') require('dotenv').load(); @@ -27,72 +29,103 @@ function OKCMS(options) { var app = express(); app.enable('strict routing'); + app.disable('x-powered-by'); + + var schemaConfig = options.schemas || {}; + var resourceConfig = options.resources || []; + var viewConfig = options.views || { + '/': { template: 'index' } + }; + var serviceConfig = options.services || {}; + var adminConfig = options.admin || {} var root = this._root = options.root || 'public'; - var adminConfig = options.admin || {}; + var templateRoot = options.templateRoot || 'templates'; + + var adminPath = this._adminPath = adminConfig.path || '/admin'; var adminRoot = this._adminRoot = adminConfig.root || path.join(__dirname, '../themes/okadmin/public'); - var adminPath = this._adminPath = adminConfig.path || '/_admin' - var templateRoot = options.templateRoot || 'templates'; - var adminTemplateRoot = options.templateRoot || + var adminTemplateRoot = adminConfig.templateRoot || path.join(__dirname, '../themes/okadmin/templates'); - // Set metadata defaults - // TODO Abstract this out somewhere else - var meta = { - type: 'meta', - get: function() { - return Q.promise(function(resolve, reject) { - db.getMeta().then(function(metadata) { - resolve(assign({}, { - static: '' - }, metadata)); - }).fail(reject); - }); - } - }; + var debug = !!options.debug; + var production = !!options.production; - var adminMeta ={ - type: 'meta', - get: function() { - return Q.promise(function(resolve, reject) { - db.getMeta().then(function(metadata) { - resolve(assign({}, { - static: withoutTrailingSlash(adminPath) - }, metadata)); - }).fail(reject); - }); - } + var metaUser = options.meta || {}; + var metaDefault = { + project: 'OKCMS', + production: production, + debug: debug }; - var schemaConfig = options.schemas || {}; - var resourceConfig = options.resources || []; - var viewConfig = options.views || { - '/': { template: 'index' } - }; - var serviceConfig = options.services || {}; + var meta = assign({ + static: '' + }, metaDefault, metaUser); - var templateProvider = this._templateProvider = - new OKTemplate({root: templateRoot}); - var adminTemplateProvider = this._adminTemplateProvider = - new OKTemplate({root: adminTemplateRoot}); + var adminMeta = assign({ + static: withoutTrailingSlash(adminPath), + services: options.services, + }, metaDefault, metaUser); + + var templateProvider = this._templateProvider = new OKTemplate({ + root: templateRoot, + debug: debug + }); + var adminTemplateProvider = this._adminTemplateProvider = new OKTemplate({ + root: adminTemplateRoot, + debug: debug + }); - var db = new OKDB(options.db || 'fs'); var schemas = this._schemas = this._createSchemas(schemaConfig); + var db = new OKDB({ + db: options.db || 'fs', + schemas: schemas + }); var resourceCache = this._resourceCache = this._createResources(resourceConfig, db, schemas); - + this._resolveForeignKeys(resourceCache) + var errorHandler = createErrorHandlerProducer( + templateProvider, adminTemplateProvider, debug); // Create view instances from config var views = this._views = - this._createViews(viewConfig, db, meta, resourceCache, templateProvider); + this._createViews(viewConfig, db, meta, resourceCache, templateProvider, + errorHandler); var adminViews = this._adminViews = - this._createAdminViews(adminPath, app, express, resourceConfig, - resourceCache, adminTemplateProvider, adminMeta); + this._createAdminViews(adminConfig, adminPath, app, express, resourceConfig, + resourceCache, adminTemplateProvider, adminMeta, + errorHandler); // Create services - var imageService = OKImageService({ - express: express, - s3: serviceConfig.s3, + var services = {} + Object.keys(serviceConfig).forEach(function(key){ + var config = serviceConfig[key] + switch (key) { + case 's3': + services.s3 = OKS3Service({ + express: express, + s3: config, + }); + break + case 'twitter': + services.twitter = OKTwitterService({ + express: express, + credentials: config, + }); + break + case 'webhook': + services.webhook = OKWebhookService({ + express: express, + config: config, + }); + break + default: + services[key] = config.lib({ + db: resourceCache, + express: express, + config: config, + }); + break + } }); var server = this._server = new OKServer({ @@ -104,9 +137,8 @@ function OKCMS(options) { root: root, adminRoot: adminRoot, adminPath: adminPath, - services: { - image: imageService - } + services: services, + errorHandler: errorHandler }); } @@ -119,6 +151,11 @@ OKCMS.prototype._createSchemas = function(schemaConfig) { schemaConfig = schemaConfig || {}; return Object.keys(schemaConfig).reduce(function(cache, key) { var spec = schemaConfig[key]; + // All resources have an autoincrementing index so we can order them suckas + // TODO Screw the __ prefix, just consider 'index' a reserved word + spec.__index = {type: 'meta', autoincrement: true}; + // All resources have a dateCreated field + spec.dateCreated = {type: 'meta'}; cache[key] = OKSchema(spec); return cache; }, {}); @@ -130,8 +167,8 @@ OKCMS.prototype._createResources = function(resourceConfig, db, schemaCache) { var type = config.type; var schema = schemaCache[type]; if (!schema) - throw new Error('Resource config references nonexistent schema'); - var resource = OKResource({ + throw new Error('Resource config references nonexistent schema ' + type); + var resource = new OKResource({ type: type, db: db, schema: schema @@ -146,8 +183,23 @@ OKCMS.prototype._createResources = function(resourceConfig, db, schemaCache) { return ResourceCache(resources); }; +OKCMS.prototype._resolveForeignKeys = function(resourceCache) { + resourceCache.forEach(function(resource) { + Object.keys(resource.foreignKeys).forEach(function(field) { + var foreignKeyType = resource.foreignKeys[field] + var keyedResource = resourceCache.get(foreignKeyType) + if (!keyedResource) { + throw new Error(format( + "Foreign key field '%s' in '%s' resource references unknown" + + "resource of type '%s'", field, resource.type, foreignKeyType)) + } + resource._linkForeignKey(field, resourceCache.get(foreignKeyType)) + }) + }) +} + OKCMS.prototype._createViews = function(viewConfig, db, - meta, resourceCache, templateProvider) { + meta, resourceCache, templateProvider, errorHandler) { viewConfig = viewConfig || {}; var self = this; var createQueries = this._createQueries.bind(this); @@ -158,15 +210,15 @@ OKCMS.prototype._createViews = function(viewConfig, db, if (!template) { throw new Error(format('No template named "%s" found', templateName)); } - var queryConfig = config.data || []; - var queries = createQueries(queryConfig, resourceCache); + var queries = createQueries(config.data, resourceCache); // Don't forget to add that trailing slash if the user forgot cache[withTrailingSlash(route)] = OKView({ mount: 'get', // User defined views are read only route: route, template: template, queries: queries, - meta: meta + meta: meta, + errorHandler: errorHandler }); return cache; }, {}); @@ -187,8 +239,8 @@ OKCMS.prototype._createViews = function(viewConfig, db, } }; -OKCMS.prototype._createAdminViews = function(path, app, express, - resourceConfig, resourceCache, templateProvider, meta) { +OKCMS.prototype._createAdminViews = function(adminConfig, path, app, express, + resourceConfig, resourceCache, templateProvider, meta, errorHandler) { var views = {}; var withTrail = withTrailingSlash(path); var withoutTrail = withoutTrailingSlash(path); @@ -212,13 +264,16 @@ OKCMS.prototype._createAdminViews = function(path, app, express, resourceConfig: resourceConfig, resourceCache: resourceCache, templateProvider: templateProvider, - meta: meta + meta: meta, + errorHandler: errorHandler, + dashboardConfig: adminConfig.dashboard || {} }); return views; }; OKCMS.prototype._createQueries = function(queryConfig, resourceCache) { - queryConfig = queryConfig || {}; + if (!queryConfig) + return []; if (!queryConfig.length) queryConfig = [queryConfig]; return queryConfig.map(function(config) { @@ -230,7 +285,11 @@ OKCMS.prototype._createQueries = function(queryConfig, resourceCache) { var query = config.query || '*'; return new OKQuery({ resource: resource, - query: query + query: query, + as: config.as, + sortBy: config.sortBy, + descending: config.descending, + groupBy: config.groupBy }); }); }; @@ -248,7 +307,7 @@ function ResourceCache(resources) { throw new Error('Undefined resource given to ResourceCache'); if (resource.bound) { cache[resource.type] = resource.parent; - cache[resource.type + ':' + resource.id] = resource; + cache[resource.type + ':' + resource.getID()] = resource; } else { cache[resource.type] = resource; } @@ -264,6 +323,54 @@ ResourceCache.prototype.get = function(type, id) { } }; +ResourceCache.prototype.forEach = function(cb) { + cb = cb || function() {} + Object.keys(this._cache).forEach(function(key) { + cb(this._cache[key]) + }.bind(this)) +} + +/** + * Higher order function implementing customizable error handling + */ +function createErrorHandlerProducer(templateProvider, adminTemplateProvider, debugMode) { + + var template404 = templateProvider.getTemplate('404') || + adminTemplateProvider.getTemplate('404'); + var template5xx = templateProvider.getTemplate('5xx') || + adminTemplateProvider.getTemplate('5xx'); + + if (!template404 || !template5xx) + throw new Error('No error templates provided by admin theme or user') + + return debugMode ? createDebugHandler : createErrorHandler; + + function createErrorHandler(req, res, status) { + return function handleError(err) { + switch (status) { + case 404: + template404.render().then(function(rendered) { + res.status(status); + res.send(rendered); + }); + break; + default: + template5xx.render().then(function(rendered) { + res.status(status); + res.send(rendered); + }); + } + + }; + } + + function createDebugHandler(req, res, status) { + return function handleError(err) { + res.send(err.stack); + }; + } +} + module.exports = { createApp: function(options) { |
