aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.dockerignore8
-rw-r--r--.gitignore2
-rw-r--r--Dockerfile17
-rw-r--r--LICENSE21
-rw-r--r--README.md3
-rw-r--r--app.js46
-rw-r--r--lib/router.js40
-rw-r--r--lib/static.js75
-rw-r--r--package-lock.json21
-rw-r--r--package.json19
-rw-r--r--public/404.html1
-rw-r--r--public/500.html1
-rw-r--r--public/index.html16
-rw-r--r--public/main.js7
-rw-r--r--public/style.css21
-rwxr-xr-xrun.sh14
16 files changed, 312 insertions, 0 deletions
diff --git a/.dockerignore b/.dockerignore
new file mode 100644
index 0000000..7a0158b
--- /dev/null
+++ b/.dockerignore
@@ -0,0 +1,8 @@
+.dockerignore
+.git
+.gitignore
+LICENSE
+node_modules
+npm-debug.log
+run.sh
+README.md
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..93f1361
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+node_modules
+npm-debug.log
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..ec679ff
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,17 @@
+FROM node:alpine
+LABEL maintainer="[email protected]"
+WORKDIR /usr/src/app
+#RUN apk add --no-cache tini # Or `docker run --init`
+#ENTRYPOINT ["/sbin/tini", "--"]
+ENV NODE_ENV=production
+ENV PORT=8080
+EXPOSE $PORT
+#USER node
+#CMD ["npm", "start"]
+CMD ["node", "app.js"]
+
+COPY package*.json ./
+#RUN npm install
+RUN npm ci --only=production
+
+COPY . .
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..9187643
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2019 Adam Hovorka <[email protected]>
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..8ec0687
--- /dev/null
+++ b/README.md
@@ -0,0 +1,3 @@
+# nomicvote
+
+TODO
diff --git a/app.js b/app.js
new file mode 100644
index 0000000..56b3b82
--- /dev/null
+++ b/app.js
@@ -0,0 +1,46 @@
+"use strict"
+
+const http = require("http");
+const fs = require("fs");
+const ws = require("ws");
+
+const Router = require("./lib/router");
+const Static = require("./lib/static");
+
+if (!fs.existsSync("./logs")) fs.mkdirSync("./logs");
+
+Math.clamp = Math.clamp || ((x,l,h) => Math.max(l,Math.min(x,h)));
+const PORT = Math.clamp(+process.env.PORT||8080, 1, 65535);
+const HOST = "0.0.0.0";
+
+const server = http.createServer();
+const stat = new Static("./public");
+const app = new Router();
+
+function sj(res, data) { // For convenience
+ res.setHeader("Content-Type", "application/json");
+ res.end(JSON.stringify(data));
+}
+
+app.get("/hist.json", (req, res) => sj(res, []));
+
+//app.get("/(?<id>[0-9A-Z]+)", (req, res, path) => {
+// res.setHeader("Content-Type", "text/plain");
+// res.end(path[id]);
+//});
+
+//app.gpost("/(?<id>[0-9A-Z]+)/file", (req, res, path, data) => {
+// res.end(data);
+//}, 1<<28); // 256MB Max
+
+//app.put("/file/(.+)", (req, res, path) => {
+// req.pipe(fs.createWriteStream("./static/"+path[1]));
+//});
+
+server.on("request", (req, res) => {
+ console.log(`${Date.now()} ${req.method} ${req.url}`);
+ app.route(req, res) || stat.route(req, res);
+});
+
+server.listen(PORT/*, HOST*/);
+console.log(`Running on http://${HOST}:${PORT}`);
diff --git a/lib/router.js b/lib/router.js
new file mode 100644
index 0000000..deb2a8c
--- /dev/null
+++ b/lib/router.js
@@ -0,0 +1,40 @@
+"use strict" // https://github.com/mixu/minimal
+
+const url = require("url");
+const degroup = path => Object.assign(path, path.groups);
+
+class Router {
+ constructor() {
+ this.routes = [];
+ }
+
+ route(req, res) {
+ const pathname = url.parse(req.url).pathname;
+ return this.routes.some(route => {
+ const isMatch = route.method === req.method && route.re.test(pathname);
+ if (isMatch) route.cb(req, res, degroup(route.re.exec(pathname)));
+ return isMatch;
+ });
+ }
+
+ gather(cb, max) { // Handle POST data
+ return (req, res, match) => {
+ let data = "";
+ req.on("data", chunk => {
+ if ((data += chunk).length > (max||1e6)) // ~1MB
+ req.connection.destroy();
+ }).on("end", () => cb(req, res, match, data));
+ };
+ }
+
+ gpost(re, cb, max) { // Laziness
+ this.post(re, this.gather(cb, max));
+ }
+}
+
+["get", "post", "put", "delete"].forEach(method =>
+ Router.prototype[method] = function(re, cb) {
+ this.routes.push({method: method.toUpperCase(), cb,
+ re: (re instanceof RegExp)? re : new RegExp(`^${re}$`)})});
+
+module.exports = Router;
diff --git a/lib/static.js b/lib/static.js
new file mode 100644
index 0000000..2be10bc
--- /dev/null
+++ b/lib/static.js
@@ -0,0 +1,75 @@
+"use strict"
+
+const Path = require("path");
+const url = require("url");
+const fs = require("fs");
+
+const mimeTypes = {
+ ".html": "text/html",
+ ".css": "text/css",
+ ".js": "text/javascript",
+ ".json": "application/json",
+ ".wasm": "application/wasm",
+ ".pdf": "application/pdf",
+ ".txt": "text/plain",
+ ".md": "text/markdown",
+
+ ".png": "image/png",
+ ".jpg": "image/jpg",
+ ".svg": "image/svg+xml",
+ ".gif": "image/gif",
+ ".ico": "image/x-icon",
+
+ ".wav": "audio/wav",
+ ".mp3": "audio/mpeg",
+ ".mp4": "video/mp4",
+
+ ".eot": "application/vnd.ms-fontobject",
+ ".ttf": "application/font-ttf",
+ ".woff": "application/font-woff",
+};
+
+module.exports = class Static {
+ constructor(root) {
+ this.root = `${root||"."}`;
+ this.e404 = fs.readFileSync(`${this.root}/404.html`);
+ this.e500 = fs.readFileSync(`${this.root}/500.html`);
+ }
+
+ route(req, res) {
+ if (req.method !== "GET") {
+ res.writeHead(405, {"Content-Type": "text/plain"});
+ res.end("405 Method Not Allowed");
+ return;
+ }
+
+ const pathname = url.parse(req.url).pathname;
+ const sane = Path.normalize(pathname).replace(/^(\.\.\/)+/, "");
+ let path = `${this.root}${sane}`; //Path.join(__dirname, sane);
+
+ fs.stat(path, (err, stats) => {
+ if (err) { if (["EACCES", "ENOENT", "EPERM"].includes(err.code)) {
+ res.writeHead(404, {"Content-Type": "text/html"});
+ return res.end(this.e404);
+ } else { console.log(err);
+ res.writeHead(500, {"Content-Type": "text/html"});
+ return res.end(this.e500);
+ }}
+
+ if (stats.isDirectory()) path += "/index.html";
+ const ext = `${Path.extname(path)}`.toLowerCase();
+ const stream = fs.createReadStream(path);
+
+ stream.on("error", e => { console.log(e);
+ res.writeHead(500, {"Content-Type": "text/html"});
+ //res.end(`<pre>Error getting the file: ${e}</pre>`);
+ res.end(this.e500);
+ });
+
+ res.setHeader("Content-Type", mimeTypes[ext]||"application/octet-stream");
+ // TODO Caching?
+
+ stream.pipe(res);
+ });
+ }
+};
diff --git a/package-lock.json b/package-lock.json
new file mode 100644
index 0000000..c712d7d
--- /dev/null
+++ b/package-lock.json
@@ -0,0 +1,21 @@
+{
+ "name": "nomicvote",
+ "version": "0.0.1",
+ "lockfileVersion": 1,
+ "requires": true,
+ "dependencies": {
+ "async-limiter": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz",
+ "integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg=="
+ },
+ "ws": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/ws/-/ws-7.1.0.tgz",
+ "integrity": "sha512-Swie2C4fs7CkwlHu1glMePLYJJsWjzhl1vm3ZaLplD0h7OMkZyZ6kLTB/OagiU923bZrPFXuDTeEqaEN4NWG4g==",
+ "requires": {
+ "async-limiter": "^1.0.0"
+ }
+ }
+ }
+}
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..beeca60
--- /dev/null
+++ b/package.json
@@ -0,0 +1,19 @@
+{
+ "name": "nomicvote",
+ "version": "0.0.1",
+ "description": "Simple voting system for a couch game of Nomic",
+ "author": "Adam Hovorka <[email protected]>",
+ "license": "MIT",
+ "main": "app.js",
+ "scripts": {
+ "start": "node app.js"
+ },
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/adamhovorka/nomicvote.git"
+ },
+ "private": true,
+ "dependencies": {
+ "ws": "^7.1.0"
+ }
+}
diff --git a/public/404.html b/public/404.html
new file mode 100644
index 0000000..1a43d0a
--- /dev/null
+++ b/public/404.html
@@ -0,0 +1 @@
+<pre>404 Not Found</pre>
diff --git a/public/500.html b/public/500.html
new file mode 100644
index 0000000..ee07335
--- /dev/null
+++ b/public/500.html
@@ -0,0 +1 @@
+<pre>500 Internal Server Error</pre>
diff --git a/public/index.html b/public/index.html
new file mode 100644
index 0000000..c3828a5
--- /dev/null
+++ b/public/index.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <title>nomicvote</title>
+ <meta charset="UTF-8">
+ <meta name="viewport" content="width=device-width,initial-scale=1">
+
+ <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Rubik:400,500&display=swap">
+ <link rel="stylesheet" href="style.css">
+ </head>
+ <body>
+ <h1>nomicvote</h1>
+
+ <script src="main.js"></script>
+ </body>
+</html>
diff --git a/public/main.js b/public/main.js
new file mode 100644
index 0000000..eae1d63
--- /dev/null
+++ b/public/main.js
@@ -0,0 +1,7 @@
+document.addEventListener("DOMContentLoaded", async () => { "use strict"
+
+const $ = s => document.querySelector(s);
+
+// TODO
+
+});
diff --git a/public/style.css b/public/style.css
new file mode 100644
index 0000000..8db054f
--- /dev/null
+++ b/public/style.css
@@ -0,0 +1,21 @@
+* {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+ font-family: "Rubik", sans-serif;
+ transition-timing-function: ease-in-out;
+}
+
+h1 {
+ font-size: 34px;
+ font-weight: normal;
+}
+
+h4 {
+ font-size: 10px;
+ font-weight: 500;
+}
+
+p {
+ font-size: 16px;
+}
diff --git a/run.sh b/run.sh
new file mode 100755
index 0000000..c7b2940
--- /dev/null
+++ b/run.sh
@@ -0,0 +1,14 @@
+#!/bin/bash
+
+docker rm -f nomicvote &>/dev/null
+docker build -qt nomicvote:latest . | sed 's/^sha256://'
+docker run -d --init --rm -p 8080:8080 --name nomicvote nomicvote:latest
+if [[ "$1" = "-f" ]]; then docker logs -f nomicvote; fi
+
+#docker run -d --init -p 8080:8080 \
+# -m "300M" --memory-swap "1G" \
+# --name "nomicvote" \
+# --restart always \
+# nomicvote:latest
+
+#PORT=8080 NODE_ENV=production node app.js