From 6bb3e72df331ef45a69fe7f76ed4b7e7babe021a Mon Sep 17 00:00:00 2001 From: Adam Hovorka Date: Wed, 17 Jul 2019 12:35:46 -0600 Subject: Initial commit --- .dockerignore | 8 ++++++ .gitignore | 2 ++ Dockerfile | 17 +++++++++++++ LICENSE | 21 ++++++++++++++++ README.md | 3 +++ app.js | 46 ++++++++++++++++++++++++++++++++++ lib/router.js | 40 +++++++++++++++++++++++++++++ lib/static.js | 75 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ package-lock.json | 21 ++++++++++++++++ package.json | 19 ++++++++++++++ public/404.html | 1 + public/500.html | 1 + public/index.html | 16 ++++++++++++ public/main.js | 7 ++++++ public/style.css | 21 ++++++++++++++++ run.sh | 14 +++++++++++ 16 files changed, 312 insertions(+) create mode 100644 .dockerignore create mode 100644 .gitignore create mode 100644 Dockerfile create mode 100644 LICENSE create mode 100644 README.md create mode 100644 app.js create mode 100644 lib/router.js create mode 100644 lib/static.js create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 public/404.html create mode 100644 public/500.html create mode 100644 public/index.html create mode 100644 public/main.js create mode 100644 public/style.css create mode 100755 run.sh 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="adamhovorka@gmail.com" +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 + +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("/(?[0-9A-Z]+)", (req, res, path) => { +// res.setHeader("Content-Type", "text/plain"); +// res.end(path[id]); +//}); + +//app.gpost("/(?[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(`
Error getting the file: ${e}
`); + 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 ", + "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 @@ +
404 Not Found
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 @@ +
500 Internal Server Error
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 @@ + + + + nomicvote + + + + + + + +

nomicvote

+ + + + 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 -- cgit v1.2.3-54-g00ecf