summaryrefslogtreecommitdiff
path: root/modules/blog/blog.js
diff options
context:
space:
mode:
Diffstat (limited to 'modules/blog/blog.js')
-rw-r--r--modules/blog/blog.js135
1 files changed, 135 insertions, 0 deletions
diff --git a/modules/blog/blog.js b/modules/blog/blog.js
new file mode 100644
index 0000000..0b7785b
--- /dev/null
+++ b/modules/blog/blog.js
@@ -0,0 +1,135 @@
+const cmn = require("../$common.js");
+const fs = require("fs");
+const https = require("https");
+const child_process = require("child_process");
+
+const generateTemplate = require("./template.js");
+
+let moddir = null;
+let repodir = null;
+const repoRemote = "https://git.tomsmeding.com/blog";
+
+let templateCache = new Map();
+
+function fetch(url) {
+ return new Promise((resolve, reject) => {
+ https.get(url, res => {
+ if (res.statusCode != 200) {
+ reject(`HTTP status code ${res.statusCode}`);
+ return;
+ }
+
+ let buffers = [];
+ res.on("data", d => buffers.push(d));
+ res.on("end", () => {
+ resolve(Buffer.concat(buffers));
+ });
+ }).on("error", err => {
+ reject(err);
+ });
+ });
+}
+
+function runCommand(cmd, args) {
+ console.log(`blog: ${cmd} ${JSON.stringify(args)}`);
+ child_process.execFileSync(
+ cmd, args,
+ { stdio: "inherit", timeout: 20000 }
+ );
+}
+
+function runCommandOutput(cmd, args) {
+ return child_process.execFileSync(
+ cmd, args,
+ { timeout: 20000 }
+ );
+}
+
+function ensureRepo() {
+ try {
+ const stats = fs.statSync(repodir);
+ if (stats.isDirectory()) return;
+ } catch (e) {}
+
+ runCommand("git", ["clone", "https://git.tomsmeding.com/blog", repodir]);
+}
+
+function currentCommit() {
+ return runCommandOutput("git", ["-C", repodir, "rev-parse", "HEAD"]).toString().trim();
+}
+
+async function upstreamCommit() {
+ const body = await fetch(repoRemote + "/info/refs");
+ return body.toString().split("\t")[0];
+}
+
+function updateRepo() {
+ try {
+ runCommand("git", ["-C", repodir, "fetch", "--all"]);
+ runCommand("git", ["-C", repodir, "reset", "origin/master", "--hard"]);
+ // Reset cache _after_ the commands succeeded; if anything failed, we
+ // might at least still have stale cache data
+ templateCache = new Map();
+ } catch (e) {
+ console.error("Cannot update blog git repo!");
+ console.error(e);
+ }
+}
+
+async function periodicUpdateCheck() {
+ let shouldUpdate = false;
+ try {
+ const local = currentCommit();
+ const remote = await upstreamCommit();
+ if (local != remote) shouldUpdate = true;
+ } catch (e) {
+ // Also update if our pre-check fails for some reason
+ shouldUpdate = true;
+ }
+ if (shouldUpdate) updateRepo();
+}
+
+setInterval(function () {
+ periodicUpdateCheck().catch(err => console.error(err));
+}, 3600 * 1000);
+
+module.exports = (app, io, _moddir) => {
+ moddir = _moddir;
+ repodir = moddir + "/repo";
+
+ ensureRepo();
+ updateRepo();
+
+ app.get("/blog", (req, res, next) => {
+ req.url = "/blog/";
+ next();
+ });
+
+ app.get("/blog/*", (req, res) => {
+ if (req.path.indexOf("/.") != -1) {
+ res.sendStatus(404);
+ return;
+ }
+
+ const path = req.path.slice(6).replace(/\.html$/, "");
+
+ if (templateCache.has(path)) {
+ res.send(templateCache.get(path));
+ } else {
+ generateTemplate(repodir, path ? path + ".html" : undefined)
+ .then(rendered => {
+ // TODO: fix rendering race condition
+ templateCache.set(path, rendered);
+ res.send(rendered);
+ })
+ .catch(err => {
+ if (err.code && err.code == "ENOENT") {
+ res.sendStatus(404);
+ } else {
+ console.error(err);
+ res.sendStatus(500);
+ }
+ });
+ }
+ });
+};