diff options
Diffstat (limited to 'modules/blog/blog.js')
-rw-r--r-- | modules/blog/blog.js | 135 |
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); + } + }); + } + }); +}; |