summaryrefslogtreecommitdiff
path: root/app/node_modules/okadminview/index.js
diff options
context:
space:
mode:
authorSean Fridman <fridman@mail.sfsu.edu>2015-04-08 19:55:34 -0400
committerSean Fridman <fridman@mail.sfsu.edu>2015-04-08 19:55:34 -0400
commit32f87fcb58466e0e311349f96751c18e2a2cd7ea (patch)
tree9b8c5e37821e147048e5d1ddbe5435af47716949 /app/node_modules/okadminview/index.js
parent51296eb133599d98dd05d04399898c42b1662ab5 (diff)
Administrate!
Diffstat (limited to 'app/node_modules/okadminview/index.js')
-rw-r--r--app/node_modules/okadminview/index.js320
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;