Serveral improvements and features

- added notifications on inactive window
- changed max grid size to 5
- shortened phrases that exceed 200 characters
- improved sql performance
- fixed join issues
pull/15/head
Trivernis 6 years ago
parent 9d806a7935
commit c91712d367

@ -29,14 +29,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- bingo status bar - bingo status bar
- bingo chat commands - bingo chat commands
- cookie info dialog - cookie info dialog
- chat and round notifications
## Changed ### Changed
- changed export of `app.js` to the asynchronous init function that returns the app object - 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` - `bin/www` now calls the init function of `app.js`
- graphql bingo api - graphql bingo api
- bingo frontend - bingo frontend
- moved some bingo pug files to ./bingo/includes/ - moved some bingo pug files to ./bingo/includes/
- style of `code`
- font to Ubuntu and Ubuntu Monospace
- grid size limit to 5
- improved sql grid word insertion query
### Removed ### Removed
@ -54,3 +59,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- backend now returns precise error messages - backend now returns precise error messages
- setting words won't result in deleting all of them and resaving - setting words won't result in deleting all of them and resaving
- words can now only be set when no round is active - words can now only be set when no round is active
- username allowing emojis
- username can have a length of 0 (now at least 1 character)
- mozilla didn't have a fancy scrollbar (no webkit browser)
- kicked users can join lobby on active round
- users can't join lobby on active round
- server crash on too big phrases

@ -17,6 +17,7 @@ const createError = require('http-errors'),
indexRouter = require('./routes/index'), indexRouter = require('./routes/index'),
usersRouter = require('./routes/users'), usersRouter = require('./routes/users'),
riddleRouter = require('./routes/riddle'), riddleRouter = require('./routes/riddle'),
changelogRouter = require('./routes/changelog'),
bingoRouter = require('./routes/bingo'); bingoRouter = require('./routes/bingo');
@ -72,6 +73,7 @@ async function init() {
//app.use('/users', usersRouter); //app.use('/users', usersRouter);
//app.use(/\/riddle(\/.*)?/, riddleRouter); //app.use(/\/riddle(\/.*)?/, riddleRouter);
app.use('/bingo', bingoRouter); app.use('/bingo', bingoRouter);
app.use('/changelog', changelogRouter);
app.use('/graphql', graphqlHTTP(async (request, response) => { app.use('/graphql', graphqlHTTP(async (request, response) => {
return await { return await {
schema: buildSchema(importSchema('./graphql/schema.graphql')), schema: buildSchema(importSchema('./graphql/schema.graphql')),

@ -1,4 +1,5 @@
/* eslint-disable no-unused-vars, no-undef */ /* eslint-disable no-unused-vars, no-undef */
/** /**
* Returns the value of the url-param 'g' * Returns the value of the url-param 'g'
* @returns {string} * @returns {string}
@ -9,7 +10,6 @@ function getLobbyParam() {
return matches[1]; return matches[1];
else else
return ''; return '';
} }
/** /**
@ -24,6 +24,21 @@ function getRoundParam() {
return ''; return '';
} }
/**
* Spawns a notification when the window is inactive (hidden).
* @param body
* @param title
*/
function spawnNotification(body, title) {
if (Notification.permission !== 'denied' && document[getHiddenNames().hidden]) {
let options = {
body: body,
icon: '/favicon.ico'
};
let n = new Notification(title, options);
}
}
/** /**
* Submits the value of the username-input to set the username. * Submits the value of the username-input to set the username.
* @returns {Promise<Boolean>} * @returns {Promise<Boolean>}
@ -46,6 +61,8 @@ async function submitUsername() {
* @returns {Promise<boolean>} * @returns {Promise<boolean>}
*/ */
async function setUsername(username) { async function setUsername(username) {
let uname = username.substring(0, 30).replace(/[^\w- ;[\]]/g, '');
if (uname.length === username.length) {
let response = await postGraphqlQuery(` let response = await postGraphqlQuery(`
mutation($username:String!) { mutation($username:String!) {
bingo { bingo {
@ -54,14 +71,20 @@ async function setUsername(username) {
username username
} }
} }
}`, {username: username}); }`, {username: username}, '/graphql?g='+getLobbyParam());
if (response.status === 200) { if (response.status === 200) {
return true; return response.data.bingo.setUsername.username;
} else { } else {
showError(`Failed to submit username. Error: ${response.errors.join(', ')}`); if (response.errors)
showError(response.errors[0].message);
else
showError(`Failed to submit username.`);
console.error(response); console.error(response);
return false; return false;
} }
} else {
showError('Your username contains illegal characters.');
}
} }
/** /**
@ -79,11 +102,16 @@ async function ping() {
} }
/** /**
* TODO: real join logic * Joins a lobby or says to create one if none is found
* @returns {Promise<void>}
*/ */
async function joinLobby() { async function joinLobby() {
await submitUsername(); if (getLobbyParam()) {
if (await submitUsername())
window.location.reload(); window.location.reload();
} else {
showError('No lobby found. Please create one.');
}
} }
/** /**
@ -91,6 +119,7 @@ async function joinLobby() {
* @returns {Promise<boolean>} * @returns {Promise<boolean>}
*/ */
async function createLobby() { async function createLobby() {
if (await submitUsername()) {
let response = await postGraphqlQuery(` let response = await postGraphqlQuery(`
mutation { mutation {
bingo { bingo {
@ -108,6 +137,7 @@ async function createLobby() {
console.error(response); console.error(response);
return false; return false;
} }
}
} }
/** /**
@ -197,8 +227,8 @@ async function executeCommand(message) {
break; break;
case '/username': case '/username':
if (command[2]) { if (command[2]) {
await setUsername(command[2]); let uname = await setUsername(command[2]);
reply(`Your username is <b>${command[2]}</b> now.`) reply(`Your username is <b>${uname}</b> now.`);
} else { } else {
reply('You need to provide a username'); reply('You need to provide a username');
} }
@ -278,7 +308,10 @@ async function setLobbySettings(words, gridSize) {
return response.data.bingo.mutateLobby.setWords.words; return response.data.bingo.mutateLobby.setWords.words;
} else { } else {
console.error(response); console.error(response);
showError('Error when setting lobby words.'); if (response.errors)
showError(response.errors[0].message);
else
showError('Error when submitting lobby settings.');
} }
} }
@ -289,8 +322,10 @@ async function setLobbySettings(words, gridSize) {
async function startRound() { async function startRound() {
let textinput = document.querySelector('#input-bingo-words'); let textinput = document.querySelector('#input-bingo-words');
let words = getLobbyWords(); let words = getLobbyWords();
if (words.length > 0) {
let gridSize = document.querySelector('#input-grid-size').value || 3; let gridSize = document.querySelector('#input-grid-size').value || 3;
let resultWords = await setLobbySettings(words, gridSize); let resultWords = await setLobbySettings(words, gridSize);
if (resultWords) {
textinput.value = resultWords.map(x => x.content).join('\n'); textinput.value = resultWords.map(x => x.content).join('\n');
let response = await postGraphqlQuery(` let response = await postGraphqlQuery(`
mutation($lobbyId:ID!){ mutation($lobbyId:ID!){
@ -309,6 +344,10 @@ async function startRound() {
console.error(response); console.error(response);
showError('Error when starting round.'); showError('Error when starting round.');
} }
}
} else {
throw new Error('No words provided.');
}
} }
/** /**
@ -429,11 +468,9 @@ function displayWinner(roundInfo) {
<button id="button-lobbyreturn" onclick="window.location.reload()">Return to Lobby!</button> <button id="button-lobbyreturn" onclick="window.location.reload()">Return to Lobby!</button>
`; `;
greyoverDiv.setAttribute('class', 'greyover'); greyoverDiv.setAttribute('class', 'greyover');
//winnerDiv.onclick = () => {
// window.location.reload();
//};
document.body.append(greyoverDiv); document.body.append(greyoverDiv);
document.body.appendChild(winnerDiv); document.body.appendChild(winnerDiv);
spawnNotification(`${name} has won!`, 'Bingo');
} }
/** /**
@ -471,7 +508,7 @@ async function statusWrap(func) {
/** /**
* Loads information about the rounds winner and the round stats. * Loads information about the rounds winner and the round stats.
* @returns {Promise<void>} * @returns {Promise<boolean>}
*/ */
async function loadWinnerInfo() { async function loadWinnerInfo() {
let response = await postGraphqlQuery(` let response = await postGraphqlQuery(`
@ -505,8 +542,9 @@ async function loadWinnerInfo() {
/** /**
* Adds a message to the chat * Adds a message to the chat
* @param messageObject {Object} - the message object returned by graphql * @param messageObject {Object} - the message object returned by graphql
* @param player {Number} - the id of the player
*/ */
function addChatMessage(messageObject) { function addChatMessage(messageObject, player) {
let msgSpan = document.createElement('span'); let msgSpan = document.createElement('span');
msgSpan.setAttribute('class', 'chatMessage'); msgSpan.setAttribute('class', 'chatMessage');
msgSpan.setAttribute('msg-type', messageObject.type); msgSpan.setAttribute('msg-type', messageObject.type);
@ -520,6 +558,8 @@ function addChatMessage(messageObject) {
<span class="chatMessageContent ${messageObject.type}">${messageObject.htmlContent}</span>`; <span class="chatMessageContent ${messageObject.type}">${messageObject.htmlContent}</span>`;
} }
if (messageObject.author && messageObject.author.id !== player)
spawnNotification(messageObject.content, messageObject.author.username);
let chatContent = document.querySelector('#chat-content'); let chatContent = document.querySelector('#chat-content');
chatContent.appendChild(msgSpan); chatContent.appendChild(msgSpan);
chatContent.scrollTop = chatContent.scrollHeight; // auto-scroll to bottom chatContent.scrollTop = chatContent.scrollHeight; // auto-scroll to bottom
@ -552,12 +592,17 @@ async function refreshChat() {
let response = await postGraphqlQuery(` let response = await postGraphqlQuery(`
query($lobbyId:ID!){ query($lobbyId:ID!){
bingo { bingo {
player {
id
}
lobby(id:$lobbyId) { lobby(id:$lobbyId) {
messages { messages {
id id
type type
htmlContent htmlContent
content
author { author {
id
username username
} }
} }
@ -568,7 +613,7 @@ async function refreshChat() {
let messages = response.data.bingo.lobby.messages; let messages = response.data.bingo.lobby.messages;
for (let message of messages) for (let message of messages)
if (!document.querySelector(`.chatMessage[msg-id="${message.id}"]`)) if (!document.querySelector(`.chatMessage[msg-id="${message.id}"]`))
addChatMessage(message); addChatMessage(message, response.data.bingo.player.id);
} else { } else {
showError('Failed to refresh messages'); showError('Failed to refresh messages');
console.error(response); console.error(response);
@ -678,6 +723,7 @@ async function refreshLobby() {
} }
currentRound { currentRound {
id id
status
} }
words { words {
content content
@ -695,8 +741,10 @@ async function refreshLobby() {
wordContainer.innerHTML = `<span class="bingoWord"> wordContainer.innerHTML = `<span class="bingoWord">
${response.data.bingo.lobby.words.map(x => x.content).join('</span><span class="bingoWord">')}</span>`; ${response.data.bingo.lobby.words.map(x => x.content).join('</span><span class="bingoWord">')}</span>`;
if (currentRound && currentRound.id && Number(currentRound.id) !== Number(getRoundParam())) if (currentRound && currentRound.status === 'ACTIVE' && Number(currentRound.id) !== Number(getRoundParam())) {
insertParam('r', currentRound.id); insertParam('r', currentRound.id);
spawnNotification('The round started!', 'Bingo');
}
} else { } else {
showError('Failed to refresh lobby'); showError('Failed to refresh lobby');
@ -771,3 +819,14 @@ window.addEventListener("keydown", async (e) => {
} }
} }
}, false); }, false);
window.onload = async () => {
if ("Notification" in window)
if (Notification.permission !== 'denied') {
try {
await Notification.requestPermission();
} catch (err) {
showError(err.message);
}
}
};

@ -128,6 +128,10 @@ async function indicateStatus(func, indicatorSelector) {
} }
} }
/**
* posts to accept cookies.
* @returns {Promise<void>}
*/
async function acceptCookies() { async function acceptCookies() {
await postGraphqlQuery(` await postGraphqlQuery(`
mutation { mutation {
@ -135,3 +139,25 @@ async function acceptCookies() {
}`); }`);
document.querySelector('#cookie-container').remove(); document.querySelector('#cookie-container').remove();
} }
/**
* Gets the names for the windows hidden and visibility change events and properties
* @returns {{hidden: string, visibilityChange: string}}
*/
function getHiddenNames() {
let hidden, visibilityChange;
if (typeof document.hidden !== "undefined") { // Opera 12.10 and Firefox 18 and later support
hidden = "hidden";
visibilityChange = "visibilitychange";
} else if (typeof document.msHidden !== "undefined") {
hidden = "msHidden";
visibilityChange = "msvisibilitychange";
} else if (typeof document.webkitHidden !== "undefined") {
hidden = "webkitHidden";
visibilityChange = "webkitvisibilitychange";
}
return {
hidden: hidden,
visibilityChange: visibilityChange
};
}

@ -44,9 +44,11 @@
border-radius: 0 border-radius: 0
color: $primarySurface color: $primarySurface
border: 1px solid $inactive border: 1px solid $inactive
position: relative
.playerWins .playerWins
margin-left: 1em position: absolute
right: 0
.kickPlayerButton .kickPlayerButton
color: $inactive color: $inactive
@ -183,13 +185,18 @@
#statusbar #statusbar
display: grid display: grid
grid-template: 100% / 1rem calc(80% - 1rem) 20% grid-template: 100% 0 / 1rem calc(75% - 1rem) 25%
background-color: darken($primary, 5%) background-color: darken($primary, 5%)
margin: 0.5rem 0 0 0 margin: 0.5rem 0 0 0
padding: 0.25rem padding: 0.25rem
vertical-align: middle vertical-align: middle
font-size: 0.8em font-size: 0.8em
text-align: start text-align: start
position: absolute
height: 1rem
bottom: 0
left: 0
width: calc(100% - 0.5rem)
#status-indicator #status-indicator
height: 1rem height: 1rem
@ -204,6 +211,8 @@
color: $errorText color: $errorText
#container-info #container-info
@include gridPosition(1, 2, 3, 4)
text-align: left
width: 100% width: 100%
height: 100% height: 100%
margin: auto margin: auto
@ -237,9 +246,6 @@
button button
width: 100% width: 100%
#statusbar
@include gridPosition(5, 6, 1, 4)
#container-bingo-lobby #container-bingo-lobby
@include fillWindow @include fillWindow
overflow: hidden overflow: hidden
@ -264,9 +270,6 @@
@include gridPosition(3, 4, 4, 5) @include gridPosition(3, 4, 4, 5)
background-color: lighten($primary, 5%) background-color: lighten($primary, 5%)
#statusbar
@include gridPosition(4, 5, 1, 6)
#container-bingo-round #container-bingo-round
@include fillWindow @include fillWindow
overflow: hidden overflow: hidden
@ -287,6 +290,3 @@
#container-bingo-button #container-bingo-button
@include gridPosition(1, 2, 1, 2) @include gridPosition(1, 2, 1, 2)
#statusbar
@include gridPosition(4, 5, 1, 3)

@ -13,9 +13,11 @@
#info #info
@include gridPosition(1, 2, 2, 3) @include gridPosition(1, 2, 2, 3)
margin: auto margin: auto
text-align: center
h1, p h1, p, a
margin: auto margin: auto
text-align: center
#bingo-button #bingo-button
background-color: $secondary background-color: $secondary

@ -29,7 +29,8 @@
body body
background-color: $primary background-color: $primary
color: $primarySurface color: $primarySurface
font-family: Arial, sans-serif font-family: $fontRegular
scrollbar-color: $secondary lighten($primary, 5)
button button
@include default-element @include default-element
@ -52,6 +53,7 @@ input
transition-duration: 0.2s transition-duration: 0.2s
font-size: 1.2rem font-size: 1.2rem
padding: 0.7rem padding: 0.7rem
font-family: $fontRegular
input:focus input:focus
background-color: lighten($primary, 15%) background-color: lighten($primary, 15%)
@ -64,9 +66,10 @@ textarea
background-color: lighten($primary, 15%) background-color: lighten($primary, 15%)
color: $primarySurface color: $primarySurface
resize: none resize: none
font-family: $fontRegular
a a
color: $secondary color: mix($secondary, $primarySurface, 75%)
mark mark
background-color: $secondary background-color: $secondary
@ -75,6 +78,12 @@ mark
mark > a mark > a
color: white color: white
code
background-color: transparentize(darken($primary, 10%), 0.5)
border-radius: 0.25em
font-family: $fontCode
padding: 0 0.25em
::-webkit-scrollbar ::-webkit-scrollbar
width: 12px width: 12px
height: 12px height: 12px

@ -1,3 +1,5 @@
@import url('https://fonts.googleapis.com/css?family=Ubuntu|Ubuntu+Mono&display=swap')
$primary: #223 $primary: #223
$primarySurface: white $primarySurface: white
$secondary: teal $secondary: teal
@ -7,3 +9,5 @@ $error: #a00
$errorText: #f44 $errorText: #f44
$success: #0a0 $success: #0a0
$pending: #aa0 $pending: #aa0
$fontRegular: 'Ubuntu', Arial, sans-serif
$fontCode: 'Ubuntu Mono', monospace

@ -12,7 +12,6 @@ const express = require('express'),
globals = require('../lib/globals'); globals = require('../lib/globals');
let pgPool = globals.pgPool; let pgPool = globals.pgPool;
let playerNames = utils.getFileLines('./misc/usernames.txt').filter(x => (x && x.length > 0));
/** /**
* Class to manage the bingo data in the database. * Class to manage the bingo data in the database.
@ -333,6 +332,25 @@ class BingoDataManager {
return await this._queryFirstResult(this.queries.addWordToGrid.sql, [gridId, wordId, row, column]); return await this._queryFirstResult(this.queries.addWordToGrid.sql, [gridId, wordId, row, column]);
} }
/**
* Adds words to the grid
* @param gridId {Number} - the id of the grid
* @param words {Array<{wordId: Number, row: Number, column:Number}>}
* @returns {Promise<void>}
*/
async addWordsToGrid(gridId, words) {
let valueSql = buildSqlParameters(4, words.length, 0);
let values = [];
for (let word of words) {
values.push(gridId);
values.push(word.wordId);
values.push(word.row);
values.push(word.column);
}
return await this._queryFirstResult(
this.queries.addWordToGridStrip.sql + valueSql + ' RETURNING *', values);
}
/** /**
* Returns all words in the grid with location * Returns all words in the grid with location
* @param gridId {Number} - the id of the grid * @param gridId {Number} - the id of the grid
@ -707,6 +725,22 @@ class PlayerWrapper {
return new GridWrapper(result.id); return new GridWrapper(result.id);
} }
/**
* Returns if the user has a valid grid.
* @param lobbyId
* @returns {Promise<boolean>}
*/
async hasGrid(lobbyId) {
let grid = await this.grid({lobbyId: lobbyId});
if (grid) {
let fields = await grid.fields();
let lobbyWrapper = new LobbyWrapper(lobbyId);
return fields.length === (await lobbyWrapper.gridSize()) ** 2;
} else {
return false;
}
}
/** /**
* Returns the username of the player * Returns the username of the player
* @returns {Promise<String|null>} * @returns {Promise<String|null>}
@ -1006,10 +1040,11 @@ class LobbyWrapper {
let gridId = (await bdm.addGrid(this.id, player.id, currentRound)).id; let gridId = (await bdm.addGrid(this.id, player.id, currentRound)).id;
let gridContent = generateWordGrid(this.grid_size, words); let gridContent = generateWordGrid(this.grid_size, words);
let gridWords = [];
for (let i = 0; i < gridContent.length; i++) for (let i = 0; i < gridContent.length; i++)
for (let j = 0; j < gridContent[i].length; j++) for (let j = 0; j < gridContent[i].length; j++)
// eslint-disable-next-line no-await-in-loop gridWords.push({wordId: gridContent[i][j].id, row: i, column: j});
await bdm.addWordToGrid(gridId, gridContent[i][j].id, i, j); await bdm.addWordsToGrid(gridId, gridWords);
} }
} }
@ -1025,6 +1060,7 @@ class LobbyWrapper {
await currentRound.setFinished(); await currentRound.setFinished();
await this._createRound(); await this._createRound();
await this._createGrids(); await this._createGrids();
await this.setRoundStatus('ACTIVE');
} }
} }
@ -1073,6 +1109,7 @@ class LobbyWrapper {
*/ */
async setWords(words) { async setWords(words) {
if (words.length > 0 && !await this.roundActive()) { if (words.length > 0 && !await this.roundActive()) {
words = words.map(x => x.substring(0, 200));
let {newWords, removedWords} = await this._filterWords(words); let {newWords, removedWords} = await this._filterWords(words);
for (let word of newWords) for (let word of newWords)
await this.addWord(word); await this.addWord(word);
@ -1156,6 +1193,23 @@ class LobbyWrapper {
} }
} }
/**
* Returns the parameterized value sql for inserting.
* @param columnCount
* @param rowCount
* @param [offset]
* @returns {string}
*/
function buildSqlParameters(columnCount, rowCount, offset) {
let sql = '';
for (let i = 0; i < rowCount; i++) {
sql += '(';
for (let j = 0; j < columnCount; j++)
sql += `$${(i*columnCount)+j+1+offset},`;
sql = sql.replace(/,$/, '') + '),';
}
return sql.replace(/,$/, '');
}
/** /**
* Replaces tag signs with html-escaped signs. * Replaces tag signs with html-escaped signs.
@ -1341,6 +1395,20 @@ async function getGridData(lobbyId, playerId) {
return {fields: fieldGrid, bingo: await grid.bingo()}; return {fields: fieldGrid, bingo: await grid.bingo()};
} }
/**
* Returns resolved message data.
* @param lobbyId
* @returns {Promise<Array>}
*/
async function getMessageData(lobbyId) {
let lobbyWrapper = new LobbyWrapper(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()}));
return msgReturn;
}
// -- Router stuff // -- Router stuff
@ -1361,10 +1429,11 @@ router.get('/', async (req, res) => {
let info = req.session.acceptedCookies? null: globals.cookieInfo; let info = req.session.acceptedCookies? null: globals.cookieInfo;
let lobbyWrapper = new LobbyWrapper(req.query.g); let lobbyWrapper = new LobbyWrapper(req.query.g);
let playerWrapper = new PlayerWrapper(playerId); let playerWrapper = new PlayerWrapper(playerId);
if (playerId && await playerWrapper.exists() && req.query.g && await lobbyWrapper.exists()) { if (playerId && await playerWrapper.exists() && req.query.g && await lobbyWrapper.exists()) {
let lobbyId = req.query.g; let lobbyId = req.query.g;
if (!(await lobbyWrapper.roundActive())) { if (!(await lobbyWrapper.roundActive() && await playerWrapper.hasGrid(lobbyId))) {
if (!await lobbyWrapper.hasPlayer(playerId)) if (!await lobbyWrapper.hasPlayer(playerId))
await lobbyWrapper.addPlayer(playerId); await lobbyWrapper.addPlayer(playerId);
let playerData = await getPlayerData(lobbyWrapper); let playerData = await getPlayerData(lobbyWrapper);
@ -1377,10 +1446,11 @@ router.get('/', async (req, res) => {
words: words, words: words,
wordString: words.join('\n'), wordString: words.join('\n'),
gridSize: await lobbyWrapper.gridSize(), gridSize: await lobbyWrapper.gridSize(),
info: info info: info,
messages: await getMessageData(lobbyId)
}); });
} else { } else {
if (await lobbyWrapper.hasPlayer(playerId)) { if (await lobbyWrapper.hasPlayer(playerId) && await playerWrapper.hasGrid(lobbyId)) {
let playerData = await getPlayerData(lobbyWrapper); let playerData = await getPlayerData(lobbyWrapper);
let grid = await getGridData(lobbyId, playerId); let grid = await getGridData(lobbyId, playerId);
let admin = await lobbyWrapper.admin(); let admin = await lobbyWrapper.admin();
@ -1389,21 +1459,11 @@ router.get('/', async (req, res) => {
grid: grid, grid: grid,
isAdmin: (playerId === admin.id), isAdmin: (playerId === admin.id),
adminId: admin.id, adminId: admin.id,
info: info info: info,
messages: await getMessageData(lobbyId)
}); });
} else { } else {
let playerData = await getPlayerData(lobbyWrapper); res.redirect('/bingo');
let admin = await lobbyWrapper.admin();
let words = await getWordsData(lobbyWrapper);
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
});
} }
} }
} else { } else {
@ -1436,16 +1496,24 @@ router.graphqlResolver = async (req, res) => {
}, },
// mutations // mutations
setUsername: async ({username}) => { setUsername: async ({username}) => {
username = replaceTagSigns(username.substring(0, 30)); // only allow 30 characters username = replaceTagSigns(username.substring(0, 30)).replace(/[^\w- ;[\]]/g, ''); // only allow 30 characters
if (username.length > 0) {
let playerWrapper = new PlayerWrapper(playerId); let playerWrapper = new PlayerWrapper(playerId);
if (!playerId || !(await playerWrapper.exists())) { if (!playerId || !(await playerWrapper.exists())) {
req.session.bingoPlayerId = (await bdm.addPlayer(username)).id; req.session.bingoPlayerId = (await bdm.addPlayer(username)).id;
playerId = req.session.bingoPlayerId; playerId = req.session.bingoPlayerId;
} else { } else {
let oldName = await playerWrapper.username();
await bdm.updatePlayerUsername(playerId, 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); return new PlayerWrapper(playerId);
} else {
res.status(400);
return new GraphQLError('Username too short!');
}
}, },
createLobby: async({gridSize}) => { createLobby: async({gridSize}) => {
if (playerId) if (playerId)
@ -1508,6 +1576,7 @@ router.graphqlResolver = async (req, res) => {
} }
}, },
setGridSize: async ({gridSize}) => { setGridSize: async ({gridSize}) => {
if (gridSize > 0 && gridSize < 6) {
let admin = await lobbyWrapper.admin(); let admin = await lobbyWrapper.admin();
if (admin.id === playerId) { if (admin.id === playerId) {
await lobbyWrapper.setGridSize(gridSize); await lobbyWrapper.setGridSize(gridSize);
@ -1516,6 +1585,10 @@ router.graphqlResolver = async (req, res) => {
res.status(403); res.status(403);
return new GraphQLError('You are not an admin'); return new GraphQLError('You are not an admin');
} }
} else {
res.status(400);
return new GraphQLError('Grid size too big!');
}
}, },
setWords: async({words}) => { setWords: async({words}) => {
let admin = await lobbyWrapper.admin(); let admin = await lobbyWrapper.admin();

@ -0,0 +1,17 @@
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});
});
module.exports = router;

@ -6,7 +6,7 @@ const globals = require('../lib/globals');
/* GET home page. */ /* GET home page. */
router.get('/', function(req, res) { router.get('/', function(req, res) {
let info = req.session.acceptedCookies? null: globals.cookieInfo; let info = req.session.acceptedCookies? null: globals.cookieInfo;
res.render('index', { title: 'Trivernis.net', info: info}); res.render('index', { title: 'Trivernis.net', info: info, contact: 'mailto:trivernis@gmail.com'});
}); });
module.exports = router; module.exports = router;

@ -45,7 +45,7 @@ CREATE TABLE IF NOT EXISTS bingo.rounds (
id serial UNIQUE PRIMARY KEY, id serial UNIQUE PRIMARY KEY,
start timestamp DEFAULT NOW(), start timestamp DEFAULT NOW(),
finish timestamp, finish timestamp,
status varchar(8) DEFAULT 'ACTIVE', status varchar(8) DEFAULT 'BUILDING',
lobby_id serial references bingo.lobbys(id) ON DELETE CASCADE, lobby_id serial references bingo.lobbys(id) ON DELETE CASCADE,
winner integer winner integer
); );

@ -218,6 +218,12 @@ getGridInfo:
addWordToGrid: addWordToGrid:
sql: INSERT INTO bingo.grid_words (grid_id, word_id, grid_row, grid_column) VALUES ($1, $2, $3, $4) RETURNING *; sql: INSERT INTO bingo.grid_words (grid_id, word_id, grid_row, grid_column) VALUES ($1, $2, $3, $4) RETURNING *;
# inserts grid-word connections into the database
# params:
# ! need to be set in the sql
addWordToGridStrip:
sql: INSERT INTO bingo.grid_words (grid_id, word_id, grid_row, grid_column) VALUES
# sets a bingo field to submitted = not submitted # sets a bingo field to submitted = not submitted
# params: # params:
# - {Number} - the id of the grid # - {Number} - the id of the grid

@ -1,6 +1,11 @@
div(id='container-chat') div(id='container-chat')
style(id='js-style') style(id='js-style')
div(id='chat-content') div(id='chat-content')
for message in messages
span.chatMessage(type=message.type msg-id=message.id)
if message.type === 'USER'
span.chatUsername= `${message.username}: `
span(class=`chatMessageContent ${message.type}`)!= message.htmlContent
input( input(
id='chat-input' id='chat-input'
type='text' type='text'

@ -2,6 +2,10 @@ div(id='statusbar')
div(id='status-indicator' class='statusIndicator' status='idle') div(id='status-indicator' class='statusIndicator' status='idle')
span(id='error-message') span(id='error-message')
span(id='container-info') span(id='container-info')
| please report bugs a(href='https://github.com/Trivernis/whooshy/issues') Bug/Feature
| |
a(href='https://github.com/Trivernis/whooshy/issues') here |
span |
|
|
a(href='mailto:trivernis@gmail.com') Contact

@ -0,0 +1,6 @@
html
head
include ../includes/head
body
include ../includes/info-container
div(id='changelog')!= changelog

@ -5,6 +5,7 @@ block content
div(id='info') div(id='info')
h1= title h1= title
p Welcome to #{title} p Welcome to #{title}
a(href=contact) Contact
button(id='bingo-button' onclick='window.location.href="/bingo"') Bingo button(id='bingo-button' onclick='window.location.href="/bingo"') Bingo
include includes/info-container include includes/info-container

Loading…
Cancel
Save