From c0cfd2fd9b82704eb8b42c7a7307ea4087c28159 Mon Sep 17 00:00:00 2001 From: kai Date: Tue, 25 Aug 2026 12:18:53 +0200 Subject: [PATCH] admin berreich, user anmeldung --- app/main.py | 141 +++++++++++++++++++++++++++--- app/static/style.css | 10 +-- app/templates/admin.html | 69 +++++++++++++++ app/templates/concert_detail.html | 19 ++-- app/templates/index.html | 45 ++++++++-- 5 files changed, 252 insertions(+), 32 deletions(-) create mode 100644 app/templates/admin.html diff --git a/app/main.py b/app/main.py index 6b08365..e27463a 100644 --- a/app/main.py +++ b/app/main.py @@ -159,7 +159,12 @@ app = FastAPI(title="Pingu Concerts", lifespan=lifespan) @app.middleware("http") async def require_login(request: Request, call_next): - if request.url.path == "/login" or request.url.path.startswith("/static/"): + if ( + request.url.path == "/login" + or request.url.path == "/register" + or request.url.path.startswith("/register/") + or request.url.path.startswith("/static/") + ): return await call_next(request) if get_current_user(request): @@ -544,15 +549,55 @@ def resolve_venue( return selected_venue_id -@app.get("/admin/invites") -def create_invite(request: Request): +def require_admin(request: Request): user = get_current_user(request) if not user or not user["is_admin"]: - return HTMLResponse( - "

Nicht erlaubt

", - status_code=403, - ) + return None + + return user + + +@app.get("/admin", response_class=HTMLResponse) +def admin_page(request: Request): + user = require_admin(request) + + if not user: + return HTMLResponse("

Nicht erlaubt

", status_code=403) + + with get_db_connection() as connection: + with connection.cursor() as cursor: + cursor.execute( + """ + SELECT id, username, email, display_name, is_admin, created_at + FROM users + ORDER BY is_admin DESC, username ASC + """ + ) + rows = cursor.fetchall() + + users = [ + { + "id": row[0], + "username": row[1], + "email": row[2], + "display_name": row[3] or row[1], + "is_admin": bool(row[4]), + "created_at": row[5].strftime("%d.%m.%Y"), + } + for row in rows + ] + + template = templates.get_template("admin.html") + return template.render(user=user, users=users, invite_url=None) + + +@app.post("/admin/invites", response_class=HTMLResponse) +def create_invite(request: Request): + user = require_admin(request) + + if not user: + return HTMLResponse("

Nicht erlaubt

", status_code=403) token = secrets.token_urlsafe(32) @@ -576,10 +621,84 @@ def create_invite(request: Request): connection.commit() - return { - "invite_id": invite_id, - "invite_url": f"/register/{token}" - } + with get_db_connection() as connection: + with connection.cursor() as cursor: + cursor.execute( + """ + SELECT id, username, email, display_name, is_admin, created_at + FROM users + ORDER BY is_admin DESC, username ASC + """ + ) + rows = cursor.fetchall() + + users = [ + { + "id": row[0], + "username": row[1], + "email": row[2], + "display_name": row[3] or row[1], + "is_admin": bool(row[4]), + "created_at": row[5].strftime("%d.%m.%Y"), + } + for row in rows + ] + template = templates.get_template("admin.html") + return template.render( + user=user, + users=users, + invite_url=f"{str(request.base_url).rstrip('/')}/register/{token}", + invite_id=invite_id, + ) + + +@app.post("/admin/users/{user_id}") +def update_user_role( + request: Request, + user_id: int, + role: str = Form(...), +): + user = require_admin(request) + + if not user: + return HTMLResponse("

Nicht erlaubt

", status_code=403) + if role not in {"admin", "user"}: + return HTMLResponse("

Ungültige Rolle

", status_code=400) + if user_id == user["id"] and role != "admin": + return HTMLResponse( + "

Die eigenen Adminrechte können nicht entfernt werden.

", + status_code=400, + ) + + with get_db_connection() as connection: + with connection.cursor() as cursor: + cursor.execute( + "UPDATE users SET is_admin = %s WHERE id = %s", + (role == "admin", user_id), + ) + connection.commit() + + return RedirectResponse("/admin", status_code=303) + + +@app.post("/admin/users/{user_id}/delete") +def delete_user(request: Request, user_id: int): + user = require_admin(request) + + if not user: + return HTMLResponse("

Nicht erlaubt

", status_code=403) + if user_id == user["id"]: + return HTMLResponse( + "

Der eigene Account kann nicht gelöscht werden.

", + status_code=400, + ) + + with get_db_connection() as connection: + with connection.cursor() as cursor: + cursor.execute("DELETE FROM users WHERE id = %s", (user_id,)) + connection.commit() + + return RedirectResponse("/admin", status_code=303) @app.post("/register") def register_user( diff --git a/app/static/style.css b/app/static/style.css index 4c8d4ed..8ec84ad 100644 --- a/app/static/style.css +++ b/app/static/style.css @@ -31,7 +31,6 @@ a { } .edit-button { - float: right; display: inline-block; padding: 10px 14px; background: #238636; @@ -41,6 +40,10 @@ a { font-weight: bold; } +.detail-actions { + margin-top: 30px; +} + .edit-button:hover { background: #2ea043; } @@ -165,11 +168,6 @@ a { padding: 22px; } - .edit-button { - float: none; - margin: 0 0 20px; - } - .flyer-container { padding: 12px; } diff --git a/app/templates/admin.html b/app/templates/admin.html new file mode 100644 index 0000000..8e9f9c6 --- /dev/null +++ b/app/templates/admin.html @@ -0,0 +1,69 @@ + + + + + + Verwaltung · Pingu Concerts + + + + +
+
+ + {{ user.display_name }} +
+
+
+
+

⚙️ Verwaltung

+

Einladungen und Benutzerkonten verwalten.

+
+
+

Einladung erstellen

+

Der Link kann einmalig verwendet werden, um einen neuen Account anzulegen.

+
+ +
+ {% if invite_url %} + + {% endif %} +
+
+

Benutzer

+
+ {% for managed_user in users %} +
+
+ {{ managed_user.display_name }} +
@{{ managed_user.username }} · {{ managed_user.email }} · seit {{ managed_user.created_at }}
+
+
+ + +
+ {% if managed_user.id != user.id %} +
+ +
+ {% endif %} +
+ {% endfor %} +
+
+
+ + diff --git a/app/templates/concert_detail.html b/app/templates/concert_detail.html index 51394cb..739224a 100644 --- a/app/templates/concert_detail.html +++ b/app/templates/concert_detail.html @@ -27,15 +27,6 @@ ← Zurück zum Kalender - {% if can_edit %} - - - ✏️ Konzert bearbeiten - - - {% endif %} - -
{% if concert.flyer_path %} @@ -180,6 +171,16 @@ {% endif %} + {% if can_edit %} + + + + {% endif %} +
diff --git a/app/templates/index.html b/app/templates/index.html index cb99e5d..3948b71 100644 --- a/app/templates/index.html +++ b/app/templates/index.html @@ -52,6 +52,27 @@ background: #2ea043; } + .header-actions { + display: flex; + gap: 10px; + align-items: center; + } + + .admin-button { + display: inline-block; + padding: 12px 18px; + border: 1px solid #30363d; + border-radius: 10px; + color: #b8c1cc; + text-decoration: none; + white-space: nowrap; + } + + .admin-button:hover { + color: #ffffff; + border-color: #58a6ff; + } + .concert-list { display: grid; gap: 15px; @@ -144,12 +165,24 @@ - - + Konzert hinzufügen - +
+ + {% if user.is_admin %} + + + ⚙️ Verwaltung + + + {% endif %} + + + + Konzert hinzufügen + + +