From b8220732eed2ec229fc8d2628bdb6bb3bd9ed6eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Phillip=20K=C3=BChne?= Date: Tue, 28 Mar 2023 00:54:04 +0200 Subject: [PATCH] Introduce "dev environment" and dotenv There are now two dockerfiles. One for production, one for development. All configuration is now also handled through dotenv files, which these dotenv files, as well as the included VSCode launch tasks use. --- .env.dev | 14 ++++ .gitignore | 5 +- .vscode/launch.json | 5 +- .vscode/tasks.json | 2 +- backend/database.py | 153 +++++++++++++++++----------------------- docker-compose.prod.yml | 11 ++- docker-compose.yml | 15 ++++ 7 files changed, 112 insertions(+), 93 deletions(-) create mode 100644 .env.dev create mode 100644 docker-compose.yml diff --git a/.env.dev b/.env.dev new file mode 100644 index 0000000..1fa9576 --- /dev/null +++ b/.env.dev @@ -0,0 +1,14 @@ +# MariaDB +MARIADB_ROOT_PASSWORD=mariadb_root_password +MARIADB_ROOT_HOST=localhost +MARIADB_DATABASE=karaoqueue +MARIADB_USER=karaoqueue +MARIADB_PASSWORD=mariadb_karaoqueue_password + +# Karaoqueue +DEPLOYMENT_PLATFORM=Docker +DBSTRING=mysql://karaoqueue:mariadb_karaoqueue_password@127.0.0.1:3306/karaoqueue +BASIC_AUTH_USERNAME=admin +BASIC_AUTH_PASSWORD=change_me +ENTRY_QUOTA=3 +MAX_QUEUE=20 \ No newline at end of file diff --git a/.gitignore b/.gitignore index 9f3255d..184e737 100644 --- a/.gitignore +++ b/.gitignore @@ -137,4 +137,7 @@ data/ node_modules/ # Version identification file -.version \ No newline at end of file +.version + +# Docker secrets +secrets.yml \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json index 27002f1..594135a 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -15,8 +15,8 @@ "FLASK_APP": "backend/app.py", "FLASK_ENV": "development", "FLASK_DEBUG": "1", - "DBSTRING": "mysql://devuser:devpw@127.0.0.1:3306/karaoqueue" }, + "envFile": "${workspaceFolder}/.env.dev", "args": [ "run", "--no-debugger", @@ -36,6 +36,7 @@ "FLASK_ENV": "development", "FLASK_DEBUG": "1" }, + "envFile": "${workspaceFolder}/.env.dev", "args": [ "run", "--no-debugger" @@ -54,6 +55,7 @@ "FLASK_ENV": "development", "FLASK_DEBUG": "1" }, + "envFile": "${workspaceFolder}/.env.dev", "args": [ "run", "--no-debugger", @@ -73,6 +75,7 @@ "FLASK_ENV": "development", "FLASK_DEBUG": "1" }, + "envFile": "${workspaceFolder}/.env.dev", "args": [ "run", "--no-debugger", diff --git a/.vscode/tasks.json b/.vscode/tasks.json index bdaee63..96e7e93 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -12,7 +12,7 @@ { "label": "mariadb", "type": "shell", - "command": "docker run --rm --name some-mariadb --env MARIADB_USER=devuser --env MARIADB_PASSWORD=devpw --env MARIADB_ROOT_PASSWORD=devrootpw --env MARIADB_DATABASE=karaoqueue -p 3306:3306 mariadb:latest", + "command": "docker-compose -f docker-compose.yml up --remove-orphans", "isBackground": true, "activeOnStart": false } diff --git a/backend/database.py b/backend/database.py index fe9805d..a3e6d0b 100644 --- a/backend/database.py +++ b/backend/database.py @@ -12,49 +12,44 @@ entry_table = "entries" index_label = "Id" done_table = "done_songs" -connection = None +sql_engine = None -def open_db() -> engine.base.Connection: - global connection - if (not connection): +def get_db_engine() -> engine.base.Engine: + global sql_engine + if (not sql_engine): print(current_app.config.get("DBCONNSTRING")) - engine = create_engine(current_app.config.get("DBCONNSTRING")) # type: ignore - connection = engine.connect() - # cur.execute('PRAGMA encoding = "UTF-8";') - return connection + sql_engine = create_engine(current_app.config.get("DBCONNSTRING")) # type: ignore + return sql_engine def import_songs(song_csv): print("Start importing Songs...") df = pandas.read_csv(StringIO(song_csv), sep=';') - conn = open_db() - df.to_sql(song_table, conn, if_exists='replace', - index=False) - cur = conn.execute("SELECT Count(Id) FROM songs") - num_songs = cur.fetchone()[0] # type: ignore - # conn.close() + with get_db_engine().connect() as conn: + df.to_sql(song_table, conn, if_exists='replace', + index=False) + cur = conn.execute("SELECT Count(Id) FROM songs") + num_songs = cur.fetchone()[0] # type: ignore print("Imported songs ({} in Database)".format(num_songs)) return("Imported songs ({} in Database)".format(num_songs)) def create_entry_table(): - conn = open_db() - conn.execute('CREATE TABLE IF NOT EXISTS '+entry_table + + with get_db_engine().connect() as conn: + conn.execute('CREATE TABLE IF NOT EXISTS '+entry_table + ' (ID INTEGER PRIMARY KEY NOT NULL AUTO_INCREMENT, Song_Id INTEGER NOT NULL, Name VARCHAR(255), Client_Id VARCHAR(36), Transferred INTEGER DEFAULT 0)') - # conn.close() def create_done_song_table(): - conn = open_db() - conn.execute('CREATE TABLE IF NOT EXISTS '+done_table + + with get_db_engine().connect() as conn: + conn.execute('CREATE TABLE IF NOT EXISTS '+done_table + ' (Song_Id INTEGER PRIMARY KEY NOT NULL, Plays INTEGER)') - # conn.close() def create_song_table(): - conn = open_db() - conn.execute("CREATE TABLE IF NOT EXISTS `"+song_table+"""` ( + with get_db_engine().connect() as conn: + conn.execute("CREATE TABLE IF NOT EXISTS `"+song_table+"""` ( `Id` INTEGER, `Title` TEXT, `Artist` TEXT, @@ -65,122 +60,107 @@ def create_song_table(): `Styles` TEXT, `Languages` TEXT )""") - # conn.close() def create_list_view(): - conn = open_db() - conn.execute("""CREATE OR REPLACE VIEW `Liste` AS + with get_db_engine().connect() as conn: + conn.execute("""CREATE OR REPLACE VIEW `Liste` AS SELECT Name, Title, Artist, entries.Id AS entry_ID, songs.Id AS song_ID, entries.Transferred FROM entries, songs WHERE entries.Song_Id=songs.Id""") - # conn.close() def create_done_song_view(): - conn = open_db() - conn.execute("""CREATE OR REPLACE VIEW `Abspielliste` AS + with get_db_engine().connect() as conn: + conn.execute("""CREATE OR REPLACE VIEW `Abspielliste` AS SELECT CONCAT(Artist," - ", Title) AS Song, Plays AS Wiedergaben FROM songs, done_songs WHERE done_songs.Song_Id=songs.Id""") - # conn.close() def get_list(): - conn = open_db() - cur = conn.execute("SELECT * FROM Liste") + with get_db_engine().connect() as conn: + cur = conn.execute("SELECT * FROM Liste") return cur.fetchall() def get_played_list(): - conn = open_db() - cur = conn.execute("SELECT * FROM Abspielliste") + with get_db_engine().connect() as conn: + cur = conn.execute("SELECT * FROM Abspielliste") return cur.fetchall() def get_song_list(): - conn = open_db() - cur = conn.execute( + with get_db_engine().connect() as conn: + cur = conn.execute( "SELECT Artist || \" - \" || Title AS Song, Id FROM songs;") return cur.fetchall() def get_song_completions(input_string): - conn = open_db() - # Don't look, it burns... - prepared_string = "%{0}%".format( - input_string).upper() # "Test" -> "%TEST%" - print(prepared_string) - cur = conn.execute( - "SELECT CONCAT(Artist,\" - \",Title) AS Song, Id FROM songs WHERE CONCAT(Artist,\" - \",Title) LIKE (%s) LIMIT 20;", [prepared_string]) + with get_db_engine().connect() as conn: + # Don't look, it burns... + prepared_string = "%{0}%".format( + input_string).upper() # "Test" -> "%TEST%" + print(prepared_string) + cur = conn.execute( + "SELECT CONCAT(Artist,\" - \",Title) AS Song, Id FROM songs WHERE CONCAT(Artist,\" - \",Title) LIKE (%s) LIMIT 20;", [prepared_string]) return cur.fetchall() def add_entry(name, song_id, client_id): - conn = open_db() - conn.execute( + with get_db_engine().connect() as conn: + conn.execute( "INSERT INTO entries (Song_Id,Name,Client_Id) VALUES(%s,%s,%s);", (song_id, name, client_id)) - # conn.close() return def add_sung_song(entry_id): - conn = open_db() - cur = conn.execute( - """SELECT Song_Id FROM entries WHERE Id=%s""", (entry_id,)) - song_id = cur.fetchone()[0] # type: ignore - conn.execute("""INSERT INTO done_songs (Song_Id, Plays) VALUES("""+str(song_id)+""",1) ON DUPLICATE KEY UPDATE Plays=Plays + 1;""") -# SQLite bullshittery -# conn.execute("""REPLACE INTO done_songs (Song_Id, Plays) -# VALUES("""+str(song_id)+""", -# COALESCE( -# (SELECT Plays FROM done_songs -# WHERE Song_Id="""+str(song_id)+"), 0) + 1)" -# ) - delete_entry(entry_id) - # conn.close() + with get_db_engine().connect() as conn: + cur = conn.execute( + """SELECT Song_Id FROM entries WHERE Id=%s""", (entry_id,)) + song_id = cur.fetchone()[0] # type: ignore + conn.execute("""INSERT INTO done_songs (Song_Id, Plays) VALUES("""+str(song_id)+""",1) ON DUPLICATE KEY UPDATE Plays=Plays + 1;""") + delete_entry(entry_id) return True def toggle_transferred(entry_id): - conn = open_db() - cur = conn.execute( - "SELECT Transferred FROM entries WHERE ID =%s", (entry_id,)) - marked = cur.fetchall()[0][0] - if(marked == 0): - conn.execute( - "UPDATE entries SET Transferred = 1 WHERE ID =%s", (entry_id,)) - else: - conn.execute( - "UPDATE entries SET Transferred = 0 WHERE ID =%s", (entry_id,)) - # conn.close() + with get_db_engine().connect() as conn: + cur = conn.execute( + "SELECT Transferred FROM entries WHERE ID =%s", (entry_id,)) + marked = cur.fetchall()[0][0] + if(marked == 0): + conn.execute( + "UPDATE entries SET Transferred = 1 WHERE ID =%s", (entry_id,)) + else: + conn.execute( + "UPDATE entries SET Transferred = 0 WHERE ID =%s", (entry_id,)) return True def check_entry_quota(client_id): - conn = open_db() - cur = conn.execute( - "SELECT Count(*) FROM entries WHERE entries.Client_Id = %s", (client_id,)) + with get_db_engine().connect() as conn: + cur = conn.execute( + "SELECT Count(*) FROM entries WHERE entries.Client_Id = %s", (client_id,)) return cur.fetchall()[0][0] def check_queue_length(): - conn = open_db() - cur = conn.execute("SELECT Count(*) FROM entries") + with get_db_engine().connect() as conn: + cur = conn.execute("SELECT Count(*) FROM entries") return cur.fetchall()[0][0] def clear_played_songs(): - conn = open_db() - conn.execute("DELETE FROM done_songs") - # conn.close() + with get_db_engine().connect() as conn: + conn.execute("DELETE FROM done_songs") return True def delete_entry(id): - conn = open_db() - conn.execute("DELETE FROM entries WHERE id=%s", (id,)) - # conn.close() + with get_db_engine().connect() as conn: + conn.execute("DELETE FROM entries WHERE id=%s", (id,)) return True @@ -189,16 +169,15 @@ def delete_entries(ids): for x in ids: idlist.append((x,)) try: - conn = open_db() - cur = conn.execute("DELETE FROM entries WHERE id=%s", idlist) - # conn.close() + with get_db_engine().connect() as conn: + cur = conn.execute("DELETE FROM entries WHERE id=%s", idlist) + return cur.rowcount except Exception as error: return -1 def delete_all_entries(): - conn = open_db() - conn.execute("DELETE FROM entries") - # conn.close() + with get_db_engine().connect() as conn: + conn.execute("DELETE FROM entries") return True diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index 2ee48c0..a964cb2 100644 --- a/docker-compose.prod.yml +++ b/docker-compose.prod.yml @@ -1,10 +1,15 @@ version: "3.9" + +secrets: + secrets: + file: ./secrets.yml + services: karaoqueue: image: "phillipkhne/karaoqueue:latest" restart: always ports: - - "127.0.0.1:8081:80" + - "127.0.0.1:8081:80" # Please put a reverse proxy in front of this environment: DEPLOYMENT_PLATFORM: Docker DBSTRING: mysql://user:pass@host:3306/database @@ -16,8 +21,8 @@ services: image: mariadb restart: always environment: - MARIADB_ROOT_PASSWORD: dpMAZj*Mc4%FZM!V + MARIADB_ROOT_PASSWORD: changeme! MARIADB_ROOT_HOST: localhost MARIADB_DATABASE: karaoqueue MARIADB_USER: karaoqueue - MARIADB_PASSWORD: a5G@P*^tCW$$w@wE \ No newline at end of file + MARIADB_PASSWORD: change \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..eafc0e2 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,15 @@ +# This Compose file is for development only. It is not intended for production use. +# It only starts auxiliary services, such as a database, that are required for the +# application to run. The application itself is started separately, using the +# command "python -m flask run" or your favorite IDE. +# Useful for attaching a debugger to the application. + +version: "3.9" + +services: + db: + image: mariadb + restart: always + env_file: .env.dev + ports: + - "3306:3306" \ No newline at end of file