var path = require('path'); var format = require('util').format; 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 root = this._root = options.root || 'www'; var schemaConfig = options.schemas || {}; var resourceConfig = options.resources || []; var viewConfig = options.views || { '/': { template: 'index' } }; var db = new OKDB(options.db || 'fs'); var templateCache = this._templateCache = new OKTemplate({root: root}); var server = this._server = new OKServer({root: root}); var meta = this._meta = { name: 'meta', get: function() { return db.getMeta(); } }; var schemas = this._schemas = this._createSchemas(schemaConfig); var resources = this._resources = this._createResources(resourceConfig, db, schemas); var views = this._views = this._createViews(viewConfig, db, meta, resources, templateCache); this._initViews(server, views); } 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, templateCache) { 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 = templateCache.getTemplate(templateName); if (!template) throw new Error(format('No template named "%s" found', templateName)); var queryConfig = config.data || []; var queries = createQueries(queryConfig, resourceCache); // Instantiate! cache[route] = OKView({ 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 }); }); }; OKCMS.prototype._initViews = function(server, views) { Object.keys(views) // Make sure more specific routes are processed first // TODO This is not semantically correct (bro) // Will prob manifest bugs .sort(function(a, b) { return a.length < b.length; }) // Add the views .forEach(function(route) { server.addView(route, views[route]); }); }; module.exports = { createApp: function(options) { return new OKCMS(options); }, OKResource: OKResource, OKView: OKView, OKRestEndpoint: OKRestEndpoint };