Browse Source

fix responses, allow selectable (non-playername) skins

master
poikilos 6 years ago
committed by Jacob Gustafson
parent
commit
aacb40fd2c
  1. 2
      webapp/.gitignore
  2. 9
      webapp/CHANGELOG.md
  3. 13
      webapp/README.md
  4. 32
      webapp/masterserver.js
  5. 89
      webapp/minetestinfo.js
  6. 7
      webapp/no-javascript.html
  7. 5
      webapp/node_modules/cookie-parser/HISTORY.md
  8. 32
      webapp/node_modules/cookie-parser/README.md
  9. 113
      webapp/node_modules/cookie-parser/index.js
  10. 41
      webapp/node_modules/cookie-parser/package.json
  11. 222
      webapp/package-lock.json
  12. 6
      webapp/package.json
  13. 890
      webapp/server.js
  14. 0
      webapp/views.deprecated/home.handlebars
  15. 0
      webapp/views.deprecated/layouts/main.handlebars
  16. 1
      webapp/views/notes.txt
  17. 42
      webapp/views/pages/index.ejs
  18. 32
      webapp/views/pages/result.ejs
  19. 69
      webapp/views/pages/skin-selection-form.ejs
  20. 45
      webapp/views/pages/skin-upload-form.ejs
  21. 1
      webapp/views/partials/footer.ejs
  22. 8
      webapp/views/partials/head.ejs
  23. 18
      webapp/views/partials/header.ejs

2
webapp/.gitignore

@ -1,3 +1,5 @@
/public/
# Logs # Logs
logs logs
*.log *.log

9
webapp/CHANGELOG.md

@ -5,6 +5,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
## [git] - 2019-03-19 ## [git] - 2019-03-19
### Added ### Added
- skin uploading via webapp (chooses Bucket_Game, or ENLIVEN if present) - skin uploading via webapp (chooses Bucket_Game, or ENLIVEN if present)
- `npm install formidable mv` (switched from fs.rename to mv due to - choosing existing (non-player `skin_*.png`) files
- `npm install multer`
- mv is no longer needed--had switched from fs.rename to mv due to
rename not working across filesystems (tmp is commonly on a different rename not working across filesystems (tmp is commonly on a different
volume). volume), but then switched from formidable to multer since multer has
built-in functionality to cancel upload if too large, but formidable
crashes (see <https://stackoverflow.com/a/27067596/4541104>), so mv is
no longer needed)

13
webapp/README.md

@ -12,11 +12,16 @@ EnlivenMinetest Node.js webapp for web management of minetest
npm install npm install
``` ```
## Features
* upload skin
## Usage ## Usage
* start like: * start like:
`node server.js` `node server.js`
* then it will listen on port 3000 * public/skins will be created automatically. To force updating skins
* change skin at localhost:3000/skin-form during startup, delete the public/skins directory if already exists
and is outdated.
* In browser, go to http://localhost:64638
* for security, no overwrite is allowed * for security, no overwrite is allowed
@ -35,6 +40,8 @@ npm install
not `RUN_IN_PLACE` not `RUN_IN_PLACE`
* choose minetest worlds directory separately from bin in case * choose minetest worlds directory separately from bin in case
not `RUN_IN_PLACE` not `RUN_IN_PLACE`
* try https://github.com/timbuchwaldt/node-async-fileupload
* try nodemon (automatically reloads changed js)
## Developer Notes ## Developer Notes
@ -80,7 +87,7 @@ fi
cd "$target_dir" cd "$target_dir"
npm init npm init
#except changed jade to pug #except changed jade to pug
npm install express static-favicon morgan cookie-parser body-parser debug pug passport passport-local mongoose formidable mv npm install express static-favicon morgan cookie-parser body-parser debug pug passport passport-local mongoose multer mv
#NOTE: multiparty has streaming like busboy, but is non-trivial to implement #NOTE: multiparty has streaming like busboy, but is non-trivial to implement
``` ```

32
webapp/masterserver.js

@ -1,32 +1,32 @@
var express = require('express'); var express = require('express');
var app = express(); var app = express();
var mt = require('./minetestinfo.js');
app.get('/get-players', function (req, res) { app.get('/get-players', function (req, res) {
res.setHeader('Content-Type', 'application/json'); res.setHeader('Content-Type', 'application/json');
res.send(JSON.stringify(players)); res.send(JSON.stringify(mt.players(true)));
}); });
var last_announce_string = "none"; var previousAnnounceStr = "none";
app.get('/last-announce', function (req, res) { app.get('/last-announce', function (req, res) {
res.setHeader('Content-Type', 'text/plain'); res.setHeader('Content-Type', 'text/plain');
res.send(last_announce_string); res.send(previousAnnounceStr);
}); });
app.get('/announce', function (req, res) { app.get('/announce', function (req, res) {
last_announce_string = JSON.stringify(req.body); previousAnnounceStr = JSON.stringify(req.body);
console.log("announce got:"+last_announce_string); console.log("announce got:" + previousAnnounceStr);
res.setHeader('Content-Type', 'text/plain'); res.setHeader('Content-Type', 'text/plain');
res.send(); res.send();
}); });
var server = app.listen(3000, function () { var server = app.listen(3000, function () {
//console.log('express-handlebars example server listening on: 3000'); //console.log('express-handlebars example server listening on: 3000');
var host = server.address().address; var host = server.address().address;
var port = server.address().port; var port = server.address().port;
console.log("listserver address:"); console.log("listserver address:");
console.log(JSON.stringify(server.address())); console.log(JSON.stringify(server.address()));
console.log("(experimental WIP) Minetest master server is listening at http://%s:%s", host, port); console.log("(experimental WIP) Minetest master server is listening at http://%s:%s", host, port);
}); });

89
webapp/minetestinfo.js

@ -9,17 +9,94 @@ exports.minetestPath = function() {
} }
const myName = "minetestinfo.js"; const myName = "minetestinfo.js";
var skinDir = ""; var skinsPath = "";
exports.skinDir = function () { exports.skinsPath = function () {
return skinDir; return skinsPath;
}
var selectableSkinFileNames = [];
exports.selectableSkinFileNames = function() {
return selectableSkinFileNames;
}
exports.players = function(isLoggedIn) {
return []; // TODO: implement this
}
exports.setSkin = function(userName, skinFileName) {
var indirectName = "player_" + userName + ".skin";
var indirectPath = exports.skinsPath() + "/" + indirectName;
//if (skinName.endsWith('.png')) {
//console.log("WARNING: skinName should not specify extension--removing .png");
//skinName = skinName.substring(0, skinName.length-4);
//}
//var skinFileName = skinName + ".png";
fs.writeFile(indirectPath, skinFileName, function(err, data) {
if (err) {
msg = err.message;
console.log(err);
// res.write(msg + "<br/>")
}
else {
// res.write("Before the skin is applied, The minetestserver instance must be restarted.<br/>")
msg = "Successfully wrote " + skinFileName;
console.log(msg + " to " + indirectPath + ".");
// res.write(msg + "<br/>")
}
// res.end(ending);
});
} }
exports.regeneratePaths = function () { exports.regeneratePaths = function () {
skinDir = minetestPath + "/games/Bucket_Game/mods/codercore/coderskins/textures"; skinsPath = minetestPath + "/games/Bucket_Game/mods/codercore/coderskins/textures";
if (fs.existsSync( minetestPath + "/games/ENLIVEN")) { if (fs.existsSync( minetestPath + "/games/ENLIVEN")) {
skinDir = minetestPath + "/games/ENLIVEN/mods/codercore/coderskins/textures"; skinsPath = minetestPath + "/games/ENLIVEN/mods/codercore/coderskins/textures";
}
console.log("[" + myName + "] skinsPath: \"" + skinsPath + "\"");
var publicPath = __dirname + "/public";
var publicSkinsPath = publicPath + "/skins";
if (!fs.existsSync(publicPath)) {
fs.mkdirSync(publicPath, 0744);
}
if (!fs.existsSync(publicSkinsPath)) {
fs.mkdirSync(publicSkinsPath, 0744);
fs.readdir(skinsPath, (err, files) => {
selectableSkinFileNames = [];
files.forEach(file => {
if (file.startsWith("skin_") && file.endsWith(".png")) {
var srcPath = skinsPath + '/' + file;
var dstPath = publicSkinsPath + '/' + file;
console.log("copying '" + srcPath + "' to '" + dstPath + "'");
fs.copyFile(srcPath, dstPath, fs.constants.COPYFILE_EXCL, (err) => {
if (err) throw err;
selectableSkinFileNames.push(file);
// console.log('source.txt was copied to destination.txt');
});
}
else {
console.log("not a skin: " + file)
}
// console.log(file);
});
});
} }
console.log("[" + myName + "] skinDir: \"" + skinDir + "\""); else {
fs.readdir(publicSkinsPath, (err, files) => {
selectableSkinFileNames = [];
files.forEach(file => {
if (file.startsWith("skin_") && file.endsWith(".png")) {
selectableSkinFileNames.push(file);
// console.log("detected existing " + file);
}
else {
console.log("bad skin: " + file)
}
// console.log(file);
});
});
}
} }
var thisMinetest = "/tank/local/owner/minetest"; var thisMinetest = "/tank/local/owner/minetest";
if (fs.existsSync(thisMinetest)) { if (fs.existsSync(thisMinetest)) {

7
webapp/no-javascript.html

@ -0,0 +1,7 @@
<!DOCTYPE html>
<html>
<body>
This feature requires javascript.<br/>
<a href="/">Back to Home</a>
</body>
</html>

5
webapp/node_modules/cookie-parser/HISTORY.md

@ -1,3 +1,8 @@
1.4.4 / 2019-02-12
==================
* perf: normalize `secret` argument only once
1.4.3 / 2016-05-26 1.4.3 / 2016-05-26
================== ==================

32
webapp/node_modules/cookie-parser/README.md

@ -1,8 +1,7 @@
# cookie-parser # cookie-parser
[![NPM Version][npm-image]][npm-url] [![NPM Version][npm-version-image]][npm-url]
[![NPM Downloads][downloads-image]][downloads-url] [![NPM Downloads][npm-downloads-image]][npm-url]
[![Node.js Version][node-version-image]][node-version-url]
[![Build Status][travis-image]][travis-url] [![Build Status][travis-image]][travis-url]
[![Test Coverage][coveralls-image]][coveralls-url] [![Test Coverage][coveralls-image]][coveralls-url]
@ -19,7 +18,7 @@ $ npm install cookie-parser
## API ## API
```js ```js
var express = require('express') var express = require('express')
var cookieParser = require('cookie-parser') var cookieParser = require('cookie-parser')
var app = express() var app = express()
@ -38,11 +37,11 @@ Parse a cookie value as a JSON cookie. This will return the parsed JSON value if
### cookieParser.JSONCookies(cookies) ### cookieParser.JSONCookies(cookies)
Given an object, this will iterate over the keys and call `JSONCookie` on each value. This will return the same object passed in. Given an object, this will iterate over the keys and call `JSONCookie` on each value, replacing the original value with the parsed value. This returns the same object that was passed in.
### cookieParser.signedCookie(str, secret) ### cookieParser.signedCookie(str, secret)
Parse a cookie value as a signed cookie. This will return the parsed unsigned value if it was a signed cookie and the signature was valid, otherwise it will return the passed value. Parse a cookie value as a signed cookie. This will return the parsed unsigned value if it was a signed cookie and the signature was valid. If the value was not signed, the original value is returned. If the value was signed but the signature could not be validated, `false` is returned.
The `secret` argument can be an array or string. If a string is provided, this is used as the secret. If an array is provided, an attempt will be made to unsign the cookie with each secret in order. The `secret` argument can be an array or string. If a string is provided, this is used as the secret. If an array is provided, an attempt will be made to unsign the cookie with each secret in order.
@ -55,14 +54,18 @@ The `secret` argument can be an array or string. If a string is provided, this i
## Example ## Example
```js ```js
var express = require('express') var express = require('express')
var cookieParser = require('cookie-parser') var cookieParser = require('cookie-parser')
var app = express() var app = express()
app.use(cookieParser()) app.use(cookieParser())
app.get('/', function(req, res) { app.get('/', function (req, res) {
// Cookies that have not been signed
console.log('Cookies: ', req.cookies) console.log('Cookies: ', req.cookies)
// Cookies that have been signed
console.log('Signed Cookies: ', req.signedCookies)
}) })
app.listen(8080) app.listen(8080)
@ -73,13 +76,10 @@ app.listen(8080)
### [MIT Licensed](LICENSE) ### [MIT Licensed](LICENSE)
[npm-image]: https://img.shields.io/npm/v/cookie-parser.svg [coveralls-image]: https://badgen.net/coveralls/c/github/expressjs/cookie-parser/master
[coveralls-url]: https://coveralls.io/r/expressjs/cookie-parser?branch=master
[npm-downloads-image]: https://badgen.net/npm/dm/cookie-parser
[npm-url]: https://npmjs.org/package/cookie-parser [npm-url]: https://npmjs.org/package/cookie-parser
[node-version-image]: https://img.shields.io/node/v/cookie-parser.svg [npm-version-image]: https://badgen.net/npm/v/cookie-parser
[node-version-url]: https://nodejs.org/en/download [travis-image]: https://badgen.net/travis/expressjs/cookie-parser/master
[travis-image]: https://img.shields.io/travis/expressjs/cookie-parser/master.svg
[travis-url]: https://travis-ci.org/expressjs/cookie-parser [travis-url]: https://travis-ci.org/expressjs/cookie-parser
[coveralls-image]: https://img.shields.io/coveralls/expressjs/cookie-parser/master.svg
[coveralls-url]: https://coveralls.io/r/expressjs/cookie-parser?branch=master
[downloads-image]: https://img.shields.io/npm/dm/cookie-parser.svg
[downloads-url]: https://npmjs.org/package/cookie-parser

113
webapp/node_modules/cookie-parser/index.js

@ -5,26 +5,26 @@
* MIT Licensed * MIT Licensed
*/ */
'use strict'; 'use strict'
/** /**
* Module dependencies. * Module dependencies.
* @private * @private
*/ */
var cookie = require('cookie'); var cookie = require('cookie')
var signature = require('cookie-signature'); var signature = require('cookie-signature')
/** /**
* Module exports. * Module exports.
* @public * @public
*/ */
module.exports = cookieParser; module.exports = cookieParser
module.exports.JSONCookie = JSONCookie; module.exports.JSONCookie = JSONCookie
module.exports.JSONCookies = JSONCookies; module.exports.JSONCookies = JSONCookies
module.exports.signedCookie = signedCookie; module.exports.signedCookie = signedCookie
module.exports.signedCookies = signedCookies; module.exports.signedCookies = signedCookies
/** /**
* Parse Cookie header and populate `req.cookies` * Parse Cookie header and populate `req.cookies`
@ -36,39 +36,40 @@ module.exports.signedCookies = signedCookies;
* @public * @public
*/ */
function cookieParser(secret, options) { function cookieParser (secret, options) {
return function cookieParser(req, res, next) { var secrets = !secret || Array.isArray(secret)
? (secret || [])
: [secret]
return function cookieParser (req, res, next) {
if (req.cookies) { if (req.cookies) {
return next(); return next()
} }
var cookies = req.headers.cookie; var cookies = req.headers.cookie
var secrets = !secret || Array.isArray(secret)
? (secret || [])
: [secret];
req.secret = secrets[0]; req.secret = secrets[0]
req.cookies = Object.create(null); req.cookies = Object.create(null)
req.signedCookies = Object.create(null); req.signedCookies = Object.create(null)
// no cookies // no cookies
if (!cookies) { if (!cookies) {
return next(); return next()
} }
req.cookies = cookie.parse(cookies, options); req.cookies = cookie.parse(cookies, options)
// parse signed cookies // parse signed cookies
if (secrets.length !== 0) { if (secrets.length !== 0) {
req.signedCookies = signedCookies(req.cookies, secrets); req.signedCookies = signedCookies(req.cookies, secrets)
req.signedCookies = JSONCookies(req.signedCookies); req.signedCookies = JSONCookies(req.signedCookies)
} }
// parse JSON cookies // parse JSON cookies
req.cookies = JSONCookies(req.cookies); req.cookies = JSONCookies(req.cookies)
next(); next()
}; }
} }
/** /**
@ -79,15 +80,15 @@ function cookieParser(secret, options) {
* @public * @public
*/ */
function JSONCookie(str) { function JSONCookie (str) {
if (typeof str !== 'string' || str.substr(0, 2) !== 'j:') { if (typeof str !== 'string' || str.substr(0, 2) !== 'j:') {
return undefined; return undefined
} }
try { try {
return JSON.parse(str.slice(2)); return JSON.parse(str.slice(2))
} catch (err) { } catch (err) {
return undefined; return undefined
} }
} }
@ -99,21 +100,21 @@ function JSONCookie(str) {
* @public * @public
*/ */
function JSONCookies(obj) { function JSONCookies (obj) {
var cookies = Object.keys(obj); var cookies = Object.keys(obj)
var key; var key
var val; var val
for (var i = 0; i < cookies.length; i++) { for (var i = 0; i < cookies.length; i++) {
key = cookies[i]; key = cookies[i]
val = JSONCookie(obj[key]); val = JSONCookie(obj[key])
if (val) { if (val) {
obj[key] = val; obj[key] = val
} }
} }
return obj; return obj
} }
/** /**
@ -125,28 +126,28 @@ function JSONCookies(obj) {
* @public * @public
*/ */
function signedCookie(str, secret) { function signedCookie (str, secret) {
if (typeof str !== 'string') { if (typeof str !== 'string') {
return undefined; return undefined
} }
if (str.substr(0, 2) !== 's:') { if (str.substr(0, 2) !== 's:') {
return str; return str
} }
var secrets = !secret || Array.isArray(secret) var secrets = !secret || Array.isArray(secret)
? (secret || []) ? (secret || [])
: [secret]; : [secret]
for (var i = 0; i < secrets.length; i++) { for (var i = 0; i < secrets.length; i++) {
var val = signature.unsign(str.slice(2), secrets[i]); var val = signature.unsign(str.slice(2), secrets[i])
if (val !== false) { if (val !== false) {
return val; return val
} }
} }
return false; return false
} }
/** /**
@ -159,23 +160,23 @@ function signedCookie(str, secret) {
* @public * @public
*/ */
function signedCookies(obj, secret) { function signedCookies (obj, secret) {
var cookies = Object.keys(obj); var cookies = Object.keys(obj)
var dec; var dec
var key; var key
var ret = Object.create(null); var ret = Object.create(null)
var val; var val
for (var i = 0; i < cookies.length; i++) { for (var i = 0; i < cookies.length; i++) {
key = cookies[i]; key = cookies[i]
val = obj[key]; val = obj[key]
dec = signedCookie(val, secret); dec = signedCookie(val, secret)
if (val !== dec) { if (val !== dec) {
ret[key] = dec; ret[key] = dec
delete obj[key]; delete obj[key]
} }
} }
return ret; return ret
} }

41
webapp/node_modules/cookie-parser/package.json

@ -1,28 +1,28 @@
{ {
"_from": "cookie-parser", "_from": "cookie-parser@^1.4.3",
"_id": "cookie-parser@1.4.3", "_id": "cookie-parser@1.4.4",
"_inBundle": false, "_inBundle": false,
"_integrity": "sha1-D+MfoZ0AC5X0qt8fU/3CuKIDuqU=", "_integrity": "sha512-lo13tqF3JEtFO7FyA49CqbhaFkskRJ0u/UAiINgrIXeRCY41c88/zxtrECl8AKH3B0hj9q10+h3Kt8I7KlW4tw==",
"_location": "/cookie-parser", "_location": "/cookie-parser",
"_phantomChildren": {}, "_phantomChildren": {},
"_requested": { "_requested": {
"type": "tag", "type": "range",
"registry": true, "registry": true,
"raw": "cookie-parser", "raw": "cookie-parser@^1.4.3",
"name": "cookie-parser", "name": "cookie-parser",
"escapedName": "cookie-parser", "escapedName": "cookie-parser",
"rawSpec": "", "rawSpec": "^1.4.3",
"saveSpec": null, "saveSpec": null,
"fetchSpec": "latest" "fetchSpec": "^1.4.3"
}, },
"_requiredBy": [ "_requiredBy": [
"#USER", "#USER",
"/" "/"
], ],
"_resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.3.tgz", "_resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.4.tgz",
"_shasum": "0fe31fa19d000b95f4aadf1f53fdc2b8a203baa5", "_shasum": "e6363de4ea98c3def9697b93421c09f30cf5d188",
"_spec": "cookie-parser", "_spec": "cookie-parser@^1.4.3",
"_where": "/home/owner/GitHub/EnlivenMinetest/webapp", "_where": "/home/owner/git/EnlivenMinetest/webapp",
"author": { "author": {
"name": "TJ Holowaychuk", "name": "TJ Holowaychuk",
"email": "tj@vision-media.ca", "email": "tj@vision-media.ca",
@ -43,11 +43,19 @@
"cookie-signature": "1.0.6" "cookie-signature": "1.0.6"
}, },
"deprecated": false, "deprecated": false,
"description": "cookie parsing with signatures", "description": "Parse HTTP request cookies",
"devDependencies": { "devDependencies": {
"istanbul": "0.4.3", "deep-equal": "1.0.1",
"mocha": "2.5.3", "eslint": "5.13.0",
"supertest": "1.1.0" "eslint-config-standard": "12.0.0",
"eslint-plugin-import": "2.16.0",
"eslint-plugin-markdown": "1.0.0",
"eslint-plugin-node": "7.0.1",
"eslint-plugin-promise": "4.0.1",
"eslint-plugin-standard": "4.0.0",
"istanbul": "0.4.5",
"mocha": "5.2.0",
"supertest": "3.4.2"
}, },
"engines": { "engines": {
"node": ">= 0.8.0" "node": ">= 0.8.0"
@ -69,9 +77,10 @@
"url": "git+https://github.com/expressjs/cookie-parser.git" "url": "git+https://github.com/expressjs/cookie-parser.git"
}, },
"scripts": { "scripts": {
"lint": "eslint --plugin markdown --ext js,md .",
"test": "mocha --reporter spec --bail --check-leaks test/", "test": "mocha --reporter spec --bail --check-leaks test/",
"test-cov": "istanbul cover node_modules/mocha/bin/_mocha -- --reporter dot --check-leaks test/", "test-cov": "istanbul cover node_modules/mocha/bin/_mocha -- --reporter dot --check-leaks test/",
"test-travis": "istanbul cover node_modules/mocha/bin/_mocha --report lcovonly -- --reporter spec --check-leaks test/" "test-travis": "istanbul cover node_modules/mocha/bin/_mocha --report lcovonly -- --reporter spec --check-leaks test/"
}, },
"version": "1.4.3" "version": "1.4.4"
} }

222
webapp/package-lock.json

@ -13,16 +13,16 @@
"negotiator": "0.6.1" "negotiator": "0.6.1"
} }
}, },
"append-field": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz",
"integrity": "sha1-HjRA6RXwsSA9I3SOeO3XubW0PlY="
},
"array-flatten": { "array-flatten": {
"version": "1.1.1", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
"integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI="
}, },
"balanced-match": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
"integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c="
},
"body-parser": { "body-parser": {
"version": "1.18.2", "version": "1.18.2",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.2.tgz", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.2.tgz",
@ -40,13 +40,18 @@
"type-is": "~1.6.15" "type-is": "~1.6.15"
} }
}, },
"brace-expansion": { "buffer-from": {
"version": "1.1.11", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz",
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A=="
},
"busboy": {
"version": "0.2.14",
"resolved": "https://registry.npmjs.org/busboy/-/busboy-0.2.14.tgz",
"integrity": "sha1-bCpiLvz0fFe7vh4qnDetNseSVFM=",
"requires": { "requires": {
"balanced-match": "^1.0.0", "dicer": "0.2.5",
"concat-map": "0.0.1" "readable-stream": "1.1.x"
} }
}, },
"bytes": { "bytes": {
@ -54,10 +59,45 @@
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz",
"integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=" "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg="
}, },
"concat-map": { "concat-stream": {
"version": "0.0.1", "version": "1.6.2",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz",
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==",
"requires": {
"buffer-from": "^1.0.0",
"inherits": "^2.0.3",
"readable-stream": "^2.2.2",
"typedarray": "^0.0.6"
},
"dependencies": {
"isarray": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
"integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
},
"readable-stream": {
"version": "2.3.6",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
"integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
"requires": {
"core-util-is": "~1.0.0",
"inherits": "~2.0.3",
"isarray": "~1.0.0",
"process-nextick-args": "~2.0.0",
"safe-buffer": "~5.1.1",
"string_decoder": "~1.1.1",
"util-deprecate": "~1.0.1"
}
},
"string_decoder": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
"requires": {
"safe-buffer": "~5.1.0"
}
}
}
}, },
"content-disposition": { "content-disposition": {
"version": "0.5.2", "version": "0.5.2",
@ -75,9 +115,9 @@
"integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=" "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s="
}, },
"cookie-parser": { "cookie-parser": {
"version": "1.4.3", "version": "1.4.4",
"resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.3.tgz", "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.4.tgz",
"integrity": "sha1-D+MfoZ0AC5X0qt8fU/3CuKIDuqU=", "integrity": "sha512-lo13tqF3JEtFO7FyA49CqbhaFkskRJ0u/UAiINgrIXeRCY41c88/zxtrECl8AKH3B0hj9q10+h3Kt8I7KlW4tw==",
"requires": { "requires": {
"cookie": "0.3.1", "cookie": "0.3.1",
"cookie-signature": "1.0.6" "cookie-signature": "1.0.6"
@ -88,6 +128,11 @@
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
"integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw="
}, },
"core-util-is": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
"integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac="
},
"debug": { "debug": {
"version": "2.6.9", "version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
@ -106,11 +151,25 @@
"resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz",
"integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA="
}, },
"dicer": {
"version": "0.2.5",
"resolved": "https://registry.npmjs.org/dicer/-/dicer-0.2.5.tgz",
"integrity": "sha1-WZbAhrszIYyBLAkL3cCc0S+stw8=",
"requires": {
"readable-stream": "1.1.x",
"streamsearch": "0.1.2"
}
},
"ee-first": { "ee-first": {
"version": "1.1.1", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
"integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0="
}, },
"ejs": {
"version": "2.6.1",
"resolved": "https://registry.npmjs.org/ejs/-/ejs-2.6.1.tgz",
"integrity": "sha512-0xy4A/twfrRCnkhfk8ErDi5DqdAsAqeGxht4xkCUrsvhhbQNs7E+4jV0CN7+NKIY0aHE72+XvqtBIXzD31ZbXQ=="
},
"encodeurl": { "encodeurl": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
@ -177,11 +236,6 @@
"unpipe": "~1.0.0" "unpipe": "~1.0.0"
} }
}, },
"formidable": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/formidable/-/formidable-1.2.1.tgz",
"integrity": "sha512-Fs9VRguL0gqGHkXS5GQiMCr1VhZBxz0JnJs4JmMp/2jL18Fmbzvv7vOFRU+U8TBkHEE/CX1qDXzJplVULgsLeg=="
},
"forwarded": { "forwarded": {
"version": "0.1.2", "version": "0.1.2",
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz",
@ -192,18 +246,6 @@
"resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
"integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac="
}, },
"glob": {
"version": "6.0.4",
"resolved": "https://registry.npmjs.org/glob/-/glob-6.0.4.tgz",
"integrity": "sha1-DwiGD2oVUSey+t1PnOJLGqtuTSI=",
"requires": {
"inflight": "^1.0.4",
"inherits": "2",
"minimatch": "2 || 3",
"once": "^1.3.0",
"path-is-absolute": "^1.0.0"
}
},
"http-errors": { "http-errors": {
"version": "1.6.3", "version": "1.6.3",
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz",
@ -220,15 +262,6 @@
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.19.tgz", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.19.tgz",
"integrity": "sha512-oTZqweIP51xaGPI4uPa56/Pri/480R+mo7SeU+YETByQNhDG55ycFyNLIgta9vXhILrxXDmF7ZGhqZIcuN0gJQ==" "integrity": "sha512-oTZqweIP51xaGPI4uPa56/Pri/480R+mo7SeU+YETByQNhDG55ycFyNLIgta9vXhILrxXDmF7ZGhqZIcuN0gJQ=="
}, },
"inflight": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
"integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
"requires": {
"once": "^1.3.0",
"wrappy": "1"
}
},
"inherits": { "inherits": {
"version": "2.0.3", "version": "2.0.3",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
@ -239,6 +272,11 @@
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.6.0.tgz", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.6.0.tgz",
"integrity": "sha1-4/o1e3c9phnybpXwSdBVxyeW+Gs=" "integrity": "sha1-4/o1e3c9phnybpXwSdBVxyeW+Gs="
}, },
"isarray": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
"integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8="
},
"media-typer": { "media-typer": {
"version": "0.3.0", "version": "0.3.0",
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
@ -272,14 +310,6 @@
"mime-db": "~1.33.0" "mime-db": "~1.33.0"
} }
}, },
"minimatch": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
"requires": {
"brace-expansion": "^1.1.7"
}
},
"minimist": { "minimist": {
"version": "0.0.8", "version": "0.0.8",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
@ -298,14 +328,19 @@
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
}, },
"mv": { "multer": {
"version": "2.1.1", "version": "1.4.1",
"resolved": "https://registry.npmjs.org/mv/-/mv-2.1.1.tgz", "resolved": "https://registry.npmjs.org/multer/-/multer-1.4.1.tgz",
"integrity": "sha1-rmzg1vbV4KT32JN5jQPB6pVZtqI=", "integrity": "sha512-zzOLNRxzszwd+61JFuAo0fxdQfvku12aNJgnla0AQ+hHxFmfc/B7jBVuPr5Rmvu46Jze/iJrFpSOsD7afO8SDw==",
"requires": { "requires": {
"mkdirp": "~0.5.1", "append-field": "^1.0.0",
"ncp": "~2.0.0", "busboy": "^0.2.11",
"rimraf": "~2.4.0" "concat-stream": "^1.5.2",
"mkdirp": "^0.5.1",
"object-assign": "^4.1.1",
"on-finished": "^2.3.0",
"type-is": "^1.6.4",
"xtend": "^4.0.0"
} }
}, },
"n-readlines": { "n-readlines": {
@ -313,16 +348,16 @@
"resolved": "https://registry.npmjs.org/n-readlines/-/n-readlines-0.2.8.tgz", "resolved": "https://registry.npmjs.org/n-readlines/-/n-readlines-0.2.8.tgz",
"integrity": "sha512-FRr6GU0vooiPAuHMBt/Pspm4htItJKCehs8Q2urdUuigXsdQzP8V03UaQedeCmiygqJwtuGhnidWHRHhUOAg9w==" "integrity": "sha512-FRr6GU0vooiPAuHMBt/Pspm4htItJKCehs8Q2urdUuigXsdQzP8V03UaQedeCmiygqJwtuGhnidWHRHhUOAg9w=="
}, },
"ncp": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ncp/-/ncp-2.0.0.tgz",
"integrity": "sha1-GVoh1sRuNh0vsSgbo4uR6d9727M="
},
"negotiator": { "negotiator": {
"version": "0.6.1", "version": "0.6.1",
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz",
"integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=" "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk="
}, },
"object-assign": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
"integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM="
},
"on-finished": { "on-finished": {
"version": "2.3.0", "version": "2.3.0",
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz",
@ -331,29 +366,21 @@
"ee-first": "1.1.1" "ee-first": "1.1.1"
} }
}, },
"once": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
"integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
"requires": {
"wrappy": "1"
}
},
"parseurl": { "parseurl": {
"version": "1.3.2", "version": "1.3.2",
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz",
"integrity": "sha1-/CidTtiZMRlGDBViUyYs3I3mW/M=" "integrity": "sha1-/CidTtiZMRlGDBViUyYs3I3mW/M="
}, },
"path-is-absolute": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
"integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18="
},
"path-to-regexp": { "path-to-regexp": {
"version": "0.1.7", "version": "0.1.7",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
"integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w="
}, },
"process-nextick-args": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz",
"integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw=="
},
"proxy-addr": { "proxy-addr": {
"version": "2.0.3", "version": "2.0.3",
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.3.tgz", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.3.tgz",
@ -407,12 +434,15 @@
} }
} }
}, },
"rimraf": { "readable-stream": {
"version": "2.4.5", "version": "1.1.14",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.4.5.tgz", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz",
"integrity": "sha1-7nEM5dk6j9uFb7Xqj/Di11k0sto=", "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=",
"requires": { "requires": {
"glob": "^6.0.1" "core-util-is": "~1.0.0",
"inherits": "~2.0.1",
"isarray": "0.0.1",
"string_decoder": "~0.10.x"
} }
}, },
"safe-buffer": { "safe-buffer": {
@ -461,6 +491,16 @@
"resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz",
"integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==" "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew=="
}, },
"streamsearch": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-0.1.2.tgz",
"integrity": "sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo="
},
"string_decoder": {
"version": "0.10.31",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
"integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ="
},
"type-is": { "type-is": {
"version": "1.6.16", "version": "1.6.16",
"resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.16.tgz", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.16.tgz",
@ -470,11 +510,21 @@
"mime-types": "~2.1.18" "mime-types": "~2.1.18"
} }
}, },
"typedarray": {
"version": "0.0.6",
"resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
"integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c="
},
"unpipe": { "unpipe": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
"integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw="
}, },
"util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
"integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8="
},
"utils-merge": { "utils-merge": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
@ -485,10 +535,10 @@
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
"integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw="
}, },
"wrappy": { "xtend": {
"version": "1.0.2", "version": "4.0.1",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz",
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68="
} }
} }
} }

6
webapp/package.json

@ -12,10 +12,10 @@
"license": "BSD", "license": "BSD",
"dependencies": { "dependencies": {
"body-parser": "^1.18.2", "body-parser": "^1.18.2",
"cookie-parser": "^1.4.3", "cookie-parser": "^1.4.4",
"ejs": "^2.6.1",
"express": "^4.7.2", "express": "^4.7.2",
"formidable": "^1.2.1", "multer": "^1.4.1",
"mv": "^2.1.1",
"n-readlines": "^0.2.8" "n-readlines": "^0.2.8"
} }
} }

890
webapp/server.js

@ -2,457 +2,529 @@
// Howto: see README.md // Howto: see README.md
//function getUserHome() { //function getUserHome() {
//return process.env[(process.platform == 'win32') ? 'USERPROFILE' : 'HOME']; //return process.env[(process.platform == 'win32') ? 'USERPROFILE' : 'HOME'];
//} //}
var tz_offset = 240; //subtract this from server time to get local time; 4hrs is 240; 5hrs is 300 var tzOffsetMinutes = 240; //subtract this from server time to get local time; 4hrs is 240; 5hrs is 300
// TODO: handle tz_offset not divisible by 60 // TODO: handle tzOffsetMinutes not divisible by 60
// var selected_date_s = null; // var selectedDateStr = null;
// selected_date_s = "2018-05-08"; // selectedDateStr = "2018-05-08";
var express = require('express'), const express = require('express'),
multer = require('multer'),
// var exphbs = require("express-handlebars"); // var exphbs = require("express-handlebars");
// exphbs = require('../../'); // "express-handlebars" // exphbs = require('../../'); // "express-handlebars"
cookieParser = require('cookie-parser'), cookieParser = require('cookie-parser'),
bodyParser = require('body-parser'), bodyParser = require('body-parser'),
//session = require('express-session'), //session = require('express-session'),
fs = require('fs'), fs = require('fs'),
readlines = require('n-readlines'); readlines = require('n-readlines');
const os = require('os'); const os = require('os');
var formidable = require('formidable') //var formidable = require('formidable');
var querystring = require("querystring"); // built-in const querystring = require("querystring"); // built-in
var mv = require('mv'); // TODO: var config = require(storagePath + '/config.js') // config file contains all tokens and other private info
// TODO: var config = require(storage_path + '/config.js') // config file contains all tokens and other private info
// var fun = require('./functions.js'); // functions file contains our non-app-specific functions including those for our Passport and database work // var fun = require('./functions.js'); // functions file contains our non-app-specific functions including those for our Passport and database work
var mt = require('./minetestinfo.js'); // functions file contains our non-app-specific functions including those for our Passport and database work var mt = require('./minetestinfo.js'); // functions file contains our non-app-specific functions including those for our Passport and database work
// var util = require('util') // var util = require('util')
var app = express(); var app = express();
app.set('view engine', 'ejs');
// see https://medium.com/@TheJesseLewis/how-to-make-a-basic-html-form-file-upload-using-multer-in-an-express-node-js-app-16dac2476610
const port = process.env.PORT || 64638;
app.use(bodyParser.urlencoded({extended:false})); // handle body requests
app.use(bodyParser.json()); // make JSON work
app.use('/public', express.static(__dirname + '/public'));
//app.engine('handlebars', exphbs({defaultLayout: 'main'})); //app.engine('handlebars', exphbs({defaultLayout: 'main'}));
//app.set('view engine', 'handlebars'); //app.set('view engine', 'handlebars');
var players = []; var players = [];
var player_indices = {}; var playerIndices = {};
var play_dates = []; var activityDates = [];
//see https://developer.mozilla.org/en-US/docs/Learn/Server-side/Express_Nodejs/Introduction //see https://developer.mozilla.org/en-US/docs/Learn/Server-side/Express_Nodejs/Introduction
var prev_line = null; var previousLine = null;
//#region derived from mtsenliven.py //#region derived from mtsenliven.py
var msgprefix_flags = ["WARNING[Server]: ", "ACTION[Server]: "] var msgPrefixFlags = ["WARNING[Server]: ", "ACTION[Server]: "]
var msgprefix_lists = {} // where flag is key var msgPrefixLists = {} // where flag is key
var mf_len = msgprefix_flags.length; var mfLen = msgPrefixFlags.length;
for (var mf_i=0; mf_i<mf_len; mf_i++) { for (var mfIndex=0; mfIndex<mfLen; mfIndex++) {
msgprefix_lists[msgprefix_flags[mf_i]] = []; msgPrefixLists[msgPrefixFlags[mfIndex]] = [];
} }
var non_unique_wraps = []; var nonUniqueWraps = [];
non_unique_wraps.push({ nonUniqueWraps.push({
"opener":"active block modifiers took ", "opener":"active block modifiers took ",
"closer":"ms (longer than 200ms)" "closer":"ms (longer than 200ms)"
}); });
var unique_flags = [ var uniqueFlags = [
"leaves game", "leaves game",
"joins game" "joins game"
]; ];
//#endregion derived from mtsenliven.py //#endregion derived from mtsenliven.py
const skinStorage = multer.diskStorage({
// see https://medium.com/@TheJesseLewis/how-to-make-a-basic-html-form-file-upload-using-multer-in-an-express-node-js-app-16dac2476610
destination: function(req, file, next) {
next(null, mt.skinsPath()); // or something like './public/photo-storage'
},
limits: {
fileSize: 1*1024*1024 // in bytes
},
// Change filename
filename: function(req, file, next) {
const ext = "png";
// var errMsg = null;
console.log("* Checking name...");
if (!req.body.userName) {
return next(new Error("userName is missing"));
}
else if (req.body.userName.length < 1) {
return next(new Error("userName is blank."));
}
if (file.size < 1) {
return next(new Error("image not selected"));
}
//if (errMsg === null) {
var directName = "player_" + req.body.userName + '.' + ext;
console.log("* Renaming '" + file + "' to " + directName);
next(null, directName);
//}
//else {
//console.log(errMsg);
//next(new Error(errMsg));
//}
// const ext = file.mimetype.split('/')[1];
// next(null, file.fieldname + '-' + Date.now() + '.'+ext);
}
});
// see https://medium.com/@bmshamsnahid/nodejs-file-upload-using-multer-3a904516f6d2
const skinUpload = multer({
storage: skinStorage,
fileFilter: function(req, file, next) {
const ext = "png";
console.log("filtering...");
var errMsg = null;
// NOTE: return with error aborts the upload.
if (!file) {
errMsg = "You did not select a file.";
req.fileValidationError = errMsg;
return next(new Error(errMsg)); // next(null, false, new Error(errMsg))
}
else if (file.size < 1) {
errMsg = "Empty file.";
req.fileValidationError = errMsg;
return next(new Error(errMsg));
}
if (!file.mimetype.startsWith('image/')) {
errMsg = "* ERROR: file type " + file.mimetype
+ " is not supported";
req.fileValidationError = errMsg;
return next(new Error(errMsg));
}
if (errMsg === null) {
var directName = "player_" + req.body.userName + '.' + ext;
console.log("* " + file.mimetype + " '" + file + "' uploaded...");
// set player skin name to new file name:
mt.setSkin(req.body.userName, directName);
// TODO: allow setSkin output to res
next(null, true);
} else {
console.log(errMsg);
return next(new Error(errMsg));
}
}
});
function process_logline(line, line_number) { function processLogLine(line, lineNumber) {
//selected_date_s //selectedDateStr
//TODO: use store_unique_log_data instead of this function //TODO: use storeUniqueLogData instead of this function
var player_name = null; var playerName = null;
var verb = ""; var verb = "";
var time_s = ""; var timeStr = "";
var date_s = ""; var dateStr = "";
var player_ip = null; var playerIP = null;
const time_start_i = 11; const timeStartInt = 11;
var uf_len = unique_flags.length; var ufLen = uniqueFlags.length;
mf_len = msgprefix_flags.length; mfLen = msgPrefixFlags.length;
var verb_i = -1; var verbIndex = -1;
var verb_number = -1; var verbNumber = -1;
var msgprefix_i = -1; var msgPrefixIndex = -1;
var msgprefix_number = -1; var msgPrefixNumber = -1;
var msgprefix = null; var msgprefix = null;
var index_msg = ""; var indexMsg = "";
for (var mf_i=0; mf_i<mf_len; mf_i++) { for (var mfIndex=0; mfIndex<mfLen; mfIndex++) {
msgprefix_i = line.indexOf(msgprefix_flags[mf_i]); msgPrefixIndex = line.indexOf(msgPrefixFlags[mfIndex]);
if (msgprefix_i > -1) { if (msgPrefixIndex > -1) {
msgprefix_number = mf_i; msgPrefixNumber = mfIndex;
msgprefix = msgprefix_flags[mf_i]; msgprefix = msgPrefixFlags[mfIndex];
break; break;
} }
} }
var skip_date_enable = false; var skipDateEnable = false;
for (var uf_i=0; uf_i<uf_len; uf_i++) { for (var ufIndex=0; ufIndex<ufLen; ufIndex++) {
verb_i = line.indexOf(unique_flags[uf_i]); verbIndex = line.indexOf(uniqueFlags[ufIndex]);
if (verb_i > -1) { if (verbIndex > -1) {
verb_number = uf_i; verbNumber = ufIndex;
verb = unique_flags[uf_i]; verb = uniqueFlags[ufIndex];
date_s = line.substring(0,10).trim(); dateStr = line.substring(0,10).trim();
//if (selected_date_s==null || selected_date_s==date_s) { //if (selectedDateStr==null || selectedDateStr==dateStr) {
//console.log("(verbose message in process_logline) using '" + date_s + "' since selected '"+selected_date_s+"'"); //console.log("(verbose message in processLogLine) using '" + dateStr + "' since selected '"+selectedDateStr+"'");
time_s = line.substring(time_start_i, time_start_i+8); timeStr = line.substring(timeStartInt, timeStartInt+8);
//console.log("using time "+time_s); //console.log("using time "+timeStr);
if (msgprefix!=null) { if (msgprefix!=null) {
player_name = line.substring(msgprefix_i+msgprefix.length, verb_i).trim(); playerName = line.substring(msgPrefixIndex+msgprefix.length, verbIndex).trim();
var ip_flag = " ["; var ipFlag = " [";
var ip_i = player_name.indexOf(ip_flag); var ipIndex = playerName.indexOf(ipFlag);
if (ip_i > -1) { if (ipIndex > -1) {
player_ip = player_name.substring(ip_i+ip_flag.length, player_name.length-1); playerIP = playerName.substring(ipIndex+ipFlag.length, playerName.length-1);
player_name = player_name.substring(0,ip_i); playerName = playerName.substring(0, ipIndex);
} }
} }
else { else {
player_name = "&lt;missing msgprefix&rt;"; playerName = "&lt;missing msgprefix&rt;";
} }
//} //}
//else { //else {
// skip_date_enable = true; // skipDateEnable = true;
//console.log("WARNING in process_logline: skipping '" + date_s + "' since not '"+selected_date_s+"'"); //console.log("WARNING in processLogLine: skipping '" + dateStr + "' since not '"+selectedDateStr+"'");
//} //}
break; break;
} }
} }
var index = -1; // player index var index = -1; // player index
if (player_name != null) { if (playerName != null) {
if (player_name.length > 0) { if (playerName.length > 0) {
if (player_indices.hasOwnProperty(player_name)) { if (playerIndices.hasOwnProperty(playerName)) {
index = player_indices[player_name]; index = playerIndices[playerName];
index_msg = "cached "; indexMsg = "cached ";
} }
else { else {
index = players.length; index = players.length;
//players.push({}); //players.push({});
players[index] = {}; players[index] = {};
players[index].display_name = player_name; players[index].displayName = playerName;
player_indices[player_name] = index; playerIndices[playerName] = index;
//console.log("created new index "+index); //console.log("created new index "+index);
} }
} }
else { else {
console.log("WARNING in process_logline: zero-length player name"); console.log("WARNING in processLogLine: zero-length player name");
} }
} }
if (index<0 && (verb=="leaves game"||verb=="joins game")) { if (index<0 && (verb=="leaves game"||verb=="joins game")) {
console.log("(ERROR in process_logline) " + index_msg + console.log("(ERROR in processLogLine) " + indexMsg +
"index was '"+index+"' but date was present '" + "index was '"+index+"' but date was present '" +
date_s + "' for '"+line+"' (no player found, but" + dateStr + "' for '"+line+"' (no player found, but" +
"verb is a player verb)."); "verb is a player verb).");
} }
var play_date_enable = false; var playDateEnable = false;
if (verb == "leaves game") { if (verb == "leaves game") {
if (index > -1) { if (index > -1) {
var play_i = -1; var playIndex = -1;
if (!players[index].hasOwnProperty("plays")) { if (!players[index].hasOwnProperty("plays")) {
players[index].plays = {}; players[index].plays = {};
} }
if (!players[index].plays.hasOwnProperty(date_s)) { if (!players[index].plays.hasOwnProperty(dateStr)) {
//leave login time blank--player must have logged in before the available part of the log began //leave login time blank--player must have logged in before the available part of the log began
players[index].plays[date_s] = []; players[index].plays[dateStr] = [];
players[index].plays[date_s].push({}); players[index].plays[dateStr].push({});
play_i = 0; playIndex = 0;
} }
else { else {
if (players[index].plays[date_s].length==0) players[index].plays[date_s].push({}); if (players[index].plays[dateStr].length==0) players[index].plays[dateStr].push({});
play_i = players[index].plays[date_s].length - 1; playIndex = players[index].plays[dateStr].length - 1;
if (players[index].plays[date_s][play_i].hasOwnProperty("logout_time")) { if (players[index].plays[dateStr][playIndex].hasOwnProperty("logoutTime")) {
//If last entry is incomplete, start a new one: //If last entry is incomplete, start a new one:
players[index].plays[date_s].push({}); players[index].plays[dateStr].push({});
play_i++; playIndex++;
} }
} }
players[index].plays[date_s][play_i].logout_time = time_s; players[index].plays[dateStr][playIndex].logoutTime = timeStr;
play_date_enable = true; playDateEnable = true;
} }
} }
else if (verb == "joins game") { else if (verb == "joins game") {
if (index > -1) { if (index > -1) {
if (player_ip!=null) { if (playerIP!=null) {
players[index].player_ip = player_ip; players[index].playerIP = playerIP;
var play_i = -1; var playIndex = -1;
if (!players[index].hasOwnProperty("plays")) { if (!players[index].hasOwnProperty("plays")) {
players[index].plays = {}; players[index].plays = {};
} }
if (!players[index].plays.hasOwnProperty(date_s)) { if (!players[index].plays.hasOwnProperty(dateStr)) {
players[index].plays[date_s] = []; players[index].plays[dateStr] = [];
play_i = 0; playIndex = 0;
} }
else play_i = players[index].plays[date_s].length; else playIndex = players[index].plays[dateStr].length;
players[index].plays[date_s].push({}); players[index].plays[dateStr].push({});
//console.log(verb+" on "+date_s+" (length "+players[index].plays[date_s].length+") play "+play_i+"+1 for player ["+index+"] "+player_name+"..."); //console.log(verb+" on "+dateStr+" (length "+players[index].plays[dateStr].length+") play "+playIndex+"+1 for player ["+index+"] "+playerName+"...");
players[index].plays[date_s][play_i].login_time = time_s; players[index].plays[dateStr][playIndex].loginTime = timeStr;
play_date_enable = true; playDateEnable = true;
} }
// else redundant (server writes " joins game " again // else redundant (server writes " joins game " again
// and shows list of players instead of ip). // and shows list of players instead of ip).
//TODO: else analyze list of players to confirm in case player logged in all day //TODO: else analyze list of players to confirm in case player logged in all day
} }
} }
if (play_date_enable) { if (playDateEnable) {
if (date_s.length>0) { if (dateStr.length>0) {
if (play_dates.indexOf(date_s) < 0) { if (activityDates.indexOf(dateStr) < 0) {
play_dates.push(date_s); activityDates.push(dateStr);
} }
} }
} }
} }
function store_unique_log_data(output, line_number, err_flag=false) { function storeUniqueLogData(output, lineNumber, errFlag=false) {
var ret = ""; var ret = "";
var output_strip = output.trim(); var outputTrim = output.trim();
var u_prefix = "active block modifiers took "; var uPrefix = "active block modifiers took ";
var u_suffix = "ms (longer than 200ms)"; var uSuffix = "ms (longer than 200ms)";
// (out_bytes is bytes) // (outBytes is bytes)
var show_enable = true; var showEnable = true;
var found_flag = null; var foundFlag = null;
var f_i = null; var fIndex = null;
var always_show_enable = false; var alwaysShowEnable = false;
var msg_msg = "previous message"; var msgMsg = "previous message";
var uf_len = unique_flags.length; var ufLen = uniqueFlags.length;
for (var uf_i=0; uf_i<uf_len; uf_i++) { for (var ufIndex=0; ufIndex<ufLen; ufIndex++) {
if (output.includes(unique_flags[uf_i])) { if (output.includes(uniqueFlags[ufIndex])) {
always_show_enable = true; alwaysShowEnable = true;
} }
} }
if (!always_show_enable) { if (!alwaysShowEnable) {
var mf_len = msgprefix_flags.length; var mfLen = msgPrefixFlags.length;
for (var mf_i=0; mf_i<mf_len; mf_i++) { for (var mfIndex=0; mfIndex<mfLen; mfIndex++) {
// such as '2018-02-06 21:08:06: WARNING[Server]: Deprecated call to get_look_yaw, use get_look_horizontal instead' // such as '2018-02-06 21:08:06: WARNING[Server]: Deprecated call to get_look_yaw, use get_look_horizontal instead'
// or 2018-02-06 21:08:05: ACTION[Server]: [playereffects] Wrote playereffects data into /home/owner/.minetest/worlds/FCAGameAWorld/playereffects.mt. // or 2018-02-06 21:08:05: ACTION[Server]: [playereffects] Wrote playereffects data into /home/owner/.minetest/worlds/FCAGameAWorld/playereffects.mt.
f_i = output.find(msgprefix_flags[mf_i]); fIndex = output.find(msgPrefixFlags[mfIndex]);
if (f_i >= 0) { if (fIndex >= 0) {
found_flag = msgprefix_flags[mf_i]; foundFlag = msgPrefixFlags[mfIndex];
break; break;
} }
} }
if (found_flag!=null) { if (foundFlag!=null) {
var sub_msg = output.substring(f_i+flag.length).trim(); var subMsg = output.substring(fIndex+flag.length).trim();
var nuw_len = non_unique_wraps.length; var nUWLen = nonUniqueWraps.length;
for (var nuw_i=0; nuw_i<nuw_len; nuw_i++) { for (var nUWIndex=0; nUWIndex<nUWLen; nUWIndex++) {
//for (wrap in non_unique_wraps) { //for (wrap in nonUniqueWraps) {
var wrap = non_unique_wraps[nuw_i]; var wrap = nonUniqueWraps[nUWIndex];
if (sub_msg.includes(wrap["opener"]) && sub_msg.includes(wrap["closer"])) { if (subMsg.includes(wrap["opener"]) && subMsg.includes(wrap["closer"])) {
sub_msg = wrap["opener"] + "..." + wrap["closer"]; subMsg = wrap["opener"] + "..." + wrap["closer"];
msg_msg = "similar messages"; msgMsg = "similar messages";
break; break;
} }
} }
if (msgprefix_lists[found_flag].indexOf(sub_msg) > -1) { if (msgPrefixLists[foundFlag].indexOf(subMsg) > -1) {
show_enable = false; showEnable = false;
} }
else { else {
msgprefix_lists[found_flag].push(sub_msg); msgPrefixLists[foundFlag].push(subMsg);
} }
} }
} }
if (show_enable) { if (showEnable) {
ret = output_strip; ret = outputTrim;
if (found_flag != null) { if (foundFlag != null) {
ret += "\n [ EnlivenMinetest ] " + msg_msg + " will be suppressed"; ret += "\n [ EnlivenMinetest ] " + msgMsg + " will be suppressed";
} }
} }
return ret; return ret;
} }
function read_log() { function readLog() {
if (players==null) players = []; if (players==null) players = [];
if (player_indices==null) player_indices = {}; if (playerIndices==null) playerIndices = {};
// os.homedir() + "/.minetest/debug_archived/2018/05/08.txt", // os.homedir() + "/.minetest/debug_archived/2018/05/08.txt",
// var log_paths = [os.homedir() + "/.minetest/debug.txt"]; // var logPaths = [os.homedir() + "/.minetest/debug.txt"];
var log_paths = [os.homedir() + "/minetest/bin/debug.txt"]; var logPaths = [os.homedir() + "/minetest/bin/debug.txt"];
var lp_len = log_paths.length; var lpLen = logPaths.length;
for (var lp_i=0; lp_i<lp_len; lp_i++) { for (var lpIndex=0; lpIndex<lpLen; lpIndex++) {
var this_log_path = log_paths[lp_i]; var thisLogPath = logPaths[lpIndex];
console.log("EnlivenMinetest webapp reading '" + this_log_path + "'..."); console.log("EnlivenMinetest webapp reading '" + thisLogPath + "'...");
var line_number = 1; var lineNumber = 1;
if (fs.existsSync(this_log_path)) { if (fs.existsSync(thisLogPath)) {
//uses n-readlines package: see https://stackoverflow.com/questions/34223065/read-lines-synchronously-from-file-in-node-js //uses n-readlines package: see https://stackoverflow.com/questions/34223065/read-lines-synchronously-from-file-in-node-js
var liner = new readlines(this_log_path); var readLines = new readlines(thisLogPath);
var next = true; var nextLine = true;
while (next) { while (nextLine) {
next = liner.next(); nextLine = readLines.next();
if (next!=false) { if (nextLine!=false) {
process_logline(next.toString('ascii'), line_number); processLogLine(nextLine.toString('ascii'), lineNumber);
line_number++; lineNumber++;
} }
} }
} }
else { else {
console.log("WARNING: file not found: '" + this_log_path + "' (listing actual archived log folders is not yet implemented, so this is a hard-coded demo folder only on poikilos' server)"); console.log("WARNING: file not found: '" + thisLogPath + "' (listing actual archived log folders is not yet implemented, so this is a hard-coded demo folder only on poikilos' server)");
} }
} }
} }
app.get('/skin-form', function (req, res) { app.get('/skin-upload-form', function(req, res, next) {
var ret = ""; //var ending = "";
ret += '<html><body style="font-family:calibri,sans">'+"\n"; //ending += '<a href="/">Back to Main Site</a><br/>' + "\n";
ret += '<form action="/set-skin" method="post" enctype="multipart/form-data">'+"\n"; ////ending += '<a href="/skin-upload-form">Back to Upload</a><br/>' + "\n";
ret += 'User Name (case-sensitive): <input type="text" name="userName" id="userName">'+"\n"; //ending += '</body></html>';
ret += 'Select a png image to upload:'+"\n"; //res.write('<html><body style="font-family:calibri,sans">'+"\n");
ret += '<input type="file" name="userFile" id="userFile">'+"\n"; //res.write('<form action="/upload-skin" method="post" enctype="multipart/form-data">'+"\n");
ret += '<input type="submit" value="Upload Image" name="submit">'+"\n"; //res.write('User Name (case-sensitive): <input type="text" name="userName" id="userName">'+"\n");
ret += '</form>'+"\n"; //res.write('Select a png image to upload:'+"\n");
ret += '</body></html>'; //res.write('<input type="file" name="userFile" id="userFile">'+"\n");
res.send(ret); //res.write('<input type="submit" value="Upload Image" name="submit">'+"\n");
//res.render('home'); //res.write('</form>'+"\n");
//res.end(ending);
var msg = "";
res.render('pages/skin-upload-form', {
msg: msg
});
});
app.get('/skin-selection-form', function(req, res, next) {
var msg = "";
res.render('pages/skin-selection-form', {
msg: msg,
skinFileNames: mt.selectableSkinFileNames()
});
}); });
//using express & formidable: // see "new way" of handling multer errors: https://github.com/expressjs/multer#error-handling
app.post('/set-skin', function (req, res){ var singleSkinUpload = skinUpload.single('userFile');
var form = new formidable.IncomingForm(); app.post('/upload-skin', function(req, res) {
// from coderskins/readme.txt: singleSkinUpload(req, res, function(err) {
//To install a specific skin for a specific player, name the PNG file if (err instanceof multer.MulterError) {
//to be used as follows: // A Multer error occurred when uploading.
//player_NAME.png res.render('multer error', { error: err });
//where NAME is the player's in-game nick. Then copy the PNG file into //res.render('pages/result', {
//the mod's "textures" directory. //msg: "An error occurred in processing the form: " + err,
//The PNG file should be a standard Minetest 64x32 or Minecraft 64x64 //});
//"skin" file. } else if (err) {
//Or, if you prefer, create a text file, in the mod's "textures" direc- // An unknown error occurred when uploading.
//tory with a similar filename: res.render('unknown error', { error: err });
//player_NAME.skin //res.render('pages/result', {
//(OldCoder, 2019) //msg: "An error occurred in processing: " + err,
var directPath = ""; //});
var indirectPath = ""; }
var destNameNoExt = ""; else {
var msg = "Uploading..."; // var ending = "";
form.parse(req, function(err, fields, files) { var msg = "";
if (err) next(err); // ending += '<a href="/">Back to Main Site</a><br/>' + "\n";
destNameNoExt = destNameNoExt = "player_" + fields.userName; // ending += '<a href="/skin-upload-form">Back to Upload</a><br/>' + "\n";
directPath = mt.skinDir() + "/" + destNameNoExt + ".png"; // ending += '</body></html>';
indirectPath = mt.skinDir() + "/" + destNameNoExt + ".skin"; // res.write('<html>');
// TODO: make sure my_file and userName values are present // res.write('<body style="font-family:calibri,sans">');
if (files.hasOwnProperty('userFile')) { if (!req.fileValidationError) {
if (fields.hasOwnProperty('userName')) { // res.write('<p>Complete!</p>');
var originalPath = files.userFile.path; msg = "Complete!";
console.log("trying to rename " + files.userFile.path }
+ " to " + directPath); else {
// NOTE: rename does not work if tmp is on different device (common) msg = req.fileValidationError;
mv(files.userFile.path, directPath, function(err) { // res.write('<p>' + req.fileValidationError + '</p>');
// fs.rename(files.userFile.path, directPath, function(err) { }
if (err) { //res.end(ending);
msg = "Failed to rename " + originalPath res.render('pages/result', {
+ " to " + directPath; msg: msg
console.log(msg); });
console.log(JSON.stringify(err)); }
msg += "<br/>\n"; })
//next(err); });
// TODO: why does next above show:
//ReferenceError: next is not defined
//at /home/owner/git/EnlivenMinetest/webapp/server.js:355:6
//at FSReqWrap.oncomplete (fs.js:135:15)
} app.get('/select-skin', function(req, res, next) {
else { mt.setSkin(req.query.userName, req.query.skinFileName);
var thisData = destNameNoExt + ".png"; res.render('pages/result', {
fs.writeFile(indirectPath, thisData, function(err, data) { msg: "Complete."
if (err) console.log(err);
console.log("Successfully wrote " + thisData
+ " to "+indirectPath+".");
});
}
res.end();
});
}
else {
console.log("userName is undefined.");
}
}
else {
console.log("userFile is undefined.");
}
});
//form.on('fileBegin', function (name, file){
////file.path = __dirname + '/uploads/' + file.name;
//// file.path = skinDir + "/" + file.name;
//// manual_path = "player_" +
//});
form.on('file', function (name, file){
msg = 'Uploaded ' + file.name + "<br/>\n";
console.log(msg);
}); });
//res.sendFile(__dirname + '/index.html');
res.redirect("/?msg=" + querystring.stringify(msg));
}); });
app.get('/', function (req, res) { app.get('/', function(req, res, next) {
var ret = ""; //var ret = "";
ret += '<html><body style="font-family:calibri,sans">';
// Whenever server starts the following is logged
// (see also etc/example-input.txt):
//
//-------------
// Separator
//-------------
//
var selected_date_s = null;
if (req.query.date) selected_date_s = req.query.date
if (req.query.msg != undefined) {
ret += "<br/>\n";
//ret += "<b>" + querystring.parse(req.query.msg) + "</b><br>\n";
// line above causes:
//TypeError: Cannot convert object to primitive value
//at /home/owner/git/EnlivenMinetest/webapp/server.js:390:16
//at Layer.handle [as handle_request] (/home/owner/git/EnlivenMinetest/webapp/node_modules/express/lib/router/layer.js:95:5)
//at next (/home/owner/git/EnlivenMinetest/webapp/node_modules/express/lib/router/route.js:137:13)
//at Route.dispatch (/home/owner/git/EnlivenMinetest/webapp/node_modules/express/lib/router/route.js:112:3)
//at Layer.handle [as handle_request] (/home/owner/git/EnlivenMinetest/webapp/node_modules/express/lib/router/layer.js:95:5)
//at /home/owner/git/EnlivenMinetest/webapp/node_modules/express/lib/router/index.js:281:22
//at Function.process_params (/home/owner/git/EnlivenMinetest/webapp/node_modules/express/lib/router/index.js:335:12)
//at next (/home/owner/git/EnlivenMinetest/webapp/node_modules/express/lib/router/index.js:275:10)
//at expressInit (/home/owner/git/EnlivenMinetest/webapp/node_modules/express/lib/middleware/init.js:40:5)
//at Layer.handle [as handle_request] (/home/owner/git/EnlivenMinetest/webapp/node_modules/express/lib/router/layer.js:95:5)
ret += "<br/>\n"; //res.write('<html>');
} //res.write('<body style="font-family:calibri,sans">');
ret += "Server must restart for uploaded skins to take effect.<br/>\n"; // Whenever server starts the following is logged
ret += "assuming minetestserver ran as: " + os.homedir() + "<br/>\n"; // (see also etc/example-input.txt):
ret += "timezone (tz_offset/60*-1): " + (Math.floor(tz_offset/60)*-1) + '<span name="tzArea" id="tzArea"></span><br/>\n'; //
ret += 'date: <span id="dateArea" name="dateArea">' + selected_date_s + '</span><br/>'+"\n"; //-------------
//ret += 'var play_dates = [];'; // Separator
var pdLength = 0; //-------------
if (play_dates != null) pdLength = play_dates.length; //
for (var pd_i = 0; pd_i < pdLength; pd_i++) { var selectedDateStr = null;
//ret += 'play_dates.push("' + play_dates[pd_i] + '");'; var msg = "";
if (selected_date_s!=play_dates[pd_i]) { if (req.query.date) selectedDateStr = req.query.date
ret += '<a href="?date='+play_dates[pd_i]+'">'+play_dates[pd_i]+'</a> '; if (req.query.msg != undefined) {
} //res.write("<br/>");
else { //res.write("<b>" + querystring.parse(req.query.msg) + "</b><br>\n");
ret += play_dates[pd_i]+' '; // line above causes:
} //TypeError: Cannot convert object to primitive value
} //at /home/owner/git/EnlivenMinetest/webapp/server.js:390:16
//if (selected_date_s==null) { //at Layer.handle [as handle_request] (/home/owner/git/EnlivenMinetest/webapp/node_modules/express/lib/router/layer.js:95:5)
//ret += '<a href="?date=2018-05-08">2018-05-08</a>'; //at next (/home/owner/git/EnlivenMinetest/webapp/node_modules/express/lib/router/route.js:137:13)
//} //at Route.dispatch (/home/owner/git/EnlivenMinetest/webapp/node_modules/express/lib/router/route.js:112:3)
//see ~/.minetest/debug.txt //at Layer.handle [as handle_request] (/home/owner/git/EnlivenMinetest/webapp/node_modules/express/lib/router/layer.js:95:5)
//and logs archived by EnlivenMinetest: //at /home/owner/git/EnlivenMinetest/webapp/node_modules/express/lib/router/index.js:281:22
//~/.minetest/debug_archived/2018/05/08.txt //at Function.process_params (/home/owner/git/EnlivenMinetest/webapp/node_modules/express/lib/router/index.js:335:12)
ret += ` //at next (/home/owner/git/EnlivenMinetest/webapp/node_modules/express/lib/router/index.js:275:10)
<div style="color:gray" id="statusArea"></div> //at expressInit (/home/owner/git/EnlivenMinetest/webapp/node_modules/express/lib/middleware/init.js:40:5)
<canvas id="myCanvas" width="100" height="1540"> <!--style="border:1px solid #c3c3c3;">--> //at Layer.handle [as handle_request] (/home/owner/git/EnlivenMinetest/webapp/node_modules/express/lib/router/layer.js:95:5)
Your browser does not support the canvas element.
</canvas> //res.write("<br/>");
<div style="color:gray" id="outputArea">loading...</div> }
<script src="js/main.js"></script>`; //res.write('<p><a href="/skin-upload-form">Upload Skin</a></p>');
ret += '</body></html>'; //res.write("<h3>Server info</h3>");
res.send(ret); //res.write("<ul>");
//res.render('home'); //res.write("<li>assuming minetestserver ran as: " + os.homedir() + "</li>");
//res.write("<li>timezone (tzOffsetMinutes/60*-1): " + (Math.floor(tzOffsetMinutes/60)*-1) + '<span name="tzArea" id="tzArea"></span>' + "</li>");
//res.write('<li>date: <span id="dateArea" name="dateArea">' + ((selectedDateStr!=null)?selectedDateStr:"not selected") + '</span>' + "</li>");
//res.write('var activityDates = [];');
var pdLength = 0;
if (activityDates != null) pdLength = activityDates.length;
var logDates = [];
for (var pdIndex = 0; pdIndex < pdLength; pdIndex++) {
//res.write('activityDates.push("' + activityDates[pdIndex] + '");');
if (selectedDateStr!=activityDates[pdIndex]) {
//res.write('<a href="?date='+activityDates[pdIndex]+'">'+activityDates[pdIndex]+'</a> ');
logDates.push( { date:activityDates[pdIndex], active:true});
}
else {
logDates.push( { date:activityDates[pdIndex], active:false});
//res.write(activityDates[pdIndex]+' ');
}
}
//if (selectedDateStr==null) {
//res.write('<a href="?date=2018-05-08">2018-05-08</a>');
//}
//see ~/.minetest/debug.txt
//and logs archived by EnlivenMinetest:
//~/.minetest/debug_archived/2018/05/08.txt
//res.write('</body></html>');
//res.end('</body></html>');
//res.render('home');
res.render('pages/index', {
msg: msg,
serverUser: os.homedir(),
tzOffset: (Math.floor(tzOffsetMinutes/60)*-1),
selectedDate: ((selectedDateStr!=null)?selectedDateStr:"not selected"),
logDates: logDates
});
}); });
var server = app.listen(64638, function () { var server = app.listen(port, function () {
// 8123 is default for Minecraft DynMap // 8123 is default for Minecraft DynMap
// 64638 spells 'minet' on a telephone keypad, but 6463, 6472 is already Discord RPC server // 64638 spells 'minet' on a telephone keypad, but 6463, 6472 is already Discord RPC server
//console.log('express-handlebars example server listening on: 3000'); //console.log('express-handlebars example server listening on: ' + port);
var host = server.address().address; var host = server.address().address;
var port = server.address().port; var port = server.address().port;
console.log("server address:"); console.log("server address:");
console.log(JSON.stringify(server.address())); console.log(JSON.stringify(server.address()));
console.log("reading log..."); console.log("reading log...");
read_log(); readLog();
console.log("EnlivenMinetest webapp is listening at http://%s:%s", host, port); console.log("EnlivenMinetest webapp is listening at http://%s:%s", host, port);
}); });

0
webapp/views/home.handlebars → webapp/views.deprecated/home.handlebars

0
webapp/views/layouts/main.handlebars → webapp/views.deprecated/layouts/main.handlebars

1
webapp/views/notes.txt

@ -0,0 +1 @@
https://scotch.io/tutorials/use-ejs-to-template-your-node-application

42
webapp/views/pages/index.ejs

@ -0,0 +1,42 @@
<!DOCTYPE html>
<html lang="en">
<head>
<% include ../partials/head %>
</head>
<body class="container">
<header>
<% include ../partials/header %>
</header>
<main>
<div class="jumbotron">
<h1>EnlivenMinetest</h1>
<p>Welcome.</p>
<h2>&nbsp;<!--result--></h2>
<p><%= msg %></p>
</div>
<p>assuming minetestserver ran as: <%= serverUser %></p>
<p>timezone (tz_offset/60*-1): <%= tzOffset %></p>
<p>selected date: <%= selectedDate %></p>
<h2>Activity Dates:</h2>
<ul>
<% logDates.forEach(function(logDate) { %>
<li><a href="?date=<%= logDate.date %>"><%= logDate.date %></a> - <%= logDate.active %></li>
<% }); %>
</ul>
<div style="color:gray" id="statusArea"></div>
<canvas id="myCanvas" width="100" height="1540"> <!--style="border:1px solid #c3c3c3;">-->
Your browser does not support the canvas element.
</canvas>
<div style="color:gray" id="outputArea">loading...</div>
<script src="js/main.js"></script>
</main>
<footer>
<% include ../partials/footer %>
</footer>
</body>
</html>

32
webapp/views/pages/result.ejs

@ -0,0 +1,32 @@
<!DOCTYPE html>
<html lang="en">
<head>
<% include ../partials/head %>
</head>
<body class="container">
<header>
<% include ../partials/header %>
</header>
<main>
<div class="jumbotron">
<h1>EnlivenMinetest</h1>
<p></p>
<h2>&nbsp;<!--result--></h2>
<p><%= msg %></p>
</div>
<div style="color:gray" id="statusArea"></div>
<canvas id="myCanvas" width="100" height="1540"> <!--style="border:1px solid #c3c3c3;">-->
Your browser does not support the canvas element.
</canvas>
<div style="color:gray" id="outputArea">loading...</div>
<script src="js/main.js"></script>
</main>
<footer>
<% include ../partials/footer %>
</footer>
</body>
</html>

69
webapp/views/pages/skin-selection-form.ejs

@ -0,0 +1,69 @@
<!DOCTYPE html>
<html lang="en">
<head>
<% include ../partials/head %>
</head>
<body class="container">
<header>
<% include ../partials/header %>
</header>
<main>
<div class="row">
<div class="col-sm-8">
<div class="jumbotron">
<h1>EnlivenMinetest</h1>
<p>Choose Skin.</p>
<h2>&nbsp;<!--result--></h2>
<p><%= msg %></p>
</div>
<div>
<form action="/select-skin" method="post" enctype="multipart/form-data">
<div>User Name (case-sensitive): <input type="text" name="userName" id="userName"></div>
<!--<div><input type="hidden" name="skinFileName" id="skinFileName"></div>-->
<!--<div><input type="submit" value="Select" name="submit"></div>-->
</form>
</div>
<div id="setSkinResult"></div>
<script type="text/javascript">
function setSkin(skinFileName) {
if ((skinFileName != undefined) && (skinFileName.length>0)) {
var userName = document.getElementById("userName").value;
if (userName.length>0) {
document.getElementById("setSkinResult").innerHTML="You chose " + skinFileName + "...";
location.href = "/select-skin?userName="+userName+"&skinFileName="+skinFileName;
}
else {
alert("You must enter your username in the box first.");
}
}
else {
alert("No skin was specified.");
}
};
</script>
<div>
<% skinFileNames.forEach(function(skinFileName) { %>
<a id="<%= skinFileName %>" href="javascript:setSkin('<%= skinFileName %>')"><img alt="<%= skinFileName %>" src="public/skins/<%= skinFileName %>"/></a>
<% }); %>
</script>
</div>
</div>
<div class="col-sm-4">
<div class="well">
<h3>Links:</h3>
<p><a href="https://poikilos.dyndns.org">Server Page</a></p>
</div>
</div>
</div>
</main>
<footer>
<% include ../partials/footer %>
</footer>
</body>
</html>

45
webapp/views/pages/skin-upload-form.ejs

@ -0,0 +1,45 @@
<!DOCTYPE html>
<html lang="en">
<head>
<% include ../partials/head %>
</head>
<body class="container">
<header>
<% include ../partials/header %>
</header>
<main>
<div class="row">
<div class="col-sm-8">
<div class="jumbotron">
<h1>EnlivenMinetest</h1>
<p>Upload Skin.</p>
<h2>&nbsp;<!--result--></h2>
<p><%= msg %></p>
</div>
<div>
<form action="/upload-skin" method="post" enctype="multipart/form-data">
<div>User Name (case-sensitive): <input type="text" name="userName" id="userName"></div>
<div>Select a png image to upload: <input type="file" name="userFile" id="userFile"></div>
<div><input type="submit" value="Upload Image" name="submit"></div>
</form>
</div>
</div>
<div class="col-sm-4">
<div class="well">
<h3>Links:</h3>
<p><a href="https://poikilos.dyndns.org">Server Page</a></p>
</div>
</div>
</div>
</main>
<footer>
<% include ../partials/footer %>
</footer>
</body>
</html>

1
webapp/views/partials/footer.ejs

@ -0,0 +1 @@
<p class="text-center text-muted">Copyright &copy; 2019 Jake Gustafson</p>

8
webapp/views/partials/head.ejs

@ -0,0 +1,8 @@
<meta charset="UTF-8">
<title>EnlivenMinetest</title>
<!-- CSS (load bootstrap from a CDN) -->
<link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css">
<style>
body { padding-top:50px; }
</style>

18
webapp/views/partials/header.ejs

@ -0,0 +1,18 @@
<nav class="navbar navbar-default" role="navigation">
<div class="container-fluid">
<div class="navbar-header">
<a class="navbar-brand" href="#">
<span class="glyphicon glyphicon glyphicon-tree-deciduous"></span>
EnlivenMinetest
</a>
<ul class="nav navbar-nav">
<li><a href="/">Home</a></li>
<li><a href="/skin-upload-form">Upload Skin</a></li>
<li><a href="/skin-selection-form">Choose Skin</a></li>
</ul>
</div>
</div>
</nav>
Loading…
Cancel
Save