aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--app/index.ts8
-rw-r--r--app/middlewares/blogs.ts28
-rw-r--r--app/middlewares/index.ts26
-rw-r--r--app/pages.ts112
-rw-r--r--app/routes/blog/router.ts5
-rw-r--r--app/routes/page/router.ts3
-rw-r--r--docker-compose.yml1
-rw-r--r--package-lock.json108
-rw-r--r--package.json1
9 files changed, 211 insertions, 81 deletions
diff --git a/app/index.ts b/app/index.ts
index 664a751..7d73eb4 100644
--- a/app/index.ts
+++ b/app/index.ts
@@ -4,6 +4,8 @@ import * as page from './routes/page/router.js';
import * as blog from './routes/blog/router.js';
import { logger } from './logger.js'
import { PageDirectory } from './pages.js';
+import { directory } from './middlewares/index.js';
+import { blogs } from './middlewares/blogs.js';
dotenv.config()
@@ -16,6 +18,10 @@ app.use(express.static('static', {
maxAge: '1d'
}));
+const pageDirectory = new PageDirectory(process.env.PAGES_DIR);
+app.use(directory(pageDirectory));
+app.use(blogs(pageDirectory));
+
app.use(blog.router);
app.use(page.router);
@@ -36,7 +42,7 @@ const exit = () => {
})
}
-PageDirectory.rebuild('pages');
+pageDirectory.loadFromDisk();
process.on('SIGINT', exit);
process.on('SIGTERM', exit);
diff --git a/app/middlewares/blogs.ts b/app/middlewares/blogs.ts
index 8fd07c6..e1f5433 100644
--- a/app/middlewares/blogs.ts
+++ b/app/middlewares/blogs.ts
@@ -1,17 +1,19 @@
import { PageDirectory } from "../pages.js";
-export const blogs = ((req, res, next) => {
- let blogs = [];
- for (const page of Object.values(PageDirectory.pages)) {
- if (page.route.startsWith('blog/')) {
- blogs.push(page);
+export const blogs = (pageDirectory: PageDirectory) => {
+ return ((req, res, next) => {
+ let blogs = [];
+ for (const page of Object.values(pageDirectory.pages)) {
+ if (page.route.startsWith('blog/')) {
+ blogs.push(page);
+ }
}
- }
-
- blogs.sort((a, b) => {
- return b.metadata.date.getTime() - a.metadata.date.getTime();
+
+ blogs.sort((a, b) => {
+ return b.metadata.date.getTime() - a.metadata.date.getTime();
+ });
+
+ res.locals.blogs = blogs;
+ next();
});
-
- res.locals.blogs = blogs;
- next();
-});
+}
diff --git a/app/middlewares/index.ts b/app/middlewares/index.ts
index 81431ca..b5fcd34 100644
--- a/app/middlewares/index.ts
+++ b/app/middlewares/index.ts
@@ -1,16 +1,18 @@
import { PageDirectory } from "../pages.js";
-export const page = ((req, res, next) => {
- const path = req.originalUrl == "/" ? 'index' : req.originalUrl.substring(1);
- res.locals.path = path;
-
- const page = PageDirectory.get(path);
+export const directory = (pageDirectory: PageDirectory) => {
+ return ((req, res, next) => {
+ const path = req.originalUrl == "/" ? 'index' : req.originalUrl.substring(1);
+ res.locals.path = path;
+
+ const page = pageDirectory.get(path);
+
+ if (!page) {
+ next();
+ return;
+ }
- if (!page) {
+ res.locals.page = page;
next();
- return;
- }
-
- res.locals.page = page;
- next();
-});
+ });
+}
diff --git a/app/pages.ts b/app/pages.ts
index 3bef4a9..a7057da 100644
--- a/app/pages.ts
+++ b/app/pages.ts
@@ -3,9 +3,9 @@ import glob from 'glob';
import { logger } from './logger.js'
import { marked } from 'marked';
import matter from 'gray-matter';
+import chokidar from 'chokidar';
export function buildPage(page: Page) {
- logger.info(`Building ${page.path}`);
try {
const result = matter(page.raw);
const metadata = result.data;
@@ -19,61 +19,93 @@ export function buildPage(page: Page) {
}
}
-export namespace PageDirectory {
- export const pages: Record<string, Page> = {};
- export let lastBuild: number;
+function loadRaw(path: string): string {
+ return readFileSync(`${path}`, 'utf-8');
+}
+
+export class PageDirectory {
+ private pagesPath: string;
+
+ public pages: Record<string, Page> = {};
+ public lastFullBuild: number;
- export const rebuild = (pagePath: string): boolean => {
- for (const page in pages) {
- delete pages[page];
+ constructor(pagesPath: string) {
+ this.pagesPath = pagesPath;
+ }
+
+ public loadFromDisk = () => {
+ for (const page in this.pages) {
+ delete this.pages[page];
}
- const localPages = glob.sync(`**/*.{md,html}`, { cwd: pagePath })
+ const localPages = glob.sync(`**/*.{md,html}`, { cwd: this.pagesPath })
- // Load page content
- localPages.forEach(page => {
- let route = page.replace(/\.[^.]*$/,'')
- let name = /[^/]*$/.exec(route)[0];
- let path = `${pagePath}/${page}`
- let raw: string;
- try {
- raw = loadRaw(path);
- } catch (e) {
- logger.error(`Failed to read page ${path}: ${e.message}`);
- return;
- }
+ localPages.forEach(this.loadPage);
- pages[route] = {
- route: route,
- name: name,
- path: path,
- raw: raw,
- buildTime: 0,
- metadata: {
- title: "A Page"
- }
- }
+ this.lastFullBuild = Date.now();
+
+ const watcher = chokidar.watch('.', {
+ persistent: true,
+ cwd: this.pagesPath,
+ ignoreInitial: true,
});
- // Build pages
- Object.values(pages).forEach(page => buildPage(page));
+ const onPageChange = (page: string) => {
+ logger.info(`File ${page} has been modified`);
+ this.loadPage(page);
+ }
+
+ const onPageRemoval = (page: string) => {
+ logger.info(`File ${page} has been removed`);
+ this.removePage(page);
+ }
+
+ watcher.on('add', onPageChange);
+ watcher.on('change', onPageChange);
+ watcher.on('unlink', onPageRemoval);
+ }
+
+ public loadPage = (page: string): void => {
+ logger.info(`Building page ${page}`);
+ let route = page.replace(/\.[^.]*$/,'')
+ let name = /[^/]*$/.exec(route)[0];
+ let path = `${this.pagesPath}/${page}`
+ let raw: string;
+ try {
+ raw = loadRaw(path);
+ } catch (e) {
+ logger.error(`Failed to read page ${path}: ${e.message}`);
+ return;
+ }
- lastBuild = Date.now();
- return true;
+ this.pages[route] = {
+ route: route,
+ name: name,
+ path: path,
+ raw: raw,
+ buildTime: 0,
+ metadata: {
+ title: "A Page"
+ }
+ }
+
+ buildPage(this.pages[route]);
+ }
+
+ public removePage = (page: string): void => {
+ logger.info(`Unloading page ${page}`);
+ let route = page.replace(/\.[^.]*$/,'')
+ delete this.pages[route];
}
- export function get(name: string): Page {
- const page = pages[name];
+ public get(name: string): Page {
+ const page = this.pages[name];
if (!page) {
return undefined;
}
return page;
}
-
- function loadRaw(path: string): string {
- return readFileSync(`${path}`, 'utf-8');
- }
}
export type Page = {
diff --git a/app/routes/blog/router.ts b/app/routes/blog/router.ts
index bbd09d5..933946b 100644
--- a/app/routes/blog/router.ts
+++ b/app/routes/blog/router.ts
@@ -1,12 +1,7 @@
import express from 'express';
-import { page } from '../../middlewares/index.js';
-import { blogs } from '../../middlewares/blogs.js';
export const router = express.Router({ mergeParams: true });
-router.use('/blog/:page?', page);
-router.use('/blog/:page?', blogs);
-
router.get('/blog/:page?', (req, res, next) => {
let page = res.locals.page;
let index = !page || res.locals.path === 'blog';
diff --git a/app/routes/page/router.ts b/app/routes/page/router.ts
index 5c0a39b..b1b9bc1 100644
--- a/app/routes/page/router.ts
+++ b/app/routes/page/router.ts
@@ -1,10 +1,7 @@
import express from 'express';
-import { page } from '../../middlewares/index.js';
export const router = express.Router({ mergeParams: true });
-router.use('/:page?', page);
-
router.get('/:page?', (req, res, next) => {
let page = res.locals.page;
if (!page) {
diff --git a/docker-compose.yml b/docker-compose.yml
index 9572132..bfee91d 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -7,6 +7,7 @@ services:
- "3000:3000"
volumes:
- .env:/app/.env:ro
+ - .env.defaults:/app/.env.defaults:ro
- ./pages:/app/pages:ro
- ./static:/app/static:ro
- ./views:/app/views:ro
diff --git a/package-lock.json b/package-lock.json
index 98807f4..46764c8 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -14,6 +14,7 @@
"@types/express": "^4.17.13",
"@types/glob": "^7.2.0",
"axios": "^1.4.0",
+ "chokidar": "^3.5.3",
"dateformat": "^5.0.2",
"dotenv-defaults": "^3.0.0",
"ejs": "^3.1.6",
@@ -576,6 +577,18 @@
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
+ "node_modules/anymatch": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
+ "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
+ "dependencies": {
+ "normalize-path": "^3.0.0",
+ "picomatch": "^2.0.4"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
"node_modules/argparse": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
@@ -621,6 +634,14 @@
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
},
+ "node_modules/binary-extensions": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
+ "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==",
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/body-parser": {
"version": "1.20.1",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz",
@@ -670,7 +691,6 @@
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
"integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
- "dev": true,
"dependencies": {
"fill-range": "^7.0.1"
},
@@ -722,6 +742,43 @@
"url": "https://github.com/chalk/chalk?sponsor=1"
}
},
+ "node_modules/chokidar": {
+ "version": "3.5.3",
+ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz",
+ "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==",
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://paulmillr.com/funding/"
+ }
+ ],
+ "dependencies": {
+ "anymatch": "~3.1.2",
+ "braces": "~3.0.2",
+ "glob-parent": "~5.1.2",
+ "is-binary-path": "~2.1.0",
+ "is-glob": "~4.0.1",
+ "normalize-path": "~3.0.0",
+ "readdirp": "~3.6.0"
+ },
+ "engines": {
+ "node": ">= 8.10.0"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.2"
+ }
+ },
+ "node_modules/chokidar/node_modules/glob-parent": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
+ "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+ "dependencies": {
+ "is-glob": "^4.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
"node_modules/color": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/color/-/color-3.2.1.tgz",
@@ -1354,7 +1411,6 @@
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
"integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
- "dev": true,
"dependencies": {
"to-regex-range": "^5.0.1"
},
@@ -1485,6 +1541,19 @@
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
"integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="
},
+ "node_modules/fsevents": {
+ "version": "2.3.2",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
+ "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
+ "hasInstallScript": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
"node_modules/function-bind": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
@@ -1738,6 +1807,17 @@
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz",
"integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ=="
},
+ "node_modules/is-binary-path": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
+ "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
+ "dependencies": {
+ "binary-extensions": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/is-extendable": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz",
@@ -1750,7 +1830,6 @@
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
"integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
- "dev": true,
"engines": {
"node": ">=0.10.0"
}
@@ -1759,7 +1838,6 @@
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
"integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
- "dev": true,
"dependencies": {
"is-extglob": "^2.1.1"
},
@@ -1771,7 +1849,6 @@
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
- "dev": true,
"engines": {
"node": ">=0.12.0"
}
@@ -2035,6 +2112,14 @@
"node": ">= 0.6"
}
},
+ "node_modules/normalize-path": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
+ "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/object-inspect": {
"version": "1.12.3",
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz",
@@ -2181,7 +2266,6 @@
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
- "dev": true,
"engines": {
"node": ">=8.6"
},
@@ -2293,6 +2377,17 @@
"node": ">= 6"
}
},
+ "node_modules/readdirp": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
+ "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
+ "dependencies": {
+ "picomatch": "^2.2.1"
+ },
+ "engines": {
+ "node": ">=8.10.0"
+ }
+ },
"node_modules/resolve-from": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
@@ -2607,7 +2702,6 @@
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
- "dev": true,
"dependencies": {
"is-number": "^7.0.0"
},
diff --git a/package.json b/package.json
index d700de3..197ee3a 100644
--- a/package.json
+++ b/package.json
@@ -15,6 +15,7 @@
"@types/express": "^4.17.13",
"@types/glob": "^7.2.0",
"axios": "^1.4.0",
+ "chokidar": "^3.5.3",
"dateformat": "^5.0.2",
"dotenv-defaults": "^3.0.0",
"ejs": "^3.1.6",