Skip to content
This repository was archived by the owner on Jul 15, 2022. It is now read-only.

Commit 76fa67c

Browse files
committed
Breaking change: advanced routing and DotJS can now be used together
1 parent b0b9e73 commit 76fa67c

File tree

8 files changed

+82
-16
lines changed

8 files changed

+82
-16
lines changed

CHANGELOG.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,20 @@ All notable changes to this project will be documented in this file.
44

55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
66

7+
## [17.0.0] - 2021-04-25
8+
9+
### Breaking change
10+
11+
- If you have an advanced routes file (routes.js) it is loaded prior to any DotJS routes. Previously, the presence of a advanced routes file meant that routes were only loaded from it and that any DotJS routes, if they existed, were ignored. This change means you can use DotJS in your sites and (instead of or), when you need to, define some of your routes using the full expressiveness of Express routes.
12+
13+
Although this is a breaking change in that it changes behaviour, the practical impact on existing sites should be minimal given that a project that was using advanced routing would not have had DotJS routes. The only place where this might impact you is if you forgot to delete some old DotJS routes and get surprised when they’re added to your application.
14+
15+
### Added
16+
17+
- In advanced routes (in routes.js) you now have access to the Site.js class (`app.Site`) and Site.js instance (`app.site`) through the Express `app` instance.
18+
19+
- In the statistics view, any routes that begin with _/admin/…_ are shown as ‘Administration page’ to hide any cryptographically-secure paths that may be used as per convention.
20+
721
## [16.6.0] - 2021-04-23
822

923
### Changed

README.md

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1389,6 +1389,8 @@ module.exports = app => {
13891389
13901390
When using the _routes.js_ file, you can use all of the features in [express](https://expressjs.com/) and [our fork of express-ws](https://github.com/aral/express-ws) (which itself wraps [ws](https://github.com/websockets/ws#usage-examples)).
13911391
1392+
__As of Site.js 17.0.0,__ you can also use DotJS routes alongside your advanced routes file. The routes in the _routes.js_ file are loaded first (see [Routing precedence](#routing-precedence), below).
1393+
13921394
### Routing precedence
13931395
13941396
#### Between dynamic route and static route
@@ -1420,19 +1422,19 @@ The behaviour observed under Linux at the time of writing is that _fun/index.js_
14201422
14211423
#### Between the various routing methods
14221424
1423-
Each of the routing conventions are mutually exclusive and applied according to the following precedence rules:
1425+
Each of the routing conventions ­– apart from advanced _routes.js_-based routing (as of Site.js version 17.0.0) – are mutually exclusive and applied according to the following precedence rules:
14241426
1425-
1. Advanced _routes.js_-based advanced routing.
1427+
1. Advanced _routes.js_-based routing.
14261428
14271429
2. DotJS with separate folders for _.https_ and _.wss_ routes routing (the _.http_ folder itself will apply precedence rules 3 and 4 internally).
14281430
14291431
3. DotJS with separate folders for _.get_ and _.post_ routes in HTTPS-only routing.
14301432
14311433
4. DotJS with GET-only routing.
14321434
1433-
So, if Site.js finds a _routes.js_ file in the root folder of your site’s folder, it will only use the routes from that file (it will not apply file-based routing).
1435+
If Site.js finds a _routes.js_ file in the root folder of your site’s folder, as of Site.js version 17.0.0, it will load any routes defined in that file first before looking for any file-based DotJS routes.
14341436
1435-
If Site.js cannot find a _routes.js_ file, it will look to see if separate _.https_ and _.wss_ folders have been defined (the existence of just one of these is enough) and attempt to load DotJS routes from those folders. (If it finds separate _.get_ or _.post_ folders within the _.https_ folder, it will add the relevant routes from those folders; if it can’t it will load GET-only routes from the _.https_ folder and its subfolders.)
1437+
Next Site.js, will look to see if separate _.https_ and _.wss_ folders have been defined (the existence of just one of these is enough) and attempt to load DotJS routes from those folders. (If it finds separate _.get_ or _.post_ folders within the _.https_ folder, it will add the relevant routes from those folders; if it can’t it will load GET-only routes from the _.https_ folder and its subfolders.)
14361438
14371439
If separate _.https_ and _.wss_ folders do not exist, Site.js will expect all defined DotJS routes to be HTTPS and will initially look for separate _.get_ and _.post_ folders (the existence of either is enough to trigger this mode). If they exist, it will add the relevant routes from those folders and their subfolders.
14381440
@@ -1462,6 +1464,36 @@ const appPath = require.main.filename.replace('bin/site.js', '')
14621464
14631465
The code within your JavaScript routes is executed on the server. Exercise the same caution as you would when creating any Node.js app (sanitise input, etc.)
14641466
1467+
### Creating an Admin page.
1468+
1469+
Given that Site.js is for single-tenant apps and sites, you can create an admin page for your site/app using the same convention that Site.js itself uses for the statistics route: by using a cryptographically secure path for it. In fact, Site.js will hide the path from your statistics view if you adhere to the convention of creating it at _/admin/cryptographically-secure-path_. Unlike the statistics URL, you will have to implement this functionality using the advanced routing feature. e.g.,
1470+
1471+
```js
1472+
const crypto = require('crypto')
1473+
1474+
// Create a cryptographically-secure path for the admin route
1475+
// and save it in a table called admin in the built-in JSDB database.
1476+
if (db.admin === undefined) {
1477+
db.admin = {}
1478+
db.admin.route = crypto.randomBytes(16).toString('hex')
1479+
}
1480+
1481+
// Output the admin path to the logs so you know what it is.
1482+
console.log(` 🔑️ ❨My site❩ Admin page is at /${db.admin.route}`)
1483+
1484+
module.exports = app => {
1485+
// Add the admin route using the cryptographically-secure path.
1486+
app.get(`/admin/${db.admin.route}`, (request, response) => {
1487+
response.html(`
1488+
<h1>Admin page</h1>
1489+
<p>Welcome to the admin page.</p>
1490+
<hr>
1491+
<p><a href='https://${app.site.prettyLocation()}${app.site.stats.route}'>Site statistics.</a></p>
1492+
`)
1493+
})
1494+
}
1495+
```
1496+
14651497
## Wildcard routes
14661498
14671499
As of version 14.5.0, if all you want to do is to customise the behaviour of your pages using client-side JavaScript based on parameters provided through the URL path, you don’t have to use dynamic routes and a `routes.js` file, you can use wildcard routes instead, which are much simpler.

index.js

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -332,6 +332,10 @@ class Site {
332332
this.stats = this.initialiseStatistics()
333333
this.app = express()
334334

335+
// Add a reference to the to Site.js instance to the app.
336+
this.app.Site = Site
337+
this.app.site = this
338+
335339
// Create the HTTPS server.
336340
this.createServer()
337341
}
@@ -1446,9 +1450,11 @@ class Site {
14461450
// Add dynamic routes, if any, if a <pathToServe>/.dynamic/ folder exists.
14471451
// If there are errors in any of your dynamic routes, you will get 500 (server) errors.
14481452
//
1449-
// Each of the routing conventions are mutually exclusive and applied according to the following precedence rules:
1453+
// Each of the routing conventions ­– apart from advanced _routes.js_-based routing
1454+
// (as of Site.js version 17.0.0) – are mutually exclusive and applied according to
1455+
// the following precedence rules:
14501456
//
1451-
// 1. Advanced _routes.js_-based advanced routing.
1457+
// 1. Advanced _routes.js_-based routing.
14521458
//
14531459
// 2. Separate folders for _.https_ and _.wss_ routes routing (the _.http_ folder itself will apply
14541460
// precedence rules 3 and 4 internally).
@@ -1473,10 +1479,17 @@ class Site {
14731479
// Attempts to load HTTPS routes from the passed directory,
14741480
// adhering to rules 3 & 4.
14751481
const loadHttpsRoutesFrom = (httpsRoutesDirectory) => {
1482+
14761483
// Attempts to load HTTPS GET routes from the passed directory.
1477-
const loadHttpsGetRoutesFrom = (httpsGetRoutesDirectory) => {
1484+
const loadHttpsGetRoutesFrom = (httpsGetRoutesDirectory, skipAdvancedRoutingFile = false) => {
14781485
const httpsGetRoutes = getRoutes(httpsGetRoutesDirectory)
14791486
httpsGetRoutes.forEach(route => {
1487+
// Skip adding the advanced routing file as an HTTPS GET route,
1488+
// even if it looks like one.
1489+
if (skipAdvancedRoutingFile && route.path === '/routes') {
1490+
return
1491+
}
1492+
14801493
this.log(` 🐁 ❨site.js❩ Adding HTTPS GET route: ${route.path}`)
14811494

14821495
// Ensure we are loading a fresh copy in case it has changed.
@@ -1530,7 +1543,7 @@ class Site {
15301543
// ========================================================
15311544
//
15321545

1533-
loadHttpsGetRoutesFrom(httpsRoutesDirectory)
1546+
loadHttpsGetRoutesFrom(httpsRoutesDirectory, /* skipAdvancedRoutingFile = */ true)
15341547
}
15351548

15361549
//
@@ -1544,14 +1557,13 @@ class Site {
15441557
const advancedRoutesFile = fs.existsSync(routesJsFile) ? routesJsFile : fs.existsSync(routesCjsFile) ? routesCjsFile : undefined
15451558

15461559
if (advancedRoutesFile !== undefined) {
1547-
this.log(` 🐁 ❨site.js❩ Found advanced routes file (${advancedRoutesFile}), will load dynamic routes from there.`)
1560+
this.log(` 🐁 ❨site.js❩ Found advanced routes file (${advancedRoutesFile}), adding to app.`)
15481561
// We flag that this needs to be done here and actually require the file
15491562
// once the server has been created so that WebSocket routes can be added also.
15501563
this.routesJsFile = advancedRoutesFile
15511564

15521565
// Add POST handling in case there are POST routes defined.
15531566
addBodyParser()
1554-
return
15551567
}
15561568

15571569
//

lib/Stats.js

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,6 @@ class Stats {
6868
}
6969

7070
response.on('finish', () => {
71-
7271
if (response.statusCode === 404) {
7372
this.missing.add(request.path)
7473
}
@@ -114,8 +113,8 @@ class Stats {
114113
const sortedReferrers = Object.keys(this.referrers).sort((a, b) => -(this.referrers[a] - this.referrers[b]))
115114

116115
const none = '<li>None yet.</li>'
117-
const allRequestsList = requestKeysToHtml(sortedRequestKeys).replace(this.route, 'This page') || none
118-
const topThreeRequestsList = requestKeysToHtml(topThreeRequestKeys).replace(this.route, 'This page') || none
116+
const allRequestsList = requestKeysToHtml(sortedRequestKeys).replace(this.route, 'This page').replace(/\/admin.*?:/, 'Administration page:') || none
117+
const topThreeRequestsList = requestKeysToHtml(topThreeRequestKeys).replace(this.route, 'This page').replace(/\/admin.*?:/, 'Administration page:') || none
119118

120119
const sortedReferrersList = referrersToHtml(sortedReferrers) || none
121120

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
{
22
"name": "@small-tech/site.js",
3-
"version": "16.6.0",
3+
"version": "17.0.0",
44
"description": "Small Web construction set.",
55
"keywords": [
66
"web server",
77
"static site generator",
88
"dynamic site",
99
"dotJS",
1010
"hugo",
11+
"owncast",
1112
"let's encrypt",
1213
"mkcert",
1314
"automatic",

test/index.js

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -352,11 +352,19 @@ test('[site.js] dynamic route loading from routes.js file', async t => {
352352

353353
const routerStack = site.app._router.stack
354354

355-
const getRouteWithParameter = routerStack[12].route
355+
const regularDotJSRoute1 = routerStack[11].route
356+
t.true(regularDotJSRoute1.methods.get, 'request method should be GET')
357+
t.strictEquals(regularDotJSRoute1.path, '/other-route-that-should-be-loaded', 'path should be correct and contain parameter')
358+
359+
const regularDotJSRoute2 = routerStack[12].route
360+
t.true(regularDotJSRoute2.methods.get, 'request method should be GET')
361+
t.strictEquals(regularDotJSRoute2.path, '/sub-route-that-should-be-loaded', 'path should be correct and contain parameter')
362+
363+
const getRouteWithParameter = routerStack[14].route
356364
t.true(getRouteWithParameter.methods.get, 'request method should be GET')
357365
t.strictEquals(getRouteWithParameter.path, '/hello/:thing', 'path should be correct and contain parameter')
358366

359-
const wssRoute = routerStack[13].route
367+
const wssRoute = routerStack[15].route
360368
t.true(wssRoute.methods.get, 'request method should be GET (prior to WebSocket upgrade)')
361369
t.strictEquals(wssRoute.path, '/echo/.websocket', 'path should be correct and contain parameter')
362370

0 commit comments

Comments
 (0)