diff options
| author | Sean Fridman <fridman@mail.sfsu.edu> | 2015-04-08 19:55:34 -0400 |
|---|---|---|
| committer | Sean Fridman <fridman@mail.sfsu.edu> | 2015-04-08 19:55:34 -0400 |
| commit | 32f87fcb58466e0e311349f96751c18e2a2cd7ea (patch) | |
| tree | 9b8c5e37821e147048e5d1ddbe5435af47716949 /app/node_modules/okadminview/index.js | |
| parent | 51296eb133599d98dd05d04399898c42b1662ab5 (diff) | |
Administrate!
Diffstat (limited to 'app/node_modules/okadminview/index.js')
| -rw-r--r-- | app/node_modules/okadminview/index.js | 320 |
1 files changed, 320 insertions, 0 deletions
diff --git a/app/node_modules/okadminview/index.js b/app/node_modules/okadminview/index.js new file mode 100644 index 0000000..42e7f36 --- /dev/null +++ b/app/node_modules/okadminview/index.js @@ -0,0 +1,320 @@ +var bodyParser = require('body-parser'); +var methodOverride = require('method-override'); +var Q = require('q'); +var pluralize = require('pluralize'); +var OKQuery = require('okquery'); + +/** + * OKAdminView! + */ +function OKAdminView(options) { + if (!(this instanceof OKAdminView)) return new OKAdminView(options); + if (!options.app) + throw new Error('No Express app provided to OKAdminView'); + if (!options.express) + throw new Error('No Express provided to OKAdminView'); + if (!options.resourceConfig) + throw new Error('No resourceConfig provided to OKAdminView'); + if (!options.resourceCache) + throw new Error('No resourceCache provided to OKAdminView'); + if (!options.templateProvider) + throw new Error('No templateProvider provided to OKAdminView'); + if (!options.meta) + throw new Error('No meta query provided to OKAdminView'); + var app = options.app; + var express = options.express; + var meta = options.meta; + var resourceCache = this._resourceCache = options.resourceCache; + var resourceConfig = this._resourceConfig = options.resourceConfig; + var provider = options.templateProvider; + // Load templates + var templates = this._templates = + ['index', 'resource', 'resource_new'].reduce(function(cache, name) { + var template = provider.getTemplate(name); + if (!template) + throw new Error('Admin theme does not have needed template: "' + name + '"'); + cache[name] = template; + return cache; + }, {}); + // OKAdmin middleware is a router, so mounts on 'use' + Object.defineProperty(this, 'mount', { + value: 'use', + writable: false, + enumerable: true + }); + + // Resources which apper on the index are a function of + // the resource configuration. + var indexQueries = this._indexQueries = Object.keys(resourceConfig) + .map(function(key) { + var config = resourceConfig[key]; + var type = config.type; + var staticData = config.static || {}; + var resource = resourceCache.get(config.type); + if (!resource) + throw new Error('Something weird is going on'); + var id = staticData[resource.idField]; + resource = resourceCache.get(type, id) || resource; + if (resource.bound) { + // Resource instances implement the query API + return OKQuery({resource: resource});; + } else { + return OKQuery({resource: resource, query: config.query}) + } + }); + + this._middleware = createMiddleware(this); + + function createMiddleware(view) { + var router = express.Router({ + strict: app.get('strict routing') + }); + + // Parse form data + router.use(bodyParser.urlencoded({extended: true})); + // HTML forms only support POST and GET methods + // We extend this by adding hidden input fields to the forms which + // specify the actual method desired. + router.use(methodOverride(function(req, res) { + // Parse out the hidden field + if (req.body && typeof req.body === 'object' && '_method' in req.body) { + var method = req.body._method; + delete req.body._method + return method + } + })); + + router.get('/', function readIndex(req, res, next) { + fetchIndexTemplateData(meta, indexQueries).then(function(data) { + view.renderIndex(req, res, data); + }).fail(errorHandler(req, res)); + }); + + router.get('/:type/new/', function createResourceView(req, res, next) { + var type = req.params.type || ''; + var resource = resourceCache.get(type); + if (!resource) { + errorHandler(req, res)(new Error('No such resource ' + type)); + } else { + meta.get().then(function(metadata) { + view.renderResourceNew(req, res, { + meta: metadata, + resource: { + type: resource.type, + spec: resource.spec + } + }); + }).fail(errorHandler(req, res)); + } + }); + + + router.get('/:type/:id/', function readResource(req, res, next) { + var type = req.params.type || ''; + var id = req.params.id || ''; + var resource = resourceCache.get(type, id); + if (!resource) { + errorHandler(req, res)(new Error('No such resource')); + } else { + var query = OKQuery({ + resource: resource, + query: id + }); + fetchResourceTemplateData(meta, query, transform).then(function(data) { + if (!data) { + resourceMissingHandler(req, res)() + } else { + view.renderResource(req, res, data); + } + }).fail(errorHandler(req, res)); + } + + function transform(meta, resource, data) { + meta = meta || {}; + resource = resource || {}; + data = data || {}; + var spec = Object.keys(resource.spec).reduce(function(cache, prop) { + var propSpec = resource.spec[prop]; + var value = data[prop]; + cache[prop].id = data[resource.idField]; + cache[prop].value = value; + return cache; + }, resource.spec); + return { + meta: meta, + resource: { + type: resource.type, + spec: spec + } + }; + } + }); + + router.post('/:type/', function createResource(req, res, next) { + var type = req.params.type; + var resource = resourceCache.get(type); + var data = req.body; + if (!resource) { + errorHandler(req, res)(new Error('No such resource ' + type)); + } else { + meta.get().then(function(metadata) { + var templateData = { + meta: metadata, + resource: { + type: resource.type, + spec: resource.spec, + data: data + } + }; + try { + resource.assertValid(data); + resource.create(data).then(function(created) { + res.redirect(303, data[resource.idField]); + }).fail(errorHandler(req, res)); + } catch (errors) { + view.renderResource(req, res, templateData); + } + }).fail(errorHandler(req, res));; + } + }); + + router.put('/:type/:id/', function updateResource(req, res, next) { + var type = req.params.type; + var id = req.params.id; + var data = req.body; + var resource = resourceCache.get(type, id); + if (!resource) { + errorHandler(req, res)(new Error('No such resource ' + type)); + } else { + // TODO Maybe should make metadata synchronous... + meta.get().then(function(metadata) { + var templateData = { + meta: metadata, + resource: { + spec: resource.spec, + data: data + } + }; + try { + resource.assertValid(data); + resource.update(id, data).then(function(updated) { + res.redirect(303, '../' + updated[resource.idField]); + }).fail(errorHandler(req, res)); + } catch (errors) { + view.renderResource(req, res, templateData); + } + }).fail(errorHandler(req, res)); + } + }); + + return router; + } +} + +OKAdminView.prototype.middleware = function() { + return this._middleware; +}; + +OKAdminView.prototype.renderIndex = function(req, res, data) { + data = data || {}; + this._templates['index'].render(data).then(function(rendered) { + res.send(rendered); + }).fail(errorHandler(req, res)); +}; + +OKAdminView.prototype.renderResource = function(req, res, data) { + data = data || {}; + this._templates['resource'].render(data).then(function(rendered) { + res.send(rendered); + }).fail(errorHandler(req, res)); +}; + +OKAdminView.prototype.renderResourceNew = function(req, res, data) { + data = data || {meta: {}, resource: {}}; + this._templates['resource_new'].render(data).then(function(rendered) { + res.send(rendered); + }).fail(errorHandler(req, res)); +}; + +/** + * Annotate template data with schema info + */ +function fetchIndexTemplateData(meta, queries) { + return Q.Promise(function(resolve, reject) { + Q.all([meta.get()].concat(queries.map(function(query) { + return query.get(); + }))).then(function(results) { + var meta = results.shift(); + var resources = results.reduce(function(cache, result, i) { + if (!result) + return cache; + var resource = queries[i].resource; + // We want the raw object spec + var spec = resource.spec; + var key = pluralize(resource.type); + if (!cache[key]) { + cache[key] = { + type: resource.type, + spec: spec, + data: [] + }; + } + + if (result.length) { + result.forEach(addToCache) + } else { + addToCache(result); + } + + function addToCache(data) { + // Report id to template under standard name + data.id = data[resource.idField]; + cache[key].data.push(data); + } + + return cache; + }, {}); + + resolve({ + meta: meta, + resources: resources + }); + }).fail(reject); + }); +} + +/** + * Annotate template data with schema info + */ +function fetchResourceTemplateData(meta, query, fn) { + fn = fn || function(m, r, d) { return {meta: m, resource: d}; }; + return Q.Promise(function(resolve, reject) { + meta.get().then(function(metadata) { + query.get().then(function(data) { + var resource = query.resource; + resolve(fn(metadata, resource, data)); + }).fail(reject); + }).fail(reject) + }); +} + +/** + * TODO Real error handling + */ +function errorHandler(req, res) { + return function(err) { + res.send(err.stack); + }; +} + +/** + * TODO Real 404 handling + */ +function resourceMissingHandler(req, res) { + return function() { + res.status(404); + res.send('404'); + } +} + +module.exports = OKAdminView; |
