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