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

61
app.py
View File

@ -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):
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):
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/<string:size>.png')
@ -156,8 +174,8 @@ def generate_breadcrumbs(subpath):
@app.route('/api/path/<path:subpath>')
@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('/<path:path>')
@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__":

View File

@ -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 services 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

View File

@ -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 += `<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) {
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 fileName = pathParts.pop();
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();
}
} catch (error) {

View File

@ -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);

View File

@ -2,6 +2,12 @@
<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-200x200.png" />
<meta property="og:url" content="https://app.bethaus-speyer.de" />
<title>Gottesdienste</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<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='gallery.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>
<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="container">
<h2>Gottesdienste Speyer und Schwegenheim</h2>
<div id="breadcrumbs" class="breadcrumb"></div>
<div id="content"></div>
</div>

View File

@ -2,15 +2,47 @@
<html>
<head>
<meta charset="utf-8">
<title>Keine Berechtigung</title>
<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>
/* 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>
<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">
<h2>Keine Berechtigung</h2>
<div id="content">Bitte mit Link aus Telegram-Gruppe erneut anklicken.</div>
<div id="content">Bitte den Link aus der Telegram-Gruppe erneut anklicken.</div>
</div>
</body>
</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>