var path = require('path'); var format = require('util').format; var withTrailingSlash = require('okutil').withTrailingSlash; var withoutTrailingSlash = require('okutil').withoutTrailingSlash; var assign = require('object-assign'); var express = require('express'); var OKQuery = require('okquery'); var OKView = require('okview'); var OKDB = require('okdb'); var OKResource = require('okresource') var OKTemplate = require('oktemplate'); var OKServer = require('okserver'); var OKRestEndpoint = require('okrest'); var OKSchema = require('okschema'); /** * OKCMS! * Basically takes configuration and gives you a server. */ function OKCMS(options) { if (!(this instanceof OKCMS)) return new OKCMS(options); options = options || {}; var app = express(); app.enable('strict routing'); var root = this._root = options.root || 'public'; var adminConfig = options.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 || path.join(__dirname, '../themes/okadmin/templates'); var schemaConfig = options.schemas || {}; var resourceConfig = options.resources || []; var viewConfig = options.views || { '/': { template: 'index' } }; var templateProvider = this._templateProvider = new OKTemplate({root: templateRoot}); var adminTemplateProvider = this._adminTemplateProvider = new OKTemplate({root: adminTemplateRoot}); var db = new OKDB(options.db || 'fs'); // Special query to get project wide meta data var meta = this._meta = { type: 'meta', get: function() { return db.getMeta(); } }; var schemas = this._schemas = this._createSchemas(schemaConfig); var resources = this._resources = this._createResources(resourceConfig, db, schemas); // Create view instances from config var views = this._views = this._createViews(viewConfig, db, meta, resources, templateProvider); var server = this._server = new OKServer({ express: express, app: app, views: views, root: root, adminRoot: adminRoot, adminPath: adminPath }); } OKCMS.prototype.listen = function listen(port, options) { options = options || {}; this._server.listen(port); }; OKCMS.prototype._createSchemas = function(schemaConfig) { schemaConfig = schemaConfig || {}; return Object.keys(schemaConfig).reduce(function(cache, key) { var spec = schemaConfig[key]; cache[key] = OKSchema(spec); return cache; }, {}); } OKCMS.prototype._createResources = function(resourceConfig, db, schemaCache) { resourceConfig = resourceConfig || {}; var typeCache = {}; return resourceConfig.reduce(function(cache, config) { var type = config.type; var schema = schemaCache[type]; if (!schema) throw new Error('Resource config references nonexistent schema'); // If we already created resource class, just skip if (cache[type]) return cache; cache[type] = OKResource({ type: type, db: db, schema: schema }) return cache; }, {}); }; OKCMS.prototype._createViews = function(viewConfig, db, meta, resourceCache, templateProvider) { viewConfig = viewConfig || {}; var self = this; var createQueries = this._createQueries.bind(this); return Object.keys(viewConfig).reduce(function(cache, route) { var config = viewConfig[route]; var templateName = config.template || getDefaultTemplate(route, config); var template = templateProvider.getTemplate(templateName); if (!template) { throw new Error(format('No template named "%s" found', templateName)); } var queryConfig = config.data || []; var queries = createQueries(queryConfig, 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 }); return cache; }, {}); /** * Returns the default template for a view config */ function getDefaultTemplate(route, config) { // Root route defaults to index if (/^\/?$/.test(route)) return 'index'; // Otherwise default to the backing resource name else if (config && config.data && config.data.type) return config.data.type; // Otherwise dunno else return '404'; } } OKCMS.prototype._createQueries = function(queryConfig, resourceCache) { queryConfig = queryConfig || {}; if (!queryConfig.length) queryConfig = [queryConfig]; return queryConfig.map(function(config) { var type = config.type; var resource = resourceCache[type]; if (!resource) throw new Error('Query configured with nonexistent resource'); // Default to "select all" query var query = config.query || '*'; return new OKQuery({ resource: resource, query: query }); }); }; module.exports = { createApp: function(options) { return new OKCMS(options); }, OKResource: OKResource, OKView: OKView, OKRestEndpoint: OKRestEndpoint };