Skip to content

Commit 490ae91

Browse files
Merge 66dd79f into b779129
2 parents b779129 + 66dd79f commit 490ae91

File tree

10 files changed

+233
-8
lines changed

10 files changed

+233
-8
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,6 @@ yarn-error.log
1010
sandbox/*
1111
.env
1212
.nyc_output/*
13+
14+
# VIM swap files
15+
.*.sw*

README.md

Lines changed: 93 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,10 +111,102 @@ Here is the summary of existing routes at the moment:
111111
* Post: `/news/my-post`
112112
* Anything else: `/foo/bar`
113113

114+
## Extensions
115+
116+
The platform can be extended in the following ways:
117+
118+
### Theme Routes
119+
120+
Your theme needs a `routes.js` file which defines a `module.export` function that receives the express `app` object. Use `app` to add routes, middleware, etc, to the frontend app.
121+
122+
When your theme is enabled, the `routes.js` file will be parsed by the app.
123+
124+
*NOTE* You do not need to return anything from this function, just extend the app object by reference.
125+
126+
And in `/themes/your_cool_theme/routes.js`:
127+
```
128+
module.exports = function (app) {
129+
// extend the express app object as you see fit
130+
// for example, add a route /foo
131+
app.get('/foo', (req, res) => {
132+
res.render('example.html', {
133+
title: 'Example Theme route',
134+
content: {foo: 'Hello theme route'}
135+
})
136+
})
137+
}
138+
```
139+
140+
### Plugins
141+
142+
#### User Defined Plugins
143+
144+
Users can define plugins.
145+
146+
Create a directory with the plugins name in `/plugins`
147+
Add an `index.js` file that uses the app object, as in `theme routes` above.
148+
149+
Add the plugin name to your `.env` file (or to your node environment via any available method). Separate multiple plugins with a space:
150+
`PLUGINS=your_cool_plugin`
151+
152+
For example, we will create a `req_parameter_logger` plugin:
153+
154+
`plugins/req_parameter_logger/index.js`
155+
```
156+
module.exports = function(app) {
157+
app.use((req, res, next) => {
158+
console.log("EXAMPLE PLUGIN LOGGER {req query}:", req.query)
159+
next()
160+
})
161+
}
162+
```
163+
164+
Add the plugin name to your .env file.
165+
Now when we can run `$ yarn start`
166+
167+
Our plugin will be loaded:
168+
169+
```
170+
Loading configured plugins...
171+
Loading configured theme routes...
172+
Listening on :4000
173+
```
174+
175+
If we visit `localhost:4000?q=123&q1=12451`we will see the following in our console:
176+
177+
```
178+
EXAMPLE PLUGIN LOGGER {req query}: { q: '123', q1: '12451' }
179+
EXAMPLE PLUGIN LOGGER {req query}: {}
180+
```
181+
182+
#### NPM Plugins
183+
184+
If an express middleware plugin is available as a standalone module on npm you can install it as-is by installing the package via npm, and adding it to your `PLUGINS` variable in `.env`
185+
186+
For example, we will install the cookie-parser plugin, alongside our example.
187+
188+
in `.env`:
189+
`PLUGINS="example cookie-parser"`
190+
191+
now install the npm package:
192+
`$ yarn add cookie-parser`
193+
194+
Cookie-parser will now be applied to all of your requests as express middleware!
195+
196+
(For instance, you could take advantage of this in custom routes, etc)
197+
198+
For more on express middleware: https://expressjs.com/en/guide/using-middleware.html
199+
114200
## Tests
115201

116-
Run tests (note that tests are running against mocked API_URL set to http://127.0.0.1:5000/api/3/action/):
202+
Set `.env` to hit mocked services:
203+
204+
```bash
205+
API_URL=http://127.0.0.1:5000/api/3/action/
206+
WP_URL=http://127.0.0.1:6000
207+
```
117208

209+
Run tests:
118210
```bash
119211
yarn test
120212

config/index.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ require('dotenv').config()
66
nconf.argv()
77
.env()
88

9+
nconf.use('memory')
10+
911
const api_url = process.env.API_URL || 'http://127.0.0.1:5000/api/3/action/'
1012

1113
// This is the object that you want to override in your own local config
@@ -19,7 +21,10 @@ nconf.defaults({
1921
SITE_URL: process.env.SITE_URL || 'http://0.0.0.0:4000',
2022
WP_URL: process.env.WP_URL || 'http://127.0.0.1:6000',
2123
WP_TOKEN: process.env.WP_TOKEN || '',
22-
THEME_DIR: process.env.THEME_DIR || 'themes'
24+
THEME_DIR: process.env.THEME_DIR || 'themes',
25+
NODE_MODULES_PATH: process.env.NODE_MODULES_PATH || 'node_modules',
26+
PLUGINS: process.env.FE_PLUGINS || '',
27+
PLUGIN_DIR: process.env.PLUGIN_DIR || 'plugins',
2328
})
2429

2530
module.exports = {

env.template

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
API_URL=
2-
WP_URL=
1+
PLUGINS="example"
32
WP_TOKEN=
4-
NODE_ENV=staging
5-
THEME=
3+
NODE_ENV=development
4+
THEME=example
65
TRANSLATIONS=
6+
API_URL=http://127.0.0.1:5000/api/3/action/
7+
WP_URL=http://127.0.0.1:6000

index.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
'use strict'
2-
31
const path = require('path')
42
const express = require('express')
53
const cors = require('cors')
@@ -13,6 +11,7 @@ const i18n = require("i18n")
1311
const config = require('./config')
1412
const dmsRoutes = require('./routes/dms')
1513
const cmsRoutes = require('./routes/cms')
14+
const { loadThemeRoutes, loadUserPlugins } = require('./utils')
1615

1716
module.exports.makeApp = function () {
1817
const app = express()
@@ -52,6 +51,9 @@ module.exports.makeApp = function () {
5251
app.use(i18n.init)
5352
app.use(session({ secret: 'keyboard cat', cookie: { maxAge: 60000 }}))
5453
app.use(flash())
54+
55+
loadUserPlugins(app)
56+
loadThemeRoutes(app)
5557

5658
// Redirect x/y/ to x/y
5759
app.use((req, res, next) => {

plugins/example/index.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
module.exports = function(app) {
2+
app.use((req, res, next) => {
3+
console.log("EXAMPLE PLUGIN LOGGER {req query}:", req.query)
4+
res.header('x-my-custom-header', 1234)
5+
next()
6+
})
7+
}

tests/routes/index.test.js

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,44 @@ const mocks = require('../../fixtures')
55
mocks.initMocks()
66

77
const app = require('../../index').makeApp()
8+
const config = require('../../config')
9+
10+
11+
// THEME ROUTES
12+
// @@TODO Test fail case for theme routes
13+
test('Theme defined route exists when THEME is set', async t => {
14+
config.set('THEME', 'example')
15+
const app = require('../../index').makeApp()
16+
const res = await request(app)
17+
.get('/foo')
18+
19+
t.is(res.statusCode, 200)
20+
t.true(res.text.includes('Hello theme route'))
21+
})
22+
23+
24+
test('Theme defined route does NOT exists when THEME is not set', async t => {
25+
config.set('THEME', 'opendk')
26+
const app = require('../../index').makeApp()
27+
const res = await request(app)
28+
.get('/foo')
29+
30+
t.is(res.statusCode, 500)
31+
})
32+
33+
34+
// PLUGINS
35+
// @@TODO Test fail case for plugins
36+
// @@TODO Test load plugin from npm
37+
test('User-plugin-provided res has expected custom header present', async t => {
38+
config.set('PLUGINS', "example cookie-parser")
39+
const app = require('../../index').makeApp()
40+
t.plan(1)
41+
42+
const res = await request(app)
43+
.get('/')
44+
t.true(res.headers['x-my-custom-header'] === '1234')
45+
})
846

947

1048
// CMS

themes/example/routes.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
module.exports = function (app) {
2+
app.get('/foo', (req, res) => {
3+
console.log('example route')
4+
res.render('example.html', {
5+
title: 'Example Theme route',
6+
content: {foo: 'Hello theme route'}
7+
})
8+
})
9+
}

themes/example/views/example.html

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{% extends "base.html" %}
2+
3+
{% block bodyclass %}dash{% endblock %}
4+
{% block content %}
5+
<div class="pt-6">
6+
{{ content.foo }}
7+
</div>
8+
{% endblock %}

utils/index.js

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
const config = require('../config')
2+
const path = require('path')
3+
const fs = require('fs')
4+
15
module.exports.ckanToDataPackage = function (descriptor) {
26
// Make a copy
37
const datapackage = JSON.parse(JSON.stringify(descriptor))
@@ -206,3 +210,59 @@ module.exports.processMarkdown = require('markdown-it')({
206210
linkify: true,
207211
typographer: true
208212
})
213+
214+
/**
215+
* Returns an array of express Router instances
216+
* or []
217+
*/
218+
module.exports.loadThemeRoutes = function (app) {
219+
console.log('Loading configured theme routes...')
220+
221+
try {
222+
const theme = config.get('THEME')
223+
const themePath = config.get('THEME_DIR')
224+
console.log(theme, themePath)
225+
if (!theme) return
226+
const resource = path.join(process.cwd(), themePath, theme, 'routes.js')
227+
console.log(resource)
228+
require(resource)(app)
229+
} catch (e) {
230+
const theme = config.get('THEME')
231+
console.warn(`WARNING: Failed to load configured theme routes for ${theme}`)
232+
}
233+
}
234+
235+
module.exports.loadUserPlugins = function (app) {
236+
console.log('Loading configured plugins...')
237+
238+
try {
239+
const plugins = config.get('PLUGINS')
240+
const pluginPath = config.get('PLUGIN_DIR')
241+
const nodeModulesPath = config.get('NODE_MODULES_PATH')
242+
243+
if (!plugins) return
244+
245+
// try to load each resource
246+
return plugins.split(' ').forEach(plugin => {
247+
const userResource = path.join(process.cwd(), pluginPath, plugin, 'index.js')
248+
const npmResource = path.join(process.cwd(), nodeModulesPath, plugin)
249+
250+
// look for plugin in user space
251+
if (fs.existsSync(userResource)) {
252+
require(userResource)(app)
253+
// otherwise look in node_modules
254+
} else if (fs.existsSync(npmResource)) {
255+
const middleware = require(plugin)
256+
app.use(middleware())
257+
} else {
258+
// if all else fails give the user a helping hand
259+
const userPluginPath = path.resolve(process.cwd(), pluginPath, plugin)
260+
throw new Error(`Cannot find configured plugin ${plugin}. Is it installed? If the plugin is an npm module try to run\n"yarn add ${plugin}"\nIf it is a user plugin, make sure you have a directory at ${userPluginPath} and a valid index.js file there.`)
261+
}
262+
})
263+
} catch (e) {
264+
const plugins = config.get('PLUGINS').split(" ") || []
265+
console.warn('WARNING: Failed to load configured plugins',plugins, e)
266+
return []
267+
}
268+
}

0 commit comments

Comments
 (0)