Started migration to database

- added table creation script for bingo
- added sql scripts for bingo
- added data management class for bingo
- added libs with utils and global variables
pull/15/head
Trivernis 6 years ago
parent f026cd6cca
commit 70fa701fda

@ -16,6 +16,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- sql-file directory `sql` - sql-file directory `sql`
- LICENSE.md (GPL v3) - LICENSE.md (GPL v3)
- eslint to dev dependencys - eslint to dev dependencys
- table creation script for bingo
- sql scripts for bingo
- data management class for bingo
- libs with utils and global variables
## Changed ## Changed

@ -5,41 +5,33 @@ 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'),
pg = require('pg'),
pgSession = require('connect-pg-simple')(session), pgSession = require('connect-pg-simple')(session),
fsx = require('fs-extra'), fsx = require('fs-extra'),
yaml = require('js-yaml'),
graphqlHTTP = require('express-graphql'), graphqlHTTP = require('express-graphql'),
{ buildSchema } = require('graphql'), {buildSchema} = require('graphql'),
{ importSchema } = require('graphql-import'), {importSchema} = require('graphql-import'),
globals = require('./lib/globals'),
settings = globals.settings,
indexRouter = require('./routes/index'), indexRouter = require('./routes/index'),
usersRouter = require('./routes/users'), usersRouter = require('./routes/users'),
riddleRouter = require('./routes/riddle'), riddleRouter = require('./routes/riddle'),
bingoRouter = require('./routes/bingo'); bingoRouter = require('./routes/bingo');
let settings = yaml.safeLoad(fsx.readFileSync('default-config.yaml'));
if (fsx.existsSync('config.yaml'))
Object.assign(settings, yaml.safeLoad(fsx.readFileSync('config.yaml')));
async function init() { async function init() {
// grapql default resolver // grapql default resolver
let graphqlResolver = (request, response) => { let graphqlResolver = async (request, response) => {
return { return {
time: Date.now(), time: Date.now(),
bingo: bingoRouter.graphqlResolver(request, response) bingo: await bingoRouter.graphqlResolver(request, response)
}; };
}; };
// database setup // database setup
let pgPool = new pg.Pool({ let pgPool = globals.pgPool;
host: settings.postgres.host, await pgPool.query(fsx.readFileSync('./sql/init.sql', 'utf-8'));
port: settings.postgres.port, await bingoRouter.init();
user: settings.postgres.user,
password: settings.postgres.password,
database: settings.postgres.database
});
await pgPool.query(fsx.readFileSync('./sql/createSessionTable.sql', 'utf-8'));
let app = express(); let app = express();
@ -50,7 +42,7 @@ async function init() {
app.use(logger('dev')); app.use(logger('dev'));
app.use(express.json()); app.use(express.json());
app.use(express.urlencoded({ extended: false })); app.use(express.urlencoded({extended: false}));
app.use(cookieParser()); app.use(cookieParser());
app.use(session({ app.use(session({
store: new pgSession({ store: new pgSession({
@ -61,7 +53,7 @@ async function init() {
resave: false, resave: false,
saveUninitialized: true, saveUninitialized: true,
cookie: { cookie: {
maxAge: 30 * 24 * 60 * 60 * 1000 // maxAge 30 days maxAge: 7 * 24 * 60 * 60 * 1000 // maxAge 7 days
} }
})); }));
app.use('/sass', compileSass({ app.use('/sass', compileSass({
@ -76,22 +68,22 @@ 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('/graphql', graphqlHTTP((request, response) => { app.use('/graphql', graphqlHTTP(async (request, response) => {
return { return await {
schema: buildSchema(importSchema('./graphql/schema.graphql')), schema: buildSchema(importSchema('./graphql/schema.graphql')),
rootValue: graphqlResolver(request, response), rootValue: await graphqlResolver(request, response),
context: {session: request.session}, context: {session: request.session},
graphiql: true 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) { app.use(function (err, req, res) {
// 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 : {};

@ -1,114 +1,128 @@
type BingoMutation { type BingoMutation {
"creates a lobby for a game and returns the lobby id"
createLobby: ID
# creates a game of bingo and returns the game id "joins a lobby and returns the connection"
joinLobby(input: joinLobbyInput): PlayerLobbyConnection
"creates a game of bingo and returns the game"
createGame(input: CreateGameInput!): BingoGame createGame(input: CreateGameInput!): BingoGame
# submit a bingo to the active game session "submit a bingo to the active game session"
submitBingo: BingoGame submitBingo: BingoGame
# toggle a word (heared or not) on the sessions grid "toggle a word (heared or not) on the sessions grid"
toggleWord(input: WordInput!): BingoGrid toggleWord(input: WordInput!): BingoGrid
# set the username of the current session "set the username of the current session"
setUsername(input: UsernameInput!): BingoUser setUsername(input: UsernameInput!): BingoUser
# recreates the active game to a follow-up "recreates the active game to a follow-up"
createFollowupGame: BingoGame createFollowupGame: BingoGame
# sends a message to the current sessions chat "sends a message to the current sessions chat"
sendChatMessage(input: MessageInput!): ChatMessage sendChatMessage(input: MessageInput!): ChatMessage
} }
type BingoQuery { type BingoQuery {
# Returns the currently active bingo game "returns the currently active bingo game"
gameInfo(input: IdInput): BingoGame gameInfo(input: IdInput): BingoGame
# If there is a bingo in the fields. "if there is a bingo in the fields."
checkBingo: Boolean checkBingo: Boolean
# Returns the grid of the active bingo game "returns the grid of the active bingo game"
activeGrid: BingoGrid activeGrid: BingoGrid
} }
type PlayerLobbyConnection {
"the id of the player"
playerId: ID!
"the id of the lobby"
lobbyId: ID!
}
type BingoGame { type BingoGame {
# the id of the bingo game "the id of the bingo game"
id: ID! id: ID!
# the words used in the bingo game "the words used in the bingo game"
words: [String]! words: [String]!
# the size of the square-grid "the size of the square-grid"
gridSize: Int gridSize: Int
# an array of players active in the bingo game "an array of players active in the bingo game"
players(input: IdInput): [BingoUser] players(input: IdInput): [BingoUser]
# the player-ids that scored a bingo # the player-ids that scored a bingo
bingos: [String]! bingos: [String]!
# if the game has already finished "if the game has already finished"
finished: Boolean finished: Boolean
# the id of the followup game if it has been created "the id of the followup game if it has been created"
followup: ID followup: ID
# Returns the last n chat-messages "returns the last n chat-messages"
getMessages(input: MessageQueryInput): [ChatMessage!] getMessages(input: MessageQueryInput): [ChatMessage!]
} }
type BingoUser { type BingoUser {
# the id of the bingo user "the id of the bingo user"
id: ID! id: ID!
# the id of the currently active bingo game "the id of the currently active bingo game"
game: ID game: ID
# the name of the user "the name of the user"
username: String username: String
} }
type BingoGrid { type BingoGrid {
# the grid represented as string matrix "the grid represented as string matrix"
wordGrid: [[String]]! wordGrid: [[String]]!
# the grid represented as bingo field matrix "the grid represented as bingo field matrix"
fieldGrid: [[BingoField]]! fieldGrid: [[BingoField]]!
# if there is a bingo "if there is a bingo"
bingo: Boolean bingo: Boolean
} }
type BingoField { type BingoField {
# the word contained in the bingo field "the word contained in the bingo field"
word: String word: String
# if the word was already heared "if the word was already heared"
submitted: Boolean! submitted: Boolean!
# the base64 encoded word "the base64 encoded word"
base64Word: String base64Word: String
} }
type ChatMessage { type ChatMessage {
# the id of the message "the id of the message"
id: ID! id: ID!
# the content of the message "the content of the message"
content: String! content: String!
# the content of the message rendered by markdown-it # the content of the message rendered by markdown-it
htmlContent: String htmlContent: String
# the type of the message "the type of the message"
type: MessageType! type: MessageType!
# the username of the sender "the username of the sender"
username: String username: String
# the time the message was send (in milliseconds) # the time the message was send (in milliseconds)
@ -119,18 +133,24 @@ type ChatMessage {
# input Types # # input Types #
# # # #
input joinLobbyInput {
"the id of the lobby to join"
lobbyId: ID!
}
input CreateGameInput { input CreateGameInput {
# the words used to fill the bingo grid "the words used to fill the bingo grid"
words: [String!]! words: [String!]!
# the size of the bingo grid "the size of the bingo grid"
size: Int! = 3 size: Int! = 3
} }
input WordInput { input WordInput {
# the normal word string "the normal word string"
word: String word: String
# the base64-encoded word # the base64-encoded word
@ -139,28 +159,28 @@ input WordInput {
input UsernameInput { input UsernameInput {
# the username string "the username string"
username: String! username: String!
} }
input IdInput { input IdInput {
# the id "the id"
id: ID! id: ID!
} }
input MessageInput { input MessageInput {
# the message "the message"
message: String! message: String!
} }
input MessageQueryInput { input MessageQueryInput {
# search for a specific id "search for a specific id"
id: ID id: ID
# get the last n messages "get the last n messages"
last: Int = 10 last: Int = 10
} }

@ -0,0 +1,15 @@
const utils = require('./utils'),
pg = require('pg');
const settings = utils.readSettings('.');
Object.assign(exports, {
settings: settings,
pgPool: new pg.Pool({
host: settings.postgres.host,
port: settings.postgres.port,
user: settings.postgres.user,
password: settings.postgres.password,
database: settings.postgres.database
})
});

@ -0,0 +1,39 @@
const yaml = require('js-yaml'),
fsx = require('fs-extra');
/**
* Parses the `queries.yaml` file in the path. queries.yaml-format:
* exports: {List} - query keys to export
*
* queryKey:
* file: {String} name of sql-file if the sql is stored in a file.
* sql: {String} pure sql if it is not stored in a file. Will be replaced by file contents if a file was given.
* @param path {String} - the path where the queries.yaml file is stored
*/
function parseSqlYaml(path) {
let queries = yaml.safeLoad(fsx.readFileSync(`${path}/queries.yaml`));
for (let query of queries.exports)
if (queries[query].file)
queries[query].sql = fsx.readFileSync(`${path}/${queries[query].file}`, 'utf-8');
return queries;
}
/**
* Reads the default-config.yaml and config.yaml in the path directory.
* @param path {String} - the directory of the settings files.
*/
function readSettings(path) {
let settings = yaml.safeLoad(fsx.readFileSync(`${path}/default-config.yaml`));
if (fsx.existsSync('config.yaml'))
Object.assign(settings, yaml.safeLoad(fsx.readFileSync(`${path}/config.yaml`)));
return settings;
}
Object.assign(exports, {
parseSqlYaml: parseSqlYaml,
readSettings: readSettings
});

@ -6,10 +6,227 @@ const express = require('express'),
md = require('markdown-it')() md = require('markdown-it')()
.use(mdEmoji) .use(mdEmoji)
.use(mdMark) .use(mdMark)
.use(mdSmartarrows); .use(mdSmartarrows),
utils = require('../lib/utils'),
globals = require('../lib/globals');
let pgPool = globals.pgPool;
let bingoSessions = {}; let bingoSessions = {};
/**
* Class to manage the bingo data in the database.
*/
class BingoDataManager {
/**
* constructor functino
* @param postgresPool {pg.Pool} - the postgres pool
*/
constructor(postgresPool) {
this.pgPool = postgresPool;
this.queries = utils.parseSqlYaml('./sql/bingo')
}
async init() {
await this.pgPool.query(this.queries.createTables.sql);
setInterval(async () => await this._databaseCleanup(), 5*60*1000); // database cleanup every 5 minutes
}
/**
* Try-catch wrapper around the pgPool.query.
* @param query {String} - the sql query
* @param [values] {Array} - an array of values
* @returns {Promise<*>}
*/
async _queryDatabase(query, values) {
try {
return await this.pgPool.query(query, values);
} catch (err) {
console.error(`Error on query "${query}" with values ${JSON.stringify(values)}.`);
console.error(err);
console.error(err.stack);
return {
rows: null
};
}
}
/**
* Queries the database and returns all resulting rows
* @param query {String} - the sql query
* @param values {Array} - an array of parameters needed in the query
* @returns {Promise<*>}
* @private
*/
async _queryAllResults(query, values) {
let result = await this._queryDatabase(query, values);
return result.rows;
}
/**
* Query the database and return the first result or null
* @param query {String} - the sql query
* @param values {Array} - an array of parameters needed in the query
* @returns {Promise<*>}
*/
async _queryFirstResult(query, values) {
let result = await this._queryDatabase(query, values);
if (result.rows.length > 0)
return result.rows[0];
}
/**
* Clears expired values from the database.
*/
async _databaseCleanup() {
await this._queryDatabase(this.queries.cleanup.sql);
}
/**
* Add a player to the players table
* @param username {String} - the username of the player
* @returns {Promise<*>}
*/
async addPlayer(username) {
let result = await this._queryFirstResult(this.queries.addPlayer.sql, [username]);
if (result)
return result;
else
return {}; // makes things easier
}
/**
* Updates the username of a player
* @param playerId {Number} - the id of the player
* @param username {String} - the new username
* @returns {Promise<void>}
*/
async updatePlayerUsername(playerId, username) {
return await this._queryFirstResult(this.queries.updatePlayerUsername, [username, playerId]);
}
/**
* Returns the username for a player-id
* @param playerId {Number} - the id of the player
* @returns {Promise<*>}
*/
async getPlayerUsername(playerId) {
let result = await this._queryFirstResult(this.queries.getPlayerUsername, [playerId]);
if (result)
return result.username;
}
/**
* Updates the expiration date of a player
* @param playerId {Request} - thie id of the player
* @returns {Promise<void>}
*/
async updatePlayerExpiration(playerId) {
await this._queryDatabase(this.queries.updatePlayerExpire.sql, [playerId]);
}
/**
* Creates a bingo lobby.
* @param playerId
* @param gridSize
* @returns {Promise<*>}
*/
async createLobby(playerId, gridSize) {
return await this._queryFirstResult(this.queries.addLobby.sql, [playerId, gridSize]);
}
/**
* Updates the expiration date of a lobby
* @param lobbyId {Number} - the id of the lobby
* @returns {Promise<*>}
*/
async updateLobbyExpiration(lobbyId) {
return await this._queryDatabase(this.queries.updateLobbyExpire.sql, [lobbyId]);
}
/**
* Checks if a player is in a lobby.
* @param playerId {Number} - the id of the player
* @param lobbyId {Number} - the id of the lobby
* @returns {Promise<*>}
*/
async getPlayerInLobby(playerId, lobbyId) {
return (await this._queryFirstResult(this.queries.getPlayerInLobby.sql, [playerId, lobbyId]));
}
/**
* Adds a player to a lobby.
* @param playerId {Number} - the id of the player
* @param lobbyId {Number} - the id of the lobby
* @returns {Promise<*>}
*/
async addPlayerToLobby(playerId, lobbyId) {
let entry = await this.getPlayerInLobby(playerId, lobbyId);
if (entry)
return entry;
else
return await this._queryFirstResult(this.queries.addPlayerToLobby.sql, [playerId, lobbyId]);
}
/**
* Removes a player from a lobbby
* @param playerId {Number} - the id of the player
* @param lobbyId {Number} - the id of the lobby
* @returns {Promise<*>}
*/
async removePlayerFromLobby(playerId, lobbyId) {
return await this._queryFirstResult(this.queries.removePlayerFromLobby.sql, [playerId, lobbyId]);
}
/**
* Adds a word to a lobby
* @param lobbyId {Number} - the id of the lobby
* @param word {Number} - the id of the word
* @returns {Promise<void>}
*/
async addWordToLobby(lobbyId, word) {
return await this._queryFirstResult(this.queries.addWord.sql, [lobbyId, word]);
}
/**
* Returns all words used in a lobby
* @param lobbyId
* @returns {Promise<void>}
*/
async getWordsForLobby(lobbyId) {
return await this._queryAllResults(this.queries.getWordsForLobby.sql, [lobbyId]);
}
/**
* Adds a grid for a user to a lobby
* @param lobbyId {Number} - the id of the lobby
* @param playerId {Number} - the id of the user
* @returns {Promise<void>}
*/
async addGrid(lobbyId, playerId) {
return await this._queryFirstResult(this.queries.addGrid.sql, [playerId, lobbyId]);
}
/**
* Adds a word to a grid with specific location
* @param gridId {Number} - the id of the gird
* @param wordId {Number} - the id of the word
* @param row {Number} - the number of the row
* @param column {Number} - the number of the column
*/
async addWordToGrid(gridId, wordId, row, column) {
return await this._queryFirstResult(this.queries.addWordToGrid.sql, [gridId, wordId, row, column]);
}
/**
* Returns all words in the grid with location
* @param gridId {Number} - the id of the grid
* @returns {Promise<*>}
*/
async getWordsInGrid(gridId) {
return await this._queryAllResults(this.queries.getWordsInGrid.sql, [gridId]);
}
}
class BingoSession { class BingoSession {
/** /**
* constructor * constructor
@ -311,12 +528,21 @@ function checkBingo(bingoGrid) {
return false; return false;
} }
// -- Router stuff // -- Router stuff
router.use((req, res, next) => {
let bdm = new BingoDataManager(pgPool);
router.init = async () => {
await bdm.init();
};
router.use(async (req, res, next) => {
if (!req.session.bingoUser) if (!req.session.bingoUser)
req.session.bingoUser = new BingoUser(); req.session.bingoUser = new BingoUser();
if (req.session.bingoPlayerId)
await bdm.updatePlayerExpiration(req.session.bingoPlayerId);
next(); next();
}); });
@ -347,10 +573,13 @@ router.get('/', (req, res) => {
} }
}); });
router.graphqlResolver = (req, res) => { router.graphqlResolver = async (req, res) => {
if (req.session.bingoPlayerId)
await bdm.updatePlayerExpiration(req.session.bingoPlayerId);
let bingoUser = req.session.bingoUser || new BingoUser(); let bingoUser = req.session.bingoUser || new BingoUser();
let gameId = req.query.game || bingoUser.game || null; let gameId = req.query.game || bingoUser.game || null;
let bingoSession = bingoSessions[gameId]; let bingoSession = bingoSessions[gameId];
return { return {
// queries // queries
gameInfo: ({input}) => { gameInfo: ({input}) => {
@ -366,6 +595,28 @@ router.graphqlResolver = (req, res) => {
return bingoUser.grids[gameId]; return bingoUser.grids[gameId];
}, },
// mutation // mutation
createLobby: async ({input}) => {
let gridSize = (input && input.gridSize)? input.gridSize : 3;
let lobby = await bdm.createLobby(req.session.bingoPlayerId, gridSize);
if (lobby && lobby.id)
return lobby.id;
else
res.status(500);
},
joinLobby: async ({input}) => {
if (input.lobbyId) {
let entry = await bdm.addPlayerToLobby(req.session.bingoPlayerId, input.lobbyId);
if (entry && entry.lobby_id && entry.player_id)
return {
lobbyId: entry.lobby_id,
playerId: entry.player_id
};
else
res.status(500);
} else {
res.status(400);
}
},
createGame: ({input}) => { createGame: ({input}) => {
let words = input.words.filter((el) => { // remove empty strings and non-types from word array let words = input.words.filter((el) => { // remove empty strings and non-types from word array
return (!!el && el.length > 0); return (!!el && el.length > 0);
@ -413,10 +664,14 @@ router.graphqlResolver = (req, res) => {
res.status(400); res.status(400);
} }
}, },
setUsername: ({input}) => { setUsername: async ({input}) => {
if (input.username) { if (input.username) {
bingoUser.username = input.username.substring(0, 30); // only allow 30 characters bingoUser.username = input.username.substring(0, 30); // only allow 30 characters
if (!req.session.bingoPlayerId)
req.session.bingoPlayerId = (await bdm.addPlayer(input.username)).id;
else
await bdm.updatePlayerUsername(req.session.bingoPlayerId, input.username);
if (bingoSession) if (bingoSession)
bingoSession.addUser(bingoUser); bingoSession.addUser(bingoUser);

@ -0,0 +1,50 @@
/*-- remove grid-word connections for expired lobbys
DELETE FROM bingo.grid_words
WHERE EXISTS(
SELECT grids.lobby_id FROM bingo.grids
WHERE EXISTS (
SELECT lobbys.id FROM bingo.lobbys
WHERE lobbys.id = grids.lobby_id
AND NOW() > lobbys.expire
)
);
-- remove grids for expired lobbys
DELETE FROM bingo.grids
WHERE EXISTS (
SELECT lobbys.id FROM bingo.lobbys
WHERE lobbys.id = grids.lobby_id
AND NOW() > lobbys.expire
);
-- remove words for expired lobbys
DELETE FROM bingo.words
WHERE EXISTS (
SELECT lobbys.id FROM bingo.lobbys
WHERE lobbys.id = words.lobby_id
AND NOW() > lobbys.expire
);
-- remove lobby-player connections for expired lobbys or players
DELETE FROM bingo.lobby_players
WHERE EXISTS (
SELECT lobbys.id FROM bingo.lobbys
WHERE lobbys.id = lobby_players.lobby_id
AND NOW() > lobbys.expire
) OR EXISTS (
SELECT players.id FROM bingo.players
WHERE players.id = lobby_players.player_id
AND NOW() > players.expire
);
*/
-- remove expired lobbys
DELETE FROM bingo.lobbys
WHERE NOW() > lobbys.expire;
-- remove expired players
DELETE FROM bingo.players
WHERE NOW() > players.expire;
/*AND NOT EXISTS (
SELECT lobbys.admin_id FROM bingo.lobbys
WHERE lobbys.admin_id = players.id
);*/

@ -0,0 +1,69 @@
-- players table
CREATE TABLE IF NOT EXISTS bingo.players (
id serial UNIQUE PRIMARY KEY,
username varchar(32) NOT NULL,
expire timestamp DEFAULT (NOW() + interval '24 hours' )
);
-- lobbys table
CREATE TABLE IF NOT EXISTS bingo.lobbys (
id serial UNIQUE PRIMARY KEY,
admin_id serial references bingo.players(id) ON DELETE SET NULL,
grid_size integer DEFAULT 3,
expire timestamp DEFAULT (NOW() + interval '1 hour' )
);
-- lobbys-players table
CREATE TABLE IF NOT EXISTS bingo.lobby_players (
player_id serial references bingo.players(id) ON DELETE CASCADE,
lobby_id serial references bingo.lobbys(id) ON DELETE CASCADE,
score integer DEFAULT 0,
PRIMARY KEY (player_id, lobby_id)
);
-- words table
CREATE TABLE IF NOT EXISTS bingo.words (
id serial UNIQUE PRIMARY KEY,
lobby_id serial references bingo.lobbys(id) ON DELETE CASCADE,
heared integer DEFAULT 0,
content varchar(254) NOT NULL
);
-- grids table
CREATE TABLE IF NOT EXISTS bingo.grids (
id serial UNIQUE PRIMARY KEY,
player_id serial references bingo.players(id) ON DELETE CASCADE,
lobby_id serial references bingo.lobbys(id) ON DELETE CASCADE
);
-- grids_words table
CREATE TABLE IF NOT EXISTS bingo.grid_words (
grid_id serial references bingo.grids(id) ON DELETE CASCADE,
word_id serial references bingo.words(id) ON DELETE CASCADE,
grid_row integer NOT NULL,
grid_column integer NOT NULL,
PRIMARY KEY (grid_id, word_id)
);
-- messages table
CREATE TABLE IF NOT EXISTS bingo.messages (
id serial UNIQUE PRIMARY KEY,
content varchar(255) NOT NULL,
player_id serial references bingo.players(id) ON DELETE SET NULL,
lobby_id serial references bingo.lobbys(id) ON DELETE CASCADE,
type varchar(8),
created timestamp DEFAULT NOW()
);
-- rounds table
CREATE TABLE IF NOT EXISTS bingo.rounds (
id serial UNIQUE PRIMARY KEY,
start timestamp DEFAULT NOW(),
finish timestamp,
status varchar(8) DEFAULT 'undefined',
lobby_id serial references bingo.lobbys(id) ON DELETE CASCADE,
winner serial references bingo.players(id) ON DELETE SET NULL
);
-- altering
ALTER TABLE bingo.lobbys ADD COLUMN IF NOT EXISTS current_round serial references bingo.rounds(id) ON DELETE SET NULL;

@ -0,0 +1,107 @@
# a file to list sql queries by names
exports: # loaded from file
- createTables
- cleanup
# create the needed bingo tables
createTables:
file: createBingoTables.sql
# clears expired values
cleanup:
file: clearExpired.sql
# Add a player to the database
# params:
# - {String} - the username of the player
addPlayer:
sql: INSERT INTO bingo.players (username) VALUES ($1) RETURNING *;
# Updates the username of a player
# params:
# - {String} - the new username of the player
# - {Number} - the id of the player
updatePlayerUsername:
sql: UPDATE bingo.players SET players.username = $1 WHERE players.id = $2 RETURNING *;
# Selects the username for a player id
# params:
# - {Number} - the id of the player
getPlayerUsername:
sql: SELECT players.username FROM bingo.players WHERE id = $1;
# updates the expiration timestamp of the player
# params:
# - {Number} - the id of the player
updatePlayerExpire:
sql: UPDATE bingo.players SET expire = (NOW() + interval '24 hours') WHERE id = $1;
# adds a lobby to the database
# params:
# - {Number} - the id of the admin player
# - {Number} - the size of the grid
addLobby:
sql: INSERT INTO bingo.lobbys (admin_id, grid_size) VALUES ($1, $2) RETURNING *;
# updates expiration timestamp of the lobby
# params:
# - {Number} - the id of the lobby
updateLobbyExpire:
sql: UPDATE bingo.lobbys SET expire = (NOW() + interval '1 hours') WHERE id = $1;
# inserts a player into a lobby
# params:
# - {Number} - the id of the player
# - {Number} - the id of the lobby
addPlayerToLobby:
sql: INSERT INTO bingo.lobby_players (player_id, lobby_id) VALUES ($1, $2);
# removes a player from a lobby
# params:
# - {Number} - the id of the player
# - {Number} - the id of the lobby
removePlayerFromLobby:
sql: REMOVE FROM bingo.lobby_players WHERE player_id = $1 AND lobby_id = $2;
# returns the entry of the player and lobby
# params:
# - {Number} - the id of the player
# - {Number} - the id of the lobby
getPlayerInLobby:
sql: SELECT * FROM bingo.lobby_players lp WHERE lp.player_id = $1 AND lp.lobby_id = $2;
# adds a word to the database
# params:
# - {Number} - the id of the lobby where the word is used
# - {String} - the word itself
addWord:
sql: INSERT INTO bingo.words (lobby_id, content) VALUES ($1, $2) RETURNING *;
# returns all words for a bingo game (lobby)
# params:
# - {Number} - the id of the bingo lobby
getWordsForLobby:
sql: SELECT * FROM bingo.words WHERE words.lobby_id = $1;
# adds a grid to the database
# params:
# - {Number} - the id of the player
# - {Number} - the id of the lobby
addGrid:
sql: INSERT INTO bingo.grids (player_id, lobby_id) VALUES ($1, $2) RETURNING *;
# inserts grid-word connections into the database
# params:
# - {Number} - the id of the grid
# - {Number} - the id of the word
# - {Number} - the row of the word
# - {Number} - the column of the word
addWordToGrid:
sql: INSERT INTO bingo.grid_words (grid_id, word_id, grid_row, grid_column) VALUES ($1, $2, $3, $4) RETURNING *;
# returns all words for a players grid
# params:
# - {Number} - the id of the grid
getWordsForGridId:
sql: SELECT * FROM bingo.grid_words, bingo.words WHERE grid_words.grid_id = $1 AND words.id = grid_words.word_id;

@ -1,3 +1,10 @@
-- schemas
CREATE SCHEMA IF NOT EXISTS bingo;
-- public tables
-- creates the Session table
CREATE TABLE IF NOT EXISTS "user_sessions" ( CREATE TABLE IF NOT EXISTS "user_sessions" (
"sid" varchar NOT NULL COLLATE "default", "sid" varchar NOT NULL COLLATE "default",
"sess" json NOT NULL, "sess" json NOT NULL,
Loading…
Cancel
Save