From e01c2a20352d252e40ea133d0f4665b2e7513582 Mon Sep 17 00:00:00 2001 From: Sean Fridman Date: Thu, 9 Apr 2015 18:48:10 -0400 Subject: Lil fixes + defensive programming --- app/node_modules/okserver/index.js | 10 ---------- 1 file changed, 10 deletions(-) (limited to 'app/node_modules/okserver/index.js') diff --git a/app/node_modules/okserver/index.js b/app/node_modules/okserver/index.js index 1645eaa..cf06b3c 100644 --- a/app/node_modules/okserver/index.js +++ b/app/node_modules/okserver/index.js @@ -67,16 +67,6 @@ function OKServer(options) { // Make sure this lady is last. Checks whether the desired // route has a trailing-slash counterpart and redirects there app.use(slash()); - - /** - * Create a handler which redirect all requests to - * the same route with a trailing slash appended - */ - function redirect(routeNoSlash) { - return function(req, res) { - res.redirect(301, routeNoSlash + '/'); - } - } } OKServer.prototype.listen = function listen(port) { -- cgit v1.2.3-70-g09d2 From 5b7e5a133e5326a98b974b7e9e390ac393d1a05e Mon Sep 17 00:00:00 2001 From: Sean Fridman Date: Mon, 13 Apr 2015 13:42:11 -0400 Subject: Client friendly error handling --- app/index.js | 64 ++++++++++++++-- app/node_modules/okadminview/index.js | 125 ++++++++++++++------------------ app/node_modules/okserver/index.js | 7 ++ app/node_modules/okview/index.js | 133 +++++++++++++++------------------- examples/index.js | 2 + themes/okadmin/templates/404.liquid | 54 ++++++++++++++ themes/okadmin/templates/5xx.liquid | 54 ++++++++++++++ 7 files changed, 289 insertions(+), 150 deletions(-) create mode 100644 themes/okadmin/templates/404.liquid create mode 100644 themes/okadmin/templates/5xx.liquid (limited to 'app/node_modules/okserver/index.js') diff --git a/app/index.js b/app/index.js index 719c424..419adfc 100644 --- a/app/index.js +++ b/app/index.js @@ -36,6 +36,7 @@ function OKCMS(options) { var templateRoot = options.templateRoot || 'templates'; var adminTemplateRoot = options.templateRoot || path.join(__dirname, '../themes/okadmin/templates'); + var debug = options.debug || false; // Set metadata defaults // TODO Abstract this out somewhere else @@ -84,13 +85,17 @@ function OKCMS(options) { }); var resourceCache = this._resourceCache = this._createResources(resourceConfig, db, schemas); + 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); + resourceCache, adminTemplateProvider, adminMeta, + errorHandler); // Create services var imageService = OKImageService({ @@ -109,7 +114,8 @@ function OKCMS(options) { adminPath: adminPath, services: { image: imageService - } + }, + errorHandler: errorHandler }); } @@ -152,7 +158,7 @@ OKCMS.prototype._createResources = function(resourceConfig, db, schemaCache) { }; OKCMS.prototype._createViews = function(viewConfig, db, - meta, resourceCache, templateProvider) { + meta, resourceCache, templateProvider, errorHandler) { viewConfig = viewConfig || {}; var self = this; var createQueries = this._createQueries.bind(this); @@ -171,7 +177,8 @@ OKCMS.prototype._createViews = function(viewConfig, db, route: route, template: template, queries: queries, - meta: meta + meta: meta, + errorHandler: errorHandler }); return cache; }, {}); @@ -193,7 +200,8 @@ OKCMS.prototype._createViews = function(viewConfig, db, }; OKCMS.prototype._createAdminViews = function(path, app, express, - resourceConfig, resourceCache, templateProvider, meta) { + resourceConfig, resourceCache, templateProvider, meta, + errorHandler) { var views = {}; var withTrail = withTrailingSlash(path); var withoutTrail = withoutTrailingSlash(path); @@ -217,7 +225,8 @@ OKCMS.prototype._createAdminViews = function(path, app, express, resourceConfig: resourceConfig, resourceCache: resourceCache, templateProvider: templateProvider, - meta: meta + meta: meta, + errorHandler: errorHandler }); return views; }; @@ -269,6 +278,47 @@ ResourceCache.prototype.get = function(type, id) { } }; +/** + * 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) { diff --git a/app/node_modules/okadminview/index.js b/app/node_modules/okadminview/index.js index 2f31e1e..648487c 100644 --- a/app/node_modules/okadminview/index.js +++ b/app/node_modules/okadminview/index.js @@ -37,6 +37,8 @@ function OKAdminView(options) { throw new Error('No templateProvider provided to OKAdminView'); if (!options.meta) throw new Error('No meta query provided to OKAdminView'); + if (!options.errorHandler) + throw new Error('No error handler provided to OKAdminView'); var app = options.app; var express = options.express; @@ -44,6 +46,7 @@ function OKAdminView(options) { var resourceCache = this._resourceCache = options.resourceCache; var resourceConfig = this._resourceConfig = cloneDeep(options.resourceConfig); var provider = options.templateProvider; + var error = this._error = options.errorHandler; // Load templates var templates = this._templates = @@ -112,12 +115,10 @@ function OKAdminView(options) { } })); - var auth = passport.authenticate('digest', {session: false}); - // This should really be mounted on the router, but can't be due to // https://github.com/jaredhanson/passport-http/pull/16 app.use('/admin/', passport.initialize()); - app.all('/admin/:path*', auth); + app.all('/admin/:path*', passport.authenticate('digest', {session: false})); router.get('/', function readIndex(req, res, next) { fetchIndexTemplateData(meta, indexQueries).then(function(data) { @@ -125,22 +126,22 @@ function OKAdminView(options) { success: req.flash('success'), errors: req.flash('errors') })); - }).fail(errorHandler(req, res)); + }).fail(error(req, res, 500)); }); 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)); + error(req, res, 404)(new Error('No such resource ' + type)); } else { meta.get().then(function(metadata) { - var templateData = getResourceTemplateData(metadata, resource, {}); + var templateData = transformData(metadata, resource, {}); view.renderResourceNew(req, res, assign(templateData, { success: req.flash('success'), errors: req.flash('errors'), })); - }).fail(errorHandler(req, res)); + }).fail(error(req, res, 500)); } }); @@ -148,26 +149,27 @@ function OKAdminView(options) { 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, getResourceTemplateData) - .then(function(data) { - if (!data) { - resourceMissingHandler(req, res)() - } else { - view.renderResource(req, res, assign(data, { - success: req.flash('success'), - errors: req.flash('errors') - })); - } - }).fail(errorHandler(req, res)); - } - + var query = OKQuery({ + resource: resource, + query: id + }); + fetchResourceTemplateData(meta, query, transformData) + .then(function(data) { + if (!data) { + resourceMissingHandler(req, res)() + } else { + view.renderResource(req, res, assign(data, { + success: req.flash('success'), + errors: req.flash('errors') + })); + } + }).fail(function(err) { + if (err.message === 'No resource data') { + error(req, res, 404)(new Error('No such resource')); + } else { + error(req, res, 500)(err); + } + }); }); router.post('/:type/', function createResource(req, res, next) { @@ -175,7 +177,7 @@ function OKAdminView(options) { var resource = resourceCache.get(type); var data = req.body; if (!resource) { - errorHandler(req, res)(new Error('No such resource ' + type)); + error(req, res, 400)(new Error('No such resource ' + type)); } else { meta.get().then(function(metadata) { try { @@ -183,12 +185,12 @@ function OKAdminView(options) { resource.create(data).then(function(created) { req.flash('success', {action: 'create'}); res.redirect(303, resource.getID(data)); - }).fail(errorHandler(req, res)); + }).fail(error(req, res, 500)); } catch (errors) { - var templateData = getResourceTemplateData(metadata, resource, data); + var templateData = transformData(metadata, resource, data); view.renderResource(req, res, assign(templateData, {errors: errors})); } - }).fail(errorHandler(req, res));; + }).fail(error(req, res, 500));; } }); @@ -198,11 +200,9 @@ function OKAdminView(options) { var resourcesJSON = body[type]; var resource = resourceCache.get(type); if (!resourcesJSON || !resourcesJSON.length) { - res.status(400); - errorHandler(req, res)(new Error('Bad request')); + error(req, res, 400)(new Error('Bad request')); } else if (!resource) { - res.status(404); - errorHandler(req, res)(new Error('No such resource')); + error(req, res, 404)(new Error('No such resource')); } else { try { var ids = []; @@ -212,7 +212,7 @@ function OKAdminView(options) { return data; }); } catch (e) { - errorHandler(req, res)(new Error('Resource batch contains invalid JSON')); + error(req, res, 500)(new Error('Resource batch contains invalid JSON')); return; } Q.all([ @@ -222,7 +222,7 @@ function OKAdminView(options) { var metadata = results.shift(); req.flash('success', {action: 'batch_update'}); res.redirect(303, '../..'); - }).fail(errorHandler(req, res)); + }).fail(error(req, res, 500)); } }); @@ -232,7 +232,7 @@ function OKAdminView(options) { var data = req.body; var resource = resourceCache.get(type, id); if (!resource) { - errorHandler(req, res)(new Error('No such resource ' + type)); + error(req, res, 400)(new Error('No such resource ' + type)); } else { // TODO Prob should make metadata synchronous... meta.get().then(function(metadata) { @@ -241,12 +241,12 @@ function OKAdminView(options) { resource.update(id, data).then(function(updated) { req.flash('success', {action: 'update'}); res.redirect(303, '../' + resource.getID(updated)); - }).fail(errorHandler(req, res)); + }).fail(error(req, res, 500)); } catch (errors) { - var templateData = getResourceTemplateData(metadata, resource, data); + var templateData = transformData(metadata, resource, data); view.renderResource(req, res, assign(templateData, {errors: errors})); } - }).fail(errorHandler(req, res)); + }).fail(error(req, res, 500)); } }); @@ -255,14 +255,14 @@ function OKAdminView(options) { var id = req.params.id; var resource = resourceCache.get(type, id); if (!resource) { - errorHandler(req, res)(new Error('No such resource ' + type)); + error(req, res, 500)(new Error('No such resource ' + type)); } else { meta.get().then(function(metadata) { resource.destroy(id).then(function() { req.flash('success', {action: 'delete'}); res.redirect(303, '../..'); - }).fail(errorHandler(req, res)); - }).fail(errorHandler(req, res)); + }).fail(error(req, res, 500)); + }).fail(error(req, res, 500)); } }); @@ -271,9 +271,9 @@ function OKAdminView(options) { } /** - * Get template data for a single resource + * Yields formatted template data for a single resource */ -function getResourceTemplateData(meta, resource, data) { +function transformData(meta, resource, data) { meta = meta || {}; resource = resource || {}; data = data || {}; @@ -306,21 +306,21 @@ OKAdminView.prototype.renderIndex = function(req, res, data) { data = data || {}; this._templates['index'].render(data).then(function(rendered) { res.send(rendered); - }).fail(errorHandler(req, res)); + }).fail(this._error(req, res, 500)); }; OKAdminView.prototype.renderResource = function(req, res, data) { data = data || {}; this._templates['resource'].render(data).then(function(rendered) { res.send(rendered); - }).fail(errorHandler(req, res)); + }).fail(this._error(req, res, 500)); }; 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)); + }).fail(this._error(req, res, 500)); }; /** @@ -378,30 +378,15 @@ function fetchResourceTemplateData(meta, query, fn) { 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)); + if (!data) { + reject(new Error('No resource data')); + } else { + 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; diff --git a/app/node_modules/okserver/index.js b/app/node_modules/okserver/index.js index cf06b3c..abca8b5 100644 --- a/app/node_modules/okserver/index.js +++ b/app/node_modules/okserver/index.js @@ -16,6 +16,8 @@ function OKServer(options) { throw new Error('No admin root directory provided to OKServer'); if (!options.adminPath) throw new Error('No admin path provided to OKServer'); + if (!options.errorHandler) + throw new Error('No error handler provided to OKServer'); var root = options.root; var adminRoot = options.adminRoot; var adminPath = options.adminPath; @@ -25,6 +27,7 @@ function OKServer(options) { var router = express.Router({ strict: app.get('strict routing') }); + var error = options.errorHandler; var services = options.services || {}; Object.keys(views) // Sort such that more general routes are matched last @@ -67,6 +70,10 @@ function OKServer(options) { // Make sure this lady is last. Checks whether the desired // route has a trailing-slash counterpart and redirects there app.use(slash()); + // Otherwise it's a 404 + app.use(function(req, res) { + error(req, res, 404)(new Error('No matching route')); + }); } OKServer.prototype.listen = function listen(port) { diff --git a/app/node_modules/okview/index.js b/app/node_modules/okview/index.js index 2d1c0a0..63f22b5 100644 --- a/app/node_modules/okview/index.js +++ b/app/node_modules/okview/index.js @@ -24,8 +24,11 @@ function OKView(options) { throw new Error('No meta resource provided to OKView'); if (!options.route) throw new Error('No route provided to OKView'); + if (!options.errorHandler) + throw new Error('No error handler provided to OKView'); var route = options.route; var mount = options.mount || 'get'; + var error = this._error = options.errorHandler; this._template = options.template; var meta = this._meta = options.meta; var queries = this._queries = options.queries || []; @@ -47,16 +50,9 @@ function OKView(options) { enumerable: true }); - this._middleware = createMiddleware(this); - this._fetchTemplateData = unbound ? fetchUnbound : fetchBound; - - function fetchUnbound(id) { - return fetchTemplateData(meta, queries, id) - } - - function fetchBound() { - return fetchTemplateData(meta, queries); - } + this._middleware = unbound + ? unboundMiddleware(this, meta, queries, error) + : boundMiddleware(this, meta, queries, error); } OKView.prototype.middleware = function() { @@ -66,39 +62,20 @@ OKView.prototype.middleware = function() { OKView.prototype.render = function(req, res, data) { this._template.render(data).then(function(html) { res.send(html); - }).fail(errorHandler(req, res, data)); -}; - -OKView.prototype.fetchTemplateData = function() { - return this._fetchTemplateData.apply(this, arguments); + }).fail(this._error(req, res, 500)); }; -/** - * Unbound views need different middleware to resolve requests - */ -function createMiddleware(view) { - if (view.unbound) { - return unboundMiddleware(view); - } else { - return boundMiddleware(view); - } -} - -// Note that these middleware do not call next -// and should thus always be added at the end of the -// middleware chain. - /** * Creates middleware for a view which does not * yet have a resource id associated with it */ -function unboundMiddleware(view) { +function unboundMiddleware(view, meta, queries, error) { var paramName = getParamName(view.route); return function(req, res, next) { var id = req.params[paramName]; - view.fetchTemplateData(id).then(function(data) { + fetchTemplateData(meta, queries, id).then(function(data) { view.render(req, res, data); - }).fail(errorHandler(req, res, next)); + }).fail(failHandler(req, res, error)); }; } @@ -106,20 +83,22 @@ function unboundMiddleware(view) { * Creates middleware for a view which already * has a resource id associated with it */ -function boundMiddleware(view) { +function boundMiddleware(view, meta, queries, error) { return function(req, res, next) { - view.fetchTemplateData().then(function(data) { + fetchTemplateData(meta, queries).then(function(data) { view.render(req, res, data); - }).fail(errorHandler(req, res, next)); + }).fail(failHandler(req, res, error)); }; } -/** - * TODO BS error handling for now - */ -function errorHandler(req, res, next) { - return function(err) { - res.send(err.stack); +function failHandler(req, res, error) { + return function (err) { + // TODO Use custom exception type + if (err.message === 'No resource found') { + error(req, res, 404)(err); + } else { + error(req, res, 500)(err); + } } } @@ -138,9 +117,12 @@ function getParamName(route) { * and returns a promise for an object merging all queried * data, pluralizing keys where necessary. * - * Lil bit convoluted, sorry. + * Pretty convoluted, sorry. */ function fetchTemplateData(meta, queries, id) { + // If there's only one query, we assume it is for a single + // resource and will resolve errors if no data is found + var single = queries && queries.length === 1; return Q.promise(function(resolve, reject) { return Q.all( [meta.get()].concat(queries.map(function(query) { @@ -148,39 +130,44 @@ function fetchTemplateData(meta, queries, id) { }))) .then(function(results) { var metadata = results.shift(); - var normalized = results.reduce(function(cache, result, i) { - // Could be just some rogue request - if (!result) { - return cache; - } - var resource = queries[i].resource; - var type = queries[i].type; - var manyResult = isarray(result); - // Inform template of ID in generic field - if (manyResult) { - result = result.map(function(data) { - return assign({}, data, {id: resource.getID(data)}) - }); - } else { - result = assign({}, result, {id: resource.getID(result)}); - } - // If we have a lot of results for a certain type, - // we pluralize the key and yield an array of results - if (cache[type] || manyResult) { - var plural = pluralize(type); - delete cache[type]; - cache[plural] = []; + if (single && !results[0]) { + reject(new Error('No resource found')); + } else { + var normalized = results.reduce(function(cache, result, i) { + // Could be just some rogue request + if (!result) { + return cache; + } + var resource = queries[i].resource; + var type = queries[i].type; + var manyResult = isarray(result); + // Inform template of ID in generic field if (manyResult) { - cache[plural] = cache[plural].concat(result); + result = result.map(function(data) { + return assign({}, data, {id: resource.getID(data)}) + }); + } else { + result = assign({}, result, {id: resource.getID(result)}); + } + // If we have a lot of results for a certain type, + // we pluralize the key and yield an array of results + if (cache[type] || manyResult) { + var plural = pluralize(type); + delete cache[type]; + cache[plural] = []; + if (manyResult) { + cache[plural] = cache[plural].concat(result); + } else { + cache[plural].push(result); + } } else { - cache[plural].push(result); + cache[type] = result; } - } else { - cache[type] = result; - } - return cache; - }, {meta: metadata}); - resolve(normalized); + return cache; + }, {meta: metadata}); + + resolve(normalized); + } }).fail(reject); }); } diff --git a/examples/index.js b/examples/index.js index 95d2bcf..3e9f509 100644 --- a/examples/index.js +++ b/examples/index.js @@ -4,6 +4,8 @@ var app = okcms.createApp({ root: 'public', + debug: false, + schemas: { page: { id: {type: 'string'}, diff --git a/themes/okadmin/templates/404.liquid b/themes/okadmin/templates/404.liquid new file mode 100644 index 0000000..87f5342 --- /dev/null +++ b/themes/okadmin/templates/404.liquid @@ -0,0 +1,54 @@ + + + + 404 + + + +
+

¯\_(ツ)_/¯

+

We couldn't find that page.

+

Sure you have the right URL?

+ Back +
+ + diff --git a/themes/okadmin/templates/5xx.liquid b/themes/okadmin/templates/5xx.liquid new file mode 100644 index 0000000..f245545 --- /dev/null +++ b/themes/okadmin/templates/5xx.liquid @@ -0,0 +1,54 @@ + + + + 404 + + + +
+

(;一_一)

+

Looks like we experienced an error.

+

Sorry about that. Maybe try again later.

+ Back +
+ + -- cgit v1.2.3-70-g09d2 From 097f45b2dae9189b0847db0857d99fd3eec3782b Mon Sep 17 00:00:00 2001 From: Sean Fridman Date: Wed, 13 May 2015 17:17:42 -0400 Subject: Don't block serving of favicon.ico Fixes #10 --- app/node_modules/okserver/index.js | 5 ----- 1 file changed, 5 deletions(-) (limited to 'app/node_modules/okserver/index.js') diff --git a/app/node_modules/okserver/index.js b/app/node_modules/okserver/index.js index abca8b5..6d0ed17 100644 --- a/app/node_modules/okserver/index.js +++ b/app/node_modules/okserver/index.js @@ -52,11 +52,6 @@ function OKServer(options) { * other middleware. */ - // Intercept favicon requests and 404 for now - app.use('/favicon.ico', function(req, res) { - res.status(404) - return res.send(''); - }); // Serve user static files app.use(express.static(root)); // Serve admin interface static files -- cgit v1.2.3-70-g09d2 From eb2d4cd758eb06abfe9387a33f1e0f0898980d0d Mon Sep 17 00:00:00 2001 From: Sean Fridman Date: Sat, 12 Dec 2015 16:21:35 +0100 Subject: Implement Twitter service --- app/index.js | 15 +++++-- app/node_modules/okserver/index.js | 7 +++- app/node_modules/okservices/index.js | 43 ------------------- app/node_modules/okservices/oks3/index.js | 42 +++++++++++++++++++ app/node_modules/okservices/oks3/package.json | 15 +++++++ app/node_modules/okservices/oktwitter/Readme.md | 6 +++ app/node_modules/okservices/oktwitter/index.js | 48 ++++++++++++++++++++++ app/node_modules/okservices/oktwitter/package.json | 13 ++++++ app/node_modules/okservices/package.json | 7 +--- package.json | 2 +- 10 files changed, 143 insertions(+), 55 deletions(-) delete mode 100644 app/node_modules/okservices/index.js create mode 100644 app/node_modules/okservices/oks3/index.js create mode 100644 app/node_modules/okservices/oks3/package.json create mode 100644 app/node_modules/okservices/oktwitter/Readme.md create mode 100644 app/node_modules/okservices/oktwitter/index.js create mode 100644 app/node_modules/okservices/oktwitter/package.json (limited to 'app/node_modules/okserver/index.js') diff --git a/app/index.js b/app/index.js index 2507dd2..6a1c74f 100644 --- a/app/index.js +++ b/app/index.js @@ -13,7 +13,8 @@ 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') require('dotenv').load(); @@ -92,12 +93,19 @@ function OKCMS(options) { // Create services if (serviceConfig.s3) { - var imageService = OKImageService({ + var s3Service = OKS3Service({ express: express, s3: serviceConfig.s3, }); } + if (serviceConfig.twitter) { + var twitterService = OKTwitterService({ + express: express, + credentials: serviceConfig.twitter, + }) + } + var server = this._server = new OKServer({ express: express, app: app, @@ -108,7 +116,8 @@ function OKCMS(options) { adminRoot: adminRoot, adminPath: adminPath, services: { - image: imageService + s3: s3Service, + twitter: twitterService, }, errorHandler: errorHandler }); diff --git a/app/node_modules/okserver/index.js b/app/node_modules/okserver/index.js index 6d0ed17..ee1b4fb 100644 --- a/app/node_modules/okserver/index.js +++ b/app/node_modules/okserver/index.js @@ -59,8 +59,11 @@ function OKServer(options) { // Application router app.use(router); // Add services - if (services.image) { - app.use('/_services/image', services.image.middleware()); + if (services.s3) { + app.use('/_services/image', services.s3.middleware()); + } + if (services.twitter) { + app.use('/_services/twitter', services.twitter.middleware()) } // Make sure this lady is last. Checks whether the desired // route has a trailing-slash counterpart and redirects there diff --git a/app/node_modules/okservices/index.js b/app/node_modules/okservices/index.js deleted file mode 100644 index fbbdbfd..0000000 --- a/app/node_modules/okservices/index.js +++ /dev/null @@ -1,43 +0,0 @@ -var skipper = require('skipper'); - -function OKImageService(options) { - if (!(this instanceof OKImageService)) return new OKImageService(options); - options = options || {}; - if (!options.express) - throw new Error('Express not provided to OKImageService'); - if (!options.s3) - throw new Error('S3 configuration not provided to OKImageService'); - var express = options.express; - - var router = express.Router(); - - router.use(skipper()); - - router.post('/', function(req, res) { - // req should have a method `file` on it which is - // provided by skipper. Use that to do AWS stuff - req.file('image').upload({ - adapter: require('skipper-s3'), - key: options.s3.key, - secret: options.s3.secret, - bucket: options.s3.bucket, - dirname: options.s3.dirname, - maxBytes: options.s3.maxbytes, - headers: { - 'x-amz-acl': 'public-read' - } - }, function (err, uploadedFiles) { - res.json(uploadedFiles); - }); - }); - - this._middleware = router; -} - -OKImageService.prototype.middleware = function() { - return this._middleware; -}; - -module.exports = { - OKImageService: OKImageService -}; diff --git a/app/node_modules/okservices/oks3/index.js b/app/node_modules/okservices/oks3/index.js new file mode 100644 index 0000000..bd0915e --- /dev/null +++ b/app/node_modules/okservices/oks3/index.js @@ -0,0 +1,42 @@ +var skipper = require('skipper'); +var skipperS3 = require('skipper-s3') + +function OKS3(options) { + if (!(this instanceof OKS3)) return new OKS3(options); + options = options || {}; + if (!options.express) + throw new Error('Express not provided to OKS3'); + if (!options.s3) + throw new Error('S3 configuration not provided to OKS3'); + var express = options.express; + + var router = express.Router(); + + router.use(skipper()); + + router.post('/', function(req, res) { + // req should have a method `file` on it which is + // provided by skipper. Use that to do AWS stuff + req.file('image').upload({ + adapter: skipperS3, + key: options.s3.key, + secret: options.s3.secret, + bucket: options.s3.bucket, + dirname: options.s3.dirname, + maxBytes: options.s3.maxbytes, + headers: { + 'x-amz-acl': 'public-read' + } + }, function (err, uploadedFiles) { + res.json(uploadedFiles); + }); + }); + + this._middleware = router; +} + +OKS3.prototype.middleware = function() { + return this._middleware; +}; + +module.exports = OKS3 diff --git a/app/node_modules/okservices/oks3/package.json b/app/node_modules/okservices/oks3/package.json new file mode 100644 index 0000000..982fbfe --- /dev/null +++ b/app/node_modules/okservices/oks3/package.json @@ -0,0 +1,15 @@ +{ + "name": "oks3", + "version": "1.0.0", + "description": "s3 wassup", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "OKFocus", + "license": "None", + "dependencies": { + "skipper": "^0.5.7", + "skipper-s3": "^0.5.5" + } +} diff --git a/app/node_modules/okservices/oktwitter/Readme.md b/app/node_modules/okservices/oktwitter/Readme.md new file mode 100644 index 0000000..def73db --- /dev/null +++ b/app/node_modules/okservices/oktwitter/Readme.md @@ -0,0 +1,6 @@ +# oktwitter + +## Service to allow auth with Twitter API + +Requests to this service proxy to the twitter API, adding proper auth +credentials along the way diff --git a/app/node_modules/okservices/oktwitter/index.js b/app/node_modules/okservices/oktwitter/index.js new file mode 100644 index 0000000..ec4945d --- /dev/null +++ b/app/node_modules/okservices/oktwitter/index.js @@ -0,0 +1,48 @@ +var Twit = require('twit') + +/** + * Proxy to Twitter API adding auth creds + * TODO Technically can be abused by anyone right now. + * Should add some sort of same origin policy. + */ +function OKTwitter (options) { + if (!(this instanceof OKTwitter)) return new OKTwitter(options) + options = options || {} + if (!options.express) + throw new Error('Express not provided to OKTwitter'); + if (!options.credentials) + throw new Error('Twitter credentials not provided to OKTwitter'); + + var express = options.express + var router = express.Router() + var creds = options.credentials + var twitter = new Twit({ + consumer_key: creds.consumerKey, + consumer_secret: creds.consumerSecret, + access_token: creds.accessToken, + access_token_secret: creds.accessTokenSecret, + }) + + router.get('*', function (req, res) { + twitter.get(req.path.slice(1), req.query, function (err, data) { + if (err) { + res.status(err.statusCode) + res.send(err.twitterReply) + } else { + res.json(data) + } + }) + }) + + router.post('*', function (req, res) { + throw new Error('Twitter POST requests not implemented') + }) + + this._router = router +} + +OKTwitter.prototype.middleware = function () { + return this._router +} + +module.exports = OKTwitter diff --git a/app/node_modules/okservices/oktwitter/package.json b/app/node_modules/okservices/oktwitter/package.json new file mode 100644 index 0000000..ddee2f9 --- /dev/null +++ b/app/node_modules/okservices/oktwitter/package.json @@ -0,0 +1,13 @@ +{ + "name": "oktwitter", + "version": "1.0.0", + "description": "Allows auth to Twitter API", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "OKFocus", + "dependencies": { + "twit": "^2.1.1" + } +} diff --git a/app/node_modules/okservices/package.json b/app/node_modules/okservices/package.json index 2c95325..2669a49 100644 --- a/app/node_modules/okservices/package.json +++ b/app/node_modules/okservices/package.json @@ -6,10 +6,5 @@ "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, - "author": "OKFocus", - "license": "None", - "dependencies": { - "skipper": "^0.5.7", - "skipper-s3": "^0.5.5" - } + "author": "OKFocus" } diff --git a/package.json b/package.json index f91a986..8587ab7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "okcms", - "version": "0.1.12", + "version": "0.1.13", "description": "great", "main": "app/index.js", "scripts": { -- cgit v1.2.3-70-g09d2 From 56580705e3a62eaf1c498c1c0456fc33fe3b2f76 Mon Sep 17 00:00:00 2001 From: Jules Laplace Date: Tue, 5 Apr 2016 14:05:35 -0400 Subject: broaden media support --- app/node_modules/okserver/index.js | 2 +- app/node_modules/okservices/oks3/index.js | 54 ++++++++++++++++++-- examples/db.json | 29 ++++++++++- examples/index.js | 5 ++ package.json | 2 +- themes/okadmin/public/css/main.css | 3 ++ themes/okadmin/public/js/app.js | 18 ++++++- themes/okadmin/public/js/parser.js | 31 +++++++++-- themes/okadmin/public/js/upload.js | 61 ++++++++++++++++------ themes/okadmin/templates/partials/inputs.liquid | 68 ++++++++++++++++++------- 10 files changed, 226 insertions(+), 47 deletions(-) (limited to 'app/node_modules/okserver/index.js') diff --git a/app/node_modules/okserver/index.js b/app/node_modules/okserver/index.js index ee1b4fb..3428565 100644 --- a/app/node_modules/okserver/index.js +++ b/app/node_modules/okserver/index.js @@ -60,7 +60,7 @@ function OKServer(options) { app.use(router); // Add services if (services.s3) { - app.use('/_services/image', services.s3.middleware()); + app.use('/_services/s3', services.s3.middleware()); } if (services.twitter) { app.use('/_services/twitter', services.twitter.middleware()) diff --git a/app/node_modules/okservices/oks3/index.js b/app/node_modules/okservices/oks3/index.js index d556c20..dc0ca19 100644 --- a/app/node_modules/okservices/oks3/index.js +++ b/app/node_modules/okservices/oks3/index.js @@ -4,7 +4,7 @@ var skipperS3 = require('skipper-s3') // Hack to prevent this god-forsaken module from crashing our shit var d = require('domain').create() d.on('error', function (err) { - console.error('Stupid error in S3 upload. Image upload probably prematurely canceled') + console.error('Stupid error in S3 upload. Upload probably prematurely canceled') }) function OKS3(options) { @@ -20,9 +20,9 @@ function OKS3(options) { router.use(skipper()); - router.post('/', function(req, res) { - // req should have a method `file` on it which is - // provided by skipper. Use that to do AWS stuff + // req should have a method `file` on it which is + // provided by skipper. Use that to do AWS stuff + router.post('/image', function(req, res) { d.run(function () { req.file('image').upload({ adapter: skipperS3, @@ -38,7 +38,51 @@ function OKS3(options) { if (err) res.status(500).send(err) res.json(uploadedFiles); }); - }) + }); + }); + + router.post('/audio', function(req, res) { + d.run(function () { + if (! options.s3.allowAudioUploads) { + return res.status(500).json({ error: "audio uploading not permitted" }) + } + req.file('audio').upload({ + adapter: skipperS3, + key: options.s3.key, + secret: options.s3.secret, + bucket: options.s3.bucket, + dirname: options.s3.dirname, + maxBytes: options.s3.maxbytesAudio, + headers: { + 'x-amz-acl': 'public-read' + } + }, function (err, uploadedFiles) { + if (err) res.status(500).send(err) + res.json(uploadedFiles); + }); + }); + }); + + router.post('/video', function(req, res) { + d.run(function () { + if (! options.s3.allowVideoUploads) { + return res.status(500).json({ error: "video uploading not permitted" }) + } + req.file('video').upload({ + adapter: skipperS3, + key: options.s3.key, + secret: options.s3.secret, + bucket: options.s3.bucket, + dirname: options.s3.dirname, + maxBytes: options.s3.maxbytesVideo, + headers: { + 'x-amz-acl': 'public-read' + } + }, function (err, uploadedFiles) { + if (err) res.status(500).send(err) + res.json(uploadedFiles); + }); + }); }); this._middleware = router; diff --git a/examples/db.json b/examples/db.json index 1d045c3..3650355 100644 --- a/examples/db.json +++ b/examples/db.json @@ -174,7 +174,34 @@ "title": "Green", "__index": 1, "dateCreated": "Tue, 05 Apr 2016 15:17:57 GMT", - "media": [] + "media": [ + { + "type": "video", + "token": "https://ltho.s3.amazonaws.com/ee12b137-1c8a-400a-87e3-89cbee7b4da6.mp4", + "uri": "", + "width": "400", + "height": "400", + "title": "ee12b137-1c8a-400a-87e3-89cbee7b4da6.mp4", + "thumb": "http://okfocus.s3.amazonaws.com/misc/okcms/video.png" + }, + { + "type": "youtube", + "token": "y_35kXCQxN4", + "uri": "", + "width": "640", + "height": "360", + "title": "dëf lëöpär¨d¨¨¨¨<>~!@~#!:I!@", + "thumb": "http://i.ytimg.com/vi/y_35kXCQxN4/hqdefault.jpg" + }, + { + "type": "audio", + "token": "http://asdf.us/clouds35.mp3", + "uri": "", + "duration": "225.645792", + "title": "clouds35.mp3", + "thumb": "http://okfocus.s3.amazonaws.com/misc/okcms/video.png" + } + ] } ] } \ No newline at end of file diff --git a/examples/index.js b/examples/index.js index 81d9241..e9de0a3 100644 --- a/examples/index.js +++ b/examples/index.js @@ -40,6 +40,11 @@ var app = okcms.createApp({ key: process.env.S3_KEY, secret: process.env.S3_SECRET, bucket: process.env.S3_BUCKET, + allowVideoUploads: true, + allowAudioUploads: true, + maxsize: 200, + maxsizeVideo: 150000000, + maxsizeAudio: 150000000, } }, diff --git a/package.json b/package.json index c95a2bc..c3a7280 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "okcms", - "version": "0.1.24", + "version": "0.1.27", "description": "great", "main": "app/index.js", "scripts": { diff --git a/themes/okadmin/public/css/main.css b/themes/okadmin/public/css/main.css index 2f2d376..15b8781 100644 --- a/themes/okadmin/public/css/main.css +++ b/themes/okadmin/public/css/main.css @@ -303,6 +303,7 @@ button, input[type=submit] { width: 15em; height: 6em; } +.main.resource form .audio-element input[type=text], .main.resource form .video-element input[type=text] { width: 15em; } @@ -353,10 +354,12 @@ button, input[type=submit] { margin: 0; padding: 0; cursor: pointer; } +.audio-element:hover .remove, .video-element:hover .remove, .image-element:hover .remove { display: block; } +.audio-element .remove:hover, .video-element .remove:hover, .image-element .remove:hover { color: red; diff --git a/themes/okadmin/public/js/app.js b/themes/okadmin/public/js/app.js index e79f704..a12f517 100644 --- a/themes/okadmin/public/js/app.js +++ b/themes/okadmin/public/js/app.js @@ -5,7 +5,8 @@ var OKAdmin = function(){ var parent = this var uploader = new OKUpload () uploader.bind( this ) - uploader.add = function(url){ + uploader.add = function(media){ + var url = media.url var imageTemplate = $(".image-template", parent).html() var $el = $(imageTemplate) $el.find(".uri").val(url) @@ -46,7 +47,7 @@ var OKAdmin = function(){ $el.find("img").attr("src", url) $("ol", parent).prepend($el) } - uploader.addVideo = function(media){ + uploader.addMedia = function(media){ switch (media.type) { case 'youtube': case 'vimeo': @@ -64,6 +65,19 @@ var OKAdmin = function(){ $el.find("img").attr("src", media.thumbnail ) $("ol", parent).prepend($el) break + case 'audio': + var audioTemplate = $(".audio-template", parent).html() + var $el = $(audioTemplate) + $el.addClass("loaded") + $el.find(".audio-type").val( media.type ) + $el.find(".audio-token").val( media.token ) + $el.find(".audio-uri").val( media.uri ) + $el.find(".audio-title").val( media.title ) + $el.find(".audio-thumb").val( media.thumbnail ) + $el.find(".audio-duration").val( media.duration ) + $el.find("img").attr("src", media.thumbnail ) + $("ol", parent).prepend($el) + break case 'link': var linkTemplate = $(".link-template", parent).html() var $el = $(linkTemplate) diff --git a/themes/okadmin/public/js/parser.js b/themes/okadmin/public/js/parser.js index b4a087d..4ab9a6c 100644 --- a/themes/okadmin/public/js/parser.js +++ b/themes/okadmin/public/js/parser.js @@ -52,6 +52,31 @@ var Parser = { tag: function (media) { return '