Switched to postgres for session storage

- improved mobile layout (chat now visible)
- fixed bingo button not visible in mobile layout
- added postgres for session storage
- added sql directory for sql query files
pull/7/head
Trivernis 6 years ago
parent fc0e6c23c1
commit 2dff089ca0

@ -6,8 +6,25 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased] ## [Unreleased]
### Added ### Added
- CHANGELOG.md Changelog - CHANGELOG.md Changelog
- content to the README.md - content to the README.md
- Chat to the bingo game (renderd with markdown-it) - Chat to the bingo game (renderd with markdown-it)
- Postgres session storage
- sql-file directory `sql`
## Changed
- changed export of `app.js` to the asynchronous init function that returns the app object
- `bin/www` now calls the init function of `app.js`
### Removed
- sqlite3 sesssion storage
### Fixed
- mobile layout

126
app.js

@ -5,7 +5,8 @@ const createError = require('http-errors'),
logger = require('morgan'), logger = require('morgan'),
compileSass = require('express-compile-sass'), compileSass = require('express-compile-sass'),
session = require('express-session'), session = require('express-session'),
SQLiteStore = require('connect-sqlite3')(session), pg = require('pg'),
pgSession = require('connect-pg-simple')(session),
fsx = require('fs-extra'), fsx = require('fs-extra'),
yaml = require('js-yaml'), yaml = require('js-yaml'),
graphqlHTTP = require('express-graphql'), graphqlHTTP = require('express-graphql'),
@ -22,69 +23,86 @@ let settings = yaml.safeLoad(fsx.readFileSync('default-config.yaml'));
if (fsx.existsSync('config.yaml')) if (fsx.existsSync('config.yaml'))
Object.assign(settings, yaml.safeLoad(fsx.readFileSync('config.yaml'))); Object.assign(settings, yaml.safeLoad(fsx.readFileSync('config.yaml')));
let graphqlResolver = (request, response) => { async function init() {
return { // grapql default resolver
let graphqlResolver = (request, response) => {
return {
time: Date.now(), time: Date.now(),
bingo: bingoRouter.graphqlResolver(request, response) bingo: bingoRouter.graphqlResolver(request, response)
} }
}; };
let app = express(); // database setup
let pgPool = new pg.Pool({
host: settings.postgres.host,
port: settings.postgres.port,
user: settings.postgres.user,
password: settings.postgres.password,
database: settings.postgres.database
});
await pgPool.query(fsx.readFileSync('./sql/createSessionTable.sql', 'utf-8'));
// view engine setup let app = express();
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'pug');
app.set('trust proxy', 1);
app.use(logger('dev')); // view engine setup
app.use(express.json()); app.set('views', path.join(__dirname, 'views'));
app.use(express.urlencoded({ extended: false })); app.set('view engine', 'pug');
app.use(cookieParser()); app.set('trust proxy', 1);
app.use(session({
store: new SQLiteStore,
secret: settings.sessions.secret,
resave: false,
saveUninitialized: true,
cookie: {
expires: 10000000
}
}));
app.use('/sass', compileSass({
root: './public/stylesheets/sass',
sourceMap: true,
watchFiles: true,
logToConsole: true
}));
app.use(express.static(path.join(__dirname, 'public')));
app.use('/', indexRouter); app.use(logger('dev'));
app.use('/users', usersRouter); app.use(express.json());
app.use(/\/riddle(\/.*)?/, riddleRouter); app.use(express.urlencoded({ extended: false }));
app.use('/bingo', bingoRouter); app.use(cookieParser());
app.use('/graphql', graphqlHTTP((request, response) => { app.use(session({
return { store: new pgSession({
schema: buildSchema(importSchema('./graphql/schema.graphql')), pool: pgPool,
rootValue: graphqlResolver(request, response), tableName: 'user_sessions'
context: {session: request.session}, }),
graphiql: true secret: settings.sessions.secret,
}; resave: false,
})); saveUninitialized: true,
cookie: {
maxAge: 30 * 24 * 60 * 60 * 1000 // maxAge 30 days
}
}));
app.use('/sass', compileSass({
root: './public/stylesheets/sass',
sourceMap: true,
watchFiles: true,
logToConsole: true
}));
app.use(express.static(path.join(__dirname, 'public')));
app.use('/', indexRouter);
app.use('/users', usersRouter);
app.use(/\/riddle(\/.*)?/, riddleRouter);
app.use('/bingo', bingoRouter);
app.use('/graphql', graphqlHTTP((request, response) => {
return {
schema: buildSchema(importSchema('./graphql/schema.graphql')),
rootValue: graphqlResolver(request, response),
context: {session: request.session},
graphiql: true
};
}));
// catch 404 and forward to error handler // catch 404 and forward to error handler
app.use(function(req, res, next) { app.use(function(req, res, next) {
next(createError(404)); next(createError(404));
}); });
// error handler // error handler
app.use(function(err, req, res, next) { app.use(function(err, req, res, next) {
// set locals, only providing error in development // set locals, only providing error in development
res.locals.message = err.message; res.locals.message = err.message;
res.locals.error = req.app.get('env') === 'development' ? err : {}; res.locals.error = req.app.get('env') === 'development' ? err : {};
// render the error page // render the error page
res.status(err.status || 500); res.status(err.status || 500);
res.render('error'); res.render('error');
}); });
return app;
}
module.exports = app; module.exports = init;
//app.listen(settings.port); //app.listen(settings.port);

@ -4,7 +4,7 @@
* Module dependencies. * Module dependencies.
*/ */
const app = require('../app'); const appInit = require('../app');
const debug = require('debug')('whooshy:server'); const debug = require('debug')('whooshy:server');
const http = require('http'); const http = require('http');
const yaml = require('js-yaml'); const yaml = require('js-yaml');
@ -26,25 +26,31 @@ try {
*/ */
let port = normalizePort(process.env.PORT || settings.port || '3000'); let port = normalizePort(process.env.PORT || settings.port || '3000');
app.set('port', port);
/** appInit().then((app) => {
* Create HTTP server. app.set('port', port);
*/
let server = http.createServer(app); /**
* Create HTTP server.
*/
/** let server = http.createServer(app);
* Listen on provided port, on all network interfaces.
*/
server.listen(port); /**
server.on('error', onError); * Listen on provided port, on all network interfaces.
server.on('listening', onListening); */
/** server.listen(port);
* Normalize a port into a number, string, or false. server.on('error', (error) => onError(error, server));
*/ server.on('listening', () => onListening(server));
/**
* Normalize a port into a number, string, or false.
*/
}).catch((err) => {
console.error(err.message);
console.error(err.stack);
});
function normalizePort(val) { function normalizePort(val) {
let port = parseInt(val, 10); let port = parseInt(val, 10);
@ -66,7 +72,7 @@ function normalizePort(val) {
* Event listener for HTTP server "error" event. * Event listener for HTTP server "error" event.
*/ */
function onError(error) { function onError(error, server) {
if (error.syscall !== 'listen') { if (error.syscall !== 'listen') {
throw error; throw error;
} }
@ -94,7 +100,7 @@ function onError(error) {
* Event listener for HTTP server "listening" event. * Event listener for HTTP server "listening" event.
*/ */
function onListening() { function onListening(server) {
let addr = server.address(); let addr = server.address();
let bind = typeof addr === 'string' let bind = typeof addr === 'string'
? 'pipe ' + addr ? 'pipe ' + addr

@ -2,4 +2,11 @@ sessions:
secret: averysecuresessionsecret secret: averysecuresessionsecret
maxAge: 1000000 maxAge: 1000000
port: 3000 port: 3000
postgres:
host: localhost
port: 5432
user: whooshy
password: whooshypassword
database: whooshy

110
package-lock.json generated

@ -381,6 +381,11 @@
} }
} }
}, },
"buffer-writer": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/buffer-writer/-/buffer-writer-2.0.0.tgz",
"integrity": "sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw=="
},
"bytes": { "bytes": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz",
@ -572,6 +577,14 @@
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
}, },
"connect-pg-simple": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/connect-pg-simple/-/connect-pg-simple-5.0.0.tgz",
"integrity": "sha512-WZ7xkN+qe5bbDLgZ1L9GxnSbr155cJHmfNRzVR5hBvqio7Pg/vuH7Cf8lPUSFClQjtybYSejUqyO54sYt4cg+w==",
"requires": {
"pg": "^7.4.3"
}
},
"connect-sqlite3": { "connect-sqlite3": {
"version": "0.9.11", "version": "0.9.11",
"resolved": "https://registry.npmjs.org/connect-sqlite3/-/connect-sqlite3-0.9.11.tgz", "resolved": "https://registry.npmjs.org/connect-sqlite3/-/connect-sqlite3-0.9.11.tgz",
@ -2789,6 +2802,11 @@
"os-tmpdir": "^1.0.0" "os-tmpdir": "^1.0.0"
} }
}, },
"packet-reader": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/packet-reader/-/packet-reader-1.0.0.tgz",
"integrity": "sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ=="
},
"parse-json": { "parse-json": {
"version": "2.2.0", "version": "2.2.0",
"resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz",
@ -2850,6 +2868,62 @@
"resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
"integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns="
}, },
"pg": {
"version": "7.11.0",
"resolved": "https://registry.npmjs.org/pg/-/pg-7.11.0.tgz",
"integrity": "sha512-YO4V7vCmEMGoF390LJaFaohWNKaA2ayoQOEZmiHVcAUF+YsRThpf/TaKCgSvsSE7cDm37Q/Cy3Gz41xiX/XjTw==",
"requires": {
"buffer-writer": "2.0.0",
"packet-reader": "1.0.0",
"pg-connection-string": "0.1.3",
"pg-pool": "^2.0.4",
"pg-types": "~2.0.0",
"pgpass": "1.x",
"semver": "4.3.2"
},
"dependencies": {
"semver": {
"version": "4.3.2",
"resolved": "https://registry.npmjs.org/semver/-/semver-4.3.2.tgz",
"integrity": "sha1-x6BxWKgL7dBSNVt3DYLWZA+AO+c="
}
}
},
"pg-connection-string": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-0.1.3.tgz",
"integrity": "sha1-2hhHsglA5C7hSSvq9l1J2RskXfc="
},
"pg-int8": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz",
"integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw=="
},
"pg-pool": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-2.0.6.tgz",
"integrity": "sha512-hod2zYQxM8Gt482q+qONGTYcg/qVcV32VHVPtktbBJs0us3Dj7xibISw0BAAXVMCzt8A/jhfJvpZaxUlqtqs0g=="
},
"pg-types": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.0.1.tgz",
"integrity": "sha512-b7y6QM1VF5nOeX9ukMQ0h8a9z89mojrBHXfJeSug4mhL0YpxNBm83ot2TROyoAmX/ZOX3UbwVO4EbH7i1ZZNiw==",
"requires": {
"pg-int8": "1.0.1",
"postgres-array": "~2.0.0",
"postgres-bytea": "~1.0.0",
"postgres-date": "~1.0.4",
"postgres-interval": "^1.1.0"
}
},
"pgpass": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.2.tgz",
"integrity": "sha1-Knu0G2BltnkH6R2hsHwYR8h3swY=",
"requires": {
"split": "^1.0.0"
}
},
"pify": { "pify": {
"version": "2.3.0", "version": "2.3.0",
"resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
@ -2873,6 +2947,29 @@
"resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz",
"integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=" "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs="
}, },
"postgres-array": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz",
"integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA=="
},
"postgres-bytea": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz",
"integrity": "sha1-AntTPAqokOJtFy1Hz5zOzFIazTU="
},
"postgres-date": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.4.tgz",
"integrity": "sha512-bESRvKVuTrjoBluEcpv2346+6kgB7UlnqWZsnbnCccTNq/pqfj1j6oBaN5+b/NrDXepYUT/HKadqv3iS9lJuVA=="
},
"postgres-interval": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz",
"integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==",
"requires": {
"xtend": "^4.0.0"
}
},
"process-nextick-args": { "process-nextick-args": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz",
@ -3528,6 +3625,14 @@
"resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.4.tgz", "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.4.tgz",
"integrity": "sha512-7j8LYJLeY/Yb6ACbQ7F76qy5jHkp0U6jgBfJsk97bwWlVUnUWsAgpyaCvo17h0/RQGnQ036tVDomiwoI4pDkQA==" "integrity": "sha512-7j8LYJLeY/Yb6ACbQ7F76qy5jHkp0U6jgBfJsk97bwWlVUnUWsAgpyaCvo17h0/RQGnQ036tVDomiwoI4pDkQA=="
}, },
"split": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/split/-/split-1.0.1.tgz",
"integrity": "sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==",
"requires": {
"through": "2"
}
},
"split-string": { "split-string": {
"version": "3.1.0", "version": "3.1.0",
"resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz",
@ -3696,6 +3801,11 @@
"inherits": "2" "inherits": "2"
} }
}, },
"through": {
"version": "2.3.8",
"resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
"integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU="
},
"to-fast-properties": { "to-fast-properties": {
"version": "1.0.3", "version": "1.0.3",
"resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.3.tgz", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.3.tgz",

@ -6,7 +6,7 @@
"start": "node ./bin/www" "start": "node ./bin/www"
}, },
"dependencies": { "dependencies": {
"connect-sqlite3": "^0.9.11", "connect-pg-simple": "^5.0.0",
"cookie-parser": "~1.4.4", "cookie-parser": "~1.4.4",
"debug": "~2.6.9", "debug": "~2.6.9",
"express": "~4.16.1", "express": "~4.16.1",
@ -22,6 +22,7 @@
"markdown-it-emoji": "^1.4.0", "markdown-it-emoji": "^1.4.0",
"morgan": "~1.9.1", "morgan": "~1.9.1",
"node-sass": "^4.12.0", "node-sass": "^4.12.0",
"pg": "^7.11.0",
"pug": "2.0.0-beta11" "pug": "2.0.0-beta11"
} }
} }

@ -330,6 +330,17 @@ function submitOnEnter(event, func) {
func(); func();
} }
/**
* Toggles the displayChat class on the content container to switch between chat-view and grid view
*/
function toggleChatView() {
let contentContainer = document.querySelector('#content-container');
if (contentContainer.getAttribute('class') === 'displayChat')
contentContainer.setAttribute('class', '');
else
contentContainer.setAttribute('class', 'displayChat')
}
window.addEventListener("unhandledrejection", function(promiseRejectionEvent) { window.addEventListener("unhandledrejection", function(promiseRejectionEvent) {
promiseRejectionEvent.promise.catch(err => console.log(err)); promiseRejectionEvent.promise.catch(err => console.log(err));
showError('Connection problems... Is the server down?'); showError('Connection problems... Is the server down?');

@ -21,12 +21,26 @@ textarea
height: 80% height: 80%
#content-container #content-container
grid-template-columns: 0 100% !important grid-template-columns: 0 100% !important
grid-template-rows: 10% 80% 10% !important grid-template-rows: 10% 40% 40% 10% !important
#players-container, #chat-container #players-container, #chat-container
display: none !important display: none !important
padding: 0 padding: 0
.errorDiv
grid-column-start: 1 !important
grid-column-end: 4 !important
#content-container.displayChat
grid-template-columns: 100% 0 !important
grid-template-rows: 0 25% 65% 10% !important
#players-container, #chat-container
display: block !important
#words-container
display: none !important
#username-form #username-form
width: calc(100% - 2rem) !important width: calc(100% - 2rem) !important
left: 0 !important left: 0 !important
@ -38,6 +52,23 @@ textarea
width: calc(100% - 2rem) !important width: calc(100% - 2rem) !important
left: 0 !important left: 0 !important
#button-container
grid-column-start: 2 !important
grid-column-end: 3 !important
#chat-button-container
display: inline-block
grid-row-start: 4
grid-row-end: 4
grid-column-start: 1
grid-column-end: 4
overflow: hidden
margin: 0 0.5rem
button
width: 100%
margin: 0.5rem 0
@media(min-device-width: 641px) @media(min-device-width: 641px)
textarea textarea
height: 80% height: 80%
@ -45,6 +76,8 @@ textarea
#words-container #words-container
width: 100% width: 100%
height: 100% height: 100%
#chat-button-container
display: none
.number-input .number-input
width: 4rem width: 4rem
@ -179,7 +212,7 @@ textarea
word-break: break-word word-break: break-word
#chat-content #chat-content
height: calc(100% - 3.5rem) height: calc(100% - 2.5rem)
background-color: $primary background-color: $primary
overflow: auto overflow: auto
@ -199,7 +232,7 @@ textarea
#chat-input #chat-input
width: 100% width: 100%
margin: 1rem 0 0 0 margin: 0 0 0 0
height: 2.5rem height: 2.5rem
border-radius: 0 border-radius: 0

@ -4,5 +4,4 @@
background: lighten($primary, 10%) background: lighten($primary, 10%)
color: $primarySurface color: $primarySurface
border: 2px solid $primarySurface border: 2px solid $primarySurface
border-radius: $borderRadius
transition-duration: 0.2s transition-duration: 0.2s

@ -12,11 +12,11 @@
@media (min-device-width: 641px) @media (min-device-width: 641px)
html html
font-size: 4vw font-size: 2.5vw
@media (min-device-width: 961px) @media (min-device-width: 961px)
html html
font-size: 3vw font-size: 2.2vw
@media (min-device-width: 1025px) @media (min-device-width: 1025px)
html html
@ -63,8 +63,10 @@ a
::-webkit-scrollbar-thumb ::-webkit-scrollbar-thumb
background: darken($secondary, 5) background: darken($secondary, 5)
border-radius: 10px transition-duration: 0.2s
::-webkit-scrollbar-thumb:hover
background: $secondary
::-webkit-scrollbar-track ::-webkit-scrollbar-track
background: lighten($primary, 5) background: lighten($primary, 5)
border-radius: 10px

@ -0,0 +1,7 @@
CREATE TABLE IF NOT EXISTS "user_sessions" (
"sid" varchar NOT NULL COLLATE "default",
"sess" json NOT NULL,
"expire" timestamp(6) NOT NULL,
PRIMARY KEY ("sid") NOT DEFERRABLE INITIALLY IMMEDIATE
)
WITH (OIDS=FALSE);

@ -23,4 +23,6 @@ block content
div(class='bingo-word-panel', onclick=`submitWord('${field.base64Word}')`, b-word=field.base64Word, b-sub=`${field.submitted}`) div(class='bingo-word-panel', onclick=`submitWord('${field.base64Word}')`, b-word=field.base64Word, b-sub=`${field.submitted}`)
span= field.word span= field.word
div(id='button-container') div(id='button-container')
button(id='bingo-button' onclick='submitBingo()', class='hidden') Bingo! button(id='bingo-button', onclick='submitBingo()', class='hidden') Bingo!
div(id='chat-button-container')
button(id='chat-toggle-button', onclick='toggleChatView()') toggle Chat

Loading…
Cancel
Save