diff --git a/frontend/errors/404.html b/frontend/errors/404.html
new file mode 100644
index 0000000..a2a4065
--- /dev/null
+++ b/frontend/errors/404.html
@@ -0,0 +1,13 @@
+
+
+
+
+
+ {{ TITLE }}
+
+
+
+ Page not found
+ Server Error 404
+
+
diff --git a/frontend/errors/500.html b/frontend/errors/500.html
new file mode 100644
index 0000000..7cbf0ca
--- /dev/null
+++ b/frontend/errors/500.html
@@ -0,0 +1,15 @@
+
+
+
+
+
+ {{ TITLE }}
+
+
+
+ Oops! Something went wrong
+ Server Error {{ HTTP_ERROR_CODE }}
+ An error occured while processing your request. Please send the following code to the {{ DEVELOPER_CONTACT_INFO }}:
+ {{ JS_ERROR_STACK }}
+
+
diff --git a/frontend/html/index.html b/frontend/html/index.html
new file mode 100644
index 0000000..418708b
--- /dev/null
+++ b/frontend/html/index.html
@@ -0,0 +1,12 @@
+
+
+
+
+
+ {{ TITLE }}
+
+
+
+ Welcome to poll.horse!
+
+
\ No newline at end of file
diff --git a/frontend/static/css/main.css b/frontend/static/css/main.css
new file mode 100644
index 0000000..4717ad4
--- /dev/null
+++ b/frontend/static/css/main.css
@@ -0,0 +1,3 @@
+h1 {
+ color: red;
+}
\ No newline at end of file
diff --git a/src/frontend.ts b/src/frontend.ts
index 50f3209..33a32c6 100644
--- a/src/frontend.ts
+++ b/src/frontend.ts
@@ -1,6 +1,101 @@
"use strict";
-import { Router } from "express";
+import fs from "fs-extra";
+import { resolve } from "path";
+import { Transform as TransformStream, Stream } from "stream";
+import { Router, Request, Response } from "express";
+
+const RenderBuffer = new WeakMap();
+const RenderReplacements = new WeakMap();
+
+interface NodeError extends Error {
+ code?: string;
+}
+
+// TODO: Implement conditional transform
+class RenderTransform extends TransformStream {
+ constructor(replacements = {}) {
+ super();
+ RenderReplacements.set(this, replacements);
+ RenderBuffer.set(this, "");
+ }
+
+ _transform(chunk: any, encoding: string, callback: () => void) {
+ const r = RenderReplacements.get(this);
+ let c = RenderBuffer.get(this) + chunk.toString();
+ Object.entries(r).forEach(([key, value]) => c = c.replace(new RegExp("{{ ?" + key + " ?}}", "ig"), value));
+
+ if (c.match(/\{\{.*?(? void) {
+ const c = RenderBuffer.get(this);
+ if (c) this.push(c.replace(/\\\{/g, "{").replace(/\\\}/g, "}"));
+ callback();
+ }
+}
+
+class MinificationTransform extends RenderTransform {
+ constructor(replacements = {}) {
+ super(replacements);
+ }
+
+ _transform(chunk: any, encoding: string, callback: () => void) {
+ super._transform(chunk.toString().replace(/\s{2,}/g, ""), encoding, callback);
+ }
+}
+
+const defaultReplacements = {
+ "TITLE": "Poll Horse",
+ "DEVELOPER_CONTACT_INFO": "developer@poll.horse"
+};
+class Defaults2RenderTransform extends MinificationTransform {
+ constructor(replacements = {}) {
+ super(Object.assign({}, defaultReplacements, replacements));
+ }
+}
+async function displayPage(req: Request, res: Response, htmlFilename: string, replacements = {}, statusCode = 200) {
+ const promisifyStream = (fn: Stream) => new Promise(pRes => fn.on("finish", pRes));
+
+ try {
+ await fs.stat(resolve(__dirname, "../frontend/html", htmlFilename));
+ await promisifyStream(
+ fs.createReadStream(resolve(__dirname, "../frontend/html", htmlFilename))
+ .pipe(new Defaults2RenderTransform(replacements))
+ .pipe(res.status(statusCode))
+ );
+ } catch (error) {
+ if (error instanceof Error)
+ if ((error as NodeError).code === "ENOENT") {
+ await promisifyStream(
+ fs.createReadStream(resolve(__dirname, "../frontend/errors/404.html"))
+ .pipe(new Defaults2RenderTransform())
+ .pipe(res.status(404))
+ );
+ } else {
+ await promisifyStream(
+ fs.createReadStream(resolve(__dirname, "../frontend/errors/500.html")).pipe(new Defaults2RenderTransform({
+ "JS_ERROR_STACK": (error as NodeError).stack,
+ "HTTP_ERROR_CODE": 500
+ }))
+ .pipe(res.status(500))
+ );
+ }
+ }
+}
export default function init(router: Router): void {
-
+ router.get("/:id/r", async (req, res) => {
+ const id = req.params.id;
+ res.redirect(`/`);
+ });
+ router.get("/:id", async (req, res) => {
+ const id = req.params.id;
+ res.redirect(`/`);
+ });
+ router.get("/", (req, res) => displayPage(req, res, "index.html"));
}
\ No newline at end of file