🎉 Middleware refactor: Give the API its own express App (#7537)

refs #4172

* 🎨 Use bodyParser only where it is needed

This is a pretty extreme optimisation, however in the interests of killing middleware/index.js it
seemed prudent to move towards not having in there that wasn't strictly necessary 😁

We should reassess how apps do this sort of thing, but it seems pretty sane to declare bodyParsing
if and only if it is necessary.

* 🎨 Move all API code to API router

* 🎨 Refactor API into an App, not just a router

- Apps have their own rendering engines, only the frontend & the admin panel need views
- The API should be JSON only, with minimal middleware
- Individual sections within the API could/should be treated as Routers

* 🎨 Flatten API middleware inclusion

- get rid of the weird middleware object
- move the api-only middleware into the middleware/api folder
This commit is contained in:
Hannah Wolfe 2016-10-11 09:36:00 +01:00 committed by Katharina Irrgang
parent 0227efb41b
commit 61bf54ec88
14 changed files with 312 additions and 291 deletions

240
core/server/api/app.js Normal file
View File

@ -0,0 +1,240 @@
// # API routes
var express = require('express'),
tmpdir = require('os').tmpdir,
// This essentially provides the controllers for the routes
api = require('../api'),
// Include the middleware
// API specific
auth = require('../auth'),
cors = require('../middleware/api/cors'), // routes only?!
spamPrevention = require('../middleware/api/spam-prevention'), // routes only
versionMatch = require('../middleware/api/version-match'), // global
// Handling uploads & imports
upload = require('multer')({dest: tmpdir()}), // routes only
validation = require('../middleware/validation'), // routes only
// Shared
bodyParser = require('body-parser'), // global, shared
cacheControl = require('../middleware/cache-control'), // global, shared
maintenance = require('../middleware/maintenance'), // global, shared
errorHandler = require('../middleware/error-handler'), // global, shared
// Temporary
// @TODO find a more appy way to do this!
labs = require('../middleware/labs'),
// @TODO find a better way to bundle these authentication packages
// Authentication for public endpoints
authenticatePublic = [
auth.authenticate.authenticateClient,
auth.authenticate.authenticateUser,
auth.authorize.requiresAuthorizedUserPublicAPI,
// @TODO do we really need this multiple times or should it be global?
cors
],
// Require user for private endpoints
authenticatePrivate = [
auth.authenticate.authenticateClient,
auth.authenticate.authenticateUser,
auth.authorize.requiresAuthorizedUser,
// @TODO do we really need this multiple times or should it be global?
cors
];
// @TODO refactor/clean this up - how do we want the routing to work long term?
function apiRoutes() {
var apiRouter = express.Router();
// alias delete with del
apiRouter.del = apiRouter.delete;
// ## CORS pre-flight check
apiRouter.options('*', cors);
// ## Configuration
apiRouter.get('/configuration', authenticatePrivate, api.http(api.configuration.read));
apiRouter.get('/configuration/:key', authenticatePrivate, api.http(api.configuration.read));
apiRouter.get('/configuration/timezones', authenticatePrivate, api.http(api.configuration.read));
// ## Posts
apiRouter.get('/posts', authenticatePublic, api.http(api.posts.browse));
apiRouter.post('/posts', authenticatePrivate, api.http(api.posts.add));
apiRouter.get('/posts/:id', authenticatePublic, api.http(api.posts.read));
apiRouter.get('/posts/slug/:slug', authenticatePublic, api.http(api.posts.read));
apiRouter.put('/posts/:id', authenticatePrivate, api.http(api.posts.edit));
apiRouter.del('/posts/:id', authenticatePrivate, api.http(api.posts.destroy));
// ## Schedules
apiRouter.put('/schedules/posts/:id', [
auth.authenticate.authenticateClient,
auth.authenticate.authenticateUser
], api.http(api.schedules.publishPost));
// ## Settings
apiRouter.get('/settings', authenticatePrivate, api.http(api.settings.browse));
apiRouter.get('/settings/:key', authenticatePrivate, api.http(api.settings.read));
apiRouter.put('/settings', authenticatePrivate, api.http(api.settings.edit));
// ## Users
apiRouter.get('/users', authenticatePublic, api.http(api.users.browse));
apiRouter.get('/users/:id', authenticatePublic, api.http(api.users.read));
apiRouter.get('/users/slug/:slug', authenticatePublic, api.http(api.users.read));
apiRouter.get('/users/email/:email', authenticatePublic, api.http(api.users.read));
apiRouter.put('/users/password', authenticatePrivate, api.http(api.users.changePassword));
apiRouter.put('/users/owner', authenticatePrivate, api.http(api.users.transferOwnership));
apiRouter.put('/users/:id', authenticatePrivate, api.http(api.users.edit));
apiRouter.post('/users', authenticatePrivate, api.http(api.users.add));
apiRouter.del('/users/:id', authenticatePrivate, api.http(api.users.destroy));
// ## Tags
apiRouter.get('/tags', authenticatePublic, api.http(api.tags.browse));
apiRouter.get('/tags/:id', authenticatePublic, api.http(api.tags.read));
apiRouter.get('/tags/slug/:slug', authenticatePublic, api.http(api.tags.read));
apiRouter.post('/tags', authenticatePrivate, api.http(api.tags.add));
apiRouter.put('/tags/:id', authenticatePrivate, api.http(api.tags.edit));
apiRouter.del('/tags/:id', authenticatePrivate, api.http(api.tags.destroy));
// ## Subscribers
apiRouter.get('/subscribers', labs.subscribers, authenticatePrivate, api.http(api.subscribers.browse));
apiRouter.get('/subscribers/csv', labs.subscribers, authenticatePrivate, api.http(api.subscribers.exportCSV));
apiRouter.post('/subscribers/csv',
labs.subscribers,
authenticatePrivate,
upload.single('subscribersfile'),
validation.upload({type: 'subscribers'}),
api.http(api.subscribers.importCSV)
);
apiRouter.get('/subscribers/:id', labs.subscribers, authenticatePrivate, api.http(api.subscribers.read));
apiRouter.post('/subscribers', labs.subscribers, authenticatePublic, api.http(api.subscribers.add));
apiRouter.put('/subscribers/:id', labs.subscribers, authenticatePrivate, api.http(api.subscribers.edit));
apiRouter.del('/subscribers/:id', labs.subscribers, authenticatePrivate, api.http(api.subscribers.destroy));
// ## Roles
apiRouter.get('/roles/', authenticatePrivate, api.http(api.roles.browse));
// ## Clients
apiRouter.get('/clients/slug/:slug', api.http(api.clients.read));
// ## Slugs
apiRouter.get('/slugs/:type/:name', authenticatePrivate, api.http(api.slugs.generate));
// ## Themes
apiRouter.get('/themes/:name/download',
authenticatePrivate,
api.http(api.themes.download)
);
apiRouter.post('/themes/upload',
authenticatePrivate,
upload.single('theme'),
validation.upload({type: 'themes'}),
api.http(api.themes.upload)
);
apiRouter.del('/themes/:name',
authenticatePrivate,
api.http(api.themes.destroy)
);
// ## Notifications
apiRouter.get('/notifications', authenticatePrivate, api.http(api.notifications.browse));
apiRouter.post('/notifications', authenticatePrivate, api.http(api.notifications.add));
apiRouter.del('/notifications/:id', authenticatePrivate, api.http(api.notifications.destroy));
// ## DB
apiRouter.get('/db', authenticatePrivate, api.http(api.db.exportContent));
apiRouter.post('/db',
authenticatePrivate,
upload.single('importfile'),
validation.upload({type: 'db'}),
api.http(api.db.importContent)
);
apiRouter.del('/db', authenticatePrivate, api.http(api.db.deleteAllContent));
// ## Mail
apiRouter.post('/mail', authenticatePrivate, api.http(api.mail.send));
apiRouter.post('/mail/test', authenticatePrivate, api.http(api.mail.sendTest));
// ## Slack
apiRouter.post('/slack/test', authenticatePrivate, api.http(api.slack.sendTest));
// ## Authentication
apiRouter.post('/authentication/passwordreset',
spamPrevention.forgotten,
api.http(api.authentication.generateResetToken)
);
apiRouter.put('/authentication/passwordreset', api.http(api.authentication.resetPassword));
apiRouter.post('/authentication/invitation', api.http(api.authentication.acceptInvitation));
apiRouter.get('/authentication/invitation', api.http(api.authentication.isInvitation));
apiRouter.post('/authentication/setup', api.http(api.authentication.setup));
apiRouter.put('/authentication/setup', authenticatePrivate, api.http(api.authentication.updateSetup));
apiRouter.get('/authentication/setup', api.http(api.authentication.isSetup));
apiRouter.post('/authentication/token',
spamPrevention.signin,
auth.authenticate.authenticateClient,
auth.oauth.generateAccessToken
);
apiRouter.post('/authentication/ghost', [
auth.authenticate.authenticateClient,
auth.authenticate.authenticateGhostUser,
api.http(api.authentication.createTokens)
]);
apiRouter.post('/authentication/revoke', authenticatePrivate, api.http(api.authentication.revoke));
// ## Uploads
// @TODO: rename endpoint to /images/upload (or similar)
apiRouter.post('/uploads',
authenticatePrivate,
upload.single('uploadimage'),
validation.upload({type: 'images'}),
api.http(api.uploads.add)
);
// ## Invites
apiRouter.get('/invites', authenticatePrivate, api.http(api.invites.browse));
apiRouter.get('/invites/:id', authenticatePrivate, api.http(api.invites.read));
apiRouter.post('/invites', authenticatePrivate, api.http(api.invites.add));
apiRouter.del('/invites/:id', authenticatePrivate, api.http(api.invites.destroy));
return apiRouter;
}
module.exports = function setupApiApp() {
var apiApp = express();
// API middleware
// Body parsing
apiApp.use(bodyParser.json({limit: '1mb'}));
apiApp.use(bodyParser.urlencoded({extended: true, limit: '1mb'}));
// send 503 json response in case of maintenance
apiApp.use(maintenance);
// @TODO check SSL and pretty URLS is needed here too, once we've finished refactoring
// Check version matches for API requests, depends on res.locals.safeVersion being set
// Therefore must come after themeHandler.ghostLocals, for now
apiApp.use(versionMatch);
// API shouldn't be cached
apiApp.use(cacheControl('private'));
// Routing
apiApp.use(apiRoutes());
// API error handling
// @TODO: split the API error handling into its own thing?
apiApp.use(errorHandler);
return apiApp;
};

View File

@ -12,7 +12,7 @@ var path = require('path'),
function controller(req, res, next) {
var defaultView = path.resolve(__dirname, 'views', 'amp.hbs'),
paths = templates.getActiveThemePaths(req.app.get('activeTheme')),
data = req.body;
data = req.amp;
if (res.error) {
data.error = res.error;
@ -33,10 +33,12 @@ function controller(req, res, next) {
}
function getPostData(req, res, next) {
// Create a req property where we can store our data
req.amp = {};
postLookup(res.locals.relativeUrl)
.then(function (result) {
if (result && result.post) {
req.body.post = result.post;
req.amp.post = result.post;
}
next();

View File

@ -37,7 +37,7 @@ describe('AMP Controller', function () {
route: {path: '/'},
query: {r: ''},
params: {},
body: {}
amp: {}
};
defaultPath = path.join(configUtils.config.get('paths').appRoot, '/core/server/apps/amp/lib/views/amp.hbs');
@ -148,7 +148,7 @@ describe('AMP getPostData', function () {
};
req = {
body: {
amp: {
post: {}
}
};
@ -173,7 +173,7 @@ describe('AMP getPostData', function () {
ampController.__set__('postLookup', postLookupStub);
ampController.getPostData(req, res, function () {
req.body.post.should.be.eql({
req.amp.post.should.be.eql({
id: '1',
slug: 'welcome-to-ghost',
isAmpURL: true
@ -197,7 +197,7 @@ describe('AMP getPostData', function () {
err.message.should.be.eql('not found');
err.statusCode.should.be.eql(404);
err.errorType.should.be.eql('NotFoundError');
req.body.post.should.be.eql({});
req.amp.should.be.eql({});
done();
});
});
@ -210,7 +210,7 @@ describe('AMP getPostData', function () {
ampController.getPostData(req, res, function (err) {
should.exist(err);
err.should.be.eql('not found');
req.body.post.should.be.eql({});
req.amp.should.be.eql({});
done();
});
});

View File

@ -1,6 +1,7 @@
var path = require('path'),
express = require('express'),
middleware = require('./middleware'),
bodyParser = require('body-parser'),
templates = require('../../../controllers/frontend/templates'),
setResponseContext = require('../../../controllers/frontend/context'),
privateRouter = express.Router();
@ -29,6 +30,7 @@ privateRouter.route('/')
controller
)
.post(
bodyParser.urlencoded({extended: true}),
middleware.isPrivateSessionAuth,
middleware.spamPrevention,
middleware.authenticateProtection,

View File

@ -2,6 +2,7 @@ var path = require('path'),
express = require('express'),
_ = require('lodash'),
subscribeRouter = express.Router(),
bodyParser = require('body-parser'),
// Dirty requires
api = require('../../../api'),
@ -92,6 +93,7 @@ subscribeRouter.route('/')
controller
)
.post(
bodyParser.urlencoded({extended: true}),
honeyPot,
handleSource,
storeSubscriber,

View File

@ -3,7 +3,7 @@ var oauth2orize = require('oauth2orize'),
utils = require('../utils'),
errors = require('../errors'),
authenticationAPI = require('../api/authentication'),
spamPrevention = require('../middleware/spam-prevention'),
spamPrevention = require('../middleware/api/spam-prevention'),
i18n = require('../i18n'),
oauthServer,
oauth;

View File

@ -2,7 +2,7 @@ var cors = require('cors'),
_ = require('lodash'),
url = require('url'),
os = require('os'),
config = require('../config'),
config = require('../../config'),
whitelist = [],
ENABLE_CORS = {origin: true, maxAge: 86400},
DISABLE_CORS = {origin: false};

View File

@ -7,9 +7,9 @@
// Helpers to handle spam detection on signin, forgot password, and protected pages.
var _ = require('lodash'),
errors = require('../errors'),
config = require('../config'),
i18n = require('../i18n'),
errors = require('../../errors'),
config = require('../../config'),
i18n = require('../../i18n'),
loginSecurity = [],
forgottenSecurity = [],
spamPrevention;

View File

@ -1,56 +1,39 @@
var debug = require('debug')('ghost:middleware'),
bodyParser = require('body-parser'),
compress = require('compression'),
express = require('express'),
hbs = require('express-hbs'),
path = require('path'),
netjet = require('netjet'),
multer = require('multer'),
tmpdir = require('os').tmpdir,
serveStatic = require('express').static,
routes = require('../routes'),
// app requires
config = require('../config'),
storage = require('../storage'),
logging = require('../logging'),
errors = require('../errors'),
helpers = require('../helpers'),
i18n = require('../i18n'),
logging = require('../logging'),
routes = require('../routes'),
storage = require('../storage'),
utils = require('../utils'),
// This should probably be an internal app
sitemapHandler = require('../data/xml/sitemap/handler'),
cacheControl = require('./cache-control'),
checkSSL = require('./check-ssl'),
decideIsAdmin = require('./decide-is-admin'),
redirectToSetup = require('./redirect-to-setup'),
// middleware
compress = require('compression'),
netjet = require('netjet'),
serveStatic = require('express').static,
// local middleware
cacheControl = require('./cache-control'),
checkSSL = require('./check-ssl'),
decideIsAdmin = require('./decide-is-admin'),
errorHandler = require('./error-handler'),
ghostLocals = require('./ghost-locals'),
maintenance = require('./maintenance'),
prettyURLs = require('./pretty-urls'),
serveSharedFile = require('./serve-shared-file'),
spamPrevention = require('./spam-prevention'),
staticTheme = require('./static-theme'),
themeHandler = require('./theme-handler'),
maintenance = require('./maintenance'),
errorHandler = require('./error-handler'),
versionMatch = require('./api/version-match'),
cors = require('./cors'),
validation = require('./validation'),
labs = require('./labs'),
helpers = require('../helpers'),
middleware,
setupMiddleware;
redirectToSetup = require('./redirect-to-setup'),
serveSharedFile = require('./serve-shared-file'),
staticTheme = require('./static-theme'),
themeHandler = require('./theme-handler');
middleware = {
upload: multer({dest: tmpdir()}),
validation: validation,
cacheControl: cacheControl,
spamPrevention: spamPrevention,
api: {
errorHandler: errorHandler,
cors: cors,
labs: labs,
versionMatch: versionMatch,
maintenance: maintenance
}
};
setupMiddleware = function setupMiddleware(blogApp) {
module.exports = function setupMiddleware(blogApp) {
debug('Middleware start');
var adminApp = express(),
@ -171,23 +154,18 @@ setupMiddleware = function setupMiddleware(blogApp) {
// must happen AFTER asset loading and BEFORE routing
blogApp.use(prettyURLs);
// Body parsing
blogApp.use(bodyParser.json({limit: '1mb'}));
blogApp.use(bodyParser.urlencoded({extended: true, limit: '1mb'}));
// ### Caching
// Blog frontend is cacheable
blogApp.use(cacheControl('public'));
// Admin shouldn't be cached
adminApp.use(cacheControl('private'));
// API shouldn't be cached
blogApp.use(routes.apiBaseUri, cacheControl('private'));
debug('General middleware done');
// ### Routing
// Set up API routes
blogApp.use(routes.apiBaseUri, routes.api(middleware));
// Load the API
// @TODO: finish refactoring the API app
// @TODO: decide what to do with these paths - config defaults? config overrides?
blogApp.use('/ghost/api/v0.1/', require('../api/app')());
// Mount admin express app to /ghost and set up routes
adminApp.use(redirectToSetup);
@ -210,7 +188,3 @@ setupMiddleware = function setupMiddleware(blogApp) {
blogApp.use(errorHandler);
debug('Middleware end');
};
module.exports = setupMiddleware;
// Export middleware functions directly
module.exports.middleware = middleware;

View File

@ -1,193 +0,0 @@
// # API routes
var express = require('express'),
api = require('../api'),
auth = require('../auth'),
apiRoutes;
apiRoutes = function apiRoutes(middleware) {
var router = express.Router(),
// Authentication for public endpoints
authenticatePublic = [
auth.authenticate.authenticateClient,
auth.authenticate.authenticateUser,
auth.authorize.requiresAuthorizedUserPublicAPI,
middleware.api.cors
],
// Require user for private endpoints
authenticatePrivate = [
auth.authenticate.authenticateClient,
auth.authenticate.authenticateUser,
auth.authorize.requiresAuthorizedUser,
middleware.api.cors
];
// alias delete with del
router.del = router.delete;
// send 503 json response in case of maintenance
router.use(middleware.api.maintenance);
// Check version matches for API requests, depends on res.locals.safeVersion being set
// Therefore must come after themeHandler.ghostLocals, for now
router.use(middleware.api.versionMatch);
// ## CORS pre-flight check
router.options('*', middleware.api.cors);
// ## Configuration
router.get('/configuration', authenticatePrivate, api.http(api.configuration.read));
router.get('/configuration/:key', authenticatePrivate, api.http(api.configuration.read));
router.get('/configuration/timezones', authenticatePrivate, api.http(api.configuration.read));
// ## Posts
router.get('/posts', authenticatePublic, api.http(api.posts.browse));
router.post('/posts', authenticatePrivate, api.http(api.posts.add));
router.get('/posts/:id', authenticatePublic, api.http(api.posts.read));
router.get('/posts/slug/:slug', authenticatePublic, api.http(api.posts.read));
router.put('/posts/:id', authenticatePrivate, api.http(api.posts.edit));
router.del('/posts/:id', authenticatePrivate, api.http(api.posts.destroy));
// ## Schedules
router.put('/schedules/posts/:id', [
auth.authenticate.authenticateClient,
auth.authenticate.authenticateUser
], api.http(api.schedules.publishPost));
// ## Settings
router.get('/settings', authenticatePrivate, api.http(api.settings.browse));
router.get('/settings/:key', authenticatePrivate, api.http(api.settings.read));
router.put('/settings', authenticatePrivate, api.http(api.settings.edit));
// ## Users
router.get('/users', authenticatePublic, api.http(api.users.browse));
router.get('/users/:id', authenticatePublic, api.http(api.users.read));
router.get('/users/slug/:slug', authenticatePublic, api.http(api.users.read));
router.get('/users/email/:email', authenticatePublic, api.http(api.users.read));
router.put('/users/password', authenticatePrivate, api.http(api.users.changePassword));
router.put('/users/owner', authenticatePrivate, api.http(api.users.transferOwnership));
router.put('/users/:id', authenticatePrivate, api.http(api.users.edit));
router.post('/users', authenticatePrivate, api.http(api.users.add));
router.del('/users/:id', authenticatePrivate, api.http(api.users.destroy));
// ## Tags
router.get('/tags', authenticatePublic, api.http(api.tags.browse));
router.get('/tags/:id', authenticatePublic, api.http(api.tags.read));
router.get('/tags/slug/:slug', authenticatePublic, api.http(api.tags.read));
router.post('/tags', authenticatePrivate, api.http(api.tags.add));
router.put('/tags/:id', authenticatePrivate, api.http(api.tags.edit));
router.del('/tags/:id', authenticatePrivate, api.http(api.tags.destroy));
// ## Subscribers
router.get('/subscribers', middleware.api.labs.subscribers, authenticatePrivate, api.http(api.subscribers.browse));
router.get('/subscribers/csv', middleware.api.labs.subscribers, authenticatePrivate, api.http(api.subscribers.exportCSV));
router.post('/subscribers/csv',
middleware.api.labs.subscribers,
authenticatePrivate,
middleware.upload.single('subscribersfile'),
middleware.validation.upload({type: 'subscribers'}),
api.http(api.subscribers.importCSV)
);
router.get('/subscribers/:id', middleware.api.labs.subscribers, authenticatePrivate, api.http(api.subscribers.read));
router.post('/subscribers', middleware.api.labs.subscribers, authenticatePublic, api.http(api.subscribers.add));
router.put('/subscribers/:id', middleware.api.labs.subscribers, authenticatePrivate, api.http(api.subscribers.edit));
router.del('/subscribers/:id', middleware.api.labs.subscribers, authenticatePrivate, api.http(api.subscribers.destroy));
// ## Roles
router.get('/roles/', authenticatePrivate, api.http(api.roles.browse));
// ## Clients
router.get('/clients/slug/:slug', api.http(api.clients.read));
// ## Slugs
router.get('/slugs/:type/:name', authenticatePrivate, api.http(api.slugs.generate));
// ## Themes
router.get('/themes/:name/download',
authenticatePrivate,
api.http(api.themes.download)
);
router.post('/themes/upload',
authenticatePrivate,
middleware.upload.single('theme'),
middleware.validation.upload({type: 'themes'}),
api.http(api.themes.upload)
);
router.del('/themes/:name',
authenticatePrivate,
api.http(api.themes.destroy)
);
// ## Notifications
router.get('/notifications', authenticatePrivate, api.http(api.notifications.browse));
router.post('/notifications', authenticatePrivate, api.http(api.notifications.add));
router.del('/notifications/:id', authenticatePrivate, api.http(api.notifications.destroy));
// ## DB
router.get('/db', authenticatePrivate, api.http(api.db.exportContent));
router.post('/db',
authenticatePrivate,
middleware.upload.single('importfile'),
middleware.validation.upload({type: 'db'}),
api.http(api.db.importContent)
);
router.del('/db', authenticatePrivate, api.http(api.db.deleteAllContent));
// ## Mail
router.post('/mail', authenticatePrivate, api.http(api.mail.send));
router.post('/mail/test', authenticatePrivate, api.http(api.mail.sendTest));
// ## Slack
router.post('/slack/test', authenticatePrivate, api.http(api.slack.sendTest));
// ## Authentication
router.post('/authentication/passwordreset',
middleware.spamPrevention.forgotten,
api.http(api.authentication.generateResetToken)
);
router.put('/authentication/passwordreset', api.http(api.authentication.resetPassword));
router.post('/authentication/invitation', api.http(api.authentication.acceptInvitation));
router.get('/authentication/invitation', api.http(api.authentication.isInvitation));
router.post('/authentication/setup', api.http(api.authentication.setup));
router.put('/authentication/setup', authenticatePrivate, api.http(api.authentication.updateSetup));
router.get('/authentication/setup', api.http(api.authentication.isSetup));
router.post('/authentication/token',
middleware.spamPrevention.signin,
auth.authenticate.authenticateClient,
auth.oauth.generateAccessToken
);
router.post('/authentication/ghost', [
auth.authenticate.authenticateClient,
auth.authenticate.authenticateGhostUser,
api.http(api.authentication.createTokens)
]);
router.post('/authentication/revoke', authenticatePrivate, api.http(api.authentication.revoke));
// ## Uploads
// @TODO: rename endpoint to /images/upload (or similar)
router.post('/uploads',
authenticatePrivate,
middleware.upload.single('uploadimage'),
middleware.validation.upload({type: 'images'}),
api.http(api.uploads.add)
);
// ## Invites
router.get('/invites', authenticatePrivate, api.http(api.invites.browse));
router.get('/invites/:id', authenticatePrivate, api.http(api.invites.read));
router.post('/invites', authenticatePrivate, api.http(api.invites.add));
router.del('/invites/:id', authenticatePrivate, api.http(api.invites.destroy));
// API Router middleware
router.use(middleware.api.errorHandler);
return router;
};
module.exports = apiRoutes;

View File

@ -1,10 +1,4 @@
var api = require('./api'),
admin = require('./admin'),
frontend = require('./frontend');
module.exports = {
apiBaseUri: '/ghost/api/v0.1/',
api: api,
admin: admin,
frontend: frontend
admin: require('./admin'),
frontend: require('./frontend')
};

View File

@ -1,8 +1,8 @@
var sinon = require('sinon'),
should = require('should'),
rewire = require('rewire'),
configUtils = require('../../utils/configUtils'),
cors = rewire('../../../server/middleware/cors');
configUtils = require('../../../utils/configUtils'),
cors = rewire('../../../../server/middleware/api/cors');
describe('cors', function () {
var res, req, next, sandbox;
@ -33,7 +33,7 @@ describe('cors', function () {
afterEach(function () {
sandbox.restore();
configUtils.restore();
cors = rewire('../../../server/middleware/cors');
cors = rewire('../../../../server/middleware/api/cors');
});
it('should not be enabled without a request origin header', function (done) {

View File

@ -1,7 +1,7 @@
var should = require('should'),
sinon = require('sinon'),
rewire = require('rewire'),
middleware = require('../../../server/middleware').middleware;
spamPrevention = require('../../../../server/middleware/api/spam-prevention');
describe('Middleware: spamPrevention', function () {
var sandbox,
@ -19,7 +19,7 @@ describe('Middleware: spamPrevention', function () {
spyNext = sinon.spy(function (param) {
error = param;
});
middleware.spamPrevention = rewire('../../../server/middleware/spam-prevention');
spamPrevention = rewire('../../../../server/middleware/api/spam-prevention');
});
afterEach(function () {
@ -41,7 +41,7 @@ describe('Middleware: spamPrevention', function () {
it('calls next if refreshing the token', function (done) {
req.body.grant_type = 'refresh_token';
middleware.spamPrevention.signin(req, null, next);
spamPrevention.signin(req, null, next);
next.calledOnce.should.be.true();
done();
@ -50,7 +50,7 @@ describe('Middleware: spamPrevention', function () {
it ('creates a BadRequestError when there\'s no username', function (done) {
req.body = {};
middleware.spamPrevention.signin(req, null, spyNext);
spamPrevention.signin(req, null, spyNext);
should.exist(error);
error.errorType.should.eql('BadRequestError');
@ -59,10 +59,10 @@ describe('Middleware: spamPrevention', function () {
it ('rate limits after 10 attempts', function (done) {
for (var ndx = 0; ndx < 10; ndx = ndx + 1) {
middleware.spamPrevention.signin(req, null, spyNext);
spamPrevention.signin(req, null, spyNext);
}
middleware.spamPrevention.signin(req, null, spyNext);
spamPrevention.signin(req, null, spyNext);
should.exist(error);
error.errorType.should.eql('TooManyRequestsError');
@ -76,10 +76,10 @@ describe('Middleware: spamPrevention', function () {
});
for (ndx = 0; ndx < 10; ndx = ndx + 1) {
middleware.spamPrevention.signin(req, null, spyNext);
spamPrevention.signin(req, null, spyNext);
}
middleware.spamPrevention.signin(req, null, spyNext);
spamPrevention.signin(req, null, spyNext);
error.errorType.should.eql('TooManyRequestsError');
error = null;
@ -89,7 +89,7 @@ describe('Middleware: spamPrevention', function () {
return [3610, 10];
});
middleware.spamPrevention.signin(req, null, spyNext);
spamPrevention.signin(req, null, spyNext);
should(error).equal(undefined);
spyNext.called.should.be.true();
@ -117,7 +117,7 @@ describe('Middleware: spamPrevention', function () {
passwordreset: [{}]
};
middleware.spamPrevention.forgotten(req, null, spyNext);
spamPrevention.forgotten(req, null, spyNext);
error.errorType.should.eql('BadRequestError');
done();
@ -125,10 +125,10 @@ describe('Middleware: spamPrevention', function () {
it ('creates an unauthorized error after 5 attempts with same email', function (done) {
for (var ndx = 0; ndx < 6; ndx = ndx + 1) {
middleware.spamPrevention.forgotten(req, null, spyNext);
spamPrevention.forgotten(req, null, spyNext);
}
middleware.spamPrevention.forgotten(req, null, spyNext);
spamPrevention.forgotten(req, null, spyNext);
error.errorType.should.eql('TooManyRequestsError');
done();
@ -143,10 +143,10 @@ describe('Middleware: spamPrevention', function () {
{email: email}
];
middleware.spamPrevention.forgotten(req, null, spyNext);
spamPrevention.forgotten(req, null, spyNext);
}
middleware.spamPrevention.forgotten(req, null, spyNext);
spamPrevention.forgotten(req, null, spyNext);
error.errorType.should.eql('TooManyRequestsError');
done();

View File

@ -1,6 +1,6 @@
var should = require('should'),
sinon = require('sinon'),
middleware = require('../../../server/middleware').middleware;
cacheControl = require('../../../server/middleware/cache-control');
describe('Middleware: cacheControl', function () {
var sandbox,
@ -19,7 +19,7 @@ describe('Middleware: cacheControl', function () {
});
it('correctly sets the public profile headers', function (done) {
middleware.cacheControl('public')(null, res, function (a) {
cacheControl('public')(null, res, function (a) {
should.not.exist(a);
res.set.calledOnce.should.be.true();
res.set.calledWith({'Cache-Control': 'public, max-age=0'});
@ -28,7 +28,7 @@ describe('Middleware: cacheControl', function () {
});
it('correctly sets the private profile headers', function (done) {
middleware.cacheControl('private')(null, res, function (a) {
cacheControl('private')(null, res, function (a) {
should.not.exist(a);
res.set.calledOnce.should.be.true();
res.set.calledWith({
@ -40,7 +40,7 @@ describe('Middleware: cacheControl', function () {
});
it('will not set headers without a profile', function (done) {
middleware.cacheControl()(null, res, function (a) {
cacheControl()(null, res, function (a) {
should.not.exist(a);
res.set.called.should.be.false();
done();
@ -48,8 +48,8 @@ describe('Middleware: cacheControl', function () {
});
it('will not get confused between serving public and private', function (done) {
var publicCC = middleware.cacheControl('public'),
privateCC = middleware.cacheControl('private');
var publicCC = cacheControl('public'),
privateCC = cacheControl('private');
publicCC(null, res, function () {
res.set.calledOnce.should.be.true();
@ -79,7 +79,7 @@ describe('Middleware: cacheControl', function () {
it('will override public with private for private blogs', function (done) {
res.isPrivateBlog = true;
middleware.cacheControl('public')(null, res, function (a) {
cacheControl('public')(null, res, function (a) {
should.not.exist(a);
res.set.calledOnce.should.be.true();
res.set.calledWith({