summary refs log tree commit diff
path: root/src/web/pug-sync.js
blob: f87550df1467cb3820b2afbdc22d87b1b517e4e9 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
// @ts-check

const assert = require("assert/strict")
const fs = require("fs")
const {join} = require("path")
const getRelativePath = require("get-relative-path")
const h3 = require("h3")
const {defineEventHandler, defaultContentType, setResponseStatus, getQuery} = h3
const {compileFile} = require("@cloudrac3r/pug")
const pretty = process.argv.join(" ").includes("test")

const {sync} = require("../passthrough")
/** @type {import("./auth")} */
const auth = sync.require("./auth")

// Pug

let globals = {}

/** @type {Map<string, (event: import("h3").H3Event, locals: Record<string, any>) => Promise<string>>} */
const pugCache = new Map()

function addGlobals(obj) {
	Object.assign(globals, obj)
}

/**
 * @param {import("h3").H3Event} event
 * @param {string} filename
 * @param {Record<string, any>} locals
 */
function render(event, filename, locals) {
	const path = join(__dirname, "pug", filename)
	return renderPath(event, path, locals)
}

/**
 * @param {import("h3").H3Event} event
 * @param {string} path
 * @param {Record<string, any>} locals
 */
function renderPath(event, path, locals) {
	function compile() {
		try {
			const template = compileFile(path, {pretty})
			pugCache.set(path, async (event, locals) => {
				defaultContentType(event, "text/html; charset=utf-8")
				const session = await auth.useSession(event)
				const managed = await auth.getManagedGuilds(event)
				const rel = (to, paramsObject) => {
					let result = getRelativePath(event.path, to)
					if (paramsObject) {
						const params = new URLSearchParams(paramsObject)
						result += "?" + params.toString()
					}
					return result
				}
				return template(Object.assign({},
					getQuery(event), // Query parameters can be easily accessed on the top level but don't allow them to overwrite anything
					globals, // Globals
					locals, // Explicit locals overwrite globals in case we need to DI something
					{session, event, rel, managed} // These are assigned last so they overwrite everything else. It would be catastrophically bad if they can't be trusted.
				))
			})
		/* c8 ignore start */
		} catch (e) {
			pugCache.set(path, async (event) => {
				setResponseStatus(event, 500, "Internal Template Error")
				defaultContentType(event, "text/plain")
				return e.toString()
			})
		}
		/* c8 ignore stop */
	}

	if (!pugCache.has(path)) {
		compile()
		fs.watch(path, {persistent: false}, compile)
		fs.watch(join(__dirname, "pug", "includes"), {persistent: false}, compile)
	}

	const cb = pugCache.get(path)
	assert(cb)
	return cb(event, locals)
}

/**
 * @param {import("h3").Router} router
 * @param {string} url
 * @param {string} filename
 */
function createRoute(router, url, filename) {
	router.get(url, defineEventHandler(async event => {
		return render(event, filename, {})
	}))
}

module.exports.addGlobals = addGlobals
module.exports.render = render
module.exports.renderPath = renderPath
module.exports.createRoute = createRoute