Add DB initialization code. Fix DB schema

This commit is contained in:
Maximilian Keßler 2023-11-22 16:00:26 +01:00
parent 15a1b29b84
commit 8623b524de
Signed by: max
GPG key ID: BCC5A619923C0BA5
3 changed files with 71 additions and 17 deletions

View file

@ -7,3 +7,21 @@ DB_CONFIG_FILE_NAME = 'config.yaml'
DEFAULT_DB_NAME = 'hanabi-league' DEFAULT_DB_NAME = 'hanabi-league'
DEFAULT_DB_USER = 'hanabi-league' DEFAULT_DB_USER = 'hanabi-league'
DEFAULT_DB_PASS = 'hanabi-league' DEFAULT_DB_PASS = 'hanabi-league'
DB_TABLE_NAMES = [
"users"
, "user_accounts"
, "downloads"
, "variants"
, "games"
, "game_participants"
, "game_actions"
, "seeds"
, "variant_base_ratings"
, "variant_ratings"
, "user_base_ratings"
, "user_ratings"
, "statistics"
]
DATABASE_SCHEMA_PATH = 'install/database_schema.sql'

View file

@ -1,5 +1,7 @@
import psycopg2 import psycopg2
import psycopg2.extensions
import constants
from config import read_db_config from config import read_db_config
from log_setup import logger from log_setup import logger
@ -16,7 +18,7 @@ class DBConnectionManager:
)) ))
logger.debug("Established database connection.") logger.debug("Established database connection.")
def get_connection(self): def get_connection(self) -> psycopg2.extensions.connection:
""" """
Get the database connection. Get the database connection.
If not already connected, this reads the database config file and connects to the DB. If not already connected, this reads the database config file and connects to the DB.
@ -28,4 +30,33 @@ class DBConnectionManager:
# Global instance that will hold our DB connection # Global instance that will hold our DB connection
conn_manager = DBConnectionManager() conn_manager = DBConnectionManager()
def get_existing_tables():
conn = conn_manager.get_connection()
cur = conn.cursor()
table_names = ", ".join("'{}'".format(tablename) for tablename in constants.DB_TABLE_NAMES)
cur.execute(
"SELECT tablename FROM pg_tables WHERE"
" schemaname = 'public' AND"
" tablename IN ({})".format(
table_names
)
)
return [table for (table,) in cur.fetchall()]
def init_database(erase: bool = False):
tables = get_existing_tables()
if not erase and len(tables) > 0:
logger.error("Aborting database initialization: Tables {} already exist".format(", ".join(tables)))
return
conn = conn_manager.get_connection()
cur = conn.cursor()
with open(constants.DATABASE_SCHEMA_PATH, "r") as f:
cur.execute(f.read())
conn.commit()
logger.verbose("Initialized DB tables.")

View file

@ -65,25 +65,25 @@
* To be honest, I'd also like to apply all of the code to some non-league related games where I'd need this :D. * To be honest, I'd also like to apply all of the code to some non-league related games where I'd need this :D.
*/ */
DROP TABLE IF EXISTS users; DROP TABLE IF EXISTS users CASCADE;
CREATE TABLE users ( CREATE TABLE users (
id SERIAL PRIMARY KEY, id SERIAL PRIMARY KEY,
name TEXT NOT NULL name TEXT NOT NULL,
discord_tag TEXT, discord_tag TEXT
); );
DROP TABLE IF EXISTS user_accounts; DROP TABLE IF EXISTS user_accounts CASCADE;
CREATE TABLE user_accounts ( CREATE TABLE user_accounts (
/* The username from hanab.live. This will be used a) for associating games to one of our user_ids and b) show up in the player profile. */ /* The username from hanab.live. This will be used a) for associating games to one of our user_ids and b) show up in the player profile. */
username TEXT NOT NULL, username TEXT PRIMARY KEY,
/** /**
* Hanab.live stores a normalized username for each account to ensure that usernames are case-insensitive and don't have weird unicodes that resemble each other. * Hanab.live stores a normalized username for each account to ensure that usernames are case-insensitive and don't have weird unicodes that resemble each other.
* It uses go-unidecode to generate these from the actual usernames, and I suggest we do the same. * It uses go-unidecode to generate these from the actual usernames, and I suggest we do the same.
* The corresponding python package is 'unidecode', so we should just use that here. * The corresponding python package is 'unidecode', so we should just use that here.
* This will then ensure that no two sign-ups correspond to the same hanab.live account. * This will then ensure that no two sign-ups correspond to the same hanab.live account.
*/ */
normalized_username TEXT NOT NULL UNIQUE normalized_username TEXT NOT NULL UNIQUE,
user_id INTEGER NOT NULL, user_id INTEGER NOT NULL,
FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE
); );
@ -99,19 +99,18 @@ CREATE TABLE user_accounts (
DROP TABLE IF EXISTS downloads; DROP TABLE IF EXISTS downloads;
CREATE TABLE downloads ( CREATE TABLE downloads (
/** Notice this has to be a hanab.live username, not a user_id */ /** Notice this has to be a hanab.live username, not a user_id */
username INTEGER NOT NULL, username TEXT PRIMARY KEY REFERENCES user_accounts,
latest_game_id INTEGER NOT NULL, latest_game_id INTEGER NOT NULL
FOREIGN KEY (user_id) REFERENCES users (id)
); );
/** /**
* For completeness, I suggest we store the hanab.live variants here together with their names. * For completeness, I suggest we store the hanab.live variants here together with their names.
* So this will just be a static table that we populate once. * So this will just be a static table that we populate once.
*/ */
DROP TABLE IF EXISTS variants; DROP TABLE IF EXISTS variants CASCADE;
CREATE TABLE variants ( CREATE TABLE variants (
id INTEGER PRIMARY KEY, id INTEGER PRIMARY KEY,
name TEXT NOT NULL name TEXT NOT NULL,
num_suits INTEGER NOT NULL, num_suits INTEGER NOT NULL,
clue_starved BOOLEAN NOT NULL, clue_starved BOOLEAN NOT NULL,
max_score SMALLINT NOT NULL max_score SMALLINT NOT NULL
@ -122,7 +121,7 @@ CREATE TABLE variants (
* - Only download detailed data for each game once (The actions taken and the actual deck) * - Only download detailed data for each game once (The actions taken and the actual deck)
* - We can evaluate statistics (also complicated ones) whenever we want, independent of the server * - We can evaluate statistics (also complicated ones) whenever we want, independent of the server
*/ */
DROP TABLE IF EXISTS games; DROP TABLE IF EXISTS games CASCADE;
CREATE TABLE games ( CREATE TABLE games (
/** Here, we should use the same ids as the game ids from hanab.live */ /** Here, we should use the same ids as the game ids from hanab.live */
id INTEGER PRIMARY KEY, id INTEGER PRIMARY KEY,
@ -141,6 +140,7 @@ CREATE TABLE games (
FOREIGN KEY (variant_id) REFERENCES variants (id) FOREIGN KEY (variant_id) REFERENCES variants (id)
); );
DROP TABLE IF EXISTS game_participants CASCADE;
CREATE TABLE game_participants ( CREATE TABLE game_participants (
game_id INTEGER NOT NULL, game_id INTEGER NOT NULL,
/** /**
@ -223,7 +223,7 @@ CREATE TABLE seeds (
suit_index SMALLINT NOT NULL, suit_index SMALLINT NOT NULL,
rank SMALLINT NOT NULL, rank SMALLINT NOT NULL,
CONSTRAINT cards_unique UNIQUE (seed, card_index) CONSTRAINT cards_unique UNIQUE (seed, card_index)
) );
/** /**
@ -242,14 +242,16 @@ CREATE TABLE seeds (
/** /**
* Records the initial ratings of variants * Records the initial ratings of variants
*/ */
DROP TABLE IF EXISTS variant_base_ratings CASCADE;
CREATE TABLE variant_base_ratings ( CREATE TABLE variant_base_ratings (
variant_id SMALLINT PRIMARY KEY, variant_id SMALLINT PRIMARY KEY,
rating REAL NOT NULL, rating REAL NOT NULL
); );
/** /**
* Store a separate row for each elo update to one of the variants * Store a separate row for each elo update to one of the variants
*/ */
DROP TABLE IF EXISTS variant_ratings CASCADE;
CREATE TABLE variant_ratings ( CREATE TABLE variant_ratings (
/** This should reference the game that triggered the elo update */ /** This should reference the game that triggered the elo update */
league_id INTEGER PRIMARY KEY REFERENCES games, league_id INTEGER PRIMARY KEY REFERENCES games,
@ -258,9 +260,10 @@ CREATE TABLE variant_ratings (
change REAL NOT NULL, change REAL NOT NULL,
value_after REAL NOT NULL, value_after REAL NOT NULL,
FOREIGN KEY (variant_id) REFERENCES variants (id), FOREIGN KEY (variant_id) REFERENCES variants (id)
); );
DROP TABLE IF EXISTS user_base_ratings CASCADE;
CREATE TABLE user_base_ratings ( CREATE TABLE user_base_ratings (
user_id SMALLINT, user_id SMALLINT,
/** /**
@ -276,6 +279,7 @@ CREATE TABLE user_base_ratings (
FOREIGN KEY (user_id) REFERENCES users (id) FOREIGN KEY (user_id) REFERENCES users (id)
); );
DROP TABLE IF EXISTS user_ratings CASCADE;
CREATE TABLE user_ratings ( CREATE TABLE user_ratings (
/** This should reference the game that triggered the elo update. I would use the league_id here for proper ordering. */ /** This should reference the game that triggered the elo update. I would use the league_id here for proper ordering. */
league_id INTEGER PRIMARY KEY REFERENCES games, league_id INTEGER PRIMARY KEY REFERENCES games,
@ -298,6 +302,7 @@ CREATE TABLE user_ratings (
/* TABLES RELATED TO STATISTICS */ /* TABLES RELATED TO STATISTICS */
/** This is a rough outline, not really happy with it right now. */ /** This is a rough outline, not really happy with it right now. */
DROP TABLE IF EXISTS statistics CASCADE;
CREATE TABLE statistics ( CREATE TABLE statistics (
game_id INTEGER PRIMARY KEY, game_id INTEGER PRIMARY KEY,
/** I'd say all of the following can just be null in case we have not evaluated them yet. */ /** I'd say all of the following can just be null in case we have not evaluated them yet. */