This commit is contained in:
lelo 2025-03-17 20:39:35 +00:00
parent 395dfab938
commit da20329942
8 changed files with 213 additions and 39 deletions

15
allowed_secrets.json Normal file
View File

@ -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"
}
}

67
app.py
View File

@ -4,45 +4,63 @@ from PIL import Image
import io import io
from functools import wraps from functools import wraps
import mimetypes import mimetypes
from datetime import datetime from datetime import datetime, date
from urllib.parse import unquote from urllib.parse import unquote
import diskcache import diskcache
import json
cache = diskcache.Cache('./filecache', size_limit= 32 * 1024**3) # 32 GB limit cache = diskcache.Cache('./filecache', size_limit= 32 * 1024**3) # 32 GB limit
app = Flask(__name__) app = Flask(__name__)
# Use a raw string for the UNC path. # Use a raw string for the default FILE_ROOT path.
app.config['MP3_ROOT'] = r'/mp3_root' app.config['FILE_ROOT'] = r'/mp3_root'
# app.config['MP3_ROOT'] = r'\\192.168.10.10\docker2\sync-bethaus\syncfiles\folders' app.config['SECRET_KEY'] = '85c1117eb3a5f2c79f0ff395bada8ff8d9a257b99ef5e143'
app.config['SECRET_KEY'] = os.urandom(24)
app.config['ALLOWED_SECRETS'] = { def load_allowed_secrets(filename='allowed_secrets.json'):
'test': datetime(2026, 3, 31, 23, 59, 59), with open(filename) as f:
'another_secret': datetime(2024, 4, 30, 23, 59, 59) 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): def require_secret(f):
@wraps(f) @wraps(f)
def decorated_function(*args, **kwargs): def decorated_function(*args, **kwargs):
allowed_secrets = app.config['ALLOWED_SECRETS'] allowed_secrets = app.config['ALLOWED_SECRETS']
current_secret = session.get('secret') 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 def is_valid(secret_data):
if current_secret and is_valid(current_secret): expiry_date = secret_data.get('expiry')
return f(*args, **kwargs) 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 # Check secret from GET parameter
secret = request.args.get('secret') secret = request.args.get('secret')
if secret and is_valid(secret): if secret:
session['secret'] = secret secret_data = allowed_secrets.get(secret)
return f(*args, **kwargs) 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 render_template('error.html', message="Invalid or expired secret."), 403
return decorated_function return decorated_function
@app.route('/static/icons/<string:size>.png') @app.route('/static/icons/<string:size>.png')
@ -156,8 +174,8 @@ def generate_breadcrumbs(subpath):
@app.route('/api/path/<path:subpath>') @app.route('/api/path/<path:subpath>')
@require_secret @require_secret
def api_browse(subpath): def api_browse(subpath):
mp3_root = app.config['MP3_ROOT'] file_root = app.config['FILE_ROOT']
directory = os.path.join(mp3_root, subpath.replace('/', os.sep)) directory = os.path.join(file_root, subpath.replace('/', os.sep))
if not os.path.isdir(directory): if not os.path.isdir(directory):
return jsonify({'error': 'Directory not found'}), 404 return jsonify({'error': 'Directory not found'}), 404
@ -175,7 +193,7 @@ def api_browse(subpath):
@require_secret @require_secret
def serve_file(filename): def serve_file(filename):
decoded_filename = unquote(filename).replace('/', os.sep) 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): if not os.path.isfile(full_path):
app.logger.error(f"File not found: {full_path}") app.logger.error(f"File not found: {full_path}")
@ -228,7 +246,7 @@ def serve_file(filename):
@require_secret @require_secret
def get_transcript(filename): def get_transcript(filename):
fs_filename = filename.replace('/', os.sep) 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): if not os.path.isfile(full_path):
return "Transcription not found", 404 return "Transcription not found", 404
@ -242,7 +260,6 @@ def get_transcript(filename):
@app.route('/<path:path>') @app.route('/<path:path>')
@require_secret @require_secret
def index(path): def index(path):
# The SPA template (browse.html) will read the current URL and load the correct directory.
return render_template("browse.html") return render_template("browse.html")
if __name__ == "__main__": if __name__ == "__main__":

View File

@ -1,3 +1,5 @@
version: "3"
services: services:
flask-app: flask-app:
image: python:3.11-slim image: python:3.11-slim
@ -14,7 +16,38 @@ services:
- FLASK_RUN_HOST=0.0.0.0 - FLASK_RUN_HOST=0.0.0.0
- FLASK_ENV=production - FLASK_ENV=production
- MP3_ROOT=/mp3_root - MP3_ROOT=/mp3_root
ports: networks:
- "5000:5000" - 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 services internal port
- "traefik.http.services.bethaus-app.loadbalancer.server.port=5000"
command: > command: >
sh -c "pip install -r requirements.txt && flask run" sh -c "pip install -r requirements.txt && flask run"
networks:
traefik:
external: true

View File

@ -62,7 +62,7 @@ function renderContent(data) {
// Add a data-index attribute for music files. // Add a data-index attribute for music files.
const indexAttr = file.file_type === 'music' ? ` data-index="${currentMusicFiles.length - 1}"` : ''; const indexAttr = file.file_type === 'music' ? ` data-index="${currentMusicFiles.length - 1}"` : '';
contentHTML += `<li class="file-item"> contentHTML += `<li class="file-item">
<a href="#" class="play-file"${indexAttr} data-url="${file.path}" data-file-type="${file.file_type}">${symbol} ${file.name}</a>`; <a href="#" class="play-file"${indexAttr} data-url="${file.path}" data-file-type="${file.file_type}">${symbol} ${file.name.replace('.mp3', '')}</a>`;
if (file.has_transcript) { if (file.has_transcript) {
contentHTML += `<a href="#" class="show-transcript" data-url="${file.transcript_url}" title="Show Transcript">&#128196;</a>`; contentHTML += `<a href="#" class="show-transcript" data-url="${file.transcript_url}" title="Show Transcript">&#128196;</a>`;
} }
@ -187,7 +187,7 @@ document.querySelectorAll('.play-file').forEach(link => {
const pathParts = relUrl.split('/'); const pathParts = relUrl.split('/');
const fileName = pathParts.pop(); const fileName = pathParts.pop();
const pathStr = pathParts.join('/'); const pathStr = pathParts.join('/');
nowPlayingInfo.innerHTML = pathStr + '<br><span style="font-size: larger; font-weight: bold;">' + fileName + '</span>'; nowPlayingInfo.innerHTML = pathStr.replace(/\//g, ' > ') + '<br><span style="font-size: larger; font-weight: bold;">' + fileName.replace('.mp3', '') + '</span>';
preload_audio(); preload_audio();
} }
} catch (error) { } catch (error) {

View File

@ -17,9 +17,9 @@ body {
min-height: 100%; min-height: 100%;
} }
.container { .container {
width: 90%;
max-width: 900px; max-width: 900px;
margin: 0 auto; margin: 0 auto;
padding: 10px;
padding-bottom: 200px; padding-bottom: 200px;
} }
@ -69,7 +69,7 @@ li {
} }
.directories-grid .directory-item { .directories-grid .directory-item {
background-color: #fff; background-color: #fff;
padding: 15px; padding: 15px 10px;
border-radius: 5px; border-radius: 5px;
text-align: center; text-align: center;
box-shadow: 0 1px 3px rgba(0,0,0,0.1); box-shadow: 0 1px 3px rgba(0,0,0,0.1);

View File

@ -2,6 +2,12 @@
<html> <html>
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<meta property="og:title" content="Gottesdienste Speyer und Schwegenheim" />
<meta property="og:description" content="... uns aber, die wir gerettet werden, ist es eine Gotteskraft." />
<meta property="og:image" content="https://app.bethaus-speyer.de/static/icons/logo-200x200.png" />
<meta property="og:url" content="https://app.bethaus-speyer.de" />
<title>Gottesdienste</title> <title>Gottesdienste</title>
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" href="/static/icons/logo-192x192.png" type="image/png" sizes="192x192"> <link rel="icon" href="/static/icons/logo-192x192.png" type="image/png" sizes="192x192">
@ -23,11 +29,35 @@
<link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}"> <link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}">
<link rel="stylesheet" href="{{ url_for('static', filename='gallery.css') }}"> <link rel="stylesheet" href="{{ url_for('static', filename='gallery.css') }}">
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
<style>
/* Header styles */
.site-header {
display: flex;
align-items: center;
padding: 10px 20px;
background-color: #9bbbff; /* Using the theme color */
color: #000000;
}
.site-header img.logo {
height: 50px;
margin-right: 15px;
}
.site-header h1 {
font-size: 1.5em;
margin: 0;
}
.container {
padding: 20px;
}
</style>
</head> </head>
<body> <body>
<header class="site-header">
<img src="https://app.bethaus-speyer.de/static/icons/logo-300x300.png" alt="Logo" class="logo">
<h1>Gottesdienste Speyer und Schwegenheim</h1>
</header>
<div class="wrapper"> <div class="wrapper">
<div class="container"> <div class="container">
<h2>Gottesdienste Speyer und Schwegenheim</h2>
<div id="breadcrumbs" class="breadcrumb"></div> <div id="breadcrumbs" class="breadcrumb"></div>
<div id="content"></div> <div id="content"></div>
</div> </div>

View File

@ -1,16 +1,48 @@
<!doctype html> <!doctype html>
<html> <html>
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<title>Keine Berechtigung</title>
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta property="og:title" content="Gottesdienste Speyer und Schwegenheim" />
<link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}"> <meta property="og:description" content="... uns aber, die wir gerettet werden, ist es eine Gotteskraft." />
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script> <meta property="og:image" content="https://app.bethaus-speyer.de/static/icons/logo-300x300.png" />
<meta property="og:url" content="https://app.bethaus-speyer.de" />
<meta property="og:type" content="website" />
<title>Gottesdienste Speyer und Schwegenheim</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}">
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
<style>
/* Header styles */
.site-header {
display: flex;
align-items: center;
padding: 10px 20px;
background-color: #9bbbff; /* Using the theme color */
color: #000000;
}
.site-header img.logo {
height: 50px;
margin-right: 15px;
}
.site-header h1 {
font-size: 1.5em;
margin: 0;
}
.container {
padding: 20px;
}
</style>
</head> </head>
<body> <body>
<header class="site-header">
<img src="https://app.bethaus-speyer.de/static/icons/logo-300x300.png" alt="Logo" class="logo">
<h1>Gottesdienste Speyer und Schwegenheim</h1>
</header>
<div class="container"> <div class="container">
<h2>Keine Berechtigung</h2> <div id="content">Bitte den Link aus der Telegram-Gruppe erneut anklicken.</div>
<div id="content">Bitte mit Link aus Telegram-Gruppe erneut anklicken.</div>
</div> </div>
</body> </body>
</html> </html>

47
templates/index.html Normal file
View File

@ -0,0 +1,47 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta property="og:title" content="Gottesdienste Speyer und Schwegenheim" />
<meta property="og:description" content="... uns aber, die wir gerettet werden, ist es eine Gotteskraft." />
<meta property="og:image" content="https://app.bethaus-speyer.de/static/icons/logo-300x300.png" />
<meta property="og:url" content="https://app.bethaus-speyer.de" />
<meta property="og:type" content="website" />
<title>Gottesdienste Speyer und Schwegenheim</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}">
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
<style>
.site-header {
display: flex;
align-items: center;
padding: 10px 20px;
background-color: #9bbbff;
color: #000000;
}
.site-header img.logo {
height: 50px;
margin-right: 15px;
}
.site-header h1 {
font-size: 1.5em;
margin: 0;
}
.container {
padding: 20px;
}
</style>
</head>
<body>
<header class="site-header">
<img src="https://app.bethaus-speyer.de/static/icons/logo-300x300.png" alt="Logo" class="logo">
<h1>Gottesdienste Speyer und Schwegenheim</h1>
</header>
<div class="container">
<div id="content">Bitte den Link aus der Telegram-Gruppe erneut anklicken.</div>
</div>
</body>
</html>