Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
refs #9866 - Registered Content API under /ghost/api/v2/content/ - Registered Admin API under /ghost/api/v2/admin/ - Moved API v0.1 implementation to web/api/v0.1 - Created web/api/v2 for the new api endpoints - Started with reducing the implementation for the new Content API (the Content api does not serve admin api endpoints, that's why it was reducible) - Covered parent-app module with basic test checking correct applications/routes are being mounted - Added a readme file, which contains a warning using v2, because it's under active development! - This PR does only make the new endpoints available, we have not: - optimised the web folder (e.g. res.isAdmin) - started with different API controllers - reason: we want to do more preparation tasks before we copy the api controllers
- Loading branch information
Showing
14 changed files
with
547 additions
and
21 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
# Ghost APIs | ||
|
||
Ghost is moving towards providing more robust APIs in the future. A plan and decisions can be found [here](https://github.com/TryGhost/Ghost/issues/9866). | ||
|
||
## WARNING! | ||
|
||
The v2 API (`/ghost/api/v2/*` endpoints) is to be considered under active development until this message is removed. Please use with caution and don't rely too heavy on it just yet :) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
8 changes: 4 additions & 4 deletions
8
core/server/web/api/middleware.js → core/server/web/api/v0.1/middleware.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
15 changes: 7 additions & 8 deletions
15
core/server/web/api/routes.js → core/server/web/api/v0.1/routes.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
// # API routes | ||
const debug = require('ghost-ignition').debug('api'), | ||
boolParser = require('express-query-boolean'), | ||
express = require('express'), | ||
|
||
// routes | ||
routes = require('./routes'), | ||
|
||
// Include the middleware | ||
|
||
// API specific | ||
versionMatch = require('../../../middleware/api/version-match'), // global | ||
|
||
// 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 | ||
|
||
module.exports = function setupApiApp() { | ||
debug('Admin API v2 setup start'); | ||
const apiApp = express(); | ||
|
||
// @TODO finish refactoring this away. | ||
apiApp.use(function setIsAdmin(req, res, next) { | ||
// api === isAdmin | ||
res.isAdmin = true; | ||
next(); | ||
}); | ||
|
||
// API middleware | ||
|
||
// Body parsing | ||
apiApp.use(bodyParser.json({limit: '1mb'})); | ||
apiApp.use(bodyParser.urlencoded({extended: true, limit: '1mb'})); | ||
|
||
// Query parsing | ||
apiApp.use(boolParser()); | ||
|
||
// send 503 json response in case of maintenance | ||
apiApp.use(maintenance); | ||
|
||
// 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(routes()); | ||
|
||
// API error handling | ||
apiApp.use(errorHandler.resourceNotFound); | ||
apiApp.use(errorHandler.handleJSONResponse); | ||
|
||
debug('Admin API v2 setup end'); | ||
|
||
return apiApp; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
const prettyURLs = require('../../../middleware/pretty-urls'), | ||
cors = require('../../../middleware/api/cors'), | ||
urlRedirects = require('../../../middleware/url-redirects'), | ||
auth = require('../../../../services/auth'); | ||
|
||
/** | ||
* Authentication for private endpoints | ||
*/ | ||
module.exports.authenticatePrivate = [ | ||
auth.authenticate.authenticateClient, | ||
auth.authenticate.authenticateUser, | ||
auth.authorize.requiresAuthorizedUser, | ||
cors, | ||
urlRedirects, | ||
prettyURLs | ||
]; | ||
|
||
/** | ||
* Authentication for client endpoints | ||
*/ | ||
module.exports.authenticateClient = function authenticateClient(client) { | ||
return [ | ||
auth.authenticate.authenticateClient, | ||
auth.authenticate.authenticateUser, | ||
auth.authorize.requiresAuthorizedClient(client), | ||
cors, | ||
urlRedirects, | ||
prettyURLs | ||
]; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,222 @@ | ||
const express = require('express'), | ||
// This essentially provides the controllers for the routes | ||
api = require('../../../../api'), | ||
|
||
// Middleware | ||
mw = require('./middleware'), | ||
|
||
// API specific | ||
auth = require('../../../../services/auth'), | ||
cors = require('../../../middleware/api/cors'), | ||
brute = require('../../../middleware/brute'), | ||
|
||
// Handling uploads & imports | ||
tmpdir = require('os').tmpdir, | ||
upload = require('multer')({dest: tmpdir()}), | ||
validation = require('../../../middleware/validation'), | ||
image = require('../../../middleware/image'), | ||
|
||
// Temporary | ||
// @TODO find a more appy way to do this! | ||
labs = require('../../../middleware/labs'); | ||
|
||
module.exports = function apiRoutes() { | ||
const router = express.Router(); | ||
|
||
// alias delete with del | ||
router.del = router.delete; | ||
|
||
// ## CORS pre-flight check | ||
router.options('*', cors); | ||
|
||
// ## Configuration | ||
router.get('/configuration', api.http(api.configuration.read)); | ||
router.get('/configuration/:key', mw.authenticatePrivate, api.http(api.configuration.read)); | ||
|
||
// ## Posts | ||
router.get('/posts', mw.authenticatePrivate, api.http(api.posts.browse)); | ||
|
||
router.post('/posts', mw.authenticatePrivate, api.http(api.posts.add)); | ||
router.get('/posts/:id', mw.authenticatePrivate, api.http(api.posts.read)); | ||
router.get('/posts/slug/:slug', mw.authenticatePrivate, api.http(api.posts.read)); | ||
router.put('/posts/:id', mw.authenticatePrivate, api.http(api.posts.edit)); | ||
router.del('/posts/:id', mw.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/routes/yaml', mw.authenticatePrivate, api.http(api.settings.download)); | ||
router.post('/settings/routes/yaml', | ||
mw.authenticatePrivate, | ||
upload.single('routes'), | ||
validation.upload({type: 'routes'}), | ||
api.http(api.settings.upload) | ||
); | ||
|
||
router.get('/settings', mw.authenticatePrivate, api.http(api.settings.browse)); | ||
router.get('/settings/:key', mw.authenticatePrivate, api.http(api.settings.read)); | ||
router.put('/settings', mw.authenticatePrivate, api.http(api.settings.edit)); | ||
|
||
// ## Users | ||
router.get('/users', mw.authenticatePrivate, api.http(api.users.browse)); | ||
router.get('/users/:id', mw.authenticatePrivate, api.http(api.users.read)); | ||
router.get('/users/slug/:slug', mw.authenticatePrivate, api.http(api.users.read)); | ||
// NOTE: We don't expose any email addresses via the public api. | ||
router.get('/users/email/:email', mw.authenticatePrivate, api.http(api.users.read)); | ||
|
||
router.put('/users/password', mw.authenticatePrivate, api.http(api.users.changePassword)); | ||
router.put('/users/owner', mw.authenticatePrivate, api.http(api.users.transferOwnership)); | ||
router.put('/users/:id', mw.authenticatePrivate, api.http(api.users.edit)); | ||
router.del('/users/:id', mw.authenticatePrivate, api.http(api.users.destroy)); | ||
|
||
// ## Tags | ||
router.get('/tags', mw.authenticatePrivate, api.http(api.tags.browse)); | ||
router.get('/tags/:id', mw.authenticatePrivate, api.http(api.tags.read)); | ||
router.get('/tags/slug/:slug', mw.authenticatePrivate, api.http(api.tags.read)); | ||
router.post('/tags', mw.authenticatePrivate, api.http(api.tags.add)); | ||
router.put('/tags/:id', mw.authenticatePrivate, api.http(api.tags.edit)); | ||
router.del('/tags/:id', mw.authenticatePrivate, api.http(api.tags.destroy)); | ||
|
||
// ## Subscribers | ||
router.get('/subscribers', labs.subscribers, mw.authenticatePrivate, api.http(api.subscribers.browse)); | ||
router.get('/subscribers/csv', labs.subscribers, mw.authenticatePrivate, api.http(api.subscribers.exportCSV)); | ||
router.post('/subscribers/csv', | ||
labs.subscribers, | ||
mw.authenticatePrivate, | ||
upload.single('subscribersfile'), | ||
validation.upload({type: 'subscribers'}), | ||
api.http(api.subscribers.importCSV) | ||
); | ||
router.get('/subscribers/:id', labs.subscribers, mw.authenticatePrivate, api.http(api.subscribers.read)); | ||
router.get('/subscribers/email/:email', labs.subscribers, mw.authenticatePrivate, api.http(api.subscribers.read)); | ||
router.post('/subscribers', labs.subscribers, mw.authenticatePrivate, api.http(api.subscribers.add)); | ||
router.put('/subscribers/:id', labs.subscribers, mw.authenticatePrivate, api.http(api.subscribers.edit)); | ||
router.del('/subscribers/:id', labs.subscribers, mw.authenticatePrivate, api.http(api.subscribers.destroy)); | ||
router.del('/subscribers/email/:email', labs.subscribers, mw.authenticatePrivate, api.http(api.subscribers.destroy)); | ||
|
||
// ## Roles | ||
router.get('/roles/', mw.authenticatePrivate, api.http(api.roles.browse)); | ||
|
||
// ## Clients | ||
router.get('/clients/slug/:slug', api.http(api.clients.read)); | ||
|
||
// ## Slugs | ||
router.get('/slugs/:type/:name', mw.authenticatePrivate, api.http(api.slugs.generate)); | ||
|
||
// ## Themes | ||
router.get('/themes/', mw.authenticatePrivate, api.http(api.themes.browse)); | ||
|
||
router.get('/themes/:name/download', | ||
mw.authenticatePrivate, | ||
api.http(api.themes.download) | ||
); | ||
|
||
router.post('/themes/upload', | ||
mw.authenticatePrivate, | ||
upload.single('theme'), | ||
validation.upload({type: 'themes'}), | ||
api.http(api.themes.upload) | ||
); | ||
|
||
router.put('/themes/:name/activate', | ||
mw.authenticatePrivate, | ||
api.http(api.themes.activate) | ||
); | ||
|
||
router.del('/themes/:name', | ||
mw.authenticatePrivate, | ||
api.http(api.themes.destroy) | ||
); | ||
|
||
// ## Notifications | ||
router.get('/notifications', mw.authenticatePrivate, api.http(api.notifications.browse)); | ||
router.post('/notifications', mw.authenticatePrivate, api.http(api.notifications.add)); | ||
router.del('/notifications/:id', mw.authenticatePrivate, api.http(api.notifications.destroy)); | ||
|
||
// ## DB | ||
router.get('/db', mw.authenticatePrivate, api.http(api.db.exportContent)); | ||
router.post('/db', | ||
mw.authenticatePrivate, | ||
upload.single('importfile'), | ||
validation.upload({type: 'db'}), | ||
api.http(api.db.importContent) | ||
); | ||
router.del('/db', mw.authenticatePrivate, api.http(api.db.deleteAllContent)); | ||
|
||
router.post('/mail', mw.authenticatePrivate, api.http(api.mail.send)); | ||
router.post('/mail/test', mw.authenticatePrivate, api.http(api.mail.sendTest)); | ||
|
||
// ## Slack | ||
router.post('/slack/test', mw.authenticatePrivate, api.http(api.slack.sendTest)); | ||
|
||
// ## Authentication | ||
router.post('/authentication/passwordreset', | ||
brute.globalReset, | ||
brute.userReset, | ||
api.http(api.authentication.generateResetToken) | ||
); | ||
router.put('/authentication/passwordreset', brute.globalBlock, 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', mw.authenticatePrivate, api.http(api.authentication.updateSetup)); | ||
router.get('/authentication/setup', api.http(api.authentication.isSetup)); | ||
|
||
router.post('/authentication/token', | ||
mw.authenticateClient(), | ||
brute.globalBlock, | ||
brute.userLogin, | ||
auth.oauth.generateAccessToken | ||
); | ||
|
||
router.post('/authentication/revoke', mw.authenticatePrivate, api.http(api.authentication.revoke)); | ||
|
||
// ## Uploads | ||
// @TODO: rename endpoint to /images/upload (or similar) | ||
router.post('/uploads', | ||
mw.authenticatePrivate, | ||
upload.single('uploadimage'), | ||
validation.upload({type: 'images'}), | ||
image.normalize, | ||
api.http(api.uploads.add) | ||
); | ||
|
||
router.post('/db/backup', mw.authenticateClient('Ghost Backup'), api.http(api.db.backupContent)); | ||
|
||
router.post('/uploads/icon', | ||
mw.authenticatePrivate, | ||
upload.single('uploadimage'), | ||
validation.upload({type: 'icons'}), | ||
validation.blogIcon(), | ||
api.http(api.uploads.add) | ||
); | ||
|
||
// ## Invites | ||
router.get('/invites', mw.authenticatePrivate, api.http(api.invites.browse)); | ||
router.get('/invites/:id', mw.authenticatePrivate, api.http(api.invites.read)); | ||
router.post('/invites', mw.authenticatePrivate, api.http(api.invites.add)); | ||
router.del('/invites/:id', mw.authenticatePrivate, api.http(api.invites.destroy)); | ||
|
||
// ## Redirects (JSON based) | ||
router.get('/redirects/json', mw.authenticatePrivate, api.http(api.redirects.download)); | ||
router.post('/redirects/json', | ||
mw.authenticatePrivate, | ||
upload.single('redirects'), | ||
validation.upload({type: 'redirects'}), | ||
api.http(api.redirects.upload) | ||
); | ||
|
||
// ## Webhooks (RESTHooks) | ||
router.post('/webhooks', mw.authenticatePrivate, api.http(api.webhooks.add)); | ||
router.del('/webhooks/:id', mw.authenticatePrivate, api.http(api.webhooks.destroy)); | ||
|
||
// ## Oembed (fetch response from oembed provider) | ||
router.get('/oembed', mw.authenticatePrivate, api.http(api.oembed.read)); | ||
|
||
return router; | ||
}; |
Oops, something went wrong.