From da20329942c5c1cf5f720d57ec436bd43391181c Mon Sep 17 00:00:00 2001 From: lelo Date: Mon, 17 Mar 2025 20:39:35 +0000 Subject: [PATCH] update --- allowed_secrets.json | 15 ++++++++++ app.py | 67 +++++++++++++++++++++++++++---------------- docker-compose.yml | 37 ++++++++++++++++++++++-- static/app.js | 4 +-- static/styles.css | 4 +-- templates/browse.html | 32 ++++++++++++++++++++- templates/error.html | 46 ++++++++++++++++++++++++----- templates/index.html | 47 ++++++++++++++++++++++++++++++ 8 files changed, 213 insertions(+), 39 deletions(-) create mode 100644 allowed_secrets.json create mode 100644 templates/index.html diff --git a/allowed_secrets.json b/allowed_secrets.json new file mode 100644 index 0000000..00c4de4 --- /dev/null +++ b/allowed_secrets.json @@ -0,0 +1,15 @@ +{ + "test": { + "expiry": "31.03.2025", + "file_root": "/mp3_root" + }, + "fh48750p353": { + "expiry": "20.04.2025", + "file_root": "/mp3_root" + }, + "another_secret": { + "expiry": "30.04.2025", + "file_root": "/mp3_root/another" + } + } + \ No newline at end of file diff --git a/app.py b/app.py index da364ef..0acc711 100755 --- a/app.py +++ b/app.py @@ -4,45 +4,63 @@ from PIL import Image import io from functools import wraps import mimetypes -from datetime import datetime +from datetime import datetime, date from urllib.parse import unquote import diskcache +import json cache = diskcache.Cache('./filecache', size_limit= 32 * 1024**3) # 32 GB limit app = Flask(__name__) -# Use a raw string for the UNC path. -app.config['MP3_ROOT'] = r'/mp3_root' -# app.config['MP3_ROOT'] = r'\\192.168.10.10\docker2\sync-bethaus\syncfiles\folders' -app.config['SECRET_KEY'] = os.urandom(24) -app.config['ALLOWED_SECRETS'] = { - 'test': datetime(2026, 3, 31, 23, 59, 59), - 'another_secret': datetime(2024, 4, 30, 23, 59, 59) -} +# Use a raw string for the default FILE_ROOT path. +app.config['FILE_ROOT'] = r'/mp3_root' +app.config['SECRET_KEY'] = '85c1117eb3a5f2c79f0ff395bada8ff8d9a257b99ef5e143' + +def load_allowed_secrets(filename='allowed_secrets.json'): + with open(filename) as f: + secrets = json.load(f) + for key, value in secrets.items(): + if 'expiry' in value: + value['expiry'] = datetime.strptime(value['expiry'], '%d.%m.%Y').date() + return secrets + +app.config['ALLOWED_SECRETS'] = load_allowed_secrets() def require_secret(f): @wraps(f) def decorated_function(*args, **kwargs): allowed_secrets = app.config['ALLOWED_SECRETS'] current_secret = session.get('secret') - now = datetime.now() + today = date.today() + # be nice if somebody hit you without a secret (no error) + if current_secret is None: + return render_template('index.html') - def is_valid(secret): - expiry_date = allowed_secrets.get(secret) - return expiry_date and now <= expiry_date - # Check if secret from session is still valid - if current_secret and is_valid(current_secret): - return f(*args, **kwargs) + def is_valid(secret_data): + expiry_date = secret_data.get('expiry') + return expiry_date and today <= expiry_date + + # Check if the secret stored in session is still valid + if current_secret: + secret_data = allowed_secrets.get(current_secret) + if secret_data and is_valid(secret_data): + # Update FILE_ROOT based on the secret's configuration + app.config['FILE_ROOT'] = secret_data.get('file_root') + return f(*args, **kwargs) # Check secret from GET parameter secret = request.args.get('secret') - if secret and is_valid(secret): - session['secret'] = secret - return f(*args, **kwargs) + if secret: + secret_data = allowed_secrets.get(secret) + if secret_data and is_valid(secret_data): + session['secret'] = secret + app.config['FILE_ROOT'] = secret_data.get('file_root') + return f(*args, **kwargs) - # If secret invalid or expired + # If the secret is invalid or expired, show an error return render_template('error.html', message="Invalid or expired secret."), 403 + return decorated_function @app.route('/static/icons/.png') @@ -156,8 +174,8 @@ def generate_breadcrumbs(subpath): @app.route('/api/path/') @require_secret def api_browse(subpath): - mp3_root = app.config['MP3_ROOT'] - directory = os.path.join(mp3_root, subpath.replace('/', os.sep)) + file_root = app.config['FILE_ROOT'] + directory = os.path.join(file_root, subpath.replace('/', os.sep)) if not os.path.isdir(directory): return jsonify({'error': 'Directory not found'}), 404 @@ -175,7 +193,7 @@ def api_browse(subpath): @require_secret def serve_file(filename): decoded_filename = unquote(filename).replace('/', os.sep) - full_path = os.path.normpath(os.path.join(app.config['MP3_ROOT'], decoded_filename)) + full_path = os.path.normpath(os.path.join(app.config['FILE_ROOT'], decoded_filename)) if not os.path.isfile(full_path): app.logger.error(f"File not found: {full_path}") @@ -228,7 +246,7 @@ def serve_file(filename): @require_secret def get_transcript(filename): fs_filename = filename.replace('/', os.sep) - full_path = os.path.join(app.config['MP3_ROOT'], fs_filename) + full_path = os.path.join(app.config['FILE_ROOT'], fs_filename) if not os.path.isfile(full_path): return "Transcription not found", 404 @@ -242,7 +260,6 @@ def get_transcript(filename): @app.route('/') @require_secret def index(path): - # The SPA template (browse.html) will read the current URL and load the correct directory. return render_template("browse.html") if __name__ == "__main__": diff --git a/docker-compose.yml b/docker-compose.yml index 76f4b3b..6b78c95 100755 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,3 +1,5 @@ +version: "3" + services: flask-app: image: python:3.11-slim @@ -14,7 +16,38 @@ services: - FLASK_RUN_HOST=0.0.0.0 - FLASK_ENV=production - MP3_ROOT=/mp3_root - ports: - - "5000:5000" + networks: + - traefik + labels: + - "traefik.enable=true" + + # ---------------------------------------------------- + # HTTP router: Listen on entrypoint "web" (port 80) + # and apply a middleware to force redirect to HTTPS + # ---------------------------------------------------- + - "traefik.http.routers.bethaus-app.rule=Host(`app.bethaus-speyer.de`)" + - "traefik.http.routers.bethaus-app.entrypoints=web" + - "traefik.http.routers.bethaus-app.middlewares=redirect-to-https" + + # This is the "redirect-to-https" middleware definition + - "traefik.http.middlewares.redirect-to-https.redirectscheme.scheme=https" + + # ----------------------------------------------------- + # HTTPS router: Listen on entrypoint "websecure" + # using TLS via your ACME (Let's Encrypt) resolver + # ----------------------------------------------------- + - "traefik.http.routers.bethaus-app-secure.rule=Host(`app.bethaus-speyer.de`)" + - "traefik.http.routers.bethaus-app-secure.entrypoints=websecure" + - "traefik.http.routers.bethaus-app-secure.tls=true" + - "traefik.http.routers.bethaus-app-secure.tls.certresolver=myresolver" + + # The service’s internal port + - "traefik.http.services.bethaus-app.loadbalancer.server.port=5000" + command: > sh -c "pip install -r requirements.txt && flask run" + +networks: + traefik: + external: true + diff --git a/static/app.js b/static/app.js index 2ca52e0..d535fba 100644 --- a/static/app.js +++ b/static/app.js @@ -62,7 +62,7 @@ function renderContent(data) { // Add a data-index attribute for music files. const indexAttr = file.file_type === 'music' ? ` data-index="${currentMusicFiles.length - 1}"` : ''; contentHTML += `
  • - ${symbol} ${file.name}`; + ${symbol} ${file.name.replace('.mp3', '')}`; if (file.has_transcript) { contentHTML += `📄`; } @@ -187,7 +187,7 @@ document.querySelectorAll('.play-file').forEach(link => { const pathParts = relUrl.split('/'); const fileName = pathParts.pop(); const pathStr = pathParts.join('/'); - nowPlayingInfo.innerHTML = pathStr + '
    ' + fileName + ''; + nowPlayingInfo.innerHTML = pathStr.replace(/\//g, ' > ') + '
    ' + fileName.replace('.mp3', '') + ''; preload_audio(); } } catch (error) { diff --git a/static/styles.css b/static/styles.css index fc3d509..e534ce8 100644 --- a/static/styles.css +++ b/static/styles.css @@ -17,9 +17,9 @@ body { min-height: 100%; } .container { + width: 90%; max-width: 900px; margin: 0 auto; - padding: 10px; padding-bottom: 200px; } @@ -69,7 +69,7 @@ li { } .directories-grid .directory-item { background-color: #fff; - padding: 15px; + padding: 15px 10px; border-radius: 5px; text-align: center; box-shadow: 0 1px 3px rgba(0,0,0,0.1); diff --git a/templates/browse.html b/templates/browse.html index 8f899de..2e9fe94 100644 --- a/templates/browse.html +++ b/templates/browse.html @@ -2,6 +2,12 @@ + + + + + + Gottesdienste @@ -23,11 +29,35 @@ + +
    -

    Gottesdienste Speyer und Schwegenheim

    diff --git a/templates/error.html b/templates/error.html index 1f67395..ddfc28f 100644 --- a/templates/error.html +++ b/templates/error.html @@ -1,16 +1,48 @@ - - Keine Berechtigung - - - + + + + + + + + + Gottesdienste Speyer und Schwegenheim + + + + + +
    -

    Keine Berechtigung

    -
    Bitte mit Link aus Telegram-Gruppe erneut anklicken.
    +
    Bitte den Link aus der Telegram-Gruppe erneut anklicken.
    diff --git a/templates/index.html b/templates/index.html new file mode 100644 index 0000000..ae75163 --- /dev/null +++ b/templates/index.html @@ -0,0 +1,47 @@ + + + + + + + + + + + + Gottesdienste Speyer und Schwegenheim + + + + + + + + +
    +
    Bitte den Link aus der Telegram-Gruppe erneut anklicken.
    +
    + +