style: Remove tutorial upload functionality and associated template

This commit is contained in:
2026-05-15 21:46:02 +02:00
parent 442c7d24d2
commit b7ed31b117
3 changed files with 29 additions and 444 deletions
-137
View File
@@ -2466,33 +2466,6 @@ def my_instance_management():
@login_required
def my_tutorials():
"""Display tutorial videos for the logged-in user."""
client = None
origin = request.url_root.rstrip("/")
try:
client, col = _get_collection("tutorials")
tutorials = list(
col.find(
{"published": True},
{
"_id": 1,
"title": 1,
"description": 1,
"video_url": 1,
"thumbnail_url": 1,
"duration": 1,
"category": 1,
"created_at": 1,
},
).sort("created_at", -1)
)
except PyMongoError:
flash("Tutorials konnten nicht geladen werden.", "error")
tutorials = []
finally:
if client:
client.close()
if not tutorials:
tutorials = [
{
"_id": "1",
@@ -2534,116 +2507,6 @@ def my_tutorials():
return render_template("my_tutorials.html", tutorials=tutorials)
@app.route('/append_video', methods=['POST'])
@admin_required
def append_video():
title = _sanitize_text(request.form.get("title") or "", 200)
description = _sanitize_text(request.form.get("description") or "", 500)
video_url = _sanitize_text(request.form.get("video_url") or "", 300)
thumbnail_url = _sanitize_text(request.form.get("thumbnail_url") or "", 300)
duration = _sanitize_text(request.form.get("duration") or "", 20)
category = _sanitize_text(request.form.get("category") or "", 50)
if not title:
flash("Bitte einen Titel für das Tutorial eingeben.", "error")
return redirect(url_for("admin_blog"))
if not video_url:
flash("Bitte eine Video-URL eingeben.", "error")
return redirect(url_for("admin_blog"))
normalized_video_url = _normalize_tutorial_video_url(video_url, request.url_root.rstrip("/"))
if not normalized_video_url:
flash(
"Ungültige Video-URL. Erlaubt sind YouTube-Links im Format watch?v=..., youtu.be/..., shorts/... oder embed/...",
"error",
)
return redirect(url_for("admin_blog"))
client = None
try:
client, col = _get_collection("tutorials")
col.insert_one(
{
"title": title,
"description": description,
"video_url": normalized_video_url,
"thumbnail_url": thumbnail_url,
"duration": duration,
"category": category,
"published": True,
"created_at": datetime.utcnow().isoformat(timespec="seconds") + "Z",
}
)
flash("Tutorial-Video hinzugefügt.", "success")
except PyMongoError:
flash("Tutorial-Video konnte nicht hinzugefügt werden.", "error")
finally:
if client:
client.close()
return redirect(url_for("admin_blog"))
@app.route('/admin/tutorials/upload', methods=['GET', 'POST'])
@admin_required
def admin_tutorial_upload():
if request.method == 'POST':
title = _sanitize_text(request.form.get("title") or "", 200)
description = _sanitize_text(request.form.get("description") or "", 500)
duration = _sanitize_text(request.form.get("duration") or "", 20)
category = _sanitize_text(request.form.get("category") or "", 50)
thumbnail_url = _sanitize_text(request.form.get("thumbnail_url") or "", 300)
uploaded_file = request.files.get("video_file")
if not title:
flash("Bitte einen Titel fuer das Tutorial eingeben.", "error")
return redirect(url_for("admin_tutorial_upload"))
saved_video_path = _save_tutorial_video_file(uploaded_file, title)
if not saved_video_path:
flash("Bitte eine gueltige Videodatei hochladen (.mp4, .webm, .ogv, .ogg, .m4v).", "error")
return redirect(url_for("admin_tutorial_upload"))
client = None
try:
client, col = _get_collection("tutorials")
col.insert_one(
{
"title": title,
"description": description,
"video_url": saved_video_path,
"thumbnail_url": thumbnail_url,
"duration": duration,
"category": category,
"published": True,
"created_at": datetime.utcnow().isoformat(timespec="seconds") + "Z",
"source": "upload",
}
)
flash("Tutorial-Video wurde hochgeladen.", "success")
except PyMongoError:
flash("Tutorial-Video konnte nicht gespeichert werden.", "error")
finally:
if client:
client.close()
return redirect(url_for("admin_tutorial_upload"))
tutorials = []
client = None
try:
client, col = _get_collection("tutorials")
tutorials = list(col.find({"source": "upload"}, {"_id": 1, "title": 1, "description": 1, "video_url": 1, "thumbnail_url": 1, "duration": 1, "category": 1, "created_at": 1}).sort("created_at", -1))
except PyMongoError:
flash("Upload-Uebersicht konnte nicht geladen werden.", "error")
finally:
if client:
client.close()
return render_template("admin_tutorial_upload.html", tutorials=tutorials)
@app.route('/chat', methods=['GET', 'POST'])
@login_required
@@ -1,276 +0,0 @@
{% extends "base.html" %}
{% block title %}Admin | Tutorial-Upload{% endblock %}
{% block head %}
{{ super() }}
<style>
.upload-shell {
display: grid;
grid-template-columns: 0.9fr 1.1fr;
gap: 1.2rem;
align-items: start;
}
.panel {
background: #ffffff;
border: 1px solid #d7e1ea;
border-radius: 16px;
box-shadow: 0 14px 34px rgba(17, 43, 60, 0.06);
}
.upload-form {
padding: 1.2rem;
position: sticky;
top: 100px;
}
.upload-form h1,
.list-panel h2 {
margin: 0;
color: #0f354d;
font-family: "Merriweather", Georgia, serif;
}
.upload-form p,
.list-panel p {
color: #587286;
margin-top: 0.35rem;
}
.field {
margin-top: 0.9rem;
}
.field label {
display: block;
margin-bottom: 0.35rem;
font-size: 0.9rem;
font-weight: 700;
color: #1f4b65;
}
.field input,
.field textarea {
width: 100%;
border: 1px solid #c8d7e3;
border-radius: 10px;
padding: 0.7rem 0.8rem;
font: inherit;
color: #17384f;
background: #fff;
}
.field textarea {
min-height: 120px;
resize: vertical;
}
.field input:focus,
.field textarea:focus {
outline: none;
border-color: #2f79a7;
box-shadow: 0 0 0 3px rgba(58, 133, 180, 0.18);
}
.hint {
margin-top: 0.4rem;
font-size: 0.82rem;
color: #527085;
line-height: 1.5;
}
.submit-btn {
margin-top: 1rem;
width: 100%;
border: none;
border-radius: 999px;
padding: 0.8rem 1rem;
font-size: 0.96rem;
font-weight: 700;
color: #fff;
background: linear-gradient(120deg, #0a5f8f 0%, #08486c 100%);
cursor: pointer;
}
.preview-box {
margin-top: 1rem;
border-radius: 14px;
overflow: hidden;
border: 1px solid #d7e1ea;
background: linear-gradient(135deg, #f4f8fb 0%, #eaf1f6 100%);
}
.preview-aspect {
position: relative;
padding-bottom: 56.25%;
height: 0;
}
.preview-aspect video,
.preview-aspect img,
.preview-aspect iframe {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
object-fit: cover;
border: 0;
}
.preview-empty {
padding: 1rem;
text-align: center;
color: #5c7485;
font-size: 0.9rem;
}
.upload-list {
padding: 1.2rem;
}
.upload-list .entry {
border: 1px solid #d7e1ea;
border-radius: 14px;
padding: 1rem;
margin-top: 0.85rem;
background: #fff;
}
.upload-list .entry h3 {
margin: 0 0 0.35rem;
font-size: 1.05rem;
color: #113d59;
}
.meta {
font-size: 0.86rem;
color: #637b8c;
margin-top: 0.35rem;
}
.video-link {
display: inline-block;
margin-top: 0.55rem;
color: #0a4c74;
font-weight: 700;
}
.badge {
display: inline-flex;
align-items: center;
border-radius: 999px;
padding: 0.24rem 0.55rem;
background: #e7f1f5;
color: #1f5c73;
font-size: 0.75rem;
font-weight: 700;
margin-left: 0.4rem;
}
@media (max-width: 980px) {
.upload-shell {
grid-template-columns: 1fr;
}
.upload-form {
position: static;
}
}
</style>
{% endblock %}
{% block content %}
<section class="panel upload-form">
<h1>Tutorial-Upload</h1>
<p>Videodatei hochladen und sofort als Tutorial im Nutzerbereich bereitstellen.</p>
<form method="POST" action="{{ url_for('admin_tutorial_upload') }}" enctype="multipart/form-data">
<div class="field">
<label for="title">Titel</label>
<input id="title" name="title" type="text" placeholder="z. B. Erste Schritte mit Invario" required>
</div>
<div class="field">
<label for="video_file">Videodatei</label>
<input id="video_file" name="video_file" type="file" accept="video/mp4,video/webm,video/ogg,.mp4,.webm,.ogv,.ogg,.m4v" required>
<div class="hint">Erlaubte Formate: .mp4, .webm, .ogv, .ogg, .m4v</div>
</div>
<div class="field">
<label for="description">Beschreibung</label>
<textarea id="description" name="description" placeholder="Kurze Beschreibung des Tutorials"></textarea>
</div>
<div class="field">
<label for="thumbnail_url">Thumbnail-URL (optional)</label>
<input id="thumbnail_url" name="thumbnail_url" type="url" placeholder="https://...">
</div>
<div class="field">
<label for="duration">Dauer (optional)</label>
<input id="duration" name="duration" type="text" placeholder="z. B. 12:34">
</div>
<div class="field">
<label for="category">Kategorie (optional)</label>
<input id="category" name="category" type="text" placeholder="z. B. Anfänger">
</div>
<button type="submit" class="submit-btn">Video hochladen</button>
</form>
<div class="preview-box" aria-live="polite">
<div id="preview-empty" class="preview-empty">Vorschau erscheint nach Auswahl einer Videodatei.</div>
<div class="preview-aspect" id="preview-wrap" hidden>
<video id="preview-video" controls playsinline></video>
</div>
</div>
</section>
<section class="panel upload-list">
<h2>Aktuelle Uploads</h2>
<p>Neu hochgeladene Tutorials erscheinen hier und im Nutzerbereich.</p>
{% if tutorials %}
{% for tutorial in tutorials %}
<article class="entry">
<h3>{{ tutorial.title }} <span class="badge">Upload</span></h3>
<div class="meta">
{{ tutorial.category or 'Allgemein' }} | {{ tutorial.duration or 'ohne Dauer' }} | {{ tutorial.created_at[:10] }}
</div>
<p>{{ tutorial.description }}</p>
<a class="video-link" href="{{ tutorial.video_url }}" target="_blank" rel="noopener">Video öffnen</a>
</article>
{% endfor %}
{% else %}
<article class="entry"><p>Noch keine hochgeladenen Tutorials vorhanden.</p></article>
{% endif %}
</section>
<script>
(function () {
const input = document.getElementById('video_file');
const previewWrap = document.getElementById('preview-wrap');
const previewEmpty = document.getElementById('preview-empty');
const previewVideo = document.getElementById('preview-video');
let currentObjectUrl = null;
input.addEventListener('change', function () {
const file = input.files && input.files[0];
if (currentObjectUrl) {
URL.revokeObjectURL(currentObjectUrl);
currentObjectUrl = null;
}
if (!file) {
previewWrap.hidden = true;
previewEmpty.hidden = false;
previewVideo.removeAttribute('src');
previewVideo.load();
return;
}
currentObjectUrl = URL.createObjectURL(file);
previewVideo.src = currentObjectUrl;
previewVideo.load();
previewEmpty.hidden = true;
previewWrap.hidden = false;
});
})();
</script>
{% endblock %}
-2
View File
@@ -523,7 +523,6 @@
{% if session.get('is_admin') %}
<a class="nav-user-link" href="{{ url_for('admin_invoices') }}">Rechnungsverwaltung</a>
<a class="nav-user-link" href="{{ url_for('admin_blog') }}">Blog verwalten</a>
<a class="nav-user-link" href="{{ url_for('admin_tutorial_upload') }}">Tutorial-Upload</a>
<a class="nav-user-link" href="{{ url_for('admin_chats') }}">Admin-Chat</a>
<a class="nav-user-link" href="{{ url_for('admin_tickets') }}">Support-Center</a>
<a class="nav-user-link" href="{{ url_for('admin_system_tools') }}">System-Tools</a>
@@ -551,7 +550,6 @@
<a href="{{ url_for('admin_tickets') }}">Support-Center</a>
<a href="{{ url_for('admin_system_tools') }}">System-Tools</a>
<a href="{{ url_for('admin_blog') }}">Blog verwalten</a>
<a href="{{ url_for('admin_tutorial_upload') }}">Tutorial-Upload</a>
<a href="{{ url_for('admin_instances') }}">Schul-Instanzen</a>
</div>
</details>