Skip to content

Commit

Permalink
Add per route middleware configuration
Browse files Browse the repository at this point in the history
  • Loading branch information
MatthewCallis committed Apr 13, 2023
1 parent 345caec commit fbe2688
Show file tree
Hide file tree
Showing 20 changed files with 490 additions and 119 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@

All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/).

## [5.2.0](https://github.com/uttori/uttori-wiki/compare/v5.1.0...v5.2.0) - 2023-04-12

- 🧰 Add support for per route middleware through the configuration.
- 🎁 Update dependencies

## [5.1.0](https://github.com/uttori/uttori-wiki/compare/v5.0.3...v5.1.0) - 2023-04-05

- 🧰 Add support for overriding route handlers.
Expand Down
181 changes: 99 additions & 82 deletions package-lock.json

Large diffs are not rendered by default.

8 changes: 4 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,18 +34,18 @@
"express": "^4"
},
"devDependencies": {
"@typescript-eslint/parser": "^5.57.1",
"@typescript-eslint/parser": "^5.58.0",
"@uttori/search-provider-lunr": "^3.4.0",
"@uttori/storage-provider-json-memory": "^4.1.2",
"ava": "^5.2.0",
"cors": "^2.8.5",
"ejs": "^3.1.9",
"eslint": "^8.37.0",
"eslint": "^8.38.0",
"eslint-config-airbnb": "^19.0.4",
"eslint-plugin-anti-trojan-source": "^1.1.1",
"eslint-plugin-ava": "^14.0.0",
"eslint-plugin-import": "^2.27.5",
"eslint-plugin-jsdoc": "^40.1.1",
"eslint-plugin-jsdoc": "^41.1.1",
"eslint-plugin-jsx-a11y": "^6.7.1",
"eslint-plugin-no-inferred-method-name": "^2.0.0",
"eslint-plugin-node": "^11.1.0",
Expand All @@ -64,7 +64,7 @@
"release-it": "^15.10.1",
"sinon": "^15.0.3",
"supertest": "^6.3.3",
"typescript": "^5.0.3"
"typescript": "^5.0.4"
},
"files": [
"esm/*",
Expand Down
18 changes: 18 additions & 0 deletions src/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
* @property {Function} [historyRestoreRoute] A replacement route handler for the history restore route.
* @property {Function} [notFoundRoute] A replacement route handler for the 404 not found route.
* @property {Function} [saveValidRoute] A replacement route handler for the save valid route.
* @property {object} [routeMiddleware] A collection of middleware for each route.
* @property {Array} plugins Collection of Uttori Plugins. Storage Plugins should come before other plugins.
* @property {Array} [middleware] Middleware Configuration to be passed along to Express in the format of ['use', layouts], ['set', 'layout extractScripts', true], ['engine', 'html', ejs.renderFile].
*/
Expand Down Expand Up @@ -78,6 +79,23 @@ const config = {
use_cache: true,
cache_short: 60 * 60,
cache_long: 60 * 60 * 24,
routeMiddleware: {
home: [],
tagIndex: [],
tag: [],
search: [],
notFound: [],
create: [],
saveNew: [],
preview: [],
edit: [],
delete: [],
historyIndex: [],
historyDetail: [],
historyRestore: [],
save: [],
detail: [],
},
plugins: [],
middleware: [],
};
Expand Down
54 changes: 27 additions & 27 deletions src/wiki.js
Original file line number Diff line number Diff line change
Expand Up @@ -196,51 +196,51 @@ class UttoriWiki {
bindRoutes(server) {
debug('Binding routes...');
// Home
server.get('/', asyncHandler(this.home.bind(this)));
server.get('/', ...[this.config.routeMiddleware.home], asyncHandler(this.home.bind(this)));
server.get(`/${this.config.home_page}`, asyncHandler(this.homepageRedirect.bind(this)));

// Tags
server.get('/tags', asyncHandler(this.tagIndex.bind(this)));
server.get('/tags/:tag/', asyncHandler(this.tag.bind(this)));
server.get('/tags', ...[this.config.routeMiddleware.tagIndex], asyncHandler(this.tagIndex.bind(this)));
server.get('/tags/:tag/', ...[this.config.routeMiddleware.tag], asyncHandler(this.tag.bind(this)));

// Search
server.get('/search', asyncHandler(this.search.bind(this)));
server.get('/search', ...[this.config.routeMiddleware.search], asyncHandler(this.search.bind(this)));

// Not Found Placeholder
server.head('/404', asyncHandler(this.notFound.bind(this)));
server.get('/404', asyncHandler(this.notFound.bind(this)));
server.delete('/404', asyncHandler(this.notFound.bind(this)));
server.patch('/404', asyncHandler(this.notFound.bind(this)));
server.put('/404', asyncHandler(this.notFound.bind(this)));
server.post('/404', asyncHandler(this.notFound.bind(this)));
server.head('/404', ...[this.config.routeMiddleware.notFound], asyncHandler(this.notFound.bind(this)));
server.get('/404', ...[this.config.routeMiddleware.notFound], asyncHandler(this.notFound.bind(this)));
server.delete('/404', ...[this.config.routeMiddleware.notFound], asyncHandler(this.notFound.bind(this)));
server.patch('/404', ...[this.config.routeMiddleware.notFound], asyncHandler(this.notFound.bind(this)));
server.put('/404', ...[this.config.routeMiddleware.notFound], asyncHandler(this.notFound.bind(this)));
server.post('/404', ...[this.config.routeMiddleware.notFound], asyncHandler(this.notFound.bind(this)));

// Document
server.get('/new/:key', asyncHandler(this.create.bind(this)));
server.get('/new', asyncHandler(this.create.bind(this)));
server.get('/new/:key', ...[this.config.routeMiddleware.create], asyncHandler(this.create.bind(this)));
server.get('/new', ...[this.config.routeMiddleware.create], asyncHandler(this.create.bind(this)));

// Document Create
server.post('/new/:key', asyncHandler(this.saveNew.bind(this)));
server.post('/new', asyncHandler(this.saveNew.bind(this)));
server.post('/new/:key', ...[this.config.routeMiddleware.saveNew], asyncHandler(this.saveNew.bind(this)));
server.post('/new', ...[this.config.routeMiddleware.saveNew], asyncHandler(this.saveNew.bind(this)));

server.post('/preview', asyncHandler(this.preview.bind(this)));
server.get('/:slug/edit/:key', asyncHandler(this.edit.bind(this)));
server.get('/:slug/edit', asyncHandler(this.edit.bind(this)));
server.get('/:slug/delete/:key', asyncHandler(this.delete.bind(this)));
server.get('/:slug/delete', asyncHandler(this.delete.bind(this)));
server.post('/preview', ...[this.config.routeMiddleware.preview], asyncHandler(this.preview.bind(this)));
server.get('/:slug/edit/:key', ...[this.config.routeMiddleware.edit], asyncHandler(this.edit.bind(this)));
server.get('/:slug/edit', ...[this.config.routeMiddleware.edit], asyncHandler(this.edit.bind(this)));
server.get('/:slug/delete/:key', ...[this.config.routeMiddleware.delete], asyncHandler(this.delete.bind(this)));
server.get('/:slug/delete', ...[this.config.routeMiddleware.delete], asyncHandler(this.delete.bind(this)));

// Document History
if (this.config.public_history) {
server.get('/:slug/history', asyncHandler(this.historyIndex.bind(this)));
server.get('/:slug/history/:revision', asyncHandler(this.historyDetail.bind(this)));
server.get('/:slug/history/:revision/restore', asyncHandler(this.historyRestore.bind(this)));
server.get('/:slug/history', ...[this.config.routeMiddleware.historyIndex], asyncHandler(this.historyIndex.bind(this)));
server.get('/:slug/history/:revision', ...[this.config.routeMiddleware.historyDetail], asyncHandler(this.historyDetail.bind(this)));
server.get('/:slug/history/:revision/restore', ...[this.config.routeMiddleware.historyRestore], asyncHandler(this.historyRestore.bind(this)));
}

// Document Update
server.post('/:slug/save/:key', asyncHandler(this.save.bind(this)));
server.post('/:slug/save', asyncHandler(this.save.bind(this)));
server.put('/:slug/save/:key', asyncHandler(this.save.bind(this)));
server.put('/:slug/save', asyncHandler(this.save.bind(this)));
server.get('/:slug', asyncHandler(this.detail.bind(this)));
server.post('/:slug/save/:key', ...[this.config.routeMiddleware.save], asyncHandler(this.save.bind(this)));
server.post('/:slug/save', ...[this.config.routeMiddleware.save], asyncHandler(this.save.bind(this)));
server.put('/:slug/save/:key', ...[this.config.routeMiddleware.save], asyncHandler(this.save.bind(this)));
server.put('/:slug/save', ...[this.config.routeMiddleware.save], asyncHandler(this.save.bind(this)));
server.get('/:slug', ...[this.config.routeMiddleware.detail], asyncHandler(this.detail.bind(this)));

this.hooks.dispatch('bind-routes', server, this);

Expand Down
21 changes: 21 additions & 0 deletions test/create.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,27 @@ test('create(request, response, _next): can be replaced', async (t) => {
t.is(spy.called, true);
});

test('can have middleware set and used', async (t) => {
t.plan(2);

const server = serverSetup();
const uttori = new UttoriWiki({
...config,
routeMiddleware: {
...config.routeMiddleware,
create: [
(req, res, _next) => {
res.status(500).json({});
},
],
},
}, server);
await seed(uttori);
const express_response = await request(server).get('/new/test-key');
t.is(express_response.status, 500);
t.is(express_response.text, '{}');
});

test('falls to 404 when miss matched key', async (t) => {
t.plan(3);

Expand Down
21 changes: 21 additions & 0 deletions test/delete.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,27 @@ test('deletes the document and redirects to the home page', async (t) => {
t.is(express_response.text, 'Found. Redirecting to https://fake.test');
});

test('can have middleware set and used', async (t) => {
t.plan(2);

const server = serverSetup();
const uttori = new UttoriWiki({
...config,
routeMiddleware: {
...config.routeMiddleware,
delete: [
(req, res, _next) => {
res.status(500).json({});
},
],
},
}, server);
await seed(uttori);
const express_response = await request(server).get('/test-delete/delete/test-key');
t.is(express_response.status, 500);
t.is(express_response.text, '{}');
});

test('can be replaced', async (t) => {
t.plan(1);

Expand Down
21 changes: 21 additions & 0 deletions test/detail.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,27 @@ test('renders the requested slug', async (t) => {
t.is(title[1], 'Example Title | Wiki');
});

test('can have middleware set and used', async (t) => {
t.plan(2);

const server = serverSetup();
const uttori = new UttoriWiki({
...config,
routeMiddleware: {
...config.routeMiddleware,
detail: [
(req, res, _next) => {
res.status(500).json({});
},
],
},
}, server);
await seed(uttori);
const express_response = await request(server).get('/example-title');
t.is(express_response.status, 500);
t.is(express_response.text, '{}');
});

test('can be replaced', async (t) => {
t.plan(1);

Expand Down
21 changes: 21 additions & 0 deletions test/edit.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,27 @@ test('renders the edit page for a given slug', async (t) => {
t.is(title[1], 'Editing Demo Title | Wiki');
});

test('can have middleware set and used', async (t) => {
t.plan(2);

const server = serverSetup();
const uttori = new UttoriWiki({
...config,
routeMiddleware: {
...config.routeMiddleware,
edit: [
(req, res, _next) => {
res.status(500).json({});
},
],
},
}, server);
await seed(uttori);
const express_response = await request(server).get('/demo-title/edit/test-key');
t.is(express_response.status, 500);
t.is(express_response.text, '{}');
});

test('can be replaced', async (t) => {
t.plan(1);

Expand Down
22 changes: 22 additions & 0 deletions test/historyDetail.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,28 @@ test('renders the requested slug and revision', async (t) => {
t.true(title[1].startsWith('Demo Title Beta Revision 1'));
});

test('can have middleware set and used', async (t) => {
t.plan(2);

const server = serverSetup();
const uttori = new UttoriWiki({
...config,
routeMiddleware: {
...config.routeMiddleware,
historyDetail: [
(req, res, _next) => {
res.status(500).json({});
},
],
},
}, server);
await seed(uttori);
const [history] = await uttori.hooks.fetch('storage-get-history', 'demo-title', uttori);
const express_response = await request(server).get(`/demo-title/history/${history[0]}`);
t.is(express_response.status, 500);
t.is(express_response.text, '{}');
});

test('can be replaced', async (t) => {
t.plan(1);

Expand Down
21 changes: 21 additions & 0 deletions test/historyIndex.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,27 @@ test('renders the requested slug history', async (t) => {
t.is(title[1], 'Demo Title Revision History | Wiki');
});

test('can have middleware set and used', async (t) => {
t.plan(2);

const server = serverSetup();
const uttori = new UttoriWiki({
...config,
routeMiddleware: {
...config.routeMiddleware,
historyIndex: [
(req, res, _next) => {
res.status(500).json({});
},
],
},
}, server);
await seed(uttori);
const express_response = await request(server).get('/demo-title/history');
t.is(express_response.status, 500);
t.is(express_response.text, '{}');
});

test('can be replaced', async (t) => {
t.plan(1);

Expand Down
30 changes: 26 additions & 4 deletions test/historyRestore.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,35 @@ test('renders the edit page for a given slug', async (t) => {
const uttori = new UttoriWiki(config, server);
await seed(uttori);
const [history] = await uttori.hooks.fetch('storage-get-history', 'demo-title', uttori);
const espress_response = await request(server).get(`/demo-title/history/${history[0]}/restore`);
t.is(espress_response.status, 200);
t.is(espress_response.text.slice(0, 15), '<!DOCTYPE html>');
const title = espress_response.text.match(/<title>(.*?)<\/title>/i);
const express_response = await request(server).get(`/demo-title/history/${history[0]}/restore`);
t.is(express_response.status, 200);
t.is(express_response.text.slice(0, 15), '<!DOCTYPE html>');
const title = express_response.text.match(/<title>(.*?)<\/title>/i);
t.true(title[1].startsWith('Editing Demo Title Beta from Revision 1'));
});

test('can have middleware set and used', async (t) => {
t.plan(2);

const server = serverSetup();
const uttori = new UttoriWiki({
...config,
routeMiddleware: {
...config.routeMiddleware,
historyRestore: [
(req, res, _next) => {
res.status(500).json({});
},
],
},
}, server);
await seed(uttori);
const [history] = await uttori.hooks.fetch('storage-get-history', 'demo-title', uttori);
const express_response = await request(server).get(`/demo-title/history/${history[0]}/restore`);
t.is(express_response.status, 500);
t.is(express_response.text, '{}');
});

test('can be replaced', async (t) => {
t.plan(1);

Expand Down
21 changes: 21 additions & 0 deletions test/home.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,27 @@ test('can be replaced', async (t) => {
t.is(spy.called, true);
});

test('can have middleware set and used', async (t) => {
t.plan(2);

const server = serverSetup();
const uttori = new UttoriWiki({
...config,
routeMiddleware: {
...config.routeMiddleware,
home: [
(req, res, _next) => {
res.status(500).json({});
},
],
},
}, server);
await seed(uttori);
const express_response = await request(server).get('/');
t.is(express_response.status, 500);
t.is(express_response.text, '{}');
});

test('falls through to next when home document is missing', async (t) => {
t.plan(1);

Expand Down
Loading

0 comments on commit fbe2688

Please sign in to comment.