Merge pull request #39 from PhoenixTwoFive/feature/legacy/mariadb_database

Feature/legacy/mariadb database
This commit is contained in:
Phillip Kühne 2022-10-08 19:51:56 +02:00 committed by GitHub
commit 6efb234a1c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 179 additions and 120 deletions

11
.vscode/launch.json vendored
View File

@ -5,7 +5,7 @@
"version": "0.2.0", "version": "0.2.0",
"configurations": [ "configurations": [
{ {
"preLaunchTask": "versiondump", "preLaunchTask": "mariadb",
"name": "Python: Flask", "name": "Python: Flask",
"type": "python", "type": "python",
"cwd": "${workspaceFolder}/backend", "cwd": "${workspaceFolder}/backend",
@ -14,7 +14,8 @@
"env": { "env": {
"FLASK_APP": "backend/app.py", "FLASK_APP": "backend/app.py",
"FLASK_ENV": "development", "FLASK_ENV": "development",
"FLASK_DEBUG": "1" "FLASK_DEBUG": "1",
"DBSTRING": "mysql://devuser:devpw@127.0.0.1:3306/karaoqueue"
}, },
"args": [ "args": [
"run", "run",
@ -24,7 +25,7 @@
"jinja": true "jinja": true
}, },
{ {
"preLaunchTask": "versiondump", "preLaunchTask": "mariadb",
"name": "Python: Flask (with reload)", "name": "Python: Flask (with reload)",
"type": "python", "type": "python",
"cwd": "${workspaceFolder}/backend", "cwd": "${workspaceFolder}/backend",
@ -42,7 +43,7 @@
"jinja": true "jinja": true
}, },
{ {
"preLaunchTask": "versiondump", "preLaunchTask": "mariadb",
"name": "Python: Flask (with reload, externally reachable)", "name": "Python: Flask (with reload, externally reachable)",
"type": "python", "type": "python",
"cwd": "${workspaceFolder}/backend", "cwd": "${workspaceFolder}/backend",
@ -61,7 +62,7 @@
"jinja": true "jinja": true
}, },
{ {
"preLaunchTask": "versiondump", "preLaunchTask": "mariadb",
"name": "Python: Flask (externally reachable)", "name": "Python: Flask (externally reachable)",
"type": "python", "type": "python",
"cwd": "${workspaceFolder}/backend", "cwd": "${workspaceFolder}/backend",

7
.vscode/tasks.json vendored
View File

@ -8,6 +8,13 @@
"type": "shell", "type": "shell",
"command": "echo \"$(git rev-parse --abbrev-ref HEAD)-$(git describe)\"> ${workspaceFolder}/backend/.version", "command": "echo \"$(git rev-parse --abbrev-ref HEAD)-$(git describe)\"> ${workspaceFolder}/backend/.version",
"problemMatcher": [] "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
} }
] ]
} }

View File

@ -8,4 +8,4 @@ RUN pip install Flask-BasicAuth
RUN pip install bs4 RUN pip install bs4
COPY ./app /app COPY ./backend /app

View File

@ -1,4 +1,5 @@
from flask import Flask, render_template, Response, abort, request, redirect, send_from_directory from flask import Flask, render_template, abort, request, redirect, send_from_directory, jsonify
from flask.wrappers import Request, Response
import helpers import helpers
import database import database
import data_adapters import data_adapters
@ -9,7 +10,7 @@ from helpers import nocache
app = Flask(__name__, static_url_path='/static') app = Flask(__name__, static_url_path='/static')
basic_auth = BasicAuth(app) basic_auth = BasicAuth(app)
accept_entries = False accept_entries = True
@app.route("/") @app.route("/")
def home(): def home():
@ -81,12 +82,12 @@ def settings():
def settings_post(): def settings_post():
entryquota = request.form.get("entryquota") entryquota = request.form.get("entryquota")
maxqueue = request.form.get("maxqueue") maxqueue = request.form.get("maxqueue")
if entryquota.isnumeric() and int(entryquota) > 0: if entryquota.isnumeric() and int(entryquota) > 0: # type: ignore
app.config['ENTRY_QUOTA'] = int(entryquota) app.config['ENTRY_QUOTA'] = int(entryquota) # type: ignore
else: else:
abort(400) abort(400)
if maxqueue.isnumeric and int(maxqueue) > 0: if maxqueue.isnumeric and int(maxqueue) > 0: # type: ignore
app.config['MAX_QUEUE'] = int(maxqueue) app.config['MAX_QUEUE'] = int(maxqueue) # type: ignore
else: else:
abort(400) abort(400)
@ -131,8 +132,8 @@ def get_song_completions(input_string=""):
input_string = request.args.get('search', input_string) input_string = request.args.get('search', input_string)
if input_string != "": if input_string != "":
print(input_string) print(input_string)
list = database.get_song_completions(input_string=input_string) result = [list(x) for x in database.get_song_completions(input_string=input_string)]
return Response(json.dumps(list, ensure_ascii=False).encode('utf-8'), mimetype='text/json') return jsonify(result)
else: else:
return 400 return 400
@ -229,6 +230,7 @@ def admin():
@app.before_first_request @app.before_first_request
def activate_job(): def activate_job():
helpers.load_dbconfig(app)
helpers.load_version(app) helpers.load_version(app)
helpers.create_data_directory() helpers.create_data_directory()
database.create_entry_table() database.create_entry_table()

View File

@ -1,31 +1,39 @@
# -*- coding: utf_8 -*- # -*- coding: utf_8 -*-
import sqlite3 from email.mime import base
from MySQLdb import Connection
from sqlalchemy import create_engine, engine
import pandas import pandas
from io import StringIO from io import StringIO
from flask import current_app
song_table = "songs" song_table = "songs"
entry_table = "entries" entry_table = "entries"
index_label = "Id" index_label = "Id"
done_table = "done_songs" done_table = "done_songs"
connection = None
def open_db():
conn = sqlite3.connect("/tmp/karaoqueue.db") def open_db() -> engine.base.Connection:
conn.execute('PRAGMA encoding = "UTF-8";') global connection
return conn if (not connection):
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
def import_songs(song_csv): def import_songs(song_csv):
print("Start importing Songs...") print("Start importing Songs...")
df = pandas.read_csv(StringIO(song_csv), sep=';') df = pandas.read_csv(StringIO(song_csv), sep=';')
conn = open_db() conn = open_db()
cur = conn.cursor()
df.to_sql(song_table, conn, if_exists='replace', df.to_sql(song_table, conn, if_exists='replace',
index=False) index=False)
cur.execute("SELECT Count(Id) FROM songs") cur = conn.execute("SELECT Count(Id) FROM songs")
num_songs = cur.fetchone()[0] num_songs = cur.fetchone()[0] # type: ignore
conn.close() # conn.close()
print("Imported songs ({} in Database)".format(num_songs)) print("Imported songs ({} in Database)".format(num_songs))
return("Imported songs ({} in Database)".format(num_songs)) return("Imported songs ({} in Database)".format(num_songs))
@ -33,158 +41,146 @@ def import_songs(song_csv):
def create_entry_table(): def create_entry_table():
conn = open_db() conn = open_db()
conn.execute('CREATE TABLE IF NOT EXISTS '+entry_table + 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() # conn.close()
def create_done_song_table(): def create_done_song_table():
conn = open_db() conn = open_db()
conn.execute('CREATE TABLE IF NOT EXISTS '+done_table + conn.execute('CREATE TABLE IF NOT EXISTS '+done_table +
' (Song_Id INTEGER PRIMARY KEY NOT NULL, Plays INTEGER)') ' (Song_Id INTEGER PRIMARY KEY NOT NULL, Plays INTEGER)')
conn.close() # conn.close()
def create_song_table(): def create_song_table():
conn = open_db() conn = open_db()
conn.execute("CREATE TABLE IF NOT EXISTS \""+song_table+"""\" ( conn.execute("CREATE TABLE IF NOT EXISTS `"+song_table+"""` (
"Id" INTEGER, `Id` INTEGER,
"Title" TEXT, `Title` TEXT,
"Artist" TEXT, `Artist` TEXT,
"Year" INTEGER, `Year` INTEGER,
"Duo" INTEGER, `Duo` INTEGER,
"Explicit" INTEGER, `Explicit` INTEGER,
"Date Added" TEXT, `Date Added` TEXT,
"Styles" TEXT, `Styles` TEXT,
"Languages" TEXT `Languages` TEXT
)""") )""")
conn.close() # conn.close()
def create_list_view(): def create_list_view():
conn = open_db() conn = open_db()
conn.execute("""CREATE VIEW IF NOT EXISTS [Liste] AS conn.execute("""CREATE OR REPLACE VIEW `Liste` AS
SELECT Name, Title, Artist, entries.Id, songs.Id, entries.Transferred SELECT Name, Title, Artist, entries.Id AS entry_ID, songs.Id AS song_ID, entries.Transferred
FROM entries, songs FROM entries, songs
WHERE entries.Song_Id=songs.Id""") WHERE entries.Song_Id=songs.Id""")
conn.close() # conn.close()
def create_done_song_view(): def create_done_song_view():
conn = open_db() 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 SELECT CONCAT(Artist," - ", Title) AS Song, Plays AS Wiedergaben
FROM songs, done_songs FROM songs, done_songs
WHERE done_songs.Song_Id=songs.Id""") WHERE done_songs.Song_Id=songs.Id""")
conn.close() # conn.close()
def get_list(): def get_list():
conn = open_db() conn = open_db()
conn.row_factory = sqlite3.Row cur = conn.execute("SELECT * FROM Liste")
cur = conn.cursor()
cur.execute("SELECT * FROM Liste")
return cur.fetchall() return cur.fetchall()
def get_played_list(): def get_played_list():
conn = open_db() conn = open_db()
cur = conn.cursor() cur = conn.execute("SELECT * FROM Abspielliste")
cur.execute("SELECT * FROM Abspielliste")
return cur.fetchall() return cur.fetchall()
def get_song_list(): def get_song_list():
conn = open_db() conn = open_db()
cur = conn.cursor() cur = conn.execute(
cur.execute("SELECT Artist || \" - \" || Title AS Song, Id FROM songs;") "SELECT Artist || \" - \" || Title AS Song, Id FROM songs;")
return cur.fetchall() return cur.fetchall()
def get_song_completions(input_string): def get_song_completions(input_string):
conn = open_db() conn = open_db()
cur = conn.cursor()
# Don't look, it burns... # Don't look, it burns...
prepared_string = "%{0}%".format( prepared_string = "%{0}%".format(
input_string).upper() # "Test" -> "%TEST%" input_string).upper() # "Test" -> "%TEST%"
print(prepared_string) print(prepared_string)
cur.execute( cur = conn.execute(
"SELECT Title || \" - \" || Artist AS Song, Id FROM songs WHERE REPLACE(REPLACE(REPLACE(REPLACE(UPPER( SONG ),'ö','Ö'),'ü','Ü'),'ä','Ä'),'ß','') LIKE (?) LIMIT 20;", (prepared_string,)) "SELECT CONCAT(Artist,\" - \",Title) AS Song, Id FROM songs WHERE CONCAT(Artist,\" - \",Title) LIKE (%s) LIMIT 20;", [prepared_string])
return cur.fetchall() return cur.fetchall()
def add_entry(name, song_id, client_id): def add_entry(name, song_id, client_id):
conn = open_db() conn = open_db()
cur = conn.cursor() conn.execute(
cur.execute( "INSERT INTO entries (Song_Id,Name,Client_Id) VALUES(%s,%s,%s);", (song_id, name, client_id))
"INSERT INTO entries (Song_Id,Name,Client_Id) VALUES(?,?,?);", (song_id, name, client_id)) # conn.close()
conn.commit()
conn.close()
return return
def add_sung_song(entry_id): def add_sung_song(entry_id):
conn = open_db() conn = open_db()
cur = conn.cursor() cur = conn.execute(
cur.execute("""SELECT Song_Id FROM entries WHERE Id=?""", (entry_id,)) """SELECT Song_Id FROM entries WHERE Id=%s""", (entry_id,))
song_id = cur.fetchone()[0] song_id = cur.fetchone()[0] # type: ignore
cur.execute("""INSERT OR REPLACE INTO done_songs (Song_Id, Plays) conn.execute("""INSERT INTO done_songs (Song_Id, Plays) VALUES("""+str(song_id)+""",1) ON DUPLICATE KEY UPDATE Plays=Plays + 1;""")
VALUES("""+str(song_id)+""", # SQLite bullshittery
COALESCE( # conn.execute("""REPLACE INTO done_songs (Song_Id, Plays)
(SELECT Plays FROM done_songs # VALUES("""+str(song_id)+""",
WHERE Song_Id="""+str(song_id)+"), 0) + 1)" # COALESCE(
) # (SELECT Plays FROM done_songs
conn.commit() # WHERE Song_Id="""+str(song_id)+"), 0) + 1)"
# )
delete_entry(entry_id) delete_entry(entry_id)
conn.close() # conn.close()
return True return True
def toggle_transferred(entry_id): def toggle_transferred(entry_id):
conn = open_db() conn = open_db()
cur = conn.cursor() cur = conn.execute(
cur.execute("SELECT Transferred FROM entries WHERE ID =?", (entry_id,)) "SELECT Transferred FROM entries WHERE ID =%s", (entry_id,))
marked = cur.fetchall()[0][0] marked = cur.fetchall()[0][0]
if(marked == 0): if(marked == 0):
cur.execute( conn.execute(
"UPDATE entries SET Transferred = 1 WHERE ID =?", (entry_id,)) "UPDATE entries SET Transferred = 1 WHERE ID =%s", (entry_id,))
else: else:
cur.execute( conn.execute(
"UPDATE entries SET Transferred = 0 WHERE ID =?", (entry_id,)) "UPDATE entries SET Transferred = 0 WHERE ID =%s", (entry_id,))
conn.commit() # conn.close()
conn.close()
return True return True
def check_entry_quota(client_id): def check_entry_quota(client_id):
conn = open_db() conn = open_db()
cur = conn.cursor() cur = conn.execute(
cur.execute( "SELECT Count(*) FROM entries WHERE entries.Client_Id = %s", (client_id,))
"SELECT Count(*) FROM entries WHERE entries.Client_Id = ?", (client_id,))
return cur.fetchall()[0][0] return cur.fetchall()[0][0]
def check_queue_length(): def check_queue_length():
conn = open_db() conn = open_db()
cur = conn.cursor() cur = conn.execute("SELECT Count(*) FROM entries")
cur.execute("SELECT Count(*) FROM entries")
return cur.fetchall()[0][0] return cur.fetchall()[0][0]
def clear_played_songs(): def clear_played_songs():
conn = open_db() conn = open_db()
cur = conn.cursor() conn.execute("DELETE FROM done_songs")
cur.execute("DELETE FROM done_songs") # conn.close()
conn.commit()
conn.close()
return True return True
def delete_entry(id): def delete_entry(id):
conn = open_db() conn = open_db()
cur = conn.cursor() conn.execute("DELETE FROM entries WHERE id=%s", (id,))
cur.execute("DELETE FROM entries WHERE id=?", (id,)) # conn.close()
conn.commit()
conn.close()
return True return True
@ -194,19 +190,15 @@ def delete_entries(ids):
idlist.append((x,)) idlist.append((x,))
try: try:
conn = open_db() conn = open_db()
cur = conn.cursor() cur = conn.execute("DELETE FROM entries WHERE id=%s", idlist)
cur.executemany("DELETE FROM entries WHERE id=?", idlist) # conn.close()
conn.commit()
conn.close()
return cur.rowcount return cur.rowcount
except sqlite3.Error as error: except Exception as error:
return -1 return -1
def delete_all_entries(): def delete_all_entries():
conn = open_db() conn = open_db()
cur = conn.cursor() conn.execute("DELETE FROM entries")
cur.execute("DELETE FROM entries") # conn.close()
conn.commit()
conn.close()
return True return True

View File

@ -37,7 +37,7 @@ def check_config_exists():
def load_version(app): def load_version(app):
if os.environ.get("SOURCE_VERSION"): 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"): elif os.path.isfile(".version"):
with open('.version', 'r') as file: with open('.version', 'r') as file:
data = file.read().replace('\n', '') data = file.read().replace('\n', '')
@ -47,22 +47,52 @@ def load_version(app):
app.config['VERSION'] = "" app.config['VERSION'] = ""
else: else:
app.config['VERSION'] = "" app.config['VERSION'] = ""
def load_dbconfig(app):
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'] = ""
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', '')
if data:
app.config['DBCONNSTRING'] = data
else:
app.config['DBCONNSTRING'] = ""
else:
app.config['DBCONNSTRING'] = ""
def setup_config(app): def setup_config(app):
if check_config_exists(): if os.environ.get("DEPLOYMENT_PLATFORM") == "Docker":
config = json.load(open(config_file)) app.config['BASIC_AUTH_USERNAME'] = os.environ.get('BASIC_AUTH_USERNAME')
with open(config_file, 'r') as handle: app.config['BASIC_AUTH_PASSWORD'] = os.environ.get('BASIC_AUTH_PASSWORD')
config = json.load(handle) app.config['ENTRY_QUOTA'] = os.environ.get('ENTRY_QUOTA')
print("Loaded existing config") app.config['MAX_QUEUE'] = os.environ.get('MAX_QUEUE')
else: else:
config = {'username': 'admin', 'password': 'changeme', 'entryquota': 3, 'maxqueue': 20} if check_config_exists():
with open(config_file, 'w') as handle: config = json.load(open(config_file))
json.dump(config, handle, indent=4, sort_keys=True) with open(config_file, 'r') as handle:
print("Wrote new config") config = json.load(handle)
app.config['BASIC_AUTH_USERNAME'] = config['username'] print("Loaded existing config")
app.config['BASIC_AUTH_PASSWORD'] = config['password'] else:
app.config['ENTRY_QUOTA'] = config['entryquota'] config = {'username': 'admin', 'password': 'changeme', 'entryquota': 3, 'maxqueue': 20}
app.config['MAX_QUEUE'] = config['maxqueue'] 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']
@ -70,7 +100,7 @@ def nocache(view):
@wraps(view) @wraps(view)
def no_cache(*args, **kwargs): def no_cache(*args, **kwargs):
response = make_response(view(*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['Cache-Control'] = 'no-store, no-cache, must-revalidate, post-check=0, pre-check=0, max-age=0'
response.headers['Pragma'] = 'no-cache' response.headers['Pragma'] = 'no-cache'
response.headers['Expires'] = '-1' response.headers['Expires'] = '-1'

View File

@ -2,4 +2,6 @@ requests
pandas pandas
Flask-BasicAuth Flask-BasicAuth
bs4 bs4
gunicorn gunicorn
SQLAlchemy
mysqlclient

View File

@ -75,7 +75,7 @@
{% endif %} {% endif %}
<!--<a href="https://github.com/PhoenixTwoFive/karaoqueue" <!--<a href="https://github.com/PhoenixTwoFive/karaoqueue"
class="ml-1 mr-1"><i class="fab fa-github mr-1"></i><span>Github</span></a>--> class="ml-1 mr-1"><i class="fab fa-github mr-1"></i><span>Github</span></a>-->
<span class="text-muted">KaraoQueue {{karaoqueue_version}} -&nbsp;<span>&copy</span>&nbsp;2019-21 - Phillip <span class="text-muted"><a class="text-muted" href="https://github.com/PhoenixTwoFive/karaoqueue">KaraoQueue</a> {{karaoqueue_version}} -&nbsp;2019-22 - Phillip
Kühne</span> Kühne</span>
</div> </div>
</footer> </footer>

View File

@ -52,10 +52,10 @@ table td:nth-child(2) {
{% block extrajs %} {% block extrajs %}
<script> <script>
$(function () { $(function () {
refreshEntryToggle()
$('#entryToggle').change(function() { $('#entryToggle').change(function() {
$.ajax({url: "/api/entries/accept/"+($('#entryToggle').is(":checked") ? "1" : "0"), complete: setTimeout(refreshEntryToggle, 1000)}); $.ajax({url: "/api/entries/accept/"+($('#entryToggle').is(":checked") ? "1" : "0"), complete: setTimeout(refreshEntryToggle, 1000)});
}) })
refreshEntryToggle()
$("#entrytable").bootstrapTable().on('load-success.bs.table', function() { $("#entrytable").bootstrapTable().on('load-success.bs.table', function() {
$('[data-toggle="tooltip"]').tooltip() $('[data-toggle="tooltip"]').tooltip()
}) })
@ -195,17 +195,19 @@ table td:nth-child(2) {
}); });
} }
function TableActions (value, row, index) { function TableActions (value, row, index) {
console.log("Value: "+value+", Row: "+row+", Index: "+index)
console.log(row)
let outerHTML = "" let outerHTML = ""
if (row.Transferred==1) { if (row.Transferred==1) {
outerHTML = "<button type=\"button\" class=\"btn btn-default\" onclick=\"markEntryAsTransferred("+row.ID+")\"><i class=\"fas fa-backward\"></i></button>&nbsp;<button type=\"button\" class=\"btn btn-success\" data-toggle=\"tooltip\" data-placement=\"top\" title=\"Als gesungen markieren\" onclick=\"markEntryAsSung("+row.ID+")\"><i class=\"fas fa-check\"></i></button>&nbsp;<button type=\"button\" class=\"btn btn-danger\" data-toggle=\"tooltip\" data-placement=\"top\" title=\"Eintrag löschen\" onclick=\"confirmDeleteEntry('"+row.Name+"',"+row.ID+")\"><i class=\"fas fa-trash\"></i></button>"; outerHTML = "<button type=\"button\" class=\"btn btn-default\" onclick=\"markEntryAsTransferred("+row.entry_ID+")\"><i class=\"fas fa-backward\"></i></button>&nbsp;<button type=\"button\" class=\"btn btn-success\" data-toggle=\"tooltip\" data-placement=\"top\" title=\"Als gesungen markieren\" onclick=\"markEntryAsSung("+row.entry_ID+")\"><i class=\"fas fa-check\"></i></button>&nbsp;<button type=\"button\" class=\"btn btn-danger\" data-toggle=\"tooltip\" data-placement=\"top\" title=\"Eintrag löschen\" onclick=\"confirmDeleteEntry('"+row.Name+"',"+row.entry_ID+")\"><i class=\"fas fa-trash\"></i></button>";
} else { } else {
outerHTML = "<button type=\"button\" class=\"btn btn-info\" onclick=\"markEntryAsTransferred("+row.ID+")\"><i class=\"fas fa-exchange-alt\"></i></button>&nbsp;<button type=\"button\" class=\"btn btn-success\" data-toggle=\"tooltip\" data-placement=\"top\" title=\"Als gesungen markieren\" onclick=\"markEntryAsSung("+row.ID+")\"><i class=\"fas fa-check\"></i></button>&nbsp;<button type=\"button\" class=\"btn btn-danger\" data-toggle=\"tooltip\" data-placement=\"top\" title=\"Eintrag löschen\" onclick=\"confirmDeleteEntry('"+row.Name+"',"+row.ID+")\"><i class=\"fas fa-trash\"></i></button>"; outerHTML = "<button type=\"button\" class=\"btn btn-info\" onclick=\"markEntryAsTransferred("+row.entry_ID+")\"><i class=\"fas fa-exchange-alt\"></i></button>&nbsp;<button type=\"button\" class=\"btn btn-success\" data-toggle=\"tooltip\" data-placement=\"top\" title=\"Als gesungen markieren\" onclick=\"markEntryAsSung("+row.entry_ID+")\"><i class=\"fas fa-check\"></i></button>&nbsp;<button type=\"button\" class=\"btn btn-danger\" data-toggle=\"tooltip\" data-placement=\"top\" title=\"Eintrag löschen\" onclick=\"confirmDeleteEntry('"+row.Name+"',"+row.entry_ID+")\"><i class=\"fas fa-trash\"></i></button>";
} }
return outerHTML; return outerHTML;
} }
function getIdSelections() { function getIdSelections() {
return $.map($("#entrytable").bootstrapTable('getSelections'), function (row) { return $.map($("#entrytable").bootstrapTable('getSelections'), function (row) {
return row.ID return row.entry_ID
}) })
} }
</script> </script>

23
docker-compose.prod.yml Normal file
View File

@ -0,0 +1,23 @@
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
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