From faad60346fe3ffc70ffaf1f3b468684cc1e35899 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Phillip=20K=C3=BChne?= Date: Fri, 15 Jul 2022 14:00:37 +0200 Subject: [PATCH 01/85] Intial changes for MariaDB --- backend/Dockerfile => Dockerfile | 0 backend/app.py | 6 +- backend/database.py | 139 +++++++++++++++---------------- backend/requirements.txt | 5 +- docker-compose.prod.yml | 11 +++ 5 files changed, 83 insertions(+), 78 deletions(-) rename backend/Dockerfile => Dockerfile (100%) create mode 100644 docker-compose.prod.yml diff --git a/backend/Dockerfile b/Dockerfile similarity index 100% rename from backend/Dockerfile rename to Dockerfile diff --git a/backend/app.py b/backend/app.py index a36000c..7511e4e 100644 --- a/backend/app.py +++ b/backend/app.py @@ -1,4 +1,4 @@ -from flask import Flask, render_template, Response, abort, request, redirect, send_from_directory +from flask import Flask, render_template, Response, abort, request, redirect, send_from_directory, jsonify import helpers import database import data_adapters @@ -131,8 +131,8 @@ def get_song_completions(input_string=""): input_string = request.args.get('search', input_string) if input_string != "": print(input_string) - list = database.get_song_completions(input_string=input_string) - return Response(json.dumps(list, ensure_ascii=False).encode('utf-8'), mimetype='text/json') + result = [list(x) for x in database.get_song_completions(input_string=input_string)] + return jsonify(result) else: return 400 diff --git a/backend/database.py b/backend/database.py index 8fe2cd2..f34d63e 100644 --- a/backend/database.py +++ b/backend/database.py @@ -1,6 +1,10 @@ # -*- coding: utf_8 -*- +from types import NoneType +from MySQLdb import Connection +from sqlalchemy import create_engine import sqlite3 +import mariadb import pandas from io import StringIO @@ -9,23 +13,28 @@ entry_table = "entries" index_label = "Id" done_table = "done_songs" +connection = None -def open_db(): - conn = sqlite3.connect("/tmp/karaoqueue.db") - conn.execute('PRAGMA encoding = "UTF-8";') - return conn + +def open_db() -> Connection: + global connection + if (not connection): + engine = create_engine( + "mysql://ek0ur6p6ky9gdmif:jk571ov6g38g5iqv@eporqep6b4b8ql12.chr7pe7iynqr.eu-west-1.rds.amazonaws.com:3306/xdfmpudc3remzgj0") + connection = engine.connect() + # cur.execute('PRAGMA encoding = "UTF-8";') + return connection def import_songs(song_csv): print("Start importing Songs...") df = pandas.read_csv(StringIO(song_csv), sep=';') conn = open_db() - cur = conn.cursor() df.to_sql(song_table, conn, if_exists='replace', index=False) - cur.execute("SELECT Count(Id) FROM songs") + cur = conn.execute("SELECT Count(Id) FROM songs") num_songs = cur.fetchone()[0] - conn.close() + # conn.close() print("Imported songs ({} in Database)".format(num_songs)) return("Imported songs ({} in Database)".format(num_songs)) @@ -34,157 +43,143 @@ def create_entry_table(): conn = open_db() conn.execute('CREATE TABLE IF NOT EXISTS '+entry_table + ' (ID INTEGER PRIMARY KEY NOT NULL, Song_Id INTEGER NOT NULL, Name VARCHAR(255), Client_Id VARCHAR(36), Transferred INTEGER DEFAULT 0)') - conn.close() + # conn.close() def create_done_song_table(): conn = open_db() conn.execute('CREATE TABLE IF NOT EXISTS '+done_table + ' (Song_Id INTEGER PRIMARY KEY NOT NULL, Plays INTEGER)') - conn.close() + # conn.close() def create_song_table(): conn = open_db() - conn.execute("CREATE TABLE IF NOT EXISTS \""+song_table+"""\" ( - "Id" INTEGER, - "Title" TEXT, - "Artist" TEXT, - "Year" INTEGER, - "Duo" INTEGER, - "Explicit" INTEGER, - "Date Added" TEXT, - "Styles" TEXT, - "Languages" TEXT + conn.execute("CREATE TABLE IF NOT EXISTS `"+song_table+"""` ( + `Id` INTEGER, + `Title` TEXT, + `Artist` TEXT, + `Year` INTEGER, + `Duo` INTEGER, + `Explicit` INTEGER, + `Date Added` TEXT, + `Styles` TEXT, + `Languages` TEXT )""") - conn.close() + # conn.close() def create_list_view(): conn = open_db() - conn.execute("""CREATE VIEW IF NOT EXISTS [Liste] AS - SELECT Name, Title, Artist, entries.Id, songs.Id, entries.Transferred + 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() + WHERE entries.Song_Id=Song_ID""") + # conn.close() def create_done_song_view(): conn = open_db() - conn.execute("""CREATE VIEW IF NOT EXISTS [Abspielliste] AS + conn.execute("""CREATE OR REPLACE VIEW `Abspielliste` AS SELECT Artist || \" - \" || Title AS Song, Plays AS Wiedergaben FROM songs, done_songs WHERE done_songs.Song_Id=songs.Id""") - conn.close() + # conn.close() def get_list(): conn = open_db() - conn.row_factory = sqlite3.Row - cur = conn.cursor() - cur.execute("SELECT * FROM Liste") + cur = conn.execute("SELECT * FROM Liste") return cur.fetchall() def get_played_list(): conn = open_db() - cur = conn.cursor() - cur.execute("SELECT * FROM Abspielliste") + cur = conn.execute("SELECT * FROM Abspielliste") return cur.fetchall() def get_song_list(): conn = open_db() - cur = conn.cursor() - cur.execute("SELECT Artist || \" - \" || Title AS Song, Id FROM songs;") + cur = conn.execute( + "SELECT Artist || \" - \" || Title AS Song, Id FROM songs;") return cur.fetchall() def get_song_completions(input_string): conn = open_db() - cur = conn.cursor() # Don't look, it burns... prepared_string = "%{0}%".format( input_string).upper() # "Test" -> "%TEST%" print(prepared_string) - cur.execute( - "SELECT Title || \" - \" || Artist AS Song, Id FROM songs WHERE REPLACE(REPLACE(REPLACE(REPLACE(UPPER( SONG ),'ö','Ö'),'ü','Ü'),'ä','Ä'),'ß','ẞ') LIKE (?) LIMIT 20;", (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() - cur = conn.cursor() - cur.execute( - "INSERT INTO entries (Song_Id,Name,Client_Id) VALUES(?,?,?);", (song_id, name, client_id)) - conn.commit() - conn.close() + 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.cursor() - cur.execute("""SELECT Song_Id FROM entries WHERE Id=?""", (entry_id,)) + cur = conn.execute( + """SELECT Song_Id FROM entries WHERE Id=?""", (entry_id,)) song_id = cur.fetchone()[0] - cur.execute("""INSERT OR REPLACE INTO done_songs (Song_Id, Plays) + conn.execute("""INSERT OR 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)" - ) - conn.commit() + ) delete_entry(entry_id) - conn.close() + # conn.close() return True def toggle_transferred(entry_id): conn = open_db() - cur = conn.cursor() - cur.execute("SELECT Transferred FROM entries WHERE ID =?", (entry_id,)) + cur = conn.execute( + "SELECT Transferred FROM entries WHERE ID =?", (entry_id,)) marked = cur.fetchall()[0][0] if(marked == 0): - cur.execute( + conn.execute( "UPDATE entries SET Transferred = 1 WHERE ID =?", (entry_id,)) else: - cur.execute( + conn.execute( "UPDATE entries SET Transferred = 0 WHERE ID =?", (entry_id,)) - conn.commit() - conn.close() + # conn.close() return True def check_entry_quota(client_id): conn = open_db() - cur = conn.cursor() - cur.execute( + cur = conn.execute( "SELECT Count(*) FROM entries WHERE entries.Client_Id = ?", (client_id,)) return cur.fetchall()[0][0] def check_queue_length(): conn = open_db() - cur = conn.cursor() - cur.execute("SELECT Count(*) FROM entries") + cur = conn.execute("SELECT Count(*) FROM entries") return cur.fetchall()[0][0] def clear_played_songs(): conn = open_db() - cur = conn.cursor() - cur.execute("DELETE FROM done_songs") - conn.commit() - conn.close() + conn.execute("DELETE FROM done_songs") + # conn.close() return True def delete_entry(id): conn = open_db() - cur = conn.cursor() - cur.execute("DELETE FROM entries WHERE id=?", (id,)) - conn.commit() - conn.close() + conn.execute("DELETE FROM entries WHERE id=?", (id,)) + # conn.close() return True @@ -194,19 +189,15 @@ def delete_entries(ids): idlist.append((x,)) try: conn = open_db() - cur = conn.cursor() - cur.executemany("DELETE FROM entries WHERE id=?", idlist) - conn.commit() - conn.close() + cur = conn.execute("DELETE FROM entries WHERE id=?", idlist) + # conn.close() return cur.rowcount - except sqlite3.Error as error: + except mariadb.Error as error: return -1 def delete_all_entries(): conn = open_db() - cur = conn.cursor() - cur.execute("DELETE FROM entries") - conn.commit() - conn.close() + conn.execute("DELETE FROM entries") + # conn.close() return True diff --git a/backend/requirements.txt b/backend/requirements.txt index 04324d8..adb164c 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -2,4 +2,7 @@ requests pandas Flask-BasicAuth bs4 -gunicorn \ No newline at end of file +gunicorn +mariadb +SQLAlchemy +mysqlclient \ No newline at end of file diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml new file mode 100644 index 0000000..7ef04ae --- /dev/null +++ b/docker-compose.prod.yml @@ -0,0 +1,11 @@ +version: "3.9" +services: + db: + image: mariadb + restart: always + environment: + MARIADB_ROOT_PASSWORD: dpMAZj*Mc4%FZM!V + MARIADB_ROOT_HOST: localhost + MARIADB_DATABASE: karaoqueue + MARIADB_USER: karaoqueue + MARIADB_PASSWORD: a5G@P*^tCW$$w@wE \ No newline at end of file From 97dd80b03a255093ae4d0a1bcf3002676575d110 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Phillip=20K=C3=BChne?= Date: Fri, 15 Jul 2022 14:16:06 +0200 Subject: [PATCH 02/85] Add or update the Azure App Service build and deployment workflow config --- ...ure-legacy-mariadb_database_karaoqueue.yml | 63 +++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 .github/workflows/feature-legacy-mariadb_database_karaoqueue.yml diff --git a/.github/workflows/feature-legacy-mariadb_database_karaoqueue.yml b/.github/workflows/feature-legacy-mariadb_database_karaoqueue.yml new file mode 100644 index 0000000..85f3dbf --- /dev/null +++ b/.github/workflows/feature-legacy-mariadb_database_karaoqueue.yml @@ -0,0 +1,63 @@ +# Docs for the Azure Web Apps Deploy action: https://github.com/Azure/webapps-deploy +# More GitHub Actions for Azure: https://github.com/Azure/actions +# More info on Python, GitHub Actions, and Azure App Service: https://aka.ms/python-webapps-actions + +name: Build and deploy Python app to Azure Web App - karaoqueue + +on: + push: + branches: + - feature/legacy/mariadb_database + workflow_dispatch: + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + + - name: Set up Python version + uses: actions/setup-python@v1 + with: + python-version: '3.9' + + - name: Create and start virtual environment + run: | + python -m venv venv + source venv/bin/activate + + - name: Install dependencies + run: pip install -r requirements.txt + + # Optional: Add step to run tests here (PyTest, Django test suites, etc.) + + - name: Upload artifact for deployment jobs + uses: actions/upload-artifact@v2 + with: + name: python-app + path: | + . + !venv/ + + deploy: + runs-on: ubuntu-latest + needs: build + environment: + name: 'Production' + url: ${{ steps.deploy-to-webapp.outputs.webapp-url }} + + steps: + - name: Download artifact from build job + uses: actions/download-artifact@v2 + with: + name: python-app + path: . + + - name: 'Deploy to Azure Web App' + uses: azure/webapps-deploy@v2 + id: deploy-to-webapp + with: + app-name: 'karaoqueue' + slot-name: 'Production' + publish-profile: ${{ secrets.AZUREAPPSERVICE_PUBLISHPROFILE_8EBA2D69F5B7469D9CFD1F71F6A66108 }} From fb9677dc8806a4a605aca29871aa133df65879d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Phillip=20K=C3=BChne?= Date: Fri, 15 Jul 2022 14:23:17 +0200 Subject: [PATCH 03/85] Fix github workflow --- .../workflows/feature-legacy-mariadb_database_karaoqueue.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/feature-legacy-mariadb_database_karaoqueue.yml b/.github/workflows/feature-legacy-mariadb_database_karaoqueue.yml index 85f3dbf..a8028a2 100644 --- a/.github/workflows/feature-legacy-mariadb_database_karaoqueue.yml +++ b/.github/workflows/feature-legacy-mariadb_database_karaoqueue.yml @@ -28,7 +28,7 @@ jobs: source venv/bin/activate - name: Install dependencies - run: pip install -r requirements.txt + run: pip install -r backend/requirements.txt # Optional: Add step to run tests here (PyTest, Django test suites, etc.) @@ -37,7 +37,7 @@ jobs: with: name: python-app path: | - . + backend !venv/ deploy: From 3527ba4fd9cbdc518ba81ff61151ce32aed1fbe7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Phillip=20K=C3=BChne?= Date: Fri, 15 Jul 2022 14:27:39 +0200 Subject: [PATCH 04/85] Fix dependencies --- .../workflows/feature-legacy-mariadb_database_karaoqueue.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/feature-legacy-mariadb_database_karaoqueue.yml b/.github/workflows/feature-legacy-mariadb_database_karaoqueue.yml index a8028a2..96bbb70 100644 --- a/.github/workflows/feature-legacy-mariadb_database_karaoqueue.yml +++ b/.github/workflows/feature-legacy-mariadb_database_karaoqueue.yml @@ -27,6 +27,10 @@ jobs: python -m venv venv source venv/bin/activate + - name: Install mysql + run: sudo apt-get install -y mysql-server libmysqlclient-dev libmariadbclient-dev + + - name: Install dependencies run: pip install -r backend/requirements.txt From c9ad755a04383c1db1a5f7eff4de39eb30811260 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Phillip=20K=C3=BChne?= Date: Fri, 15 Jul 2022 14:29:18 +0200 Subject: [PATCH 05/85] Fix? --- .../workflows/feature-legacy-mariadb_database_karaoqueue.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/feature-legacy-mariadb_database_karaoqueue.yml b/.github/workflows/feature-legacy-mariadb_database_karaoqueue.yml index 96bbb70..317b045 100644 --- a/.github/workflows/feature-legacy-mariadb_database_karaoqueue.yml +++ b/.github/workflows/feature-legacy-mariadb_database_karaoqueue.yml @@ -28,7 +28,7 @@ jobs: source venv/bin/activate - name: Install mysql - run: sudo apt-get install -y mysql-server libmysqlclient-dev libmariadbclient-dev + run: sudo apt-get install -y mariadb-client - name: Install dependencies From b88aac69e63e23955edccef994876da438f9554a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Phillip=20K=C3=BChne?= Date: Fri, 15 Jul 2022 14:32:06 +0200 Subject: [PATCH 06/85] Fix.. --- .../workflows/feature-legacy-mariadb_database_karaoqueue.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/feature-legacy-mariadb_database_karaoqueue.yml b/.github/workflows/feature-legacy-mariadb_database_karaoqueue.yml index 317b045..5e5798f 100644 --- a/.github/workflows/feature-legacy-mariadb_database_karaoqueue.yml +++ b/.github/workflows/feature-legacy-mariadb_database_karaoqueue.yml @@ -28,7 +28,7 @@ jobs: source venv/bin/activate - name: Install mysql - run: sudo apt-get install -y mariadb-client + run: sudo sudo aptitude install -y mariadb-server - name: Install dependencies From 2a92c394508e7795d410b8818b5f3796fa45517d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Phillip=20K=C3=BChne?= Date: Fri, 15 Jul 2022 14:32:51 +0200 Subject: [PATCH 07/85] Test --- .../workflows/feature-legacy-mariadb_database_karaoqueue.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/feature-legacy-mariadb_database_karaoqueue.yml b/.github/workflows/feature-legacy-mariadb_database_karaoqueue.yml index 5e5798f..3938261 100644 --- a/.github/workflows/feature-legacy-mariadb_database_karaoqueue.yml +++ b/.github/workflows/feature-legacy-mariadb_database_karaoqueue.yml @@ -28,7 +28,7 @@ jobs: source venv/bin/activate - name: Install mysql - run: sudo sudo aptitude install -y mariadb-server + run: sudo sudo apt install -y mariadb-server - name: Install dependencies From 8151e615c0d716de991e31a4d06148a9ace5584a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Phillip=20K=C3=BChne?= Date: Fri, 15 Jul 2022 14:34:07 +0200 Subject: [PATCH 08/85] Test --- .../workflows/feature-legacy-mariadb_database_karaoqueue.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/feature-legacy-mariadb_database_karaoqueue.yml b/.github/workflows/feature-legacy-mariadb_database_karaoqueue.yml index 3938261..38da577 100644 --- a/.github/workflows/feature-legacy-mariadb_database_karaoqueue.yml +++ b/.github/workflows/feature-legacy-mariadb_database_karaoqueue.yml @@ -28,7 +28,7 @@ jobs: source venv/bin/activate - name: Install mysql - run: sudo sudo apt install -y mariadb-server + run: sudo sudo apt install -y mysql-server - name: Install dependencies From 69e252e28a32b4453c29b4c7fd4b6e0cba4edb78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Phillip=20K=C3=BChne?= Date: Fri, 15 Jul 2022 14:35:52 +0200 Subject: [PATCH 09/85] Add missing libraries to install --- .../workflows/feature-legacy-mariadb_database_karaoqueue.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/feature-legacy-mariadb_database_karaoqueue.yml b/.github/workflows/feature-legacy-mariadb_database_karaoqueue.yml index 38da577..d81fb22 100644 --- a/.github/workflows/feature-legacy-mariadb_database_karaoqueue.yml +++ b/.github/workflows/feature-legacy-mariadb_database_karaoqueue.yml @@ -28,7 +28,7 @@ jobs: source venv/bin/activate - name: Install mysql - run: sudo sudo apt install -y mysql-server + run: sudo sudo apt install -y mysql-server libmysqlclient-dev libmariadbclient-dev - name: Install dependencies From 2a18f3dc2c1cca744dea981d28f72adbdc3dc3a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Phillip=20K=C3=BChne?= Date: Fri, 15 Jul 2022 14:36:47 +0200 Subject: [PATCH 10/85] Completely remove mariadb libs to hopefully fix conflicts --- .../workflows/feature-legacy-mariadb_database_karaoqueue.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/feature-legacy-mariadb_database_karaoqueue.yml b/.github/workflows/feature-legacy-mariadb_database_karaoqueue.yml index d81fb22..9f8f323 100644 --- a/.github/workflows/feature-legacy-mariadb_database_karaoqueue.yml +++ b/.github/workflows/feature-legacy-mariadb_database_karaoqueue.yml @@ -28,7 +28,7 @@ jobs: source venv/bin/activate - name: Install mysql - run: sudo sudo apt install -y mysql-server libmysqlclient-dev libmariadbclient-dev + run: sudo sudo apt install -y mysql-server libmysqlclient-dev - name: Install dependencies From 9907a19066320c90fc8f6aefb9d37781b9a28adb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Phillip=20K=C3=BChne?= Date: Fri, 15 Jul 2022 14:43:16 +0200 Subject: [PATCH 11/85] Test --- .../workflows/feature-legacy-mariadb_database_karaoqueue.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/feature-legacy-mariadb_database_karaoqueue.yml b/.github/workflows/feature-legacy-mariadb_database_karaoqueue.yml index 9f8f323..4a6170f 100644 --- a/.github/workflows/feature-legacy-mariadb_database_karaoqueue.yml +++ b/.github/workflows/feature-legacy-mariadb_database_karaoqueue.yml @@ -28,7 +28,7 @@ jobs: source venv/bin/activate - name: Install mysql - run: sudo sudo apt install -y mysql-server libmysqlclient-dev + run: sudo sudo apt install -y mysql-server libmysqlclient-dev python-dev - name: Install dependencies From 2138189dff5d94d75c16dc55b4f3e3157d0b7ee9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Phillip=20K=C3=BChne?= Date: Fri, 15 Jul 2022 14:44:26 +0200 Subject: [PATCH 12/85] python3 is still not default on Ubuntu? --- .../workflows/feature-legacy-mariadb_database_karaoqueue.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/feature-legacy-mariadb_database_karaoqueue.yml b/.github/workflows/feature-legacy-mariadb_database_karaoqueue.yml index 4a6170f..2675db4 100644 --- a/.github/workflows/feature-legacy-mariadb_database_karaoqueue.yml +++ b/.github/workflows/feature-legacy-mariadb_database_karaoqueue.yml @@ -28,7 +28,7 @@ jobs: source venv/bin/activate - name: Install mysql - run: sudo sudo apt install -y mysql-server libmysqlclient-dev python-dev + run: sudo sudo apt install -y mysql-server libmysqlclient-dev python3-dev - name: Install dependencies From 3ae2803fccc2086502772d184b45a460713af4d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Phillip=20K=C3=BChne?= Date: Fri, 15 Jul 2022 14:51:05 +0200 Subject: [PATCH 13/85] Remove mysql and install mariadb --- .../feature-legacy-mariadb_database_karaoqueue.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/feature-legacy-mariadb_database_karaoqueue.yml b/.github/workflows/feature-legacy-mariadb_database_karaoqueue.yml index 2675db4..4c4640a 100644 --- a/.github/workflows/feature-legacy-mariadb_database_karaoqueue.yml +++ b/.github/workflows/feature-legacy-mariadb_database_karaoqueue.yml @@ -27,8 +27,10 @@ jobs: python -m venv venv source venv/bin/activate - - name: Install mysql - run: sudo sudo apt install -y mysql-server libmysqlclient-dev python3-dev + - name: Install mariadb + run: | + sudo apt-get remove --purge mysql-server mysql-client mysql-common libmysqlclient-dev + sudo sudo apt install -y libmariadbclient-dev python3-dev - name: Install dependencies From 67a9552fee06adbde680373bd02e0a3d11591e65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Phillip=20K=C3=BChne?= Date: Fri, 15 Jul 2022 14:55:14 +0200 Subject: [PATCH 14/85] Update ubuntu to get newer mariadb connector --- .../workflows/feature-legacy-mariadb_database_karaoqueue.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/feature-legacy-mariadb_database_karaoqueue.yml b/.github/workflows/feature-legacy-mariadb_database_karaoqueue.yml index 4c4640a..da661e3 100644 --- a/.github/workflows/feature-legacy-mariadb_database_karaoqueue.yml +++ b/.github/workflows/feature-legacy-mariadb_database_karaoqueue.yml @@ -12,7 +12,7 @@ on: jobs: build: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v2 From adfd120636dbef8743449c936c0bb7de991ea706 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Phillip=20K=C3=BChne?= Date: Fri, 15 Jul 2022 15:01:07 +0200 Subject: [PATCH 15/85] Update package names for 22.04 --- .../workflows/feature-legacy-mariadb_database_karaoqueue.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/feature-legacy-mariadb_database_karaoqueue.yml b/.github/workflows/feature-legacy-mariadb_database_karaoqueue.yml index da661e3..3c0e492 100644 --- a/.github/workflows/feature-legacy-mariadb_database_karaoqueue.yml +++ b/.github/workflows/feature-legacy-mariadb_database_karaoqueue.yml @@ -30,7 +30,7 @@ jobs: - name: Install mariadb run: | sudo apt-get remove --purge mysql-server mysql-client mysql-common libmysqlclient-dev - sudo sudo apt install -y libmariadbclient-dev python3-dev + sudo sudo apt install -y libmariadbd-dev python3-dev - name: Install dependencies From fc78bdc4feaf7821d6649ae0db694e3184ad9ebe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Phillip=20K=C3=BChne?= Date: Fri, 15 Jul 2022 15:09:48 +0200 Subject: [PATCH 16/85] Fix types --- backend/database.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/backend/database.py b/backend/database.py index f34d63e..c7bdc48 100644 --- a/backend/database.py +++ b/backend/database.py @@ -1,9 +1,8 @@ # -*- coding: utf_8 -*- -from types import NoneType +from email.mime import base from MySQLdb import Connection -from sqlalchemy import create_engine -import sqlite3 +from sqlalchemy import create_engine, engine import mariadb import pandas from io import StringIO @@ -16,7 +15,7 @@ done_table = "done_songs" connection = None -def open_db() -> Connection: +def open_db() -> engine.base.Connection: global connection if (not connection): engine = create_engine( From 57ced69dddb72b46e61d97374240382209b3dc1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Phillip=20K=C3=BChne?= Date: Fri, 15 Jul 2022 15:27:36 +0200 Subject: [PATCH 17/85] Load DB Connection from ENV --- backend/app.py | 1 + backend/database.py | 4 ++-- backend/helpers.py | 13 +++++++++++++ 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/backend/app.py b/backend/app.py index 7511e4e..0a86157 100644 --- a/backend/app.py +++ b/backend/app.py @@ -229,6 +229,7 @@ def admin(): @app.before_first_request def activate_job(): + helpers.load_dbconfig(app) helpers.load_version(app) helpers.create_data_directory() database.create_entry_table() diff --git a/backend/database.py b/backend/database.py index c7bdc48..7ba70c7 100644 --- a/backend/database.py +++ b/backend/database.py @@ -6,6 +6,7 @@ from sqlalchemy import create_engine, engine import mariadb import pandas from io import StringIO +from flask import current_app song_table = "songs" entry_table = "entries" @@ -18,8 +19,7 @@ connection = None def open_db() -> engine.base.Connection: global connection if (not connection): - engine = create_engine( - "mysql://ek0ur6p6ky9gdmif:jk571ov6g38g5iqv@eporqep6b4b8ql12.chr7pe7iynqr.eu-west-1.rds.amazonaws.com:3306/xdfmpudc3remzgj0") + engine = create_engine(current_app.config.get("DBCONNSTRING")) connection = engine.connect() # cur.execute('PRAGMA encoding = "UTF-8";') return connection diff --git a/backend/helpers.py b/backend/helpers.py index 17545a6..c937f04 100644 --- a/backend/helpers.py +++ b/backend/helpers.py @@ -47,6 +47,19 @@ def load_version(app): app.config['VERSION'] = "" else: app.config['VERSION'] = "" + +def load_dbconfig(app): + if os.environ.get("JAWSDB_MARIA_URL"): + app.config['VERSION'] = os.environ.get("JAWSDB_MARIA_URL") + elif os.path.isfile(".dbconn"): + with open('.dbconn', 'r') as file: + data = file.read().replace('\n', '') + if data: + app.config['DBCONNSTRING'] = data + else: + app.config['DBCONNSTRING'] = "" + else: + app.config['DBCONNSTRING'] = "" def setup_config(app): if check_config_exists(): From 72914109c054653965c8abcd1402a76509a83478 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Phillip=20K=C3=BChne?= Date: Fri, 15 Jul 2022 15:33:42 +0200 Subject: [PATCH 18/85] prune requirements --- backend/database.py | 3 +-- backend/requirements.txt | 1 - 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/backend/database.py b/backend/database.py index 7ba70c7..528221d 100644 --- a/backend/database.py +++ b/backend/database.py @@ -3,7 +3,6 @@ from email.mime import base from MySQLdb import Connection from sqlalchemy import create_engine, engine -import mariadb import pandas from io import StringIO from flask import current_app @@ -191,7 +190,7 @@ def delete_entries(ids): cur = conn.execute("DELETE FROM entries WHERE id=?", idlist) # conn.close() return cur.rowcount - except mariadb.Error as error: + except Exception as error: return -1 diff --git a/backend/requirements.txt b/backend/requirements.txt index adb164c..d816517 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -3,6 +3,5 @@ pandas Flask-BasicAuth bs4 gunicorn -mariadb SQLAlchemy mysqlclient \ No newline at end of file From bc44985fed2a34b3435409f322ca73a22ca1ae76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Phillip=20K=C3=BChne?= Date: Fri, 15 Jul 2022 15:35:58 +0200 Subject: [PATCH 19/85] Fix DBCONNSTRING --- backend/database.py | 1 + 1 file changed, 1 insertion(+) diff --git a/backend/database.py b/backend/database.py index 528221d..579f141 100644 --- a/backend/database.py +++ b/backend/database.py @@ -18,6 +18,7 @@ connection = None def open_db() -> engine.base.Connection: global connection if (not connection): + print(current_app.config.get("DBCONNSTRING")) engine = create_engine(current_app.config.get("DBCONNSTRING")) connection = engine.connect() # cur.execute('PRAGMA encoding = "UTF-8";') From a22960eae92713d744e76ae2f6693a87cf3ebdac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Phillip=20K=C3=BChne?= Date: Fri, 15 Jul 2022 15:54:15 +0200 Subject: [PATCH 20/85] Fix DB Connection --- .vscode/launch.json | 3 ++- backend/helpers.py | 24 +++++++++++++++--------- 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index 2e00725..d9b4ed5 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -14,7 +14,8 @@ "env": { "FLASK_APP": "backend/app.py", "FLASK_ENV": "development", - "FLASK_DEBUG": "1" + "FLASK_DEBUG": "1", + "DBSTRING": "mysql://ek0ur6p6ky9gdmif:kpmi2bav4mvh4jbx@eporqep6b4b8ql12.chr7pe7iynqr.eu-west-1.rds.amazonaws.com:3306/xdfmpudc3remzgj0" }, "args": [ "run", diff --git a/backend/helpers.py b/backend/helpers.py index c937f04..53ee6a1 100644 --- a/backend/helpers.py +++ b/backend/helpers.py @@ -49,17 +49,23 @@ def load_version(app): app.config['VERSION'] = "" def load_dbconfig(app): - if os.environ.get("JAWSDB_MARIA_URL"): - app.config['VERSION'] = os.environ.get("JAWSDB_MARIA_URL") - elif os.path.isfile(".dbconn"): - with open('.dbconn', 'r') as file: - data = file.read().replace('\n', '') - if data: - app.config['DBCONNSTRING'] = data + if os.environ.get("FLASK_ENV") == "development": + app.config['DBCONNSTRING'] = os.environ.get("DBSTRING") + else: + if os.environ.get("DEPLOYMENT_PLATFORM") == "Heroku": + if os.environ.get("JAWSDB_MARIA_URL"): + app.config['DBCONNSTRING'] = os.environ.get("JAWSDB_MARIA_URL") else: app.config['DBCONNSTRING'] = "" - else: - app.config['DBCONNSTRING'] = "" + elif os.path.isfile(".dbconn"): + with open('.dbconn', 'r') as file: + data = file.read().replace('\n', '') + if data: + app.config['DBCONNSTRING'] = data + else: + app.config['DBCONNSTRING'] = "" + else: + app.config['DBCONNSTRING'] = "" def setup_config(app): if check_config_exists(): From 8dd90b728cd59f330f971c251664be634b2e879e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Phillip=20K=C3=BChne?= Date: Sat, 8 Oct 2022 19:47:29 +0200 Subject: [PATCH 21/85] MySQL Working, bug fixes. --- .vscode/launch.json | 10 ++++---- .vscode/tasks.json | 7 ++++++ Dockerfile | 2 +- backend/app.py | 13 +++++----- backend/database.py | 40 ++++++++++++++++-------------- backend/helpers.py | 41 ++++++++++++++++++++----------- backend/templates/base.html | 2 +- backend/templates/main_admin.html | 10 +++++--- docker-compose.prod.yml | 12 +++++++++ 9 files changed, 86 insertions(+), 51 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index d9b4ed5..27002f1 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -5,7 +5,7 @@ "version": "0.2.0", "configurations": [ { - "preLaunchTask": "versiondump", + "preLaunchTask": "mariadb", "name": "Python: Flask", "type": "python", "cwd": "${workspaceFolder}/backend", @@ -15,7 +15,7 @@ "FLASK_APP": "backend/app.py", "FLASK_ENV": "development", "FLASK_DEBUG": "1", - "DBSTRING": "mysql://ek0ur6p6ky9gdmif:kpmi2bav4mvh4jbx@eporqep6b4b8ql12.chr7pe7iynqr.eu-west-1.rds.amazonaws.com:3306/xdfmpudc3remzgj0" + "DBSTRING": "mysql://devuser:devpw@127.0.0.1:3306/karaoqueue" }, "args": [ "run", @@ -25,7 +25,7 @@ "jinja": true }, { - "preLaunchTask": "versiondump", + "preLaunchTask": "mariadb", "name": "Python: Flask (with reload)", "type": "python", "cwd": "${workspaceFolder}/backend", @@ -43,7 +43,7 @@ "jinja": true }, { - "preLaunchTask": "versiondump", + "preLaunchTask": "mariadb", "name": "Python: Flask (with reload, externally reachable)", "type": "python", "cwd": "${workspaceFolder}/backend", @@ -62,7 +62,7 @@ "jinja": true }, { - "preLaunchTask": "versiondump", + "preLaunchTask": "mariadb", "name": "Python: Flask (externally reachable)", "type": "python", "cwd": "${workspaceFolder}/backend", diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 563a855..bdaee63 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -8,6 +8,13 @@ "type": "shell", "command": "echo \"$(git rev-parse --abbrev-ref HEAD)-$(git describe)\"> ${workspaceFolder}/backend/.version", "problemMatcher": [] + }, + { + "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", + "isBackground": true, + "activeOnStart": false } ] } \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 0e36a09..7389e11 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,4 +8,4 @@ RUN pip install Flask-BasicAuth RUN pip install bs4 -COPY ./app /app \ No newline at end of file +COPY ./backend /app \ No newline at end of file diff --git a/backend/app.py b/backend/app.py index 0a86157..370c2b6 100644 --- a/backend/app.py +++ b/backend/app.py @@ -1,4 +1,5 @@ -from flask import Flask, render_template, Response, abort, request, redirect, send_from_directory, jsonify +from flask import Flask, render_template, abort, request, redirect, send_from_directory, jsonify +from flask.wrappers import Request, Response import helpers import database import data_adapters @@ -9,7 +10,7 @@ from helpers import nocache app = Flask(__name__, static_url_path='/static') basic_auth = BasicAuth(app) -accept_entries = False +accept_entries = True @app.route("/") def home(): @@ -81,12 +82,12 @@ def settings(): def settings_post(): entryquota = request.form.get("entryquota") maxqueue = request.form.get("maxqueue") - if entryquota.isnumeric() and int(entryquota) > 0: - app.config['ENTRY_QUOTA'] = int(entryquota) + if entryquota.isnumeric() and int(entryquota) > 0: # type: ignore + app.config['ENTRY_QUOTA'] = int(entryquota) # type: ignore else: abort(400) - if maxqueue.isnumeric and int(maxqueue) > 0: - app.config['MAX_QUEUE'] = int(maxqueue) + if maxqueue.isnumeric and int(maxqueue) > 0: # type: ignore + app.config['MAX_QUEUE'] = int(maxqueue) # type: ignore else: abort(400) diff --git a/backend/database.py b/backend/database.py index 579f141..fe9805d 100644 --- a/backend/database.py +++ b/backend/database.py @@ -19,7 +19,7 @@ def open_db() -> engine.base.Connection: global connection if (not connection): print(current_app.config.get("DBCONNSTRING")) - engine = create_engine(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 @@ -32,7 +32,7 @@ def import_songs(song_csv): df.to_sql(song_table, conn, if_exists='replace', index=False) cur = conn.execute("SELECT Count(Id) FROM songs") - num_songs = cur.fetchone()[0] + num_songs = cur.fetchone()[0] # type: ignore # conn.close() print("Imported songs ({} in Database)".format(num_songs)) return("Imported songs ({} in Database)".format(num_songs)) @@ -41,7 +41,7 @@ def import_songs(song_csv): def create_entry_table(): conn = open_db() conn.execute('CREATE TABLE IF NOT EXISTS '+entry_table + - ' (ID INTEGER PRIMARY KEY NOT NULL, Song_Id INTEGER NOT NULL, Name VARCHAR(255), Client_Id VARCHAR(36), Transferred INTEGER DEFAULT 0)') + ' (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() @@ -73,14 +73,14 @@ def create_list_view(): 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=Song_ID""") + WHERE entries.Song_Id=songs.Id""") # conn.close() def create_done_song_view(): conn = open_db() conn.execute("""CREATE OR REPLACE VIEW `Abspielliste` AS - SELECT Artist || \" - \" || Title AS Song, Plays AS Wiedergaben + SELECT CONCAT(Artist," - ", Title) AS Song, Plays AS Wiedergaben FROM songs, done_songs WHERE done_songs.Song_Id=songs.Id""") # conn.close() @@ -127,14 +127,16 @@ def add_entry(name, song_id, client_id): def add_sung_song(entry_id): conn = open_db() cur = conn.execute( - """SELECT Song_Id FROM entries WHERE Id=?""", (entry_id,)) - song_id = cur.fetchone()[0] - conn.execute("""INSERT OR 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)" - ) + """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() return True @@ -143,14 +145,14 @@ def add_sung_song(entry_id): def toggle_transferred(entry_id): conn = open_db() cur = conn.execute( - "SELECT Transferred FROM entries WHERE ID =?", (entry_id,)) + "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 =?", (entry_id,)) + "UPDATE entries SET Transferred = 1 WHERE ID =%s", (entry_id,)) else: conn.execute( - "UPDATE entries SET Transferred = 0 WHERE ID =?", (entry_id,)) + "UPDATE entries SET Transferred = 0 WHERE ID =%s", (entry_id,)) # conn.close() return True @@ -158,7 +160,7 @@ def toggle_transferred(entry_id): def check_entry_quota(client_id): conn = open_db() cur = conn.execute( - "SELECT Count(*) FROM entries WHERE entries.Client_Id = ?", (client_id,)) + "SELECT Count(*) FROM entries WHERE entries.Client_Id = %s", (client_id,)) return cur.fetchall()[0][0] @@ -177,7 +179,7 @@ def clear_played_songs(): def delete_entry(id): conn = open_db() - conn.execute("DELETE FROM entries WHERE id=?", (id,)) + conn.execute("DELETE FROM entries WHERE id=%s", (id,)) # conn.close() return True @@ -188,7 +190,7 @@ def delete_entries(ids): idlist.append((x,)) try: conn = open_db() - cur = conn.execute("DELETE FROM entries WHERE id=?", idlist) + cur = conn.execute("DELETE FROM entries WHERE id=%s", idlist) # conn.close() return cur.rowcount except Exception as error: diff --git a/backend/helpers.py b/backend/helpers.py index 53ee6a1..656a6bf 100644 --- a/backend/helpers.py +++ b/backend/helpers.py @@ -37,7 +37,7 @@ def check_config_exists(): def load_version(app): if os.environ.get("SOURCE_VERSION"): - app.config['VERSION'] = os.environ.get("SOURCE_VERSION")[0:7] + app.config['VERSION'] = os.environ.get("SOURCE_VERSION")[0:7] # type: ignore elif os.path.isfile(".version"): with open('.version', 'r') as file: data = file.read().replace('\n', '') @@ -57,6 +57,11 @@ def load_dbconfig(app): app.config['DBCONNSTRING'] = os.environ.get("JAWSDB_MARIA_URL") else: app.config['DBCONNSTRING'] = "" + if os.environ.get("DEPLOYMENT_PLATFORM") == "Docker": + if os.environ.get("DBSTRING"): + app.config['DBCONNSTRING'] = os.environ.get("DBSTRING") + else: + app.config['DBCONNSTRING'] = "" elif os.path.isfile(".dbconn"): with open('.dbconn', 'r') as file: data = file.read().replace('\n', '') @@ -68,20 +73,26 @@ def load_dbconfig(app): app.config['DBCONNSTRING'] = "" def setup_config(app): - if check_config_exists(): - config = json.load(open(config_file)) - with open(config_file, 'r') as handle: - config = json.load(handle) - print("Loaded existing config") + if os.environ.get("DEPLOYMENT_PLATFORM") == "Docker": + app.config['BASIC_AUTH_USERNAME'] = os.environ.get('BASIC_AUTH_USERNAME') + app.config['BASIC_AUTH_PASSWORD'] = os.environ.get('BASIC_AUTH_PASSWORD') + app.config['ENTRY_QUOTA'] = os.environ.get('ENTRY_QUOTA') + app.config['MAX_QUEUE'] = os.environ.get('MAX_QUEUE') else: - config = {'username': 'admin', 'password': 'changeme', 'entryquota': 3, 'maxqueue': 20} - with open(config_file, 'w') as handle: - json.dump(config, handle, indent=4, sort_keys=True) - print("Wrote new config") - app.config['BASIC_AUTH_USERNAME'] = config['username'] - app.config['BASIC_AUTH_PASSWORD'] = config['password'] - app.config['ENTRY_QUOTA'] = config['entryquota'] - app.config['MAX_QUEUE'] = config['maxqueue'] + if check_config_exists(): + config = json.load(open(config_file)) + with open(config_file, 'r') as handle: + config = json.load(handle) + print("Loaded existing config") + else: + config = {'username': 'admin', 'password': 'changeme', 'entryquota': 3, 'maxqueue': 20} + with open(config_file, 'w') as handle: + json.dump(config, handle, indent=4, sort_keys=True) + print("Wrote new config") + app.config['BASIC_AUTH_USERNAME'] = config['username'] + app.config['BASIC_AUTH_PASSWORD'] = config['password'] + app.config['ENTRY_QUOTA'] = config['entryquota'] + app.config['MAX_QUEUE'] = config['maxqueue'] @@ -89,7 +100,7 @@ def nocache(view): @wraps(view) def no_cache(*args, **kwargs): response = make_response(view(*args, **kwargs)) - response.headers['Last-Modified'] = datetime.now() + response.headers['Last-Modified'] = datetime.now() # type: ignore response.headers['Cache-Control'] = 'no-store, no-cache, must-revalidate, post-check=0, pre-check=0, max-age=0' response.headers['Pragma'] = 'no-cache' response.headers['Expires'] = '-1' diff --git a/backend/templates/base.html b/backend/templates/base.html index f243850..8b598ea 100644 --- a/backend/templates/base.html +++ b/backend/templates/base.html @@ -75,7 +75,7 @@ {% endif %} - KaraoQueue {{karaoqueue_version}} - © 2019-21 - Phillip + KaraoQueue {{karaoqueue_version}} - 2019-22 - Phillip Kühne diff --git a/backend/templates/main_admin.html b/backend/templates/main_admin.html index 5234a88..5379dc2 100644 --- a/backend/templates/main_admin.html +++ b/backend/templates/main_admin.html @@ -52,10 +52,10 @@ table td:nth-child(2) { {% block extrajs %} diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index 7ef04ae..2ee48c0 100644 --- a/docker-compose.prod.yml +++ b/docker-compose.prod.yml @@ -1,5 +1,17 @@ version: "3.9" services: + karaoqueue: + image: "phillipkhne/karaoqueue:latest" + restart: always + ports: + - "127.0.0.1:8081:80" + environment: + DEPLOYMENT_PLATFORM: Docker + DBSTRING: mysql://user:pass@host:3306/database + BASIC_AUTH_USERNAME: admin + BASIC_AUTH_PASSWORD: changeme + ENTRY_QUOTA: 3 + MAX_QUEUE: 20 db: image: mariadb restart: always From 698c3717fda1b0d638bf2cf0ab21358f1019753c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Phillip=20K=C3=BChne?= Date: Sat, 8 Oct 2022 19:50:35 +0200 Subject: [PATCH 22/85] Remove outdated workflows --- ...ure-legacy-mariadb_database_karaoqueue.yml | 69 ------------------- 1 file changed, 69 deletions(-) delete mode 100644 .github/workflows/feature-legacy-mariadb_database_karaoqueue.yml diff --git a/.github/workflows/feature-legacy-mariadb_database_karaoqueue.yml b/.github/workflows/feature-legacy-mariadb_database_karaoqueue.yml deleted file mode 100644 index 3c0e492..0000000 --- a/.github/workflows/feature-legacy-mariadb_database_karaoqueue.yml +++ /dev/null @@ -1,69 +0,0 @@ -# Docs for the Azure Web Apps Deploy action: https://github.com/Azure/webapps-deploy -# More GitHub Actions for Azure: https://github.com/Azure/actions -# More info on Python, GitHub Actions, and Azure App Service: https://aka.ms/python-webapps-actions - -name: Build and deploy Python app to Azure Web App - karaoqueue - -on: - push: - branches: - - feature/legacy/mariadb_database - workflow_dispatch: - -jobs: - build: - runs-on: ubuntu-22.04 - - steps: - - uses: actions/checkout@v2 - - - name: Set up Python version - uses: actions/setup-python@v1 - with: - python-version: '3.9' - - - name: Create and start virtual environment - run: | - python -m venv venv - source venv/bin/activate - - - name: Install mariadb - run: | - sudo apt-get remove --purge mysql-server mysql-client mysql-common libmysqlclient-dev - sudo sudo apt install -y libmariadbd-dev python3-dev - - - - name: Install dependencies - run: pip install -r backend/requirements.txt - - # Optional: Add step to run tests here (PyTest, Django test suites, etc.) - - - name: Upload artifact for deployment jobs - uses: actions/upload-artifact@v2 - with: - name: python-app - path: | - backend - !venv/ - - deploy: - runs-on: ubuntu-latest - needs: build - environment: - name: 'Production' - url: ${{ steps.deploy-to-webapp.outputs.webapp-url }} - - steps: - - name: Download artifact from build job - uses: actions/download-artifact@v2 - with: - name: python-app - path: . - - - name: 'Deploy to Azure Web App' - uses: azure/webapps-deploy@v2 - id: deploy-to-webapp - with: - app-name: 'karaoqueue' - slot-name: 'Production' - publish-profile: ${{ secrets.AZUREAPPSERVICE_PUBLISHPROFILE_8EBA2D69F5B7469D9CFD1F71F6A66108 }} From 41a24ad9cea35ca04ca62e2fc5d8849642c571b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Phillip=20K=C3=BChne?= Date: Mon, 27 Mar 2023 01:02:26 +0200 Subject: [PATCH 23/85] readd main.py --- backend/{wsgi.py => main.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename backend/{wsgi.py => main.py} (100%) diff --git a/backend/wsgi.py b/backend/main.py similarity index 100% rename from backend/wsgi.py rename to backend/main.py From 831166f38bd9046e8e98b819a03cc08ade21cc2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Phillip=20K=C3=BChne?= Date: Mon, 27 Mar 2023 23:38:22 +0200 Subject: [PATCH 24/85] Intall dependencies using requirements.txt --- Dockerfile | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/Dockerfile b/Dockerfile index 7389e11..1dcac9d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,11 +1,5 @@ FROM tiangolo/uwsgi-nginx-flask:python3.7 -RUN pip install requests +COPY ./backend /app -RUN pip install pandas - -RUN pip install Flask-BasicAuth - -RUN pip install bs4 - -COPY ./backend /app \ No newline at end of file +RUN pip install -r /app/requirements.txt \ No newline at end of file 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 25/85] 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 From 551536bcb4d9fbb7d1fc1d7317d2018694bc5f4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Phillip=20K=C3=BChne?= Date: Tue, 28 Mar 2023 01:00:21 +0200 Subject: [PATCH 26/85] Update bootstrap-tables --- backend/templates/base.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/backend/templates/base.html b/backend/templates/base.html index 8b598ea..70ebb30 100644 --- a/backend/templates/base.html +++ b/backend/templates/base.html @@ -12,7 +12,7 @@ {% block title %}{% endblock %} - KaraoQueue - + - + + src="https://unpkg.com/bootstrap-table@1.21.2/dist/extensions/auto-refresh/bootstrap-table-auto-refresh.min.js"> {% block extrajs %}{% endblock %} From e84ff1a381bc4eb9c7d9ef93f7d6af8b3b17c58f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Phillip=20K=C3=BChne?= Date: Thu, 30 Mar 2023 17:05:17 +0200 Subject: [PATCH 39/85] Change container registry to github --- docker-compose.prod.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index 3fe46c5..d8cf50d 100644 --- a/docker-compose.prod.yml +++ b/docker-compose.prod.yml @@ -6,7 +6,7 @@ secrets: services: karaoqueue: - image: "phillipkhne/karaoqueue:latest" + image: "ghcr.io/phoenixtwofive/karaoqueue:v2023.03" build: . restart: always ports: From a2cf4fc47f878b0ad8c041069d084b1f8637c3d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Phillip=20K=C3=BChne?= Date: Thu, 30 Mar 2023 17:05:46 +0200 Subject: [PATCH 40/85] Change .env.dev to reflect new settings storage --- .env.dev | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/.env.dev b/.env.dev index 1fa9576..e22ecaa 100644 --- a/.env.dev +++ b/.env.dev @@ -7,8 +7,4 @@ 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 +DBSTRING="mysql+pymysql://karaoqueue:mariadb_karaoqueue_password@127.0.0.1:3306/karaoqueue?charset=utf8mb4" \ No newline at end of file From 24458a78d0c45f13248d615df0fe11b9eaa76b9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Phillip=20K=C3=BChne?= Date: Thu, 30 Mar 2023 17:06:36 +0200 Subject: [PATCH 41/85] Fix bugs --- backend/app.py | 2 +- backend/static/css/style.css | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/app.py b/backend/app.py index fced038..0b20c7b 100644 --- a/backend/app.py +++ b/backend/app.py @@ -133,7 +133,7 @@ def update_songs(): return Response('{"status": "%s" }' % status, mimetype='text/json') -@app.route("/api/songs/compl") +@app.route("/api/songs/compl") # type: ignore @nocache def get_song_completions(input_string=""): input_string = request.args.get('search', input_string) diff --git a/backend/static/css/style.css b/backend/static/css/style.css index 91b3f5c..46603a7 100644 --- a/backend/static/css/style.css +++ b/backend/static/css/style.css @@ -115,7 +115,7 @@ body { } .footer { - background-color: #232323; + background-color: var(--background-color-var); } .modal-content { From cf6d586856577ff7c8c4e3b6ddf252519e5b459f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Phillip=20K=C3=BChne?= Date: Thu, 30 Mar 2023 17:06:52 +0200 Subject: [PATCH 42/85] Overhaul database code --- backend/database.py | 163 +++++++++++++++++++++++++++++--------------- backend/helpers.py | 12 +--- 2 files changed, 109 insertions(+), 66 deletions(-) diff --git a/backend/database.py b/backend/database.py index d9bd68c..59129a2 100644 --- a/backend/database.py +++ b/backend/database.py @@ -2,7 +2,7 @@ from email.mime import base from MySQLdb import Connection -from sqlalchemy import create_engine, engine +from sqlalchemy import create_engine, engine, text import pandas from io import StringIO from flask import current_app @@ -19,7 +19,8 @@ def get_db_engine() -> engine.base.Engine: global sql_engine if (not sql_engine): print(current_app.config.get("DBCONNSTRING")) - sql_engine = create_engine(current_app.config.get("DBCONNSTRING")) # type: ignore + sql_engine = create_engine( + current_app.config.get("DBCONNSTRING")) # type: ignore return sql_engine @@ -28,147 +29,169 @@ def import_songs(song_csv): df = pandas.read_csv(StringIO(song_csv), sep=';') 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 + index=False) + cur = conn.execute(text("SELECT Count(Id) FROM songs")) + num_songs = cur.fetchone()[0] # type: ignore + conn.commit() print("Imported songs ({} in Database)".format(num_songs)) - return("Imported songs ({} in Database)".format(num_songs)) + return ("Imported songs ({} in Database)".format(num_songs)) def create_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)') + stmt = text( + f'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.execute(stmt) + conn.commit() def create_done_song_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)') + stmt = text( + f'CREATE TABLE IF NOT EXISTS `{done_table}` (Song_Id INTEGER PRIMARY KEY NOT NULL, Plays INTEGER)') + conn.execute(stmt) + conn.commit() def create_song_table(): with get_db_engine().connect() as conn: - conn.execute("CREATE TABLE IF NOT EXISTS `"+song_table+"""` ( + stmt = text(f"""CREATE TABLE IF NOT EXISTS `{song_table}` ( `Id` INTEGER, `Title` TEXT, `Artist` TEXT, - `Year` INTEGER, - `Duo` INTEGER, + `Year` VARCHAR(4), + `Duo` BOOLEAN, `Explicit` INTEGER, - `Date Added` TEXT, + `Date Added` TIMESTAMP, `Styles` TEXT, `Languages` TEXT - )""") + )""") + conn.execute(stmt) + conn.commit() def create_list_view(): with get_db_engine().connect() as conn: - conn.execute("""CREATE OR REPLACE VIEW `Liste` AS + stmt = text("""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.execute(stmt) + conn.commit() def create_done_song_view(): with get_db_engine().connect() as conn: - conn.execute("""CREATE OR REPLACE VIEW `Abspielliste` AS + stmt = text("""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.execute(stmt) + conn.commit() + def create_config_table(): with get_db_engine().connect() as conn: - conn.execute("""CREATE TABLE IF NOT EXISTS `config` ( + stmt = text("""CREATE TABLE IF NOT EXISTS `config` ( `Key` VARCHAR(50) NOT NULL PRIMARY KEY, `Value` TEXT - )""") + )""") + conn.execute(stmt) + conn.commit() def get_list(): with get_db_engine().connect() as conn: - cur = conn.execute("SELECT * FROM Liste") + stmt = text("SELECT * FROM Liste") + cur = conn.execute(stmt) return cur.fetchall() def get_played_list(): with get_db_engine().connect() as conn: - cur = conn.execute("SELECT * FROM Abspielliste") + stmt = text("SELECT * FROM Abspielliste") + cur = conn.execute(stmt) return cur.fetchall() def get_song_list(): with get_db_engine().connect() as conn: - cur = conn.execute( - "SELECT Artist || \" - \" || Title AS Song, Id FROM songs;") + stmt = text("SELECT Artist || \" - \" || Title AS Song, Id FROM songs;") + cur = conn.execute(stmt) return cur.fetchall() def get_song_completions(input_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) + prepared_string = f"%{input_string.upper()}%" + stmt = text( + "SELECT CONCAT(Artist, ' - ', Title) AS Song, Id FROM songs WHERE CONCAT(Artist, ' - ', Title) LIKE :prepared_string LIMIT 20;") 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() + stmt, {"prepared_string": prepared_string}) # type: ignore + return cur.fetchall() def add_entry(name, song_id, client_id): 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)) - return + stmt = text( + "INSERT INTO entries (Song_Id,Name,Client_Id) VALUES (:par_song_id,:par_name,:par_client_id);") + conn.execute(stmt, {"par_song_id": song_id, "par_name": name, + "par_client_id": client_id}) # type: ignore + conn.commit() + return True def add_sung_song(entry_id): 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;""") + stmt = text("SELECT Song_Id FROM entries WHERE Id=:par_entry_id") + cur = conn.execute(stmt, {"par_entry_id": entry_id}) # type: ignore + song_id = cur.fetchone()[0] # type: ignore + stmt = text( + "INSERT INTO done_songs (Song_Id,Plays) VALUES (:par_song_id,1) ON DUPLICATE KEY UPDATE Plays=Plays + 1;") + conn.execute(stmt, {"par_song_id": song_id}) # type: ignore delete_entry(entry_id) + conn.commit() return True def toggle_transferred(entry_id): with get_db_engine().connect() as conn: - cur = conn.execute( - "SELECT Transferred FROM entries WHERE ID =%s", (entry_id,)) + cur = conn.execute(text("SELECT Transferred FROM entries WHERE ID = :par_entry_id"), + {"par_entry_id": entry_id}) # type: ignore marked = cur.fetchall()[0][0] - if(marked == 0): - conn.execute( - "UPDATE entries SET Transferred = 1 WHERE ID =%s", (entry_id,)) + if (marked == 0): + conn.execute(text("UPDATE entries SET Transferred = 1 WHERE ID = :par_entry_id"), + {"par_entry_id": entry_id}) # type: ignore else: - conn.execute( - "UPDATE entries SET Transferred = 0 WHERE ID =%s", (entry_id,)) + conn.execute(text("UPDATE entries SET Transferred = 0 WHERE ID = :par_entry_id"), + {"par_entry_id": entry_id}) # type: ignore + conn.commit() return True def check_entry_quota(client_id): with get_db_engine().connect() as conn: - cur = conn.execute( - "SELECT Count(*) FROM entries WHERE entries.Client_Id = %s", (client_id,)) + cur = conn.execute(text("SELECT Count(*) FROM entries WHERE entries.Client_Id = :par_client_id"), + {"par_client_id": client_id}) # type: ignore return cur.fetchall()[0][0] def check_queue_length(): with get_db_engine().connect() as conn: - cur = conn.execute("SELECT Count(*) FROM entries") + cur = conn.execute(text("SELECT Count(*) FROM entries")) return cur.fetchall()[0][0] def clear_played_songs(): with get_db_engine().connect() as conn: - conn.execute("DELETE FROM done_songs") + conn.execute(text("DELETE FROM done_songs")) return True def delete_entry(id): with get_db_engine().connect() as conn: - conn.execute("DELETE FROM entries WHERE id=%s", (id,)) + conn.execute(text("DELETE FROM entries WHERE id= :par_id"), { + "par_id": id}) # type: ignore return True @@ -178,7 +201,8 @@ def delete_entries(ids): idlist.append((x,)) try: with get_db_engine().connect() as conn: - cur = conn.execute("DELETE FROM entries WHERE id=%s", idlist) + cur = conn.execute(text("DELETE FROM entries WHERE id= :par_id"), { + "par_id": idlist}) return cur.rowcount except Exception as error: @@ -187,23 +211,50 @@ def delete_entries(ids): def delete_all_entries() -> bool: with get_db_engine().connect() as conn: - conn.execute("DELETE FROM entries") + conn.execute(text("DELETE FROM entries")) + conn.commit() return True + def get_config(key: str) -> str: - with get_db_engine().connect() as conn: - cur = conn.execute("SELECT `Value` FROM config WHERE `Key`=%s", (key,)) - return cur.fetchall()[0][0] + try: + with get_db_engine().connect() as conn: + cur = conn.execute( + text("SELECT `Value` FROM config WHERE `Key`= :par_key"), {"par_key": key}) # type: ignore + conn.commit() + return cur.fetchall()[0][0] + except IndexError as error: + return "" + def set_config(key: str, value: str) -> bool: + print(f"Setting config {key} to {value}") with get_db_engine().connect() as conn: - conn.execute("INSERT INTO config (`Key`, `Value`) VALUES (%s,%s) ON DUPLICATE KEY UPDATE `Value`=%s", (key, value, value)) + conn.execute(text( + "INSERT INTO config (`Key`, `Value`) VALUES ( :par_key , :par_value) ON DUPLICATE KEY UPDATE `Value`= :par_value"), + {"par_key": key, "par_value": value} + ) # type: ignore + conn.commit() return True + def get_config_list() -> dict: with get_db_engine().connect() as conn: - cur = conn.execute("SELECT * FROM config") + cur = conn.execute(text("SELECT * FROM config")) result_dict = {} for row in cur.fetchall(): result_dict[row[0]] = row[1] return result_dict + + +def check_config_table() -> bool: + with get_db_engine().connect() as conn: + if conn.dialect.has_table(conn, 'config'): + # type: ignore + # type: ignore + if (conn.execute(text("SELECT COUNT(*) FROM config")).fetchone()[0] > 0): # type: ignore + return True + else: + return False + else: + return False diff --git a/backend/helpers.py b/backend/helpers.py index 24cd637..03fb713 100644 --- a/backend/helpers.py +++ b/backend/helpers.py @@ -34,15 +34,7 @@ def is_valid_uuid(val): return False def check_config_exists(): - eng = database.get_db_engine() - with eng.connect() as conn: - if conn.dialect.has_table(conn, 'config'): - if (conn.execute("SELECT COUNT(*) FROM config").fetchone()[0] > 0): # type: ignore - return True - else: - return False - else: - return False + return database.check_config_table() def load_version(app: Flask): if os.environ.get("SOURCE_VERSION"): @@ -87,7 +79,7 @@ def setup_config(app: Flask): config = database.get_config_list() print("Loaded existing config") else: - config = {'username': 'admin', 'password': 'changeme', 'entryquota': 3, 'maxqueue': 20, 'entries_allowed': 1, 'theme': 'default'} + config = {'username': 'admin', 'password': 'changeme', 'entryquota': 3, 'maxqueue': 20, 'entries_allowed': 1, 'theme': 'default.css'} for key, value in config.items(): database.set_config(key, value) print("Created new config") From ca73d57567e75f969cbd156a378417309c6a0e15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Phillip=20K=C3=BChne?= Date: Thu, 30 Mar 2023 17:07:01 +0200 Subject: [PATCH 43/85] Update Requirements --- backend/requirements.txt | 31 +++++++++++++++++++++++++++---- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/backend/requirements.txt b/backend/requirements.txt index d816517..b00ae16 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -1,7 +1,30 @@ -requests -pandas -Flask-BasicAuth +autopep8 +beautifulsoup4 bs4 +certifi +charset-normalizer +click +Flask +Flask-BasicAuth +greenlet gunicorn +idna +itsdangerous +Jinja2 +mariadb +MarkupSafe +mysql +mysqlclient +numpy +pandas +pycodestyle +PyMySQL +python-dateutil +pytz +requests +six +soupsieve SQLAlchemy -mysqlclient \ No newline at end of file +toml +urllib3 +Werkzeug From 429ffddced5a0d6fe427cf06b384fc5863a67ed0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Phillip=20K=C3=BChne?= Date: Thu, 30 Mar 2023 17:38:34 +0200 Subject: [PATCH 44/85] Update data_adapters for sqlalchemy upgrade --- backend/data_adapters.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/backend/data_adapters.py b/backend/data_adapters.py index e3437bc..20cd2b4 100644 --- a/backend/data_adapters.py +++ b/backend/data_adapters.py @@ -1,8 +1,5 @@ -def dict_from_row(row): - return dict(zip(row.keys(), row)) - def dict_from_rows(rows): outlist=[] for row in rows: - outlist.append(dict_from_row(row)) + outlist.append(dict(row._mapping)) return outlist \ No newline at end of file From 16cb9e7d5a6a6c1af48814bb7736652e85474a63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Phillip=20K=C3=BChne?= Date: Thu, 30 Mar 2023 17:39:13 +0200 Subject: [PATCH 45/85] Fix theme setting persistence --- backend/helpers.py | 1 + 1 file changed, 1 insertion(+) diff --git a/backend/helpers.py b/backend/helpers.py index 03fb713..36afedd 100644 --- a/backend/helpers.py +++ b/backend/helpers.py @@ -88,6 +88,7 @@ def setup_config(app: Flask): app.config['ENTRY_QUOTA'] = config['entryquota'] app.config['MAX_QUEUE'] = config['maxqueue'] app.config['ENTRIES_ALLOWED'] = bool(config['entries_allowed']) + app.config['THEME'] = config['theme'] # set queue admittance def set_accept_entries(app: Flask, allowed: bool): From 12207c1246f130815b8826fdb36f7dae24bb341d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Phillip=20K=C3=BChne?= Date: Thu, 30 Mar 2023 17:39:44 +0200 Subject: [PATCH 46/85] Admin credentials can be changed via settings --- backend/app.py | 20 +++++++++++++++----- backend/templates/settings.html | 12 ++++++++++++ 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/backend/app.py b/backend/app.py index 0b20c7b..66817bf 100644 --- a/backend/app.py +++ b/backend/app.py @@ -83,6 +83,9 @@ def settings_post(): entryquota = request.form.get("entryquota") maxqueue = request.form.get("maxqueue") theme = request.form.get("theme") + username = request.form.get("username") + password = request.form.get("password") + changed_credentials = False if entryquota.isnumeric() and int(entryquota) > 0: # type: ignore app.config['ENTRY_QUOTA'] = int(entryquota) # type: ignore else: @@ -91,14 +94,21 @@ def settings_post(): app.config['MAX_QUEUE'] = int(maxqueue) # type: ignore else: abort(400) - if theme in helpers.get_themes(): - app.config['THEME'] = theme + if theme is not None and theme in helpers.get_themes(): + helpers.set_theme(app,theme) else: abort(400) - + if username != "" and username != app.config['BASIC_AUTH_USERNAME']: + app.config['BASIC_AUTH_USERNAME'] = username + changed_credentials = True + if password != "": + app.config['BASIC_AUTH_PASSWORD'] = password + changed_credentials = True helpers.persist_config(app=app) - - return render_template('settings.html', app=app, auth=basic_auth.authenticate()) + if changed_credentials: + return redirect("/") + else: + return render_template('settings.html', app=app, auth=basic_auth.authenticate(), themes=helpers.get_themes()) @app.route("/api/queue") diff --git a/backend/templates/settings.html b/backend/templates/settings.html index 8ea5118..7722183 100644 --- a/backend/templates/settings.html +++ b/backend/templates/settings.html @@ -18,6 +18,18 @@ {% endfor %}

+ +

+ + +

+

+ + +

From 7ef938a5ff5b3adf39174bed06b08d4b4873699c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Phillip=20K=C3=BChne?= Date: Thu, 30 Mar 2023 17:51:20 +0200 Subject: [PATCH 47/85] Create new config with credentials from env vars --- .env.dev | 4 ++- backend/helpers.py | 63 +++++++++++++++++++++++++++++++++++----------- 2 files changed, 51 insertions(+), 16 deletions(-) diff --git a/.env.dev b/.env.dev index e22ecaa..df90e9c 100644 --- a/.env.dev +++ b/.env.dev @@ -7,4 +7,6 @@ MARIADB_PASSWORD=mariadb_karaoqueue_password # Karaoqueue DEPLOYMENT_PLATFORM=Docker -DBSTRING="mysql+pymysql://karaoqueue:mariadb_karaoqueue_password@127.0.0.1:3306/karaoqueue?charset=utf8mb4" \ No newline at end of file +DBSTRING="mysql+pymysql://karaoqueue:mariadb_karaoqueue_password@127.0.0.1:3306/karaoqueue?charset=utf8mb4" +INITIAL_USERNAME=admin +INITIAL_PASSWORD=changeme \ No newline at end of file diff --git a/backend/helpers.py b/backend/helpers.py index 36afedd..0fc7e22 100644 --- a/backend/helpers.py +++ b/backend/helpers.py @@ -11,6 +11,7 @@ import database data_directory = "data" config_file = data_directory+"/config.json" + def create_data_directory(): if not os.path.exists(data_directory): os.makedirs(data_directory) @@ -19,13 +20,16 @@ def create_data_directory(): def get_catalog_url(): r = requests.get('https://www.karafun.de/karaoke-song-list.html') soup = BeautifulSoup(r.content, 'html.parser') - url = soup.findAll('a', href=True, text='Verfügbar in CSV-Format')[0]['href'] + url = soup.findAll( + 'a', href=True, text='Verfügbar in CSV-Format')[0]['href'] return url + def get_songs(url): r = requests.get(url) return r.text + def is_valid_uuid(val): try: uuid.UUID(str(val)) @@ -33,12 +37,15 @@ def is_valid_uuid(val): except ValueError: return False + def check_config_exists(): return database.check_config_table() + def load_version(app: Flask): if os.environ.get("SOURCE_VERSION"): - app.config['VERSION'] = os.environ.get("SOURCE_VERSION")[0:7] # type: ignore + app.config['VERSION'] = os.environ.get("SOURCE_VERSION")[ + 0:7] # type: ignore elif os.path.isfile(".version"): with open('.version', 'r') as file: data = file.read().replace('\n', '') @@ -48,21 +55,22 @@ def load_version(app: Flask): app.config['VERSION'] = "" else: app.config['VERSION'] = "" - + + def load_dbconfig(app: Flask): if os.environ.get("FLASK_ENV") == "development": app.config['DBCONNSTRING'] = os.environ.get("DBSTRING") else: - if os.environ.get("DEPLOYMENT_PLATFORM") == "Heroku": + if os.environ.get("DEPLOYMENT_PLATFORM") == "Heroku": if os.environ.get("JAWSDB_MARIA_URL"): app.config['DBCONNSTRING'] = os.environ.get("JAWSDB_MARIA_URL") else: app.config['DBCONNSTRING'] = "" - if os.environ.get("DEPLOYMENT_PLATFORM") == "Docker": + if os.environ.get("DEPLOYMENT_PLATFORM") == "Docker": if os.environ.get("DBSTRING"): app.config['DBCONNSTRING'] = os.environ.get("DBSTRING") else: - app.config['DBCONNSTRING'] = "" + app.config['DBCONNSTRING'] = "" elif os.path.isfile(".dbconn"): with open('.dbconn', 'r') as file: data = file.read().replace('\n', '') @@ -74,15 +82,31 @@ def load_dbconfig(app: Flask): exit("No database connection string found. Cannot continue. Please set the environment variable DBSTRING or create a file .dbconn in the root directory of the project.") # Check if config exists in DB, if not, create it. + + def setup_config(app: Flask): - if check_config_exists(): - config = database.get_config_list() - print("Loaded existing config") - else: - config = {'username': 'admin', 'password': 'changeme', 'entryquota': 3, 'maxqueue': 20, 'entries_allowed': 1, 'theme': 'default.css'} - for key, value in config.items(): + if check_config_exists() == False: + print("No config found, creating new config"): + initial_username = os.environ.get("INITIAL_USERNAME") + initial_password = os.environ.get("INITIAL_PASSWORD") + if initial_username is None: + print( + "No initial username set. Please set the environment variable INITIAL_USERNAME") + exit() + if initial_password is None: + print( + "No initial password set. Please set the environment variable INITIAL_PASSWORD") + exit() + default_config = {'username': initial_username, + 'password': initial_password, + 'entryquota': 3, + 'maxqueue': 20, + 'entries_allowed': 1, + 'theme': 'default.css'} + for key, value in default_config.items(): database.set_config(key, value) print("Created new config") + config = database.get_config_list() app.config['BASIC_AUTH_USERNAME'] = config['username'] app.config['BASIC_AUTH_PASSWORD'] = config['password'] app.config['ENTRY_QUOTA'] = config['entryquota'] @@ -91,6 +115,8 @@ def setup_config(app: Flask): app.config['THEME'] = config['theme'] # set queue admittance + + def set_accept_entries(app: Flask, allowed: bool): if allowed: app.config['ENTRIES_ALLOWED'] = True @@ -100,6 +126,8 @@ def set_accept_entries(app: Flask, allowed: bool): database.set_config('entries_allowed', '0') # get queue admittance + + def get_accept_entries(app: Flask) -> bool: state = bool(int(database.get_config('entries_allowed'))) app.config['ENTRIES_ALLOWED'] = state @@ -108,11 +136,14 @@ def get_accept_entries(app: Flask) -> bool: # Write settings from current app.config to DB def persist_config(app: Flask): - config = {'username': app.config['BASIC_AUTH_USERNAME'], 'password': app.config['BASIC_AUTH_PASSWORD'], 'entryquota': app.config['ENTRY_QUOTA'], 'maxqueue': app.config['MAX_QUEUE']} + config = {'username': app.config['BASIC_AUTH_USERNAME'], 'password': app.config['BASIC_AUTH_PASSWORD'], + 'entryquota': app.config['ENTRY_QUOTA'], 'maxqueue': app.config['MAX_QUEUE']} for key, value in config.items(): database.set_config(key, value) # Get available themes from themes directory + + def get_themes(): themes = [] for theme in os.listdir('./static/css/themes'): @@ -120,6 +151,8 @@ def get_themes(): return themes # Set theme + + def set_theme(app: Flask, theme: str): if theme in get_themes(): app.config['THEME'] = theme @@ -137,5 +170,5 @@ def nocache(view): response.headers['Pragma'] = 'no-cache' response.headers['Expires'] = '-1' return response - - return update_wrapper(no_cache, view) \ No newline at end of file + + return update_wrapper(no_cache, view) From fe71fa2d8cc78b634b37835fb193039253bbd36e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Phillip=20K=C3=BChne?= Date: Thu, 30 Mar 2023 18:00:08 +0200 Subject: [PATCH 48/85] Fix Caching --- backend/app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/app.py b/backend/app.py index 66817bf..bc8727a 100644 --- a/backend/app.py +++ b/backend/app.py @@ -266,7 +266,7 @@ def add_header(response): and also to cache the rendered page for 10 minutes. """ if not 'Cache-Control' in response.headers: - response.headers['Cache-Control'] = 'private, max-age=600' + response.headers['Cache-Control'] = 'private, max-age=600, no-cache, must-revalidate' return response @app.context_processor From dc53d8a8b18b3a9a86eb420f4b24009f9da04900 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Phillip=20K=C3=BChne?= Date: Thu, 30 Mar 2023 18:00:23 +0200 Subject: [PATCH 49/85] Fix Typos --- backend/helpers.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/backend/helpers.py b/backend/helpers.py index 0fc7e22..d67d5d2 100644 --- a/backend/helpers.py +++ b/backend/helpers.py @@ -44,8 +44,7 @@ def check_config_exists(): def load_version(app: Flask): if os.environ.get("SOURCE_VERSION"): - app.config['VERSION'] = os.environ.get("SOURCE_VERSION")[ - 0:7] # type: ignore + app.config['VERSION'] = os.environ.get("SOURCE_VERSION")[0:7] # type: ignore # noqa: E501 elif os.path.isfile(".version"): with open('.version', 'r') as file: data = file.read().replace('\n', '') @@ -86,7 +85,7 @@ def load_dbconfig(app: Flask): def setup_config(app: Flask): if check_config_exists() == False: - print("No config found, creating new config"): + print("No config found, creating new config") initial_username = os.environ.get("INITIAL_USERNAME") initial_password = os.environ.get("INITIAL_PASSWORD") if initial_username is None: From f055a59a38234beb74fb371b2e4ed786ec85cad3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Phillip=20K=C3=BChne?= Date: Thu, 30 Mar 2023 18:14:40 +0200 Subject: [PATCH 50/85] Fix Dockerfile with in-Container update --- Dockerfile | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Dockerfile b/Dockerfile index 47aa3bf..cc88883 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,12 @@ FROM tiangolo/uwsgi-nginx-flask:python3.10 +RUN apt-key adv --recv-keys --keyserver keyserver.ubuntu.com 0xcbcb082a1bb943db +RUN curl -LsS https://downloads.mariadb.com/MariaDB/mariadb_repo_setup | bash + +RUN apt-get update +RUN apt-get upgrade -y +RUN apt-get dist-upgrade + COPY ./backend /app RUN pip install -r /app/requirements.txt \ No newline at end of file From 1bfe3b5d4be887ce6a873cc05d865da722e63520 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Phillip=20K=C3=BChne?= Date: Thu, 30 Mar 2023 19:14:24 +0200 Subject: [PATCH 51/85] Update Dockerfile --- Dockerfile | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index cc88883..d1425c5 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM tiangolo/uwsgi-nginx-flask:python3.10 +FROM tiangolo/meinheld-gunicorn-flask:python3.9 RUN apt-key adv --recv-keys --keyserver keyserver.ubuntu.com 0xcbcb082a1bb943db RUN curl -LsS https://downloads.mariadb.com/MariaDB/mariadb_repo_setup | bash @@ -7,6 +7,8 @@ RUN apt-get update RUN apt-get upgrade -y RUN apt-get dist-upgrade -COPY ./backend /app +COPY ./backend/requirements.txt /app/requirements.txt -RUN pip install -r /app/requirements.txt \ No newline at end of file +RUN pip install --no-cache-dir --upgrade -r /app/requirements.txt + +COPY ./backend /app \ No newline at end of file From 3f09b79844391fa8ba9be7e8bab5b1bbc99f4cb1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Phillip=20K=C3=BChne?= Date: Thu, 30 Mar 2023 19:30:13 +0200 Subject: [PATCH 52/85] Fix Dockerfile for "new" WSGI server --- Dockerfile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Dockerfile b/Dockerfile index d1425c5..2b0ceb6 100644 --- a/Dockerfile +++ b/Dockerfile @@ -11,4 +11,6 @@ COPY ./backend/requirements.txt /app/requirements.txt RUN pip install --no-cache-dir --upgrade -r /app/requirements.txt +RUN pip install --no-cache-dir -U meinheld + COPY ./backend /app \ No newline at end of file From fad21ba6c5a8e8ef6c61f82082ecc4a9dcd24a7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Phillip=20K=C3=BChne?= Date: Thu, 30 Mar 2023 19:30:27 +0200 Subject: [PATCH 53/85] Fix DELETE statements --- backend/database.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/backend/database.py b/backend/database.py index 59129a2..b0d153c 100644 --- a/backend/database.py +++ b/backend/database.py @@ -149,8 +149,8 @@ def add_sung_song(entry_id): stmt = text( "INSERT INTO done_songs (Song_Id,Plays) VALUES (:par_song_id,1) ON DUPLICATE KEY UPDATE Plays=Plays + 1;") conn.execute(stmt, {"par_song_id": song_id}) # type: ignore - delete_entry(entry_id) conn.commit() + delete_entry(entry_id) return True @@ -185,6 +185,7 @@ def check_queue_length(): def clear_played_songs(): with get_db_engine().connect() as conn: conn.execute(text("DELETE FROM done_songs")) + conn.commit() return True @@ -192,6 +193,7 @@ def delete_entry(id): with get_db_engine().connect() as conn: conn.execute(text("DELETE FROM entries WHERE id= :par_id"), { "par_id": id}) # type: ignore + conn.commit() return True @@ -203,7 +205,7 @@ def delete_entries(ids): with get_db_engine().connect() as conn: cur = conn.execute(text("DELETE FROM entries WHERE id= :par_id"), { "par_id": idlist}) - + conn.commit() return cur.rowcount except Exception as error: return -1 From 4c64144f3df2c0da5100799f18489f9c2dfb2abc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Phillip=20K=C3=BChne?= Date: Thu, 30 Mar 2023 21:05:36 +0200 Subject: [PATCH 54/85] Fix sticky Popups --- backend/templates/main_admin.html | 133 ++++++++++++++---------------- 1 file changed, 62 insertions(+), 71 deletions(-) diff --git a/backend/templates/main_admin.html b/backend/templates/main_admin.html index 5379dc2..64b9252 100644 --- a/backend/templates/main_admin.html +++ b/backend/templates/main_admin.html @@ -1,15 +1,13 @@ - - {% extends 'base.html' %} {% block title %}Warteliste-Admin{% endblock %} {% block content %}
@@ -18,24 +16,13 @@ table td:nth-child(2) { - +
- +
@@ -53,15 +40,18 @@ table td:nth-child(2) { From 035394c36b66b9abafb298f86598360aae0457b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Phillip=20K=C3=BChne?= Date: Thu, 30 Mar 2023 22:37:39 +0200 Subject: [PATCH 59/85] Rephrase Information text --- backend/templates/main.html | 2 +- backend/templates/songlist.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/templates/main.html b/backend/templates/main.html index 469ede0..f0851ac 100644 --- a/backend/templates/main.html +++ b/backend/templates/main.html @@ -30,7 +30,7 @@ $.getJSON("/api/entries/accept", (data) => { $("#bfb").prop("aria-disabled",true); $("#bfb").prop("tabindex","-1"); $("#bfb").wrap(""); - $(".tooltip-span").prop("title", "Eintragungen sind leider nicht mehr möglich.") + $(".tooltip-span").prop("title", "Eintragungen sind leider momentan nicht möglich.") $('[data-toggle="tooltip"]').tooltip() } }) diff --git a/backend/templates/songlist.html b/backend/templates/songlist.html index e961c64..b1fb95a 100644 --- a/backend/templates/songlist.html +++ b/backend/templates/songlist.html @@ -118,7 +118,7 @@ $(".enqueueButton").prop("disabled", true) $(".enqueueButton").prop("style", "pointer-events: none;") $(".enqueueButton").wrap(""); - $(".tooltip-span").prop("title", "Eintragungen sind leider nicht mehr möglich.") + $(".tooltip-span").prop("title", "Eintragungen sind leider momentan nicht möglich.") $('[data-toggle="tooltip"]').tooltip() } else { $(".enqueueButton").prop("disabled", false) From 8c735866a302eb8cd449f312b2807572c1a1c683 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Phillip=20K=C3=BChne?= Date: Thu, 30 Mar 2023 22:45:08 +0200 Subject: [PATCH 60/85] Remove remnants of file based config --- backend/app.py | 1 - backend/helpers.py | 9 --------- 2 files changed, 10 deletions(-) diff --git a/backend/app.py b/backend/app.py index bc8727a..29f55fd 100644 --- a/backend/app.py +++ b/backend/app.py @@ -248,7 +248,6 @@ def admin(): def activate_job(): helpers.load_dbconfig(app) helpers.load_version(app) - helpers.create_data_directory() database.create_entry_table() database.create_song_table() database.create_done_song_table() diff --git a/backend/helpers.py b/backend/helpers.py index d67d5d2..2b8d4eb 100644 --- a/backend/helpers.py +++ b/backend/helpers.py @@ -8,15 +8,6 @@ from functools import wraps, update_wrapper from datetime import datetime import database -data_directory = "data" -config_file = data_directory+"/config.json" - - -def create_data_directory(): - if not os.path.exists(data_directory): - os.makedirs(data_directory) - - def get_catalog_url(): r = requests.get('https://www.karafun.de/karaoke-song-list.html') soup = BeautifulSoup(r.content, 'html.parser') From 3921a9ea76afd2d77536c61d9b723108dc7ad1ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Phillip=20K=C3=BChne?= Date: Thu, 30 Mar 2023 22:56:09 +0200 Subject: [PATCH 61/85] Update docker-Compose --- docker-compose.prod.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index f36ced8..3b1851d 100644 --- a/docker-compose.prod.yml +++ b/docker-compose.prod.yml @@ -6,7 +6,7 @@ secrets: services: karaoqueue: - image: "ghcr.io/phoenixtwofive/karaoqueue:v2023.03.1" + image: "ghcr.io/phoenixtwofive/karaoqueue:v2023.03.2" build: . restart: always ports: From f32f02dc443fafd36ab577488a8430da52998040 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Phillip=20K=C3=BChne?= Date: Fri, 31 Mar 2023 20:57:34 +0200 Subject: [PATCH 62/85] Fix Sorting in List view --- backend/database.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/backend/database.py b/backend/database.py index b0d153c..59a0d4c 100644 --- a/backend/database.py +++ b/backend/database.py @@ -75,7 +75,9 @@ def create_list_view(): stmt = text("""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""") + WHERE entries.Song_Id=songs.Id + ORDER BY entries.Id ASC + """) conn.execute(stmt) conn.commit() From 10717e753b5179404d938c6702a3369346c96092 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Phillip=20K=C3=BChne?= Date: Fri, 31 Mar 2023 20:58:47 +0200 Subject: [PATCH 63/85] Update Compose File --- docker-compose.prod.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index 3b1851d..4301aa2 100644 --- a/docker-compose.prod.yml +++ b/docker-compose.prod.yml @@ -6,7 +6,7 @@ secrets: services: karaoqueue: - image: "ghcr.io/phoenixtwofive/karaoqueue:v2023.03.2" + image: "ghcr.io/phoenixtwofive/karaoqueue:v2023.03.3" build: . restart: always ports: From 58dd0dd93ba24e320650319d3044e125aacef2ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Phillip=20K=C3=BChne?= Date: Sun, 2 Apr 2023 17:21:56 +0200 Subject: [PATCH 64/85] Add devcontainer.json setup --- .devcontainer/devcontainer.json | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 .devcontainer/devcontainer.json diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000..b69688f --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,32 @@ +{ + "image": "mcr.microsoft.com/devcontainers/universal:2", + "hostRequirements": { + "cpus": 4 + }, + "waitFor": "onCreateCommand", + "updateContentCommand": "pip install -r requirements.txt", + "postCreateCommand": "", + "postAttachCommand": { + "server": "flask --debug run" + }, + "portsAttributes": { + "5000": { + "label": "Application", + "onAutoForward": "openPreview" + } + }, + "customizations": { + "codespaces": { + "openFiles": [ + "templates/index.html" + ] + }, + "vscode": { + "extensions": [ + "ms-python.python" + ] + } + }, + "forwardPorts": [5000] + } + \ No newline at end of file From d2caaac4bcb8f3906cf8b79d3bdeb41600046a41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Phillip=20K=C3=BChne?= Date: Tue, 25 Apr 2023 16:46:43 +0200 Subject: [PATCH 65/85] Codecheck (#54) * Add GitHub Action * Add Linting * Add .editorconfig --- .editorconfig | 36 ++++++++++++++++++++++++++++++++++++ .flake8 | 3 +++ .github/workflows/lint.yaml | 23 +++++++++++++++++++++++ .vscode/settings.json | 13 ++++++++++++- backend/app.py | 18 ++++++++++-------- backend/data_adapters.py | 4 ++-- backend/database.py | 8 +++----- backend/helpers.py | 8 +++++--- backend/main.py | 2 +- 9 files changed, 95 insertions(+), 20 deletions(-) create mode 100644 .editorconfig create mode 100644 .flake8 create mode 100644 .github/workflows/lint.yaml diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..eabd9a7 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,36 @@ +# EditorConfig is awesome: https://EditorConfig.org + +# top-most EditorConfig file +root = true + +[*] +indent_style = space +indent_size = 4 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = false +insert_final_newline = false + +[*.md] +trim_trailing_whitespace = true + +[*.py] +indent_size = 4 + +[*.js] +indent_size = 2 + +[*.html] +indent_size = 2 + +[*.css] +indent_size = 2 + +[*.scss] +indent_size = 2 + +[*.yaml] +indent_size = 2 + +[*.yml] +indent_size = 2 \ No newline at end of file diff --git a/.flake8 b/.flake8 new file mode 100644 index 0000000..1cfd4e0 --- /dev/null +++ b/.flake8 @@ -0,0 +1,3 @@ +[flake8] +ignore = E501 +max-line-length = 120 \ No newline at end of file diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml new file mode 100644 index 0000000..e83fef6 --- /dev/null +++ b/.github/workflows/lint.yaml @@ -0,0 +1,23 @@ +name: Lint + +on: [push, pull_request] + +jobs: + flake8_py3: + runs-on: ubuntu-latest + steps: + - name: Setup Python + uses: actions/setup-python@v4.6.0 + with: + python-version: '3.10' + architecture: x64 + - name: Checkout PyTorch + uses: actions/checkout@master + - name: Install flake8 + run: pip install flake8 + - name: Run flake8 + uses: suo/flake8-github-action@releases/v1 + with: + checkName: 'flake8_py3' # NOTE: this needs to be the same as the job name + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index d2a6c12..93f9daa 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,3 +1,14 @@ { - "python.pythonPath": "/usr/bin/python" + "python.pythonPath": "/usr/bin/python", + "python.testing.unittestArgs": [ + "-v", + "-s", + "./backend/tests", + "-p", + "*test.py" + ], + "python.testing.pytestEnabled": false, + "python.testing.unittestEnabled": true, + "python.linting.pylintEnabled": false, + "python.linting.flake8Enabled": true } \ No newline at end of file diff --git a/backend/app.py b/backend/app.py index 29f55fd..6ea7dab 100644 --- a/backend/app.py +++ b/backend/app.py @@ -1,5 +1,5 @@ from flask import Flask, render_template, abort, request, redirect, send_from_directory, jsonify -from flask.wrappers import Request, Response +from flask.wrappers import Response import helpers import database import data_adapters @@ -12,6 +12,7 @@ app = Flask(__name__, static_url_path='/static') basic_auth = BasicAuth(app) accept_entries = True + @app.route("/") def home(): if basic_auth.authenticate(): @@ -95,7 +96,7 @@ def settings_post(): else: abort(400) if theme is not None and theme in helpers.get_themes(): - helpers.set_theme(app,theme) + helpers.set_theme(app, theme) else: abort(400) if username != "" and username != app.config['BASIC_AUTH_USERNAME']: @@ -143,7 +144,7 @@ def update_songs(): return Response('{"status": "%s" }' % status, mimetype='text/json') -@app.route("/api/songs/compl") # type: ignore +@app.route("/api/songs/compl") # type: ignore @nocache def get_song_completions(input_string=""): input_string = request.args.get('search', input_string) @@ -176,7 +177,7 @@ def delete_entries(): return updates = database.delete_entries(request.json) if updates >= 0: - return Response('{"status": "OK", "updates": '+str(updates)+'}', mimetype='text/json') + return Response('{"status": "OK", "updates": ' + str(updates) + '}', mimetype='text/json') else: return Response('{"status": "FAIL"}', mimetype='text/json', status=400) @@ -190,6 +191,7 @@ def mark_sung(entry_id): else: return Response('{"status": "FAIL"}', mimetype='text/json') + @app.route("/api/entries/mark_transferred/") @nocache @basic_auth.required @@ -205,7 +207,7 @@ def mark_transferred(entry_id): @basic_auth.required def set_accept_entries(value): if (value == '0' or value == '1'): - helpers.set_accept_entries(app,bool(int(value))) + helpers.set_accept_entries(app, bool(int(value))) return Response('{"status": "OK"}', mimetype='text/json') else: return Response('{"status": "FAIL"}', mimetype='text/json', status=400) @@ -215,7 +217,7 @@ def set_accept_entries(value): @nocache def get_accept_entries(): accept_entries = helpers.get_accept_entries(app) - return Response('{"status": "OK", "value": '+str(int(accept_entries))+'}', mimetype='text/json') + return Response('{"status": "OK", "value": ' + str(int(accept_entries)) + '}', mimetype='text/json') @app.route("/api/played/clear") @@ -257,17 +259,17 @@ def activate_job(): helpers.setup_config(app) - @app.after_request def add_header(response): """ Add headers to both force latest IE rendering engine or Chrome Frame, and also to cache the rendered page for 10 minutes. """ - if not 'Cache-Control' in response.headers: + if 'Cache-Control' not in response.headers: response.headers['Cache-Control'] = 'private, max-age=600, no-cache, must-revalidate' return response + @app.context_processor def inject_version(): return dict(karaoqueue_version=app.config['VERSION']) diff --git a/backend/data_adapters.py b/backend/data_adapters.py index 20cd2b4..0136322 100644 --- a/backend/data_adapters.py +++ b/backend/data_adapters.py @@ -1,5 +1,5 @@ def dict_from_rows(rows): - outlist=[] + outlist = [] for row in rows: outlist.append(dict(row._mapping)) - return outlist \ No newline at end of file + return outlist diff --git a/backend/database.py b/backend/database.py index 59a0d4c..fdf6d86 100644 --- a/backend/database.py +++ b/backend/database.py @@ -1,7 +1,5 @@ # -*- coding: utf_8 -*- -from email.mime import base -from MySQLdb import Connection from sqlalchemy import create_engine, engine, text import pandas from io import StringIO @@ -209,7 +207,7 @@ def delete_entries(ids): "par_id": idlist}) conn.commit() return cur.rowcount - except Exception as error: + except Exception: return -1 @@ -227,7 +225,7 @@ def get_config(key: str) -> str: text("SELECT `Value` FROM config WHERE `Key`= :par_key"), {"par_key": key}) # type: ignore conn.commit() return cur.fetchall()[0][0] - except IndexError as error: + except IndexError: return "" @@ -256,7 +254,7 @@ def check_config_table() -> bool: if conn.dialect.has_table(conn, 'config'): # type: ignore # type: ignore - if (conn.execute(text("SELECT COUNT(*) FROM config")).fetchone()[0] > 0): # type: ignore + if (conn.execute(text("SELECT COUNT(*) FROM config")).fetchone()[0] > 0): # type: ignore return True else: return False diff --git a/backend/helpers.py b/backend/helpers.py index 2b8d4eb..fff6b8d 100644 --- a/backend/helpers.py +++ b/backend/helpers.py @@ -1,6 +1,5 @@ import requests from bs4 import BeautifulSoup -import json import os import uuid from flask import make_response, Flask @@ -8,6 +7,7 @@ from functools import wraps, update_wrapper from datetime import datetime import database + def get_catalog_url(): r = requests.get('https://www.karafun.de/karaoke-song-list.html') soup = BeautifulSoup(r.content, 'html.parser') @@ -69,13 +69,15 @@ def load_dbconfig(app: Flask): else: app.config['DBCONNSTRING'] = "" else: - exit("No database connection string found. Cannot continue. Please set the environment variable DBSTRING or create a file .dbconn in the root directory of the project.") + exit("""No database connection string found. Cannot continue. + Please set the environment variable DBSTRING or + create a file .dbconn in the root directory of the project.""") # Check if config exists in DB, if not, create it. def setup_config(app: Flask): - if check_config_exists() == False: + if check_config_exists() is False: print("No config found, creating new config") initial_username = os.environ.get("INITIAL_USERNAME") initial_password = os.environ.get("INITIAL_PASSWORD") diff --git a/backend/main.py b/backend/main.py index 210a38e..6026b0f 100644 --- a/backend/main.py +++ b/backend/main.py @@ -1,4 +1,4 @@ from app import app if __name__ == "__main__": - app.run() \ No newline at end of file + app.run() From adebf35d08a1091dedb9cf35ace77ae35ecba70c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Phillip=20K=C3=BChne?= Date: Tue, 25 Apr 2023 16:52:36 +0200 Subject: [PATCH 66/85] Update lint.yaml Remove action run on every push --- .github/workflows/lint.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml index e83fef6..6812373 100644 --- a/.github/workflows/lint.yaml +++ b/.github/workflows/lint.yaml @@ -1,6 +1,6 @@ name: Lint -on: [push, pull_request] +on: [pull_request] jobs: flake8_py3: @@ -20,4 +20,4 @@ jobs: with: checkName: 'flake8_py3' # NOTE: this needs to be the same as the job name env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From 865df5d58866ff0c1f2f17e505943fe4df2de89f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Phillip=20K=C3=BChne?= Date: Wed, 26 Apr 2023 18:08:03 +0200 Subject: [PATCH 67/85] Implement EventID to scope ClientIDs and Entry IDs Implement an EventID saved in settings. Currently this is used to scope clientIDs and entryIDs to an event. The client checks the event currently going on on the server, and discards its localstorage (containing the clientID) if it has changed --- backend/app.py | 32 +++++++++++++++++++++++++--- backend/database.py | 37 ++++++++++++++++++++++++++++++++- backend/helpers.py | 11 ++++++++++ backend/templates/base.html | 19 ++++++++++++++++- backend/templates/songlist.html | 3 ++- 5 files changed, 96 insertions(+), 6 deletions(-) diff --git a/backend/app.py b/backend/app.py index 6ea7dab..b3ebe97 100644 --- a/backend/app.py +++ b/backend/app.py @@ -138,6 +138,7 @@ def songs(): @basic_auth.required def update_songs(): database.delete_all_entries() + helpers.reset_current_event_id(app) status = database.import_songs( helpers.get_songs(helpers.get_catalog_url())) print(status) @@ -149,7 +150,6 @@ def update_songs(): def get_song_completions(input_string=""): input_string = request.args.get('search', input_string) if input_string != "": - print(input_string) result = [list(x) for x in database.get_song_completions(input_string=input_string)] return jsonify(result) @@ -157,10 +157,29 @@ def get_song_completions(input_string=""): return 400 -@app.route("/api/entries/delete/") +@app.route("/api/entries/delete/", methods=['GET']) @nocache @basic_auth.required -def delete_entry(entry_id): +def delete_entry_admin(entry_id): + if database.delete_entry(entry_id): + return Response('{"status": "OK"}', mimetype='text/json') + else: + return Response('{"status": "FAIL"}', mimetype='text/json') + + +@app.route("/api/entries/delete/", methods=['POST']) +@nocache +def delete_entry_user(entry_id): + if not request.json: + print(request.data) + abort(400) + client_id = request.json['client_id'] + if not helpers.is_valid_uuid(client_id): + print(request.data) + abort(400) + if database.get_raw_entry(entry_id)['client_id'] != client_id: # type: ignore + print(request.data) + abort(403) if database.delete_entry(entry_id): return Response('{"status": "OK"}', mimetype='text/json') else: @@ -235,6 +254,7 @@ def clear_played_songs(): @basic_auth.required def delete_all_entries(): if database.delete_all_entries(): + helpers.reset_current_event_id(app) return Response('{"status": "OK"}', mimetype='text/json') else: return Response('{"status": "FAIL"}', mimetype='text/json') @@ -246,6 +266,12 @@ def admin(): return redirect("/", code=303) +@app.route("/api/events/current") +@nocache +def get_current_event(): + return Response('{"status": "OK", "event": "' + helpers.get_current_event_id(app) + '"}', mimetype='text/json') + + @app.before_first_request def activate_job(): helpers.load_dbconfig(app) diff --git a/backend/database.py b/backend/database.py index fdf6d86..68e264a 100644 --- a/backend/database.py +++ b/backend/database.py @@ -4,6 +4,7 @@ from sqlalchemy import create_engine, engine, text import pandas from io import StringIO from flask import current_app +import uuid song_table = "songs" entry_table = "entries" @@ -16,7 +17,6 @@ sql_engine = None def get_db_engine() -> engine.base.Engine: global sql_engine if (not sql_engine): - print(current_app.config.get("DBCONNSTRING")) sql_engine = create_engine( current_app.config.get("DBCONNSTRING")) # type: ignore return sql_engine @@ -189,6 +189,26 @@ def clear_played_songs(): return True +def get_entry(id): + try: + with get_db_engine().connect() as conn: + cur = conn.execute(text("SELECT * FROM Liste WHERE entry_ID = :par_id"), + {"par_id": id}) # type: ignore + return cur.fetchall()[0] + except Exception: + return None + + +def get_raw_entry(id): + try: + with get_db_engine().connect() as conn: + cur = conn.execute(text("SELECT * FROM entries WHERE ID = :par_id"), + {"par_id": id}) # type: ignore + return cur.fetchall()[0] + except Exception: + return None + + def delete_entry(id): with get_db_engine().connect() as conn: conn.execute(text("DELETE FROM entries WHERE id= :par_id"), { @@ -260,3 +280,18 @@ def check_config_table() -> bool: return False else: return False + + +def init_event_id() -> bool: + if not get_config("EventID"): + set_config("EventID", str(uuid.uuid4())) + return True + + +def reset_event_id() -> bool: + set_config("EventID", str(uuid.uuid4())) + return True + + +def get_event_id() -> str: + return get_config("EventID") diff --git a/backend/helpers.py b/backend/helpers.py index fff6b8d..9dab0a5 100644 --- a/backend/helpers.py +++ b/backend/helpers.py @@ -98,6 +98,7 @@ def setup_config(app: Flask): for key, value in default_config.items(): database.set_config(key, value) print("Created new config") + database.init_event_id() config = database.get_config_list() app.config['BASIC_AUTH_USERNAME'] = config['username'] app.config['BASIC_AUTH_PASSWORD'] = config['password'] @@ -105,6 +106,7 @@ def setup_config(app: Flask): app.config['MAX_QUEUE'] = config['maxqueue'] app.config['ENTRIES_ALLOWED'] = bool(config['entries_allowed']) app.config['THEME'] = config['theme'] + app.config['EVENT_ID'] = database.get_event_id() # set queue admittance @@ -153,6 +155,15 @@ def set_theme(app: Flask, theme: str): print("Theme not found, not setting theme.") +def get_current_event_id(app: Flask): + return app.config['EVENT_ID'] + + +def reset_current_event_id(app: Flask): + database.reset_event_id() + app.config['EVENT_ID'] = database.get_event_id() + + def nocache(view): @wraps(view) def no_cache(*args, **kwargs): diff --git a/backend/templates/base.html b/backend/templates/base.html index 852d0eb..26d0ec2 100644 --- a/backend/templates/base.html +++ b/backend/templates/base.html @@ -108,7 +108,7 @@ {% block extrajs %}{% endblock %} diff --git a/backend/templates/songlist.html b/backend/templates/songlist.html index b1fb95a..1225a90 100644 --- a/backend/templates/songlist.html +++ b/backend/templates/songlist.html @@ -79,7 +79,7 @@ $.ajax({ type: 'POST', url: '/api/enqueue', - data: JSON.stringify(data), // or JSON.stringify ({name: 'jonas'}), + data: JSON.stringify(data), success: success_callback, statusCode: { 423: blocked_callback @@ -99,6 +99,7 @@ enqueue(localStorage.getItem("clientId"),id, name, function () { $("#enqueueModal").modal('hide'); window.location.href = '/#end'; + }, function (response) { bootbox.alert({ message: "Deine Eintragung konnte leider nicht vorgenommen werden.\nGrund: "+response.responseJSON.status, From add528fb8036de4ba023e70e960e5783a8737409 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Phillip=20K=C3=BChne?= Date: Wed, 26 Apr 2023 19:20:21 +0200 Subject: [PATCH 68/85] =?UTF-8?q?L=C3=B6schung=20eigener=20Entr=C3=A4ge=20?= =?UTF-8?q?implementiert.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .vscode/settings.json | 5 ++++- backend/app.py | 10 +++++----- backend/database.py | 8 ++++---- backend/templates/base.html | 23 +++++++++++++++++++++++ backend/templates/main.html | 33 +++++++++++++++++++++++++++++++++ backend/templates/songlist.html | 10 ++++++++-- 6 files changed, 77 insertions(+), 12 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 93f9daa..aa69af9 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -10,5 +10,8 @@ "python.testing.pytestEnabled": false, "python.testing.unittestEnabled": true, "python.linting.pylintEnabled": false, - "python.linting.flake8Enabled": true + "python.linting.flake8Enabled": true, + "emmet.includeLanguages": { + "django-html": "html" + } } \ No newline at end of file diff --git a/backend/app.py b/backend/app.py index b3ebe97..50ce785 100644 --- a/backend/app.py +++ b/backend/app.py @@ -40,8 +40,8 @@ def enqueue(): name = request.json['name'] song_id = request.json['id'] if request.authorization: - database.add_entry(name, song_id, client_id) - return Response('{"status":"OK"}', mimetype='text/json') + entry_id = database.add_entry(name, song_id, client_id) + return Response(f"""{{"status":"OK", "entry_id":{entry_id}}}""", mimetype='text/json') else: if helpers.get_accept_entries(app): if not request.json: @@ -55,8 +55,8 @@ def enqueue(): song_id = request.json['id'] if database.check_queue_length() < int(app.config['MAX_QUEUE']): if database.check_entry_quota(client_id) < int(app.config['ENTRY_QUOTA']): - database.add_entry(name, song_id, client_id) - return Response('{"status":"OK"}', mimetype='text/json') + entry_id = database.add_entry(name, song_id, client_id) + return Response(f"""{{"status":"OK", "entry_id":{entry_id}}}""", mimetype='text/json') else: return Response('{"status":"Du hast bereits ' + str(database.check_entry_quota(client_id)) + ' Songs eingetragen, dies ist das Maximum an Einträgen die du in der Warteliste haben kannst."}', mimetype='text/json', status=423) else: @@ -177,7 +177,7 @@ def delete_entry_user(entry_id): if not helpers.is_valid_uuid(client_id): print(request.data) abort(400) - if database.get_raw_entry(entry_id)['client_id'] != client_id: # type: ignore + if database.get_raw_entry(entry_id)[3] != client_id: # type: ignore print(request.data) abort(403) if database.delete_entry(entry_id): diff --git a/backend/database.py b/backend/database.py index 68e264a..2c25b2c 100644 --- a/backend/database.py +++ b/backend/database.py @@ -134,11 +134,11 @@ def get_song_completions(input_string): def add_entry(name, song_id, client_id): with get_db_engine().connect() as conn: stmt = text( - "INSERT INTO entries (Song_Id,Name,Client_Id) VALUES (:par_song_id,:par_name,:par_client_id);") - conn.execute(stmt, {"par_song_id": song_id, "par_name": name, - "par_client_id": client_id}) # type: ignore + "INSERT INTO entries (Song_Id,Name,Client_Id) VALUES (:par_song_id,:par_name,:par_client_id) RETURNING entries.ID;") + cur = conn.execute(stmt, {"par_song_id": song_id, "par_name": name, + "par_client_id": client_id}) # type: ignore conn.commit() - return True + return cur.fetchone()[0] # type: ignore def add_sung_song(entry_id): diff --git a/backend/templates/base.html b/backend/templates/base.html index 26d0ec2..54676d3 100644 --- a/backend/templates/base.html +++ b/backend/templates/base.html @@ -147,6 +147,29 @@ loadOrGenerateClientId() } } + + function addEntry(entryId) { + entryArray = JSON.parse(localStorage.getItem("ownedEntries")) + if (entryArray == null) { + entryArray = [] + } + entryArray.push(entryId) + localStorage.setItem("ownedEntries", JSON.stringify(entryArray)) + } + + function removeEntry(entryId) { + entryArray = JSON.parse(localStorage.getItem("ownedEntries")) + if (entryArray == null) { + entryArray = [] + } + entryArray = entryArray.filter(function(value, index, arr){ return value != entryId;}); + localStorage.setItem("ownedEntries", JSON.stringify(entryArray)) + } + + function getOwnedEntries() { + return JSON.parse(localStorage.getItem("ownedEntries")) + } + diff --git a/backend/templates/main.html b/backend/templates/main.html index f0851ac..7af0e06 100644 --- a/backend/templates/main.html +++ b/backend/templates/main.html @@ -17,6 +17,7 @@ +
Name Song Künstler
@@ -34,5 +35,37 @@ $.getJSON("/api/entries/accept", (data) => { $('[data-toggle="tooltip"]').tooltip() } }) + +function TableActionsFormatter(value,row,index) { + console.log("Value: " + value + ", Row: " + row + ", Index: " + index) + console.log(row) + if (getOwnedEntries().includes(row.entry_ID)) { + return "" + } + return "" +} + +function requestDeletionAsUser(id) { + bootbox.confirm("Wirklich den Eintrag zurückziehen? Das könnte zu einer langen Wartezeit führen!", function (result) { + if (result) { + payload = { + "client_id": localStorage.getItem("clientId"), + "entry_id": id + } + $.ajax({ + url: "/api/entries/delete/"+id, + type: "POST", + data: JSON.stringify(payload), + contentType: "application/json; charset=utf-8", + dataType: "json", + success: function(result) { + bootbox.alert("Eintrag zurückgezogen!") + location.reload() + } + }) + } + }) +} + {% endblock %} \ No newline at end of file diff --git a/backend/templates/songlist.html b/backend/templates/songlist.html index 1225a90..7392a81 100644 --- a/backend/templates/songlist.html +++ b/backend/templates/songlist.html @@ -96,10 +96,16 @@ function submitModal() { var name = $("#singerNameInput").val(); var id = $("#selectedId").attr("value"); - enqueue(localStorage.getItem("clientId"),id, name, function () { + enqueue(localStorage.getItem("clientId"),id, name, function (response) { + console.log(response); + entryID = response["entry_id"]; + bootbox.alert({ + message: "Deine Eintragung wurde erfolgreich vorgenommen.", + }); + console.log("Entry ID: " + entryID); + addEntry(entryID); $("#enqueueModal").modal('hide'); window.location.href = '/#end'; - }, function (response) { bootbox.alert({ message: "Deine Eintragung konnte leider nicht vorgenommen werden.\nGrund: "+response.responseJSON.status, From b76fcfd8e471534e3b1d3ca7f85e9e1f2d8ff6f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Phillip=20K=C3=BChne?= Date: Fri, 28 Apr 2023 12:40:28 +0200 Subject: [PATCH 69/85] Add Copy functionality and corresponding toasts --- backend/templates/base.html | 24 +++++++++++++----------- backend/templates/main.html | 8 +++++++- backend/templates/main_admin.html | 22 +++++++++++++++++++--- backend/templates/songlist.html | 10 +++++++--- 4 files changed, 46 insertions(+), 18 deletions(-) diff --git a/backend/templates/base.html b/backend/templates/base.html index 54676d3..afd2047 100644 --- a/backend/templates/base.html +++ b/backend/templates/base.html @@ -15,9 +15,12 @@ + + + - + @@ -88,16 +91,12 @@ - + - - + + @@ -105,6 +104,9 @@ + + {% block extrajs %}{% endblock %} {% endblock %} \ No newline at end of file diff --git a/backend/templates/songlist.html b/backend/templates/songlist.html index 7392a81..10df759 100644 --- a/backend/templates/songlist.html +++ b/backend/templates/songlist.html @@ -99,9 +99,13 @@ enqueue(localStorage.getItem("clientId"),id, name, function (response) { console.log(response); entryID = response["entry_id"]; - bootbox.alert({ - message: "Deine Eintragung wurde erfolgreich vorgenommen.", - }); + toast = { + title: "Erfolgreich eingetragen", + message: "Du wurdest erfolgreich eingetragen.", + status: TOAST_STATUS.SUCCESS, + timeout: 5000 + } + Toast.create(toast); console.log("Entry ID: " + entryID); addEntry(entryID); $("#enqueueModal").modal('hide'); From f2b4611ea658ea43e169e87a2aa9157f86245446 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Phillip=20K=C3=BChne?= Date: Fri, 28 Apr 2023 12:52:03 +0200 Subject: [PATCH 70/85] Remove problematic tooltip and debug log --- backend/templates/main_admin.html | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/backend/templates/main_admin.html b/backend/templates/main_admin.html index 16ad86f..ac98b48 100644 --- a/backend/templates/main_admin.html +++ b/backend/templates/main_admin.html @@ -185,8 +185,6 @@ }); } function TableActions(value, row, index) { - console.log("Value: " + value + ", Row: " + row + ", Index: " + index) - console.log(row) let outerHTML = "" if (row.Transferred == 1) { outerHTML = "  "; @@ -197,7 +195,7 @@ } function CopyFormatter(value, row, index) { - return ""+value+""; + return ""+value+""; } function getIdSelections() { From d0d8e41b4863b8c42743bf978559cef7be659a16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Phillip=20K=C3=BChne?= Date: Fri, 28 Apr 2023 12:53:03 +0200 Subject: [PATCH 71/85] Indicate copyability of table text Indicate copyability on click of table contents by highlighting hovered element --- backend/static/css/style.css | 365 ++++++++++++++++++----------------- 1 file changed, 186 insertions(+), 179 deletions(-) diff --git a/backend/static/css/style.css b/backend/static/css/style.css index 5d0fb40..52298d9 100644 --- a/backend/static/css/style.css +++ b/backend/static/css/style.css @@ -1,4 +1,180 @@ :root { + /* Navbar */ + --navbar-background-color: #343a40; + --navbar-text-color: rgba(255, 255, 255, .5); + --navbar-text-color-hover: rgba(255, 255, 255, .75); + --navbar-text-color-active: rgba(255, 255, 255, 1); + + /* Common */ + --background-color: #ffffff; + --background-color-var: #f5f5f5; + --text-color: #212529; + --text-color-var: #343a40; + + /* Modals */ + --modal-background-color: #ffffff; + --modal-separator-color: #dee2e6; + --modal-close-color: #212529; + + /* Tables */ + --table-border-color: #dee2e6; + + /* Input */ + --input-background-color: #ffffff; + + /* Misc */ + --copy-highlight-color: rgba(251, 255, 0, 0.6); +} + + +body { + padding-top: 5rem; + background-color: var(--background-color); +} + +html, +body { + height: 100%; +} + +.site { + height: auto; + min-height: 100%; +} + +main { + padding-bottom: 60px; + /* Höhe des Footers */ +} + +.footer { + margin-top: -60px; + width: 100%; + height: 60px; + /* Set the fixed height of the footer here */ + /*line-height: 60px; /* Vertically center the text there */ + background-color: var(--background-color-var); +} + +.topbutton { + width: 100%; +} + +table td { + overflow: hidden; + text-overflow: ellipsis; +} + +table.entries tbody tr[data-index="0"] { + background-color: #007bff80; + font-weight: 600; +} + +table.entries tbody tr[data-index="1"] { + background-color: #007bff40; + font-weight: 500; +} + +table.entries tbody tr[data-index="2"] { + background-color: #007bff20; + font-weight: 400; +} + +table.entries tbody tr[data-index="3"] { + background-color: #007bff10; +} + +table td:first-child { + max-width: 200px !important; +} + +.fa-solid { + vertical-align: auto; +} + +@media (min-width: 768px) { + .topbutton { + width: auto; + } +} + +@media print { + body { + font-size: 1.3em; + } + + .footer { + display: none !important; + } + + .admincontrols { + display: none; + } +} + +body { + background-color: var(--background-color); + color: var(--text-color); +} + +.footer { + background-color: var(--background-color-var); +} + +.modal-content { + background-color: var(--background-color); + color: var(--text-color); +} + +.modal-header { + background-color: var(--background-color); + color: var(--text-color-var); + border-color: var(var(--modal-separator-color)); +} + +.modal-footer { + background-color: var(--background-color); + color: var(--text-color-var); + border-color: var(var(--modal-separator-color)); +} + +.form-control { + background-color: var(--input-background-color); + color: var(--text-color) +} + +.form-control:focus { + background-color: var(--input-background-color); + color: var(--text-color) +} + +.table td, +.table th { + border-color: var(--table-border-color) +} + +.table thead th { + border-color: var(--table-border-color) +} + +table td.buttoncell { + text-align: end; +} + +.close { + color: var(--text-color) +} + +pre { + color: var(--text-color-var) +} + +#entrytable td>span:hover { + background-color: var(--copy-highlight-color); +} + +@media (prefers-color-scheme: dark) { + :root { /* Navbar */ --navbar-background-color: #343a40; --navbar-text-color: rgba(255, 255, 255, .5); @@ -6,189 +182,20 @@ --navbar-text-color-active: rgba(255, 255, 255, 1); /* Common */ - --background-color: #ffffff; - --background-color-var: #f5f5f5; - --text-color: #212529; - --text-color-var: #343a40; + --background-color: #121212; + --background-color-var: #232323; + --text-color: #f5f5f5; + --text-color-var: #a2a2a2; /* Modals */ - --modal-background-color: #ffffff; - --modal-separator-color: #dee2e6; - --modal-close-color: #212529; + --modal-background-color: #121212; + --modal-separator-color: #232323; + --modal-close-color: #f5f5f5; /* Tables */ - --table-border-color: #dee2e6; + --table-border-color: #232323; /* Input */ - --input-background-color: #ffffff; -} - - -body { - padding-top: 5rem; - background-color: var(--background-color); -} - -html, -body { - height: 100%; -} - -.site { - height: auto; - min-height: 100%; -} - -main { - padding-bottom: 60px; - /* Höhe des Footers */ -} - -.footer { - margin-top: -60px; - width: 100%; - height: 60px; - /* Set the fixed height of the footer here */ - /*line-height: 60px; /* Vertically center the text there */ - background-color: var(--background-color-var); -} - -.topbutton { - width: 100%; -} - -table td { - overflow: hidden; - text-overflow: ellipsis; -} - -table.entries tbody tr[data-index="0"] { - background-color: #007bff80; - font-weight: 600; -} - -table.entries tbody tr[data-index="1"] { - background-color: #007bff40; - font-weight: 500; -} - -table.entries tbody tr[data-index="2"] { - background-color: #007bff20; - font-weight: 400; -} - -table.entries tbody tr[data-index="3"] { - background-color: #007bff10; -} - -table td:first-child { - max-width: 200px !important; -} - -.fa-solid { - vertical-align: auto; -} - -@media (min-width: 768px) { - .topbutton { - width: auto; - } -} - -@media print { - body { - font-size: 1.3em; - } - - .footer { - display: none !important; - } - - .admincontrols { - display: none; - } -} - -body { - background-color: var(--background-color); - color: var(--text-color); -} - -.footer { - background-color: var(--background-color-var); -} - -.modal-content { - background-color: var(--background-color); - color: var(--text-color); -} - -.modal-header { - background-color: var(--background-color); - color: var(--text-color-var); - border-color: var(var(--modal-separator-color)); -} - -.modal-footer { - background-color: var(--background-color); - color: var(--text-color-var); - border-color: var(var(--modal-separator-color)); -} - -.form-control { - background-color: var(--input-background-color); - color: var(--text-color) -} - -.form-control:focus { - background-color: var(--input-background-color); - color: var(--text-color) -} - -.table td, -.table th { - border-color: var(--table-border-color) -} - -.table thead th { - border-color: var(--table-border-color) -} - -table td.buttoncell { - text-align: end; -} - -.close { - color: var(--text-color) -} - -pre { - color: var(--text-color-var) -} - -@media (prefers-color-scheme: dark) { - :root { - /* Navbar */ - --navbar-background-color: #343a40; - --navbar-text-color: rgba(255, 255, 255, .5); - --navbar-text-color-hover: rgba(255, 255, 255, .75); - --navbar-text-color-active: rgba(255, 255, 255, 1); - - /* Common */ - --background-color: #121212; - --background-color-var: #232323; - --text-color: #f5f5f5; - --text-color-var: #a2a2a2; - - /* Modals */ - --modal-background-color: #121212; - --modal-separator-color: #232323; - --modal-close-color: #f5f5f5; - - /* Tables */ - --table-border-color: #232323; - - /* Input */ - --input-background-color: #343434; - } + --input-background-color: #343434; + } } \ No newline at end of file From 04511a91a1b705c3ca6a9f602a6a60f92a245882 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Phillip=20K=C3=BChne?= Date: Fri, 28 Apr 2023 13:03:53 +0200 Subject: [PATCH 72/85] Freeze versions to avoid Flask 2.3 deprecation problems --- backend/requirements.txt | 66 ++++++++++++++++++++++------------------ 1 file changed, 36 insertions(+), 30 deletions(-) diff --git a/backend/requirements.txt b/backend/requirements.txt index b00ae16..7d9442e 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -1,30 +1,36 @@ -autopep8 -beautifulsoup4 -bs4 -certifi -charset-normalizer -click -Flask -Flask-BasicAuth -greenlet -gunicorn -idna -itsdangerous -Jinja2 -mariadb -MarkupSafe -mysql -mysqlclient -numpy -pandas -pycodestyle -PyMySQL -python-dateutil -pytz -requests -six -soupsieve -SQLAlchemy -toml -urllib3 -Werkzeug +autopep8==2.0.2 +beautifulsoup4==4.12.0 +bs4==0.0.1 +certifi==2022.12.7 +charset-normalizer==3.1.0 +click==8.1.3 +flake8==6.0.0 +Flask==2.2.3 +Flask-BasicAuth==0.2.0 +greenlet==2.0.2 +gunicorn==20.1.0 +idna==3.4 +itsdangerous==2.1.2 +Jinja2==3.1.2 +mariadb==1.1.6 +MarkupSafe==2.1.2 +mccabe==0.7.0 +mysql==0.0.3 +mysqlclient==2.1.1 +numpy==1.24.2 +packaging==23.0 +pandas==1.5.3 +pycodestyle==2.10.0 +pyflakes==3.0.1 +PyMySQL==1.0.3 +python-dateutil==2.8.2 +pytz==2023.3 +requests==2.28.2 +six==1.16.0 +soupsieve==2.4 +SQLAlchemy==2.0.7 +toml==0.10.2 +tomli==2.0.1 +typing_extensions==4.5.0 +urllib3==1.26.15 +Werkzeug==2.2.3 From 08d0e5557b2886d9b20452442341fa43179b653a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Phillip=20K=C3=BChne?= Date: Fri, 28 Apr 2023 13:13:47 +0200 Subject: [PATCH 73/85] Update docker-compose to new version --- docker-compose.prod.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index 4301aa2..dd43fc4 100644 --- a/docker-compose.prod.yml +++ b/docker-compose.prod.yml @@ -6,7 +6,7 @@ secrets: services: karaoqueue: - image: "ghcr.io/phoenixtwofive/karaoqueue:v2023.03.3" + image: "ghcr.io/phoenixtwofive/karaoqueue:v2023.04" build: . restart: always ports: From e66e4a6c198c57812bff934ea932cd8f701ca62f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Phillip=20K=C3=BChne?= Date: Fri, 28 Apr 2023 13:39:20 +0200 Subject: [PATCH 74/85] Fix error on no owned entries --- backend/templates/base.html | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/backend/templates/base.html b/backend/templates/base.html index afd2047..58c2bda 100644 --- a/backend/templates/base.html +++ b/backend/templates/base.html @@ -169,7 +169,11 @@ } function getOwnedEntries() { - return JSON.parse(localStorage.getItem("ownedEntries")) + var entries = JSON.parse(localStorage.getItem("ownedEntries")) + if (entries == null) { + entries = [] + } + return entries; } From 93c8a2cb7b4f61d960831b2e41d23b731d260ae9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Phillip=20K=C3=BChne?= Date: Fri, 28 Apr 2023 13:41:39 +0200 Subject: [PATCH 75/85] Update Version --- docker-compose.prod.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index dd43fc4..0ec4117 100644 --- a/docker-compose.prod.yml +++ b/docker-compose.prod.yml @@ -6,7 +6,7 @@ secrets: services: karaoqueue: - image: "ghcr.io/phoenixtwofive/karaoqueue:v2023.04" + image: "ghcr.io/phoenixtwofive/karaoqueue:v2023.04.1" build: . restart: always ports: From 9cb93d2d49e562d5ba71549d0ca1102053c6e625 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 2 May 2023 00:33:58 +0000 Subject: [PATCH 76/85] Bump flask from 2.2.3 to 2.3.2 in /backend Bumps [flask](https://github.com/pallets/flask) from 2.2.3 to 2.3.2. - [Release notes](https://github.com/pallets/flask/releases) - [Changelog](https://github.com/pallets/flask/blob/main/CHANGES.rst) - [Commits](https://github.com/pallets/flask/compare/2.2.3...2.3.2) --- updated-dependencies: - dependency-name: flask dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- backend/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/requirements.txt b/backend/requirements.txt index 7d9442e..83eff14 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -5,7 +5,7 @@ certifi==2022.12.7 charset-normalizer==3.1.0 click==8.1.3 flake8==6.0.0 -Flask==2.2.3 +Flask==2.3.2 Flask-BasicAuth==0.2.0 greenlet==2.0.2 gunicorn==20.1.0 From bd3bec8c4f1cc227c74a1f78f2f4d49481df501e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 23 May 2023 06:41:06 +0000 Subject: [PATCH 77/85] Bump requests from 2.28.2 to 2.31.0 in /backend Bumps [requests](https://github.com/psf/requests) from 2.28.2 to 2.31.0. - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.28.2...v2.31.0) --- updated-dependencies: - dependency-name: requests dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- backend/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/requirements.txt b/backend/requirements.txt index 7d9442e..db72173 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -25,7 +25,7 @@ pyflakes==3.0.1 PyMySQL==1.0.3 python-dateutil==2.8.2 pytz==2023.3 -requests==2.28.2 +requests==2.31.0 six==1.16.0 soupsieve==2.4 SQLAlchemy==2.0.7 From 93e6606d6d5469be1c659aab4e496a43d63a7d36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Phillip=20K=C3=BChne?= Date: Fri, 30 Jun 2023 00:13:48 +0200 Subject: [PATCH 78/85] Set focus on singer name input when modal is shown --- backend/templates/songlist.html | 3 +++ 1 file changed, 3 insertions(+) diff --git a/backend/templates/songlist.html b/backend/templates/songlist.html index 10df759..5799757 100644 --- a/backend/templates/songlist.html +++ b/backend/templates/songlist.html @@ -67,6 +67,9 @@ e.preventDefault(); submitModal(); }); + $('#enqueueModal').on('shown.bs.modal', function (e) { + $("#singerNameInput").focus(); + }) }); From b7a79462dc814201c09a18e801f932940fa0962b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Phillip=20K=C3=BChne?= Date: Fri, 30 Jun 2023 00:17:44 +0200 Subject: [PATCH 79/85] Add pipfile --- .gitignore | 2 +- backend/Pipfile | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+), 1 deletion(-) create mode 100644 backend/Pipfile diff --git a/.gitignore b/.gitignore index 184e737..61e520e 100644 --- a/.gitignore +++ b/.gitignore @@ -87,7 +87,7 @@ ipython_config.py # However, in case of collaboration, if having platform-specific dependencies or dependencies # having no cross-platform support, pipenv may install dependencies that don't work, or not # install all needed dependencies. -#Pipfile.lock +Pipfile.lock # celery beat schedule file celerybeat-schedule diff --git a/backend/Pipfile b/backend/Pipfile new file mode 100644 index 0000000..d24971b --- /dev/null +++ b/backend/Pipfile @@ -0,0 +1,48 @@ +[[source]] +url = "https://pypi.org/simple" +verify_ssl = true +name = "pypi" + +[packages] +autopep8 = "==2.0.2" +beautifulsoup4 = "==4.12.0" +bs4 = "==0.0.1" +certifi = "==2022.12.7" +charset-normalizer = "==3.1.0" +click = "==8.1.3" +flake8 = "==6.0.0" +flask = "==2.2.3" +flask-basicauth = "==0.2.0" +greenlet = "==2.0.2" +gunicorn = "==20.1.0" +idna = "==3.4" +itsdangerous = "==2.1.2" +jinja2 = "==3.1.2" +mariadb = "==1.1.6" +markupsafe = "==2.1.2" +mccabe = "==0.7.0" +mysql = "==0.0.3" +mysqlclient = "==2.1.1" +numpy = "==1.24.2" +packaging = "==23.0" +pandas = "==1.5.3" +pycodestyle = "==2.10.0" +pyflakes = "==3.0.1" +pymysql = "==1.0.3" +python-dateutil = "==2.8.2" +pytz = "==2023.3" +requests = "==2.28.2" +six = "==1.16.0" +soupsieve = "==2.4" +sqlalchemy = "==2.0.7" +toml = "==0.10.2" +tomli = "==2.0.1" +typing-extensions = "==4.5.0" +urllib3 = "==1.26.15" +werkzeug = "==2.2.3" + +[dev-packages] + +[requires] +python_version = "3.9" +python_full_version = "3.9.17" From 349eff9a090da138892aaf8ef0ba5d962a2d53f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Phillip=20K=C3=BChne?= Date: Fri, 30 Jun 2023 14:50:32 +0200 Subject: [PATCH 80/85] strip whitespace from participant name in enqueue endpoint --- backend/app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/app.py b/backend/app.py index 50ce785..7b117bf 100644 --- a/backend/app.py +++ b/backend/app.py @@ -37,7 +37,7 @@ def enqueue(): if not helpers.is_valid_uuid(client_id): print(request.data) abort(400) - name = request.json['name'] + name = request.json['name'].strip() song_id = request.json['id'] if request.authorization: entry_id = database.add_entry(name, song_id, client_id) From a54953ff0d69e628fce31a79b44e801b79e8b4fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Phillip=20K=C3=BChne?= Date: Fri, 30 Jun 2023 15:28:46 +0200 Subject: [PATCH 81/85] fix copy functionality Fixed copy functionality by no longer rendering the data into the function call as string literal, but instead accessing it from the DOM in the function call. --- backend/templates/main_admin.html | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/backend/templates/main_admin.html b/backend/templates/main_admin.html index ac98b48..4649063 100644 --- a/backend/templates/main_admin.html +++ b/backend/templates/main_admin.html @@ -187,15 +187,16 @@ function TableActions(value, row, index) { let outerHTML = "" if (row.Transferred == 1) { - outerHTML = "  "; + outerHTML = "  "; } else { - outerHTML = "  "; + outerHTML = "  "; } return outerHTML; } function CopyFormatter(value, row, index) { - return ""+value+""; + let escapedString = value.replace("\"","\\\"").replace("\'", "\\\'") + return ""+value+""; } function getIdSelections() { From 7525708dcee226df1181e42f81796071a07d6236 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Phillip=20K=C3=BChne?= Date: Fri, 30 Jun 2023 15:32:13 +0200 Subject: [PATCH 82/85] lowered maxqueue to 10 and participant quota to 2 --- backend/helpers.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/helpers.py b/backend/helpers.py index 9dab0a5..9e3cde4 100644 --- a/backend/helpers.py +++ b/backend/helpers.py @@ -91,8 +91,8 @@ def setup_config(app: Flask): exit() default_config = {'username': initial_username, 'password': initial_password, - 'entryquota': 3, - 'maxqueue': 20, + 'entryquota': 2, + 'maxqueue': 10, 'entries_allowed': 1, 'theme': 'default.css'} for key, value in default_config.items(): From 4c806c3550b9162ca93498f9baa477ebb6750207 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Phillip=20K=C3=BChne?= Date: Fri, 30 Jun 2023 15:58:34 +0200 Subject: [PATCH 83/85] update docker-compose container version --- docker-compose.prod.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index 0ec4117..9dd49db 100644 --- a/docker-compose.prod.yml +++ b/docker-compose.prod.yml @@ -6,7 +6,7 @@ secrets: services: karaoqueue: - image: "ghcr.io/phoenixtwofive/karaoqueue:v2023.04.1" + image: "ghcr.io/phoenixtwofive/karaoqueue:v2023.06" build: . restart: always ports: From 5efa21924baee62cda74f0cd96d74c51306293c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Phillip=20K=C3=BChne?= Date: Fri, 30 Jun 2023 15:59:04 +0200 Subject: [PATCH 84/85] Update dockerfile to adapt to new debian version --- Dockerfile | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 2b0ceb6..ef63b2d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,12 +1,16 @@ FROM tiangolo/meinheld-gunicorn-flask:python3.9 -RUN apt-key adv --recv-keys --keyserver keyserver.ubuntu.com 0xcbcb082a1bb943db -RUN curl -LsS https://downloads.mariadb.com/MariaDB/mariadb_repo_setup | bash +# Currently unusable, mariadb is not available through installer for debian 12 +# RUN apt-key adv --recv-keys --keyserver keyserver.ubuntu.com 0xcbcb082a1bb943db +# RUN curl -LsS https://downloads.mariadb.com/MariaDB/mariadb_repo_setup | bash RUN apt-get update RUN apt-get upgrade -y RUN apt-get dist-upgrade +# In the meantime, acquire the mariadb packages through apt +RUN apt-get install -y libmariadb3 libmariadb-dev + COPY ./backend/requirements.txt /app/requirements.txt RUN pip install --no-cache-dir --upgrade -r /app/requirements.txt From e8e0bca6480bdea043bea9ca0d69604e7f45f0f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Phillip=20K=C3=BChne?= Date: Fri, 22 Sep 2023 16:55:05 +0000 Subject: [PATCH 85/85] Update devcontainer.json --- .devcontainer/devcontainer.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index b69688f..35ed8c9 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -4,7 +4,8 @@ "cpus": 4 }, "waitFor": "onCreateCommand", - "updateContentCommand": "pip install -r requirements.txt", + "onCreateCommand": "curl -LsS https://r.mariadb.com/downloads/mariadb_repo_setup | sudo bash && sudo apt install -y libmariadb3 libmariadb-dev", + "updateContentCommand": "pip install -r backend/requirements.txt", "postCreateCommand": "", "postAttachCommand": { "server": "flask --debug run"