summaryrefslogtreecommitdiff
path: root/app/index.js
diff options
context:
space:
mode:
Diffstat (limited to 'app/index.js')
-rw-r--r--app/index.js233
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) {