Bingo Sql queries

- added sql queries to the BingoSql class
- added Lobby class
- moved bingo-wrappers (will probably be changed)
typescript
Trivernis 5 years ago
parent aff0579a45
commit 059721218a

@ -3,7 +3,7 @@ import {Pool, PoolClient, QueryConfig, QueryResult} from "pg";
const logger = globals.logger;
class SqlTransaction {
export class SqlTransaction {
constructor(private client: PoolClient) {
}
@ -44,7 +44,7 @@ class SqlTransaction {
}
}
class QueryHelper {
export class QueryHelper {
private pool: Pool;
constructor(pgPool: Pool) {
@ -96,4 +96,19 @@ class QueryHelper {
}
}
export default QueryHelper
/**
* Returns the parameterized value sql for inserting
* @param columnCount
* @param rowCount
* @param [offset]
*/
export function buildSqlParameters(columnCount: number, rowCount: number, offset?: number): string {
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(/,$/, '');
}

@ -1,5 +1,5 @@
import {Router} from 'express';
import {Server} from 'socket.io';
import {Namespace, Server} from 'socket.io';
/**
* Abstract Route class to be implemented by each route.
@ -7,13 +7,14 @@ import {Server} from 'socket.io';
* for each route.
*/
abstract class Route {
private io?: Server;
protected io?: Server;
protected ions?: Namespace;
public router?: Router;
public resolver?: object;
abstract async init(...params: any): Promise<any>;
abstract async destroy(...params: any): Promise<any>;
abstract async resolver(request: any, response: any): Promise<object>;
}
export default Route;

@ -0,0 +1,59 @@
import {BingoSql, LobbyRow} from "../db/BingoSql";
import {Room} from 'socket.io';
export class BingoLobby {
public readonly room: Room;
private lobbyId: number;
private adminId: number;
private gridSize: number;
private currentRoundId: number;
private initialized: boolean = false;
constructor(private bingoSql: BingoSql, adminId: number, room: Room, id?: number) {
this.adminId = adminId;
this.lobbyId = id;
this.room = room;
}
/**
* Readonly for others to avoid complications
*/
public get id() {
return this.lobbyId;
}
/**
* Initializes the lobby. Creates one if no id is assigned.
*/
public async init() {
let data: LobbyRow;
if (!this.id) {
data = await this.bingoSql.createLobby(this.adminId);
} else {
data = await this.bingoSql.getLobby(this.id);
}
this.assignData(data);
this.initialized = true;
}
/**
* Reloads the lobby data if neccessary or forced.
* @param force
*/
private async loadData(force: boolean) {
if(!this.initialized || force)
await this.init();
}
/**
* Loads the data from the row into the parameters.
* @param row
*/
private assignData(row: LobbyRow) {
this.lobbyId = row.id;
this.adminId = row.admin_id;
this.gridSize = row.grid_size;
this.currentRoundId = row.current_round;
}
}

@ -0,0 +1,18 @@
export class GridFieldWrapper {
public row: any;
public column: any;
public submitted: boolean;
public word: any;
public grid: any;
/**
* @param row {Object} - the resulting row
*/
constructor(row: any) {
this.row = row.grid_row;
this.column = row.grid_column;
this.submitted = row.submitted;
// TODO
//this.word = new WordWrapper(row.word_id, row);
//this.grid = new GridWrapper(row.grid_id);
}
}

@ -1,4 +1,336 @@
class BingoSql {
constructor() {
import {Pool} from "pg";
import {QueryHelper, buildSqlParameters} from '../QueryHelper';
enum MessageType {
USER = "USER",
INFO = "INFO",
ERROR = "ERROR"
}
enum RoundStatus {
BUILDING = "BUILDING",
FINISHED = "FINISHED",
ACTIVE = "ACTIVE"
}
export type PlayerRow = {id: number, username: string, expire: string};
export type LobbyRow = {id: number, admin_id: number, grid_size: number, current_round: number, expire: string};
export type LobbyPlayerRow = {player_id: number, lobby_id: number, score: number};
export type WordRow = {id: number|BigInteger, lobby_id: number, heared: number, content: string};
export type MessageRow = {id: number|BigInteger, content: string, player_id: number, lobby_id: number, type: MessageType, created: string};
export type RoundRow = {id: number, start: string, finish: string, status: RoundStatus, lobby_id: number, winner: number};
export type GridRow = {id: number, player_id: number, lobby_id: number, round_id: number};
export type GridWordRow = {grid_id: number, word_id: number, grid_row: number, grid_column: number, submitted: boolean};
interface BingoDatabaseInterface {
addPlayer(name: string): Promise<PlayerRow>;
getPlayer(playerId: number): Promise<PlayerRow>;
updatePlayerUsername(playerId: number, name: string): Promise<PlayerRow>;
updatePlayerExpiration(playerId: number): Promise<void>;
getPlayerWins(playerId: number, lobbyId: number): Promise<number>
createLobby(playerId: number, gridSize: number): Promise<LobbyRow>;
setLobbyGridSize(lobbyId: number, gridSize: number): Promise<LobbyRow>;
setLobbyRound(lobbyId: number, roundId: number): Promise<LobbyRow>;
addPlayerToLobby(playerId: number, lobbyId: number): Promise<LobbyPlayerRow>;
removePlayerFromLobby(playerId: number, lobbyId: number): Promise<void>;
getLobby(lobbyId: number): Promise<LobbyRow>;
getLobbyIds(): Promise<number[]>;
checkPlayerInLobby(playerId: number, lobbyId: number): Promise<boolean>;
getLobbyMembers(lobbyId: number): Promise<LobbyPlayerRow[]>;
getLobbyMessages(lobbyId: number, limit: number): Promise<MessageRow[]>;
updateLobbyExpiration(lobbyId: number): Promise<void>;
clearGrids(lobbyId: number): Promise<void>;
clearWords(lobbyId: number): Promise<void>;
addWord(lobbyId: number, word: string): Promise<WordRow>;
removeWord(lobbyid: number, wordId: number): Promise<void>;
getWords(lobbyId: number): Promise<WordRow[]>;
getWord(wordId: number): Promise<WordRow>;
getRounds(lobbyId: number): Promise<RoundRow[]>;
getRound(roundId: number): Promise<RoundRow>;
updateRound(roundId: number, status: RoundStatus): Promise<RoundRow>;
setRoundFinished(roundId: number): Promise<RoundRow>;
setRoundWinner(roundId: number, playerId: number): Promise<RoundRow>;
addGrid(lobbyId: number, playerId: number, roundId: number): Promise<GridRow>;
addGridWords(words: GridWordRow[]): Promise<GridRow>;
getGridWords(gridId: number): Promise<(GridWordRow & WordRow)[]>;
getGridField(gridId: number, row: number, column: number): Promise<(GridWordRow & WordRow)>;
addPlayerMessage(lobbyId: number, playerId: number, content: string): Promise<MessageRow>;
addInfoMessage(lobbyId: number, content: string): Promise<MessageRow>;
editMessage(messageId: number, content: string): Promise<MessageRow>;
deleteMessage(messageId: number): Promise<void>;
getMessage(messageId: number): Promise<MessageRow>;
}
export class BingoSql implements BingoDatabaseInterface{
private queryHelper: QueryHelper;
constructor(pgPool: Pool) {
this.queryHelper = new QueryHelper(pgPool);
}
async addPlayer(name: string): Promise<PlayerRow> {
return await this.queryHelper.first({
text: "INSERT INTO bingo.players (username) VALUES ($1) RETURNING *;",
values: [name]
});
}
async getPlayer(playerId: number): Promise<PlayerRow> {
return await this.queryHelper.first({
text: "SELECT * FROM bingo.players WHERE id = $1;",
values: [playerId]
});
}
async updatePlayerUsername(playerId: number, name: string): Promise<PlayerRow> {
return await this.queryHelper.first({
text: "UPDATE bingo.players SET username = $1 WHERE id = $2 RETURNING *;",
values: [name, playerId]
});
}
async updatePlayerExpiration(playerId: number): Promise<void> {
await this.queryHelper.all({
name: 'update-player-expire',
text: "UPDATE bingo.players SET expire = (NOW() + interval '24 hours') WHERE id = $1;",
values: [playerId]
});
}
async getPlayerWins(playerId: number, lobbyId: number): Promise<number> {
return (await this.queryHelper.first({
name: "select-player-wins",
text: " SELECT COUNT(*) wins FROM bingo.rounds WHERE rounds.lobby_id = $1 AND rounds.winner = $2;",
values: [lobbyId, playerId]
})).wins;
}
async createLobby(playerId: number, gridSize: number=3): Promise<LobbyRow> {
return await this.queryHelper.first({
text: "INSERT INTO bingo.lobbys (admin_id, grid_size) VALUES ($1, $2) RETURNING *;",
values: [playerId, gridSize]
});
}
async setLobbyGridSize(lobbyId: number, gridSize: number): Promise<LobbyRow> {
return await this.queryHelper.first({
text: "INSERT INTO bingo.lobbys (admin_id, grid_size) VALUES ($1, $2) RETURNING *;",
values: [lobbyId, gridSize]
});
}
async setLobbyRound(lobbyId: number, roundId: number): Promise<LobbyRow> {
return await this.queryHelper.first({
text: "UPDATE bingo.lobbys SET current_round = $2 WHERE id = $1 RETURNING *;",
values: [lobbyId, roundId]
});
}
async addPlayerToLobby(playerId: number, lobbyId: number): Promise<LobbyPlayerRow> {
return await this.queryHelper.first({
text: "INSERT INTO bingo.lobby_players (player_id, lobby_id) VALUES ($1, $2) RETURNING *;",
values: [playerId, lobbyId]
});
}
async removePlayerFromLobby(playerId: number, lobbyId: number): Promise<void> {
await this.queryHelper.all({
text: "DELETE FROM bingo.lobby_players WHERE player_id = $1 AND lobby_id = $2;",
values: [playerId, lobbyId]
});
}
async getLobby(lobbyId: number): Promise<LobbyRow> {
return await this.queryHelper.first({
text: "SELECT * FROM bingo.lobbys WHERE lobbys.id = $1;",
values: [lobbyId]
});
}
async getLobbyIds(): Promise<number[]> {
return (await this.queryHelper.all({
text: "SELECT lobbys.id FROM bingo.lobbys;"
})).map(x => x.id);
}
async checkPlayerInLobby(playerId: number, lobbyId: number): Promise<boolean> {
return !!await this.queryHelper.first({
text: "SELECT * FROM bingo.lobby_players lp WHERE lp.player_id = $1 AND lp.lobby_id = $2;",
values: [playerId, lobbyId]
});
}
async getLobbyMembers(lobbyId: number): Promise<LobbyPlayerRow[]> {
return await this.queryHelper.all({
text: "SELECT * FROM bingo.lobby_players WHERE lobby_players.lobby_id = $1;",
values: [lobbyId]
});
}
async getLobbyMessages(lobbyId: number, limit: number = 20): Promise<MessageRow[]> {
return await this.queryHelper.all({
text: "SELECT * FROM bingo.messages WHERE messages.lobby_id = $1 ORDER BY messages.created DESC LIMIT $2;",
values: [lobbyId, limit]
});
}
async updateLobbyExpiration(lobbyId: number): Promise<void> {
await this.queryHelper.all({
text: "UPDATE bingo.lobbys SET expire = (NOW() + interval '4 hours') WHERE id = $1 RETURNING *;",
values: [lobbyId]
});
}
async clearGrids(lobbyId: number): Promise<void> {
await this.queryHelper.all({
text: "DELETE FROM bingo.grids WHERE lobby_id = $1;",
values: [lobbyId]
});
}
async clearWords(lobbyId: number): Promise<void> {
await this.queryHelper.all({
text: "DELETE FROM bingo.words WHERE lobby_id = $1;",
values: [lobbyId]
});
}
async addWord(lobbyId: number, word: string): Promise<WordRow> {
return await this.queryHelper.first({
text: "INSERT INTO bingo.words (lobby_id, content) VALUES ($1, $2) RETURNING *;",
values: [lobbyId, word]
});
}
async removeWord(lobbyid: number, wordId: number): Promise<void> {
await this.queryHelper.all({
text: "DELETE FROM bingo.words WHERE lobby_id = $1 AND id = $2;",
values: [lobbyid, wordId]
});
}
async getWords(lobbyId: number): Promise<WordRow[]> {
return await this.queryHelper.all({
text: "SELECT * FROM bingo.words WHERE words.lobby_id = $1;",
values: [lobbyId]
});
}
async getWord(wordId: number): Promise<WordRow> {
return await this.queryHelper.first({
text: "SELECT * FROM bingo.words WHERE words.id = $1;",
values: [wordId]
});
}
async getRounds(lobbyId: number): Promise<RoundRow[]> {
return await this.queryHelper.all({
text: "SELECT * FROM bingo.rounds WHERE rounds.lobby_id = $1;",
values: [lobbyId]
});
}
async getRound(roundId: number): Promise<RoundRow> {
return await this.queryHelper.first({
text: "SELECT * FROM bingo.rounds WHERE rounds.id = $1;",
values: [roundId]
});
}
async updateRound(roundId: number, status: RoundStatus): Promise<RoundRow> {
return await this.queryHelper.first({
text: "UPDATE bingo.rounds SET status = $2 WHERE id = $1 RETURNING *;",
values: [roundId, status]
});
}
async setRoundFinished(roundId: number): Promise<RoundRow> {
return await this.queryHelper.first({
text: "UPDATE bingo.rounds SET status = 'FINISHED', finish = NOW() WHERE id = $1 RETURNING *;",
values: [roundId]
});
}
async setRoundWinner(roundId: number, playerId: number): Promise<RoundRow> {
return await this.queryHelper.first({
text: "UPDATE bingo.rounds SET winner = $2 WHERE id = $1 RETURNING *;",
values: [roundId, playerId]
});
}
async addGrid(lobbyId: number, playerId: number, roundId: number): Promise<GridRow> {
return await this.queryHelper.first({
text: "INSERT INTO bingo.grids (player_id, lobby_id, round_id) VALUES ($1, $2, $3) RETURNING *;",
values: [playerId, lobbyId, roundId]
});
}
async addGridWords(words: GridWordRow[]): Promise<GridRow> {
let valueSql = buildSqlParameters(4, words.length, 0);
let values = [];
for (let word of words) {
values.push(word.grid_id);
values.push(word.word_id);
values.push(word.grid_row);
values.push(word.grid_column);
}
return await this.queryHelper.first({
text: `INSERT INTO bingo.grid_words (grid_id, word_id, grid_row, grid_column) VALUES ${valueSql} RETURNING *;`,
values: values
});
}
async getGridWords(gridId: number): Promise<(GridWordRow & WordRow)[]> {
return await this.queryHelper.all({
text: "SELECT * FROM bingo.grid_words, bingo.words WHERE grid_words.grid_id = $1 AND words.id = grid_words.word_id;",
values: [gridId]
});
}
async getGridField(gridId: number, row: number, column: number): Promise<GridWordRow & WordRow> {
return await this.queryHelper.first({
text: "SELECT * FROM bingo.grid_words WHERE grid_words.grid_id = $1 AND grid_words.grid_row = $2 and grid_words.grid_column = $3;",
values: [gridId, row, column]
})
}
async addPlayerMessage(lobbyId: number, playerId: number, content: string): Promise<MessageRow> {
return await this.queryHelper.first({
text: "INSERT INTO bingo.messages (player_id, lobby_id, content) VALUES ($1, $2, $3) RETURNING *;",
values: [playerId, lobbyId, content]
});
}
async addInfoMessage(lobbyId: number, content: string): Promise<MessageRow> {
return await this.queryHelper.first({
text: "INSERT INTO bingo.messages (type, lobby_id, content) VALUES ('INFO', $1, $2) RETURNING *;",
values: [lobbyId, content]
});
}
async editMessage(messageId: number, content: string): Promise<MessageRow> {
return await this.queryHelper.first({
text: "UPDATE bingo.messages SET content = $2 WHERE id = $1 RETURNING *;",
values: [messageId, content]
});
}
async deleteMessage(messageId: number): Promise<void> {
await this.queryHelper.all({
text: "DELETE FROM bingo.messages WHERE id = $1;",
values: [messageId]
});
}
async getMessage(messageId: number): Promise<MessageRow> {
return await this.queryHelper.first({
text: "SELECT * from bingo.messages WHERE id = $1;",
values: [messageId]
});
}
}

@ -0,0 +1,19 @@
import * as pg from "pg";
import globals from '../globals';
import {BingoSql} from './BingoSql';
const settings = globals.settings;
export 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
});
namespace queries {
export const bingoSql = new BingoSql(pgPool);
}
export default pgPool;

@ -1,24 +1,33 @@
const utils = require('./utils'),
fsx = require('fs-extra'),
pg = require('pg');
import * as utils from './utils';
import * as fsx from 'fs-extra';
import * as winston from 'winston';
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,
user: settings.postgres.user,
password: settings.postgres.password,
database: settings.postgres.database
}),
cookieInfo: {
/**
* Defines global variables to be used.
*/
namespace globals {
export const settings = utils.readSettings('.');
export const changelog: string = fsx.readFileSync('CHANGELOG.md', 'utf-8');
export const cookieInfo = {
headline: 'This website uses cookies',
content: "This website uses cookies to store your session data. No data is permanently stored.",
onclick: 'acceptCookies()',
id: 'cookie-container',
button: 'All right!'
}
});
};
export const logger = winston.createLogger({
transports: [
new winston.transports.Console({
format: winston.format.combine(
winston.format.timestamp(),
winston.format.colorize(),
winston.format.printf(({ level, message, label, timestamp }) => {
return `${timestamp} [${label}] ${level}: ${message}`;
})
)
})
]
});
}
export default globals;

@ -1,4 +1,5 @@
import {Router} from 'express';
import {Namespace} from 'socket.io';
import {GraphQLError} from "graphql";
import * as markdownIt from 'markdown-it';
@ -6,27 +7,33 @@ import * as utils from '../lib/utils';
import globals from '../lib/globals';
import Route from '../lib/Route';
import * as wrappers from '../lib/bingo-wrappers';
import * as wrappers from '../lib/bingo/bingo-wrappers';
import {BingoSql} from "../lib/db/BingoSql";
import {pgPool} from "../lib/db";
let mdEmoji = require('markdown-it-emoji');
let mdMark = require('markdown-it-mark');
let mdSmartarrows = require('markdown-it-smartarrows');
const pgPool = globals.pgPool;
class BingoRoute extends Route {
bingoSql: BingoSql;
constructor() {
super();
this.router = Router();
this.resolver = this.getResolver();
this.bingoSql = new BingoSql(pgPool);
}
/**
* Inits the Route
*/
public async init() {
public async init(ioNamespace: Namespace) {
this.ions = ioNamespace;
this.ions.on('connection', (socket) => {
socket.on('joinChat', (lobbyId: number) => {
socket.join(`lobby-${lobbyId}`);
});
});
}
/**
@ -37,15 +44,19 @@ class BingoRoute extends Route {
this.resolver = null;
}
private getResolver(): object {
return async (req: any, res: any) => {
/**
* Graphql resolver function
* @param req
* @param res
*/
public async resolver(req: any, res: any): Promise<object> {
let playerId = req.session.bingoPlayerId;
return {
return await {
player: () => {
return playerId;
}
};
};
}
}

@ -2,18 +2,22 @@ import {Router} from 'express';
import {Server} from 'socket.io';
import * as homeRouter from './home';
import * as bingoRouter from './bingo';
import BingoRoute from './bingo';
import changelogRouter from './changelog';
namespace routes {
export const router = Router();
const bingoRoute = new BingoRoute();
router.use('/', homeRouter);
router.use('/bingo', bingoRouter);
router.use('/bingo', bingoRoute.router);
router.use('/changelog', changelogRouter);
export const resolvers = (request: any, response: any):object => {
export const resolvers = async (request: any, response: any): Promise<object> => {
return await {
bingo: await bingoRoute.resolver(request, response)
};
};
export const ioListeners = (io: Server) => {

@ -8,7 +8,8 @@
"sourceMap": true,
"target": "es2018",
"allowJs": true,
"moduleResolution": "node"
"moduleResolution": "node",
"module": "commonjs"
},
"include": [
"src/**/*"

Loading…
Cancel
Save