Merge pull request #27 from Trivernis/dependabot/npm_and_yarn/tar-2.2.2

Bump tar from 2.2.1 to 2.2.2
pull/29/head
Trivernis 6 years ago committed by GitHub
commit bd9759e870
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -4,6 +4,39 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [0.2.0] - 2019-05-23
### Added
- socket.io for real time communication
- compression and minify
- auto replacing image links with images in the chat
- auto replacing urls to urls with link in the chat
- message editing and deleting *(undo your mistakes)*
- changelog to `bingo-create`
### Changed
- frontend to use socket.io instead of graphql for refreshing
- use of socket.io for toggeling binogo fields
- button behaviour on `bingo-create` to respond to the situation *(whatever that means)*
### Removed
- graphql frontend functions to send messages and refresh
### Fixed
- error message when loading `bingo-create`
- chat doesn't scroll down when an image is send *(r/mildlyinfuriating)*
- some style issues
## [0.1.1] - 2019-05-21
### Fixed
- preloaded info messages can't be hidden (#18)
- no special character allowed in username (#19)
## [0.1.0] - 2019-05-19
### Added

@ -1,9 +1,12 @@
const createError = require('http-errors'),
express = require('express'),
path = require('path'),
express = require('express'),
cookieParser = require('cookie-parser'),
logger = require('morgan'),
compileSass = require('express-compile-sass'),
minify = require('express-minify'),
compression = require('compression'),
uglifyEs = require('uglify-es'),
session = require('express-session'),
pgSession = require('connect-pg-simple')(session),
fsx = require('fs-extra'),
@ -20,6 +23,9 @@ const createError = require('http-errors'),
changelogRouter = require('./routes/changelog'),
bingoRouter = require('./routes/bingo');
let app = require('express')(),
server = require('http').Server(app),
io = require('socket.io')(server);
async function init() {
// grapql default resolver
@ -36,15 +42,19 @@ async function init() {
// database setup
let pgPool = globals.pgPool;
await pgPool.query(fsx.readFileSync('./sql/init.sql', 'utf-8'));
await bingoRouter.init();
let app = express();
let bingoIo = io.of('/bingo');
await bingoRouter.init(bingoIo, io);
// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'pug');
app.set('trust proxy', 1);
app.use(compression());
app.use(minify({
uglifyJsModule: uglifyEs
}));
app.use(logger('dev'));
app.use(express.json());
app.use(express.urlencoded({extended: false}));
@ -98,7 +108,7 @@ async function init() {
res.status(err.status || 500);
res.render('error');
});
return app;
return [app, server];
}
module.exports = init;

@ -6,7 +6,6 @@
const appInit = require('../app');
const debug = require('debug')('whooshy:server');
const http = require('http');
const yaml = require('js-yaml');
const fsx = require('fs-extra');
@ -27,15 +26,9 @@ try {
let port = normalizePort(process.env.PORT || settings.port || '3000');
appInit().then((app) => {
appInit().then(([app, server]) => {
app.set('port', port);
/**
* Create HTTP server.
*/
let server = http.createServer(app);
/**
* Listen on provided port, on all network interfaces.
*/

@ -1,10 +1,12 @@
const utils = require('./utils'),
fsx = require('fs-extra'),
pg = require('pg');
const settings = utils.readSettings('.');
Object.assign(exports, {
settings: settings,
changelog: fsx.readFileSync('CHANGELOG.md', 'utf-8'),
pgPool: new pg.Pool({
host: settings.postgres.host,
port: settings.postgres.port,
@ -14,7 +16,7 @@ Object.assign(exports, {
}),
cookieInfo: {
headline: 'This website uses cookies',
content: 'This website uses cookies to store your session data (like for bingo).',
content: "This website uses cookies to store your session data. No data is permanently stored.",
onclick: 'acceptCookies()',
id: 'cookie-container',
button: 'All right!'

384
package-lock.json generated

@ -77,6 +77,11 @@
"integrity": "sha512-HJ7CfNHrfJLlNTzIEUTj43LNWGkqpRLxm3YjAlcD0ACydk9XynzYsCBHxut+iqt+1aBXkx9UP/w/ZqMr13XIzg==",
"dev": true
},
"after": {
"version": "0.8.2",
"resolved": "https://registry.npmjs.org/after/-/after-0.8.2.tgz",
"integrity": "sha1-/ts5T58OAqqXaOcCvaI7UF+ufh8="
},
"ajv": {
"version": "6.10.0",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.0.tgz",
@ -219,6 +224,11 @@
"resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz",
"integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg="
},
"arraybuffer.slice": {
"version": "0.0.7",
"resolved": "https://registry.npmjs.org/arraybuffer.slice/-/arraybuffer.slice-0.0.7.tgz",
"integrity": "sha512-wGUIVQXuehL5TCqQun8OW81jGzAWycqzFF8lFp+GOM5BXLYj3bKNsYC4daB7n6XjCqxQA/qgTJ+8ANR3acjrog=="
},
"asap": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz",
@ -258,6 +268,11 @@
"resolved": "https://registry.npmjs.org/async-foreach/-/async-foreach-0.1.3.tgz",
"integrity": "sha1-NhIfhFwFeBct5Bmpfb6x0W7DRUI="
},
"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=="
},
"asynckit": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
@ -347,6 +362,11 @@
"resolved": "https://registry.npmjs.org/babylon/-/babylon-6.18.0.tgz",
"integrity": "sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ=="
},
"backo2": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz",
"integrity": "sha1-MasayLEpNjRj41s+u2n038+6eUc="
},
"balanced-match": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
@ -402,6 +422,16 @@
}
}
},
"base64-arraybuffer": {
"version": "0.1.5",
"resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.1.5.tgz",
"integrity": "sha1-c5JncZI7Whl0etZmqlzUv5xunOg="
},
"base64id": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/base64id/-/base64id-1.0.0.tgz",
"integrity": "sha1-R2iMuZu2gE8OBtPnY7HDLlfY5rY="
},
"basic-auth": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz",
@ -418,11 +448,24 @@
"tweetnacl": "^0.14.3"
}
},
"better-assert": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/better-assert/-/better-assert-1.0.2.tgz",
"integrity": "sha1-QIZrnhueC1W0gYlDEeaPr/rrxSI=",
"requires": {
"callsite": "1.0.0"
}
},
"binary-extensions": {
"version": "1.13.1",
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz",
"integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw=="
},
"blob": {
"version": "0.0.5",
"resolved": "https://registry.npmjs.org/blob/-/blob-0.0.5.tgz",
"integrity": "sha512-gaqbzQPqOoamawKg0LGVd7SzLgXS+JH61oWprSLH+P+abTczqJbhTR8CmJ2u9/bUYNmHTGJx/UEmn6doAvvuig=="
},
"block-stream": {
"version": "0.0.9",
"resolved": "https://registry.npmjs.org/block-stream/-/block-stream-0.0.9.tgz",
@ -566,6 +609,11 @@
}
}
},
"callsite": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/callsite/-/callsite-1.0.0.tgz",
"integrity": "sha1-KAOY5dZkvXQDi28JBRU+borxvCA="
},
"callsites": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
@ -752,11 +800,58 @@
"delayed-stream": "~1.0.0"
}
},
"commander": {
"version": "2.20.0",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.0.tgz",
"integrity": "sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ=="
},
"component-bind": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/component-bind/-/component-bind-1.0.0.tgz",
"integrity": "sha1-AMYIq33Nk4l8AAllGx06jh5zu9E="
},
"component-emitter": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz",
"integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg=="
},
"component-inherit": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/component-inherit/-/component-inherit-0.0.3.tgz",
"integrity": "sha1-ZF/ErfWLcrZJ1crmUTVhnbJv8UM="
},
"compressible": {
"version": "2.0.17",
"resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.17.tgz",
"integrity": "sha512-BGHeLCK1GV7j1bSmQQAi26X+GgWcTjLr/0tzSvMCl3LH1w1IJ4PFSPoV5316b30cneTziC+B1a+3OjoSUcQYmw==",
"requires": {
"mime-db": ">= 1.40.0 < 2"
}
},
"compression": {
"version": "1.7.4",
"resolved": "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz",
"integrity": "sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==",
"requires": {
"accepts": "~1.3.5",
"bytes": "3.0.0",
"compressible": "~2.0.16",
"debug": "2.6.9",
"on-headers": "~1.0.2",
"safe-buffer": "5.1.2",
"vary": "~1.1.2"
},
"dependencies": {
"debug": {
"version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
"requires": {
"ms": "2.0.0"
}
}
}
},
"concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
@ -1043,6 +1138,74 @@
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
"integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k="
},
"engine.io": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/engine.io/-/engine.io-3.3.2.tgz",
"integrity": "sha512-AsaA9KG7cWPXWHp5FvHdDWY3AMWeZ8x+2pUVLcn71qE5AtAzgGbxuclOytygskw8XGmiQafTmnI9Bix3uihu2w==",
"requires": {
"accepts": "~1.3.4",
"base64id": "1.0.0",
"cookie": "0.3.1",
"debug": "~3.1.0",
"engine.io-parser": "~2.1.0",
"ws": "~6.1.0"
},
"dependencies": {
"debug": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
"integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
"requires": {
"ms": "2.0.0"
}
}
}
},
"engine.io-client": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-3.3.2.tgz",
"integrity": "sha512-y0CPINnhMvPuwtqXfsGuWE8BB66+B6wTtCofQDRecMQPYX3MYUZXFNKDhdrSe3EVjgOu4V3rxdeqN/Tr91IgbQ==",
"requires": {
"component-emitter": "1.2.1",
"component-inherit": "0.0.3",
"debug": "~3.1.0",
"engine.io-parser": "~2.1.1",
"has-cors": "1.1.0",
"indexof": "0.0.1",
"parseqs": "0.0.5",
"parseuri": "0.0.5",
"ws": "~6.1.0",
"xmlhttprequest-ssl": "~1.5.4",
"yeast": "0.1.2"
},
"dependencies": {
"component-emitter": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz",
"integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY="
},
"debug": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
"integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
"requires": {
"ms": "2.0.0"
}
}
}
},
"engine.io-parser": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-2.1.3.tgz",
"integrity": "sha512-6HXPre2O4Houl7c4g7Ic/XzPnHBvaEmN90vtRO9uLmwtRqQmTOw0QMevL1TOfL2Cpu1VzsaTmMotQgMdkzGkVA==",
"requires": {
"after": "0.8.2",
"arraybuffer.slice": "~0.0.7",
"base64-arraybuffer": "0.1.5",
"blob": "0.0.5",
"has-binary2": "~1.0.2"
}
},
"entities": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz",
@ -1618,6 +1781,32 @@
}
}
},
"express-minify": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/express-minify/-/express-minify-1.0.0.tgz",
"integrity": "sha512-04/iYxB79jGeNZBBkbAW7L7FMG4Wtu78F1SayXIKiJD6MfqYnOI3DD8no7QOntgedYCdYUpj+Skg8QWR/2WnMQ==",
"requires": {
"clean-css": "^4.1.7",
"on-headers": "^1.0.1",
"uglify-js": "^3.0.28"
},
"dependencies": {
"source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="
},
"uglify-js": {
"version": "3.5.14",
"resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.5.14.tgz",
"integrity": "sha512-dgyjIw8KFK6AyVl5vm2tEqPewv5TKGEiiVFLI1LbF+oHua/Njd8tZk3lIbF1AWU1rNdEg7scaceADb4zqCcWXg==",
"requires": {
"commander": "~2.20.0",
"source-map": "~0.6.1"
}
}
}
},
"express-session": {
"version": "1.16.1",
"resolved": "https://registry.npmjs.org/express-session/-/express-session-1.16.1.tgz",
@ -2598,6 +2787,26 @@
"ansi-regex": "^2.0.0"
}
},
"has-binary2": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/has-binary2/-/has-binary2-1.0.3.tgz",
"integrity": "sha512-G1LWKhDSvhGeAQ8mPVQlqNcOB2sJdwATtZKl2pDKKHfpf/rYj24lkinxf69blJbnsvtqqNU+L3SL50vzZhXOnw==",
"requires": {
"isarray": "2.0.1"
},
"dependencies": {
"isarray": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz",
"integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4="
}
}
},
"has-cors": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/has-cors/-/has-cors-1.1.0.tgz",
"integrity": "sha1-XkdHk/fqmEPRu5nCPu9J/xJv/zk="
},
"has-flag": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
@ -2725,6 +2934,11 @@
"repeating": "^2.0.0"
}
},
"indexof": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/indexof/-/indexof-0.0.1.tgz",
"integrity": "sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10="
},
"inflight": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
@ -3658,6 +3872,11 @@
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
"integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM="
},
"object-component": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/object-component/-/object-component-0.0.3.tgz",
"integrity": "sha1-8MaapQ78lbhmwYb0AKM3acsvEpE="
},
"object-copy": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz",
@ -3833,6 +4052,22 @@
"error-ex": "^1.2.0"
}
},
"parseqs": {
"version": "0.0.5",
"resolved": "https://registry.npmjs.org/parseqs/-/parseqs-0.0.5.tgz",
"integrity": "sha1-1SCKNzjkZ2bikbouoXNoSSGouJ0=",
"requires": {
"better-assert": "~1.0.0"
}
},
"parseuri": {
"version": "0.0.5",
"resolved": "https://registry.npmjs.org/parseuri/-/parseuri-0.0.5.tgz",
"integrity": "sha1-gCBKUNTbt3m/3G6+J3jZDkvOMgo=",
"requires": {
"better-assert": "~1.0.0"
}
},
"parseurl": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
@ -4852,6 +5087,90 @@
}
}
},
"socket.io": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/socket.io/-/socket.io-2.2.0.tgz",
"integrity": "sha512-wxXrIuZ8AILcn+f1B4ez4hJTPG24iNgxBBDaJfT6MsyOhVYiTXWexGoPkd87ktJG8kQEcL/NBvRi64+9k4Kc0w==",
"requires": {
"debug": "~4.1.0",
"engine.io": "~3.3.1",
"has-binary2": "~1.0.2",
"socket.io-adapter": "~1.1.0",
"socket.io-client": "2.2.0",
"socket.io-parser": "~3.3.0"
}
},
"socket.io-adapter": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-1.1.1.tgz",
"integrity": "sha1-KoBeihTWNyEk3ZFZrUUC+MsH8Gs="
},
"socket.io-client": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-2.2.0.tgz",
"integrity": "sha512-56ZrkTDbdTLmBIyfFYesgOxsjcLnwAKoN4CiPyTVkMQj3zTUh0QAx3GbvIvLpFEOvQWu92yyWICxB0u7wkVbYA==",
"requires": {
"backo2": "1.0.2",
"base64-arraybuffer": "0.1.5",
"component-bind": "1.0.0",
"component-emitter": "1.2.1",
"debug": "~3.1.0",
"engine.io-client": "~3.3.1",
"has-binary2": "~1.0.2",
"has-cors": "1.1.0",
"indexof": "0.0.1",
"object-component": "0.0.3",
"parseqs": "0.0.5",
"parseuri": "0.0.5",
"socket.io-parser": "~3.3.0",
"to-array": "0.1.4"
},
"dependencies": {
"component-emitter": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz",
"integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY="
},
"debug": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
"integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
"requires": {
"ms": "2.0.0"
}
}
}
},
"socket.io-parser": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.3.0.tgz",
"integrity": "sha512-hczmV6bDgdaEbVqhAeVMM/jfUfzuEZHsQg6eOmLgJht6G3mPKMxYm75w2+qhAQZ+4X+1+ATZ+QFKeOZD5riHng==",
"requires": {
"component-emitter": "1.2.1",
"debug": "~3.1.0",
"isarray": "2.0.1"
},
"dependencies": {
"component-emitter": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz",
"integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY="
},
"debug": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
"integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
"requires": {
"ms": "2.0.0"
}
},
"isarray": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz",
"integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4="
}
}
},
"source-map": {
"version": "0.4.4",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz",
@ -5392,13 +5711,26 @@
}
},
"tar": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/tar/-/tar-2.2.1.tgz",
"integrity": "sha1-jk0qJWwOIYXGsYrWlK7JaLg8sdE=",
"version": "2.2.2",
"resolved": "https://registry.npmjs.org/tar/-/tar-2.2.2.tgz",
"integrity": "sha512-FCEhQ/4rE1zYv9rYXJw/msRqsnmlje5jHP6huWeBZ704jUTy02c5AZyWujpMR1ax6mVw9NyJMfuK2CMDWVIfgA==",
"requires": {
"block-stream": "*",
"fstream": "^1.0.2",
"fstream": "^1.0.12",
"inherits": "2"
},
"dependencies": {
"fstream": {
"version": "1.0.12",
"resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.12.tgz",
"integrity": "sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg==",
"requires": {
"graceful-fs": "^4.1.2",
"inherits": "~2.0.0",
"mkdirp": ">=0.5 0",
"rimraf": "2"
}
}
}
},
"text-table": {
@ -5421,6 +5753,11 @@
"os-tmpdir": "~1.0.2"
}
},
"to-array": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/to-array/-/to-array-0.1.4.tgz",
"integrity": "sha1-F+bBH3PdTz10zaek/zI46a2b+JA="
},
"to-fast-properties": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.3.tgz",
@ -5545,6 +5882,27 @@
"resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz",
"integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA=="
},
"uglify-es": {
"version": "3.3.9",
"resolved": "https://registry.npmjs.org/uglify-es/-/uglify-es-3.3.9.tgz",
"integrity": "sha512-r+MU0rfv4L/0eeW3xZrd16t4NZfK8Ld4SWVglYBb7ez5uXFWHuVRs6xCTrf1yirs9a4j4Y27nn7SRfO6v67XsQ==",
"requires": {
"commander": "~2.13.0",
"source-map": "~0.6.1"
},
"dependencies": {
"commander": {
"version": "2.13.0",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.13.0.tgz",
"integrity": "sha512-MVuS359B+YzaWqjCL/c+22gfryv+mCBPHAv3zyVI2GN8EY6IRP8VwtasXn8jyyhvvq84R4ImN1OKRtcbIasjYA=="
},
"source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="
}
}
},
"uglify-js": {
"version": "2.8.29",
"resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.8.29.tgz",
@ -5796,6 +6154,19 @@
"mkdirp": "^0.5.1"
}
},
"ws": {
"version": "6.1.4",
"resolved": "https://registry.npmjs.org/ws/-/ws-6.1.4.tgz",
"integrity": "sha512-eqZfL+NE/YQc1/ZynhojeV8q+H050oR8AZ2uIev7RU10svA9ZnJUddHcOUZTJLinZ9yEfdA2kSATS2qZK5fhJA==",
"requires": {
"async-limiter": "~1.0.0"
}
},
"xmlhttprequest-ssl": {
"version": "1.5.5",
"resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.5.tgz",
"integrity": "sha1-wodrBhaKrcQOV9l+gRkayPQ5iz4="
},
"xtend": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz",
@ -5836,6 +6207,11 @@
"integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo="
}
}
},
"yeast": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/yeast/-/yeast-0.1.2.tgz",
"integrity": "sha1-AI4G2AlDIMNy28L47XagymyKxBk="
}
}
}

@ -6,12 +6,14 @@
"start": "node ./bin/www"
},
"dependencies": {
"compression": "^1.7.4",
"connect-pg-simple": "5.0.0",
"cookie-parser": "1.4.4",
"debug": "4.1.1",
"express": "4.17.0",
"express-compile-sass": "latest",
"express-graphql": "0.8.0",
"express-minify": "^1.0.0",
"express-session": "latest",
"fs-extra": "8.0.1",
"graphql": "14.3.1",
@ -25,7 +27,9 @@
"morgan": "1.9.1",
"node-sass": "4.12.0",
"pg": "7.11.0",
"pug": "2.0.3"
"pug": "2.0.3",
"socket.io": "^2.2.0",
"uglify-es": "^3.3.9"
},
"devDependencies": {
"eslint": "5.16.0",

File diff suppressed because it is too large Load Diff

@ -1,5 +1,47 @@
/* eslint-disable no-unused-vars, no-undef */
/**
* A simple WebSocket
*/
class SimpleSocket {
/**
* Constructor
* @param url
* @param emitContext
*/
constructor(url, emitContext) {
this.socket = io.connect(url);
this.context = emitContext;
}
/**
* Wrapper for the emit function
* @param event
* @param data
* @param callback
*/
emit(event, data, callback) {
this.socket.emit(event, this.context, data, callback);
}
/**
* Wrapper for on event function
* @param event
* @param callback
*/
on(event, callback) {
this.socket.on(event, callback);
}
/**
* Returns if the socket is connected
* @returns {*|boolean}
*/
get connected() {
return this.socket.connected;
}
}
/**
* HTTP POST to an url with a post body
* @param url {String} - the url to post to
@ -122,9 +164,15 @@ async function indicateStatus(func, indicatorSelector) {
statusIndicator.setAttribute('status', 'success');
else
statusIndicator.setAttribute('status', 'error');
setTimeout(() => {
statusIndicator.setAttribute('status', '');
}, 1000);
} catch (err) {
console.error(err);
statusIndicator.setAttribute('status', 'error');
setTimeout(() => {
statusIndicator.setAttribute('status', '');
}, 1000);
}
}

@ -1,31 +1,46 @@
@import ../mixins
@import ../vars
//@media(max-device-width: 641px)
@media(max-device-height: 500px)
div#container-winner
margin: 5% auto !important
@media(max-device-width: 600px)
div#container-bingo-lobby
grid-template: 0 10% 85% 5% / 0 0 0 100% 0 !important
div#container-lobby-settings
display: none !important
div#container-players
display: none !important
//@media(min-device-width: 641px)
.popup
@include default-element
position: fixed
display: grid
height: calc(50% - 1rem)
width: calc(40% - 1rem)
top: 25%
left: 30%
text-align: center
vertical-align: middle
padding: 1rem
top: 0
left: 0
height: 100%
width: 100%
z-index: 1000
grid-template: 60% 40% / 100%
h1
@include gridPosition(1, 2, 1, 1)
button
margin: 1rem
font-size: 2rem
@include gridPosition(2, 3, 1, 1)
#container-winner
@include default-element
position: relative
margin: 20% auto
text-align: center
width: 50%
h1
@include gridPosition(1, 2, 1, 1)
button
margin: 1rem
font-size: 2rem
@include gridPosition(2, 3, 1, 1)
.greyover
width: 100%
@ -74,8 +89,12 @@
height: auto
border-radius: 0.2em
.chatMessage.selected
background-color: lighten($primary, 15%)
#container-chat
height: calc(100% - 2px)
position: relative
#chat-content
height: calc(100% - 3rem)
width: calc(100% - 2px)
@ -222,29 +241,55 @@
#container-bingo-create
display: grid
grid-template: 5% 10% 10% 70% 5% /10% 80% 10%
grid-template: 5% 45% 45% 5% /5% 35% 5% 50% 5%
height: 100%
width: 100%
#username-form
@include gridPosition(2, 3, 2, 3)
margin: auto
position: relative
h2
text-align: center
margin: 0 0 1rem 0
> *
margin: auto
width: 100%
.statusIndicator
height: 1em
width: 1em
display: inline-block
margin: auto 1em
#submit-username[status='success']
background-color: $success
transition-duration: 1s
#input-username
margin: 0 0 0 3em
#submit-username[status='pending']
transition-duration: 1s
background-color: $pending
animation-name: pulse-opacity
animation-duration: 4s
animation-iteration-count: infinite
#lobby-form
@include gridPosition(3, 5, 2, 3)
margin: 10% auto
@include gridPosition(3, 4, 2, 3)
margin: auto
width: 100%
button
width: 100%
margin: auto
button.inactive
background-color: lighten($primary, 10%)
#changelog
@include gridPosition(2, 4, 4, 5)
height: 100%
width: calc(100% - 2rem)
overflow-y: auto
margin: 0 0.5rem
padding: 0 0.5rem
box-shadow: inset 0 0 1rem darken($primary, 10%)
#container-bingo-lobby
@include fillWindow
@ -256,7 +301,7 @@
#lobby-title
@include gridPosition(2, 3, 2, 5)
margin: auto
margin: 0.5rem auto
#container-players
@include gridPosition(3, 4, 2, 3)

@ -7,14 +7,6 @@
.hidden
display: None !important
.popup
height: 60%
width: 40%
z-index: 1000
position: fixed
top: 20%
left: 30%
.grid
display: grid
@ -45,6 +37,21 @@
animation-duration: 5s
animation-iteration-count: infinite
.socketStatusIndicator[socket-status='connected']
background-color: $success
.socketStatusIndicator[socket-status='reconnecting']
background-color: mix($pending, $error)
animation-name: pulse-opacity
animation-duration: 5s
animation-iteration-count: infinite
.socketStatusIndicator[socket-status='disconnected']
background-color: $error
animation-name: pulse-opacity
animation-duration: 2s
animation-iteration-count: infinite
.pending
background-color: $pending !important
animation-name: pulse-opacity

@ -4,7 +4,10 @@ const express = require('express'),
mdEmoji = require('markdown-it-emoji'),
mdMark = require('markdown-it-mark'),
mdSmartarrows = require('markdown-it-smartarrows'),
md = require('markdown-it')()
md = require('markdown-it')({
linkify: true,
typographer: true
})
.use(mdEmoji)
.use(mdMark)
.use(mdSmartarrows),
@ -12,6 +15,7 @@ const express = require('express'),
globals = require('../lib/globals');
let pgPool = globals.pgPool;
let sockets = {};
/**
* Class to manage the bingo data in the database.
@ -138,7 +142,16 @@ class BingoDataManager {
* @returns {Promise<*>}
*/
async updateLobbyExpiration(lobbyId) {
return await this._queryDatabase(this.queries.updateLobbyExpire.sql, [lobbyId]);
return await this._queryFirstResult(this.queries.updateLobbyExpire.sql, [lobbyId]);
}
/**
* Returns all lobby ids
* @returns {Promise<*>}
*/
async getLobbyIds() {
let results = await this._queryAllResults(this.queries.getLobbyIds.sql, []);
return results.map(x => x.id);
}
/**
@ -430,6 +443,34 @@ class BingoDataManager {
return await this._queryFirstResult(this.queries.addUserMessage.sql, [playerId, lobbyId, messageContent]);
}
/**
* Edits a message
* @param messageId {Number} - the id of the message
* @param messageContent {String} - the new content of the message
* @returns {Promise<*>}
*/
async editMessage(messageId, messageContent) {
return await this._queryFirstResult(this.queries.editMessage.sql, [messageId, messageContent]);
}
/**
* Deletes a message
* @param messageId {Number} - the id of the message
* @returns {Promise<*>}
*/
async deleteMessage(messageId) {
return await this._queryFirstResult(this.queries.deleteMessage.sql, [messageId]);
}
/**
* Returns the data of a message
* @param messageId {Number} - the id of the message
* @returns {Promise<*>}
*/
async getMessageData(messageId) {
return await this._queryFirstResult(this.queries.getMessageData.sql, [messageId]);
}
/**
* Adds a message of type "INFO" to the lobby
* @param lobbyId {Number} - the id of the lobby
@ -650,12 +691,12 @@ class GridWrapper {
let gridField = new GridFieldWrapper(result);
let username = await (await this.player()).username();
let word = await gridField.word.content();
let lobbyWrapper = await this.lobby();
if (gridField.submitted)
await bdm.addInfoMessage(this.lobbyId,
`${username} toggled "${word}"`);
await lobbyWrapper.addInfoMessage(`${username} toggled "${word}"`);
else
await bdm.addInfoMessage(this.lobbyId,
`${username} untoggled "${word}"`);
await lobbyWrapper.addInfoMessage(`${username} untoggled "${word}"`);
return gridField;
}
}
@ -668,7 +709,7 @@ class MessageWrapper {
constructor(row) {
this.id = row.id;
this.content = row.content;
this.htmlContent = md.renderInline(this.content);
this.htmlContent = md.renderInline(preMarkdownParse(this.content));
this.author = new PlayerWrapper(row.player_id);
this.lobby = new LobbyWrapper(row.lobby_id);
this.type = row.type;
@ -881,6 +922,7 @@ class RoundWrapper {
let updateResult = await bdm.setRoundWinner(this.id, winnerId);
if (updateResult)
await this.setFinished();
(await this.lobby()).socket.emit('statusChange', 'FINISHED', await resolvePlayer(new PlayerWrapper(winnerId)));
return true;
}
}
@ -894,6 +936,7 @@ class LobbyWrapper {
*/
constructor(id, row) {
this.id = id;
this.socket = sockets[id];
this._infoLoaded = false;
if (row)
this._assignProperties(row);
@ -907,7 +950,7 @@ class LobbyWrapper {
*/
async _loadLobbyInfo(force) {
if (!this._infoLoaded && !force) {
let row = await bdm.getLobbyInfo(this.id);
let row = await bdm.updateLobbyExpiration(this.id);
this._assignProperties(row);
}
}
@ -928,6 +971,14 @@ class LobbyWrapper {
}
}
/**
* Emits an event is a socket exists for the lobby
*/
emit() {
if (this.socket)
this.socket.emit(...arguments);
}
/**
* Returns if the lobby exists (based on one loaded attribute)
* @returns {Promise<boolean>}
@ -1061,6 +1112,7 @@ class LobbyWrapper {
await this._createRound();
await this._createGrids();
await this.setRoundStatus('ACTIVE');
this.emit('statusChange', 'ACTIVE');
}
}
@ -1115,6 +1167,7 @@ class LobbyWrapper {
await this.addWord(word);
for (let word of removedWords)
await this.removeWord(word.id);
this.emit('wordsChange');
}
}
@ -1144,6 +1197,16 @@ class LobbyWrapper {
};
}
/**
* Adds an info message and emits the message event.
* @param message {String} - the info messages content
* @returns {Promise<void>}
*/
async addInfoMessage(message) {
let result = await bdm.addInfoMessage(this.id, message);
this.emit('message', await resolveMessage(new MessageWrapper(result)));
}
/**
* Adds a player to the lobby.
* @param playerId
@ -1151,8 +1214,10 @@ class LobbyWrapper {
*/
async addPlayer(playerId) {
await bdm.addPlayerToLobby(playerId, this.id);
let username = await new PlayerWrapper(playerId).username();
await bdm.addInfoMessage(this.id, `${username} joined.`);
let playerWrapper = new PlayerWrapper(playerId);
this.emit('playerJoin', await resolvePlayer(playerWrapper));
let username = await playerWrapper.username();
await this.addInfoMessage(`${username} joined.`);
await this._loadLobbyInfo(true);
}
@ -1164,7 +1229,8 @@ class LobbyWrapper {
async removePlayer(playerId) {
await bdm.removePlayerFromLobby(playerId, this.id);
let username = await new PlayerWrapper(playerId).username();
await bdm.addInfoMessage(this.id, `${username} left.`);
this.emit('playerLeave', playerId);
await this.addInfoMessage(`${username} left.`);
await this._loadLobbyInfo(true);
}
@ -1185,7 +1251,8 @@ class LobbyWrapper {
async setRoundStatus(status) {
let currentRound = await this.currentRound();
await currentRound.updateStatus(status);
await bdm.addInfoMessage(this.id, `Admin set round status to ${status}`);
await this.addInfoMessage(`Admin set round status to ${status}`);
this.emit('statusChange', status);
if (status === 'FINISHED')
await bdm.clearGrids(this.id);
@ -1333,6 +1400,28 @@ function checkBingo(fg) {
return diagonalBingo || verticalCheck || horizontalCheck;
}
/**
* Parses the message and replaces all links with markdown-links and images with markdown-images.
* @param message {String} - the raw message
*/
function preMarkdownParse(message) {
let linkMatch = /(^|[^(])https?:\/\/((([\w-]+\.)+[\w-]+)(\S*))([^)]|$)/g;
let imageMatch = /.*\.(\w+)/g;
let imageExtensions = ['png', 'jpg', 'jpeg', 'gif', 'bmp', 'svg'];
let links = message.match(linkMatch);
if (links)
for (let link of links) {
let linkGroups = linkMatch.exec(link);
let imgGroups = imageMatch.exec(link);
if (imgGroups && imgGroups[1] && imageExtensions.includes(imgGroups[1]))
message = message.replace(link, `![${linkGroups[1]}](${link})`);
}
return message;
}
/**
* Gets player data for a lobby
* @param lobbyWrapper
@ -1395,6 +1484,53 @@ async function getGridData(lobbyId, playerId) {
return {fields: fieldGrid, bingo: await grid.bingo()};
}
/**
* Resolves a message wrapper object
* @param msgWrapper
* @returns {Promise<{author: {id: (*|MessageWrapper.author.id), username: String}, id: *, content: *, timestamp: Timestamp | * | number, htmlContent: *}>}
*/
async function resolveMessage(msgWrapper) {
return {
id: msgWrapper.id,
type: msgWrapper.type,
content: msgWrapper.content,
timestamp: msgWrapper.timestamp,
htmlContent: msgWrapper.htmlContent,
author: {
id: msgWrapper.author.id,
username: await msgWrapper.author.username()
}
};
}
/**
* Resolves a player wrapper object
* @param playerWrapper
* @param lobbyId
* @returns {Promise<{wins: PlayerWrapper.wins, id: *, username: (String|*)}>}
*/
async function resolvePlayer(playerWrapper, lobbyId) {
return {
id: playerWrapper.id,
username: await playerWrapper.username(),
wins: await playerWrapper.wins({lobbyId: lobbyId})
};
}
/**
* Resolves a fieldWrapper object
* @param fieldWrapper
* @returns {Promise<{submitted: (Object.submitted|*), column: *, bingo: boolean, row: (*)}>}
*/
async function resolveGridField(fieldWrapper) {
return {
row: fieldWrapper.row,
column: fieldWrapper.column,
submitted: fieldWrapper.submitted,
bingo: await fieldWrapper.grid.bingo()
};
}
/**
* Returns resolved message data.
* @param lobbyId
@ -1405,239 +1541,313 @@ async function getMessageData(lobbyId) {
let messages = await lobbyWrapper.messages({limit: 20});
let msgReturn = [];
for (let message of messages)
msgReturn.push(Object.assign(message, {username: await message.author.username()}));
msgReturn.push(Object.assign(message, {
playerId: message.author.id,
username: await message.author.username()
}));
return msgReturn;
}
// -- Router stuff
/**
* Creates a lobby socket if none exists.
* @param io
* @param lobbyId
*/
function createSocketIfNotExist(io, lobbyId) {
if (!sockets[lobbyId]) {
let lobbySocket = io.of(`/bingo/${lobbyId}`);
sockets[lobbyId] = lobbySocket;
lobbySocket.on('connection', (socket) => {
socket.on('message', async (context, message) => {
try {
let result = await bdm.addUserMessage(lobbyId, context.playerId, message);
let messageWrapper = new MessageWrapper(result);
lobbySocket.emit('message', await resolveMessage(messageWrapper));
} catch (err) {
console.error(err);
}
});
socket.on('messageEdit', async (context, message, messageId) => {
try {
let row = await bdm.getMessageData(messageId);
if (row.player_id === Number(context.playerId)) {
let result = await bdm.editMessage(messageId, message);
let messageWrapper = new MessageWrapper(result);
lobbySocket.emit('messageEdit', await resolveMessage(messageWrapper));
} else {
socket.emit('userError', "You are only allowed to edit your messages.");
}
} catch (err) {
console.error(err);
}
});
socket.on('messageDelete', async (context, messageId) => {
try {
let row = await bdm.getMessageData(messageId);
if (row.player_id === Number(context.playerId)) {
await bdm.deleteMessage(messageId);
lobbySocket.emit('messageDelete', messageId);
}
} catch (err) {
console.error(err);
}
});
socket.on('fieldToggle', async (context, location) => {
let {row, column} = location;
let result = await (await (new PlayerWrapper(context.playerId)).grid({lobbyId: lobbyId}))
.toggleField(row, column);
socket.emit('fieldChange', await resolveGridField(result));
});
});
}
}
let bdm = new BingoDataManager(pgPool);
router.init = async () => {
router.init = async (bingoIo, io) => {
await bdm.init();
};
router.use(async (req, res, next) => {
if (req.session.bingoPlayerId)
await bdm.updatePlayerExpiration(req.session.bingoPlayerId);
next();
});
for (let id of await bdm.getLobbyIds())
createSocketIfNotExist(io, id);
router.get('/', async (req, res) => {
let playerId = req.session.bingoPlayerId;
let info = req.session.acceptedCookies? null: globals.cookieInfo;
let lobbyWrapper = new LobbyWrapper(req.query.g);
let playerWrapper = new PlayerWrapper(playerId);
router.use(async (req, res, next) => {
if (req.session.bingoPlayerId)
await bdm.updatePlayerExpiration(req.session.bingoPlayerId);
next();
});
if (playerId && await playerWrapper.exists() && req.query.g && await lobbyWrapper.exists()) {
let lobbyId = req.query.g;
if (!(await lobbyWrapper.roundActive() && await playerWrapper.hasGrid(lobbyId))) {
if (!await lobbyWrapper.hasPlayer(playerId))
await lobbyWrapper.addPlayer(playerId);
let playerData = await getPlayerData(lobbyWrapper);
let words = await getWordsData(lobbyWrapper);
let admin = await lobbyWrapper.admin();
res.render('bingo/bingo-lobby', {
players: playerData,
isAdmin: (playerId === admin.id),
adminId: admin.id,
words: words,
wordString: words.join('\n'),
gridSize: await lobbyWrapper.gridSize(),
info: info,
messages: await getMessageData(lobbyId)
});
} else {
if (await lobbyWrapper.hasPlayer(playerId) && await playerWrapper.hasGrid(lobbyId)) {
router.get('/', async (req, res) => {
let playerId = req.session.bingoPlayerId;
let info = req.session.acceptedCookies? null: globals.cookieInfo;
let lobbyWrapper = new LobbyWrapper(req.query.g);
let playerWrapper = new PlayerWrapper(playerId);
if (playerId && await playerWrapper.exists() && req.query.g && await lobbyWrapper.exists()) {
let lobbyId = req.query.g;
createSocketIfNotExist(io, lobbyId);
if (!(await lobbyWrapper.roundActive() && await playerWrapper.hasGrid(lobbyId))) {
if (!await lobbyWrapper.hasPlayer(playerId))
await lobbyWrapper.addPlayer(playerId);
let playerData = await getPlayerData(lobbyWrapper);
let grid = await getGridData(lobbyId, playerId);
let words = await getWordsData(lobbyWrapper);
let admin = await lobbyWrapper.admin();
res.render('bingo/bingo-round', {
res.render('bingo/bingo-lobby', {
players: playerData,
grid: grid,
isAdmin: (playerId === admin.id),
adminId: admin.id,
words: words,
wordString: words.join('\n'),
gridSize: await lobbyWrapper.gridSize(),
info: info,
messages: await getMessageData(lobbyId)
});
} else {
res.redirect('/bingo');
if (await lobbyWrapper.hasPlayer(playerId) && await playerWrapper.hasGrid(lobbyId)) {
let playerData = await getPlayerData(lobbyWrapper);
let grid = await getGridData(lobbyId, playerId);
let admin = await lobbyWrapper.admin();
res.render('bingo/bingo-round', {
players: playerData,
grid: grid,
isAdmin: (playerId === admin.id),
adminId: admin.id,
info: info,
messages: await getMessageData(lobbyId)
});
} else {
res.redirect('/bingo');
}
}
} else {
res.render('bingo/bingo-create', {
info: info,
username: await playerWrapper.username(),
changelog: md.render(globals.changelog),
primaryJoin: (req.query.g && await lobbyWrapper.exists())
});
}
} else {
res.render('bingo/bingo-create', {
info: info,
username: await playerWrapper.username()
});
}
});
});
router.graphqlResolver = async (req, res) => {
let playerId = req.session.bingoPlayerId;
if (playerId)
await bdm.updatePlayerExpiration(playerId);
router.graphqlResolver = async (req, res) => {
let playerId = req.session.bingoPlayerId;
if (playerId)
await bdm.updatePlayerExpiration(playerId);
return {
// queries
lobby: async ({id}) => {
await bdm.updateLobbyExpiration(id);
return new LobbyWrapper(id);
},
player: ({id}) => {
if (id)
return new PlayerWrapper(id);
else
return {
// queries
lobby: async ({id}) => {
await bdm.updateLobbyExpiration(id);
return new LobbyWrapper(id);
},
player: ({id}) => {
if (id)
return new PlayerWrapper(id);
else
if (playerId)
return new PlayerWrapper(playerId);
else
res.status(400);
},
// mutations
setUsername: async ({username}) => {
username = replaceTagSigns(username.substring(0, 30)).replace(/[^\w- ;[\]]/g, ''); // only allow 30 characters
if (username.length > 0) {
let playerWrapper = new PlayerWrapper(playerId);
if (!playerId || !(await playerWrapper.exists())) {
req.session.bingoPlayerId = (await bdm.addPlayer(username)).id;
playerId = req.session.bingoPlayerId;
} else {
let oldName = await playerWrapper.username();
await bdm.updatePlayerUsername(playerId, username);
if (req.query.g)
await bdm.addInfoMessage(req.query.g, `${oldName} changed username to ${username}`);
}
return new PlayerWrapper(playerId);
} else {
res.status(400);
return new GraphQLError('Username too short!');
}
},
createLobby: async({gridSize}) => {
if (playerId)
if (gridSize > 0 && gridSize < 10) {
let result = await bdm.createLobby(playerId, gridSize);
return new LobbyWrapper(result.id);
} else {
res.status(413);
}
res.status(400);
},
mutateLobby: async ({id}) => {
let lobbyId = id;
await bdm.updateLobbyExpiration(lobbyId);
let lobbyWrapper = new LobbyWrapper(lobbyId);
return {
join: async () => {
if (playerId) {
await lobbyWrapper.addPlayer(playerId);
return lobbyWrapper;
} else {
res.status(400);
}
},
leave: async () => {
if (playerId) {
await lobbyWrapper.removePlayer(playerId);
return true;
} else {
res.status(400);
}
},
kickPlayer: async ({pid}) => {
let admin = await lobbyWrapper.admin();
if (admin.id === playerId) {
await lobbyWrapper.removePlayer(pid);
return new PlayerWrapper(pid);
} else {
res.status(403);
return new GraphQLError('You are not an admin');
}
},
startRound: async () => {
let admin = await lobbyWrapper.admin();
if (admin.id === playerId) {
await lobbyWrapper.startNewRound();
return lobbyWrapper.currentRound();
},
// mutations
setUsername: async ({username}) => {
username = replaceTagSigns(username.substring(0, 30)).replace(/[\n\t👑🌟]|^\s+|\s+$/gu, ''); // only allow 30 characters
if (username.length > 0) {
let playerWrapper = new PlayerWrapper(playerId);
if (!playerId || !(await playerWrapper.exists())) {
req.session.bingoPlayerId = (await bdm.addPlayer(username)).id;
playerId = req.session.bingoPlayerId;
} else {
res.status(403);
return new GraphQLError('You are not an admin');
let oldName = await playerWrapper.username();
await bdm.updatePlayerUsername(playerId, username);
if (req.query.g) {
let lobbyWrapper = new LobbyWrapper(req.query.g);
if (await lobbyWrapper.exists()) {
lobbyWrapper.emit('usernameChange',
await resolvePlayer(new PlayerWrapper(playerId), req.query.g));
await lobbyWrapper.addInfoMessage(`${oldName} changed username to ${username}`);
}
}
}
},
setRoundStatus: async ({status}) => {
let admin = await lobbyWrapper.admin();
if (admin.id === playerId) {
return await lobbyWrapper.setRoundStatus(status);
return new PlayerWrapper(playerId);
} else {
res.status(400);
return new GraphQLError('Username too short!');
}
},
createLobby: async({gridSize}) => {
if (playerId)
if (gridSize > 0 && gridSize < 10) {
let result = await bdm.createLobby(playerId, gridSize);
createSocketIfNotExist(io, result.id);
return new LobbyWrapper(result.id, result);
} else {
res.status(403);
return new GraphQLError('You are not an admin');
res.status(413);
}
},
setGridSize: async ({gridSize}) => {
if (gridSize > 0 && gridSize < 6) {
res.status(400);
},
mutateLobby: async ({id}) => {
let lobbyId = id;
createSocketIfNotExist(io, lobbyId);
await bdm.updateLobbyExpiration(lobbyId);
let lobbyWrapper = new LobbyWrapper(lobbyId);
return {
join: async () => {
if (playerId) {
await lobbyWrapper.addPlayer(playerId);
return lobbyWrapper;
} else {
res.status(400);
}
},
leave: async () => {
if (playerId) {
await lobbyWrapper.removePlayer(playerId);
return true;
} else {
res.status(400);
}
},
kickPlayer: async ({pid}) => {
let admin = await lobbyWrapper.admin();
if (admin.id === playerId) {
await lobbyWrapper.setGridSize(gridSize);
return lobbyWrapper;
await lobbyWrapper.removePlayer(pid);
return new PlayerWrapper(pid);
} else {
res.status(403);
return new GraphQLError('You are not an admin');
}
} else {
res.status(400);
return new GraphQLError('Grid size too big!');
}
},
setWords: async({words}) => {
let admin = await lobbyWrapper.admin();
if (admin.id === playerId)
if (words.length < 10000) {
await lobbyWrapper.setWords(words);
return lobbyWrapper;
},
startRound: async () => {
let admin = await lobbyWrapper.admin();
if (admin.id === playerId) {
await lobbyWrapper.startNewRound();
return lobbyWrapper.currentRound();
} else {
res.status(413); // request entity too large
return new GraphQLError('Too many words');
res.status(403);
return new GraphQLError('You are not an admin');
}
else
res.status(403); // forbidden
},
sendMessage: async ({message}) => {
if (await lobbyWrapper.hasPlayer(playerId)) {
let result = await bdm.addUserMessage(lobbyId, playerId, message);
return new MessageWrapper(result);
} else {
res.status(401); // unautorized
return new GraphQLError('You are not in the lobby');
}
},
submitBingo: async () => {
let isBingo = await (await (new PlayerWrapper(playerId)).grid({lobbyId: lobbyId})).bingo();
let currentRound = await lobbyWrapper.currentRound();
if (isBingo && await lobbyWrapper.hasPlayer(playerId)) {
let result = await currentRound.setWinner(playerId);
let username = await new PlayerWrapper(playerId).username();
if (result) {
await bdm.addInfoMessage(lobbyId, `**${username}** won!`);
await bdm.clearGrids(lobbyId);
return currentRound;
},
setRoundStatus: async ({status}) => {
let admin = await lobbyWrapper.admin();
if (admin.id === playerId) {
return await lobbyWrapper.setRoundStatus(status);
} else {
res.status(500);
res.status(403);
return new GraphQLError('You are not an admin');
}
} else {
res.status(400);
return new GraphQLError('Bingo check failed. This is not a bingo!');
},
setGridSize: async ({gridSize}) => {
if (gridSize > 0 && gridSize < 6) {
let admin = await lobbyWrapper.admin();
if (admin.id === playerId) {
await lobbyWrapper.setGridSize(gridSize);
return lobbyWrapper;
} else {
res.status(403);
return new GraphQLError('You are not an admin');
}
} else {
res.status(400);
return new GraphQLError('Grid size too big!');
}
},
setWords: async({words}) => {
let admin = await lobbyWrapper.admin();
if (admin.id === playerId)
if (words.length < 10000) {
await lobbyWrapper.setWords(words);
return lobbyWrapper;
} else {
res.status(413); // request entity too large
return new GraphQLError('Too many words');
}
else
res.status(403); // forbidden
},
sendMessage: async ({message}) => {
if (await lobbyWrapper.hasPlayer(playerId)) {
let result = await bdm.addUserMessage(lobbyId, playerId, message);
return new MessageWrapper(result);
} else {
res.status(401); // unautorized
return new GraphQLError('You are not in the lobby');
}
},
submitBingo: async () => {
let isBingo = await (await (new PlayerWrapper(playerId)).grid({lobbyId: lobbyId})).bingo();
let currentRound = await lobbyWrapper.currentRound();
if (isBingo && await lobbyWrapper.hasPlayer(playerId)) {
let result = await currentRound.setWinner(playerId);
let username = await new PlayerWrapper(playerId).username();
if (result) {
await bdm.addInfoMessage(lobbyId, `**${username}** won!`);
await bdm.clearGrids(lobbyId);
return currentRound;
} else {
res.status(500);
}
} else {
res.status(400);
return new GraphQLError('Bingo check failed. This is not a bingo!');
}
},
toggleGridField: async ({location}) => {
let {row, column} = location;
return await (await (new PlayerWrapper(playerId)).grid({lobbyId: lobbyId}))
.toggleField(row, column);
}
},
toggleGridField: async ({location}) => {
let {row, column} = location;
return await (await (new PlayerWrapper(playerId)).grid({lobbyId: lobbyId}))
.toggleField(row, column);
}
};
}
};
}
};
};
};

@ -1,17 +1,14 @@
const express = require('express'),
router = express.Router(),
globals = require('../lib/globals'),
fsx = require('fs-extra'),
mdEmoji = require('markdown-it-emoji'),
md = require('markdown-it')()
.use(mdEmoji);
let changelog = fsx.readFileSync('CHANGELOG.md', 'utf-8');
/* GET home page. */
router.get('/', (req, res) => {
let info = req.session.acceptedCookies? null: globals.cookieInfo;
res.render('changelog/changes', { changelog: md.render(changelog), info: info});
res.render('changelog/changes', { changelog: md.render(globals.changelog), info: info});
});
module.exports = router;

@ -11,7 +11,7 @@ CREATE TABLE IF NOT EXISTS bingo.lobbys (
admin_id serial references bingo.players(id) ON DELETE SET NULL,
grid_size integer DEFAULT 3 NOT NULL,
current_round integer,
expire timestamp DEFAULT (NOW() + interval '1 hour' )
expire timestamp DEFAULT (NOW() + interval '4 hour' )
);
-- lobbys-players table

@ -48,7 +48,7 @@ addLobby:
# params:
# - {Number} - the id of the lobby
updateLobbyExpire:
sql: UPDATE bingo.lobbys SET expire = (NOW() + interval '1 hours') WHERE id = $1;
sql: UPDATE bingo.lobbys SET expire = (NOW() + interval '4 hours') WHERE id = $1 RETURNING *;
# inserts a player into a lobby
# params:
@ -77,6 +77,9 @@ getPlayerInLobby:
getLobbyPlayers:
sql: SELECT * FROM bingo.lobby_players WHERE lobby_players.lobby_id = $1;
getLobbyIds:
sql: SELECT lobbys.id FROM bingo.lobbys;
# returns all direct information about the lobby
# params:
# - {Number} - the id of the lobby
@ -259,9 +262,28 @@ getWordsForGridId:
addUserMessage:
sql: INSERT INTO bingo.messages (player_id, lobby_id, content) VALUES ($1, $2, $3) RETURNING *;
# edits a message
# params:
# - {Number} - the id of the message
# - {Number} - the new content of the message
editMessage:
sql: UPDATE bingo.messages SET content = $2 WHERE id = $1 RETURNING *;
# inserts a info message
# params:
# - {Number} - the id of the lobby
# - {String} - the content of the message
addInfoMessage:
sql: INSERT INTO bingo.messages (type, lobby_id, content) VALUES ('INFO', $1, $2) RETURNING *;
# returns the data of a message
# params:
# - {Number} - the id of the message
getMessageData:
sql: SELECT * from bingo.messages WHERE id = $1;
# deletes a message
# params:
# - {Number} - the id of the message
deleteMessage:
sql: DELETE FROM bingo.messages WHERE id = $1;

@ -3,16 +3,25 @@ extends includes/bingo-layout
block content
div(id='container-bingo-create')
div(id='username-form')
h2 Please enter a username
input(id='input-username'
type='text'
placeholder='Enter your name'
value=username
maxlength=30
onkeydown='submitOnEnter(event, () => indicateStatus(submitUsername, "#username-status"))')
button(
id='submit-username'
onclick='indicateStatus(submitUsername, "#username-status")') Set Username
div(id='username-status' class='statusIndicator')
if primaryJoin
button(
id='submit-username'
onclick='joinLobby()') Join Lobby
else
button(
id='submit-username'
onclick='indicateStatus(submitUsername, "#submit-username")') Set Username
div(id='lobby-form')
button(id='join-lobby' onclick='joinLobby()') Join Lobby
button(id='create-lobby' onclick='createLobby()') Create Lobby
if primaryJoin
button(id='create-lobby' class='inactive' onclick='createLobby()') Create Lobby
else
button(id='create-lobby' onclick='createLobby()') Create Lobby
div(id='changelog')!= changelog
include includes/bingo-statusbar

@ -19,4 +19,4 @@ block content
include includes/bingo-chat
include includes/bingo-statusbar
script(type='text/javascript') refreshLobby();
script(type='text/javascript') initRefresh();

@ -23,4 +23,4 @@ block content
b-column=field.column
b-sub=`${field.submitted}`)
span= field.word
script(type='text/javascript') refreshRound();
script(type='text/javascript') initRefresh();

@ -2,7 +2,12 @@ div(id='container-chat')
style(id='js-style')
div(id='chat-content')
for message in messages
span.chatMessage(type=message.type msg-id=message.id)
span.chatMessage(
msg-type=message.type
msg-id=message.id
msg-pid=message.playerId
msg-raw=message.content)
if message.type === 'USER'
span.chatUsername= `${message.username}: `
span(class=`chatMessageContent ${message.type}`)!= message.htmlContent
@ -10,6 +15,6 @@ div(id='container-chat')
id='chat-input'
type='text'
placeholder='send message'
onkeypress='submitOnEnter(event, () => statusWrap(sendChatMessage))'
onkeypress='onInputKeypress(event)'
maxlength="250"
autocomplete='off')

@ -1,5 +1,5 @@
div(id='statusbar')
div(id='status-indicator' class='statusIndicator' status='idle')
div(id='status-indicator' class='statusIndicator socketStatusIndicator' status='idle')
span(id='error-message')
span(id='container-info')
a(href='https://github.com/Trivernis/whooshy/issues') Bug/Feature

@ -1,2 +1,3 @@
link(rel='stylesheet', href='/sass/style.sass')
script(type='text/javascript', src='/javascripts/common.js')
link(rel='stylesheet' href='/sass/style.sass')
script(type='text/javascript' src='/javascripts/common.js')
script(type='text/javascript' src='/socket.io/socket.io.js')

Loading…
Cancel
Save