Implement Prepaid user functionality: add login, drink handling, and money management features; update templates for user interaction

This commit is contained in:
2025-05-17 15:45:58 +02:00
parent 1fb1b27480
commit 4ee1d99224
7 changed files with 334 additions and 12 deletions

View File

@@ -27,7 +27,7 @@ Functions:
logout(request): Logs the user out and redirects to the identity provider's logout endpoint.
"""
import os
from fastapi import APIRouter
from fastapi import APIRouter, Form, HTTPException
from fastapi.responses import RedirectResponse
from authlib.integrations.starlette_client import OAuth
from starlette.requests import Request
@@ -126,8 +126,23 @@ async def logout(request: Request):
"""
request.session.pop(SESSION_KEY, None)
request.session.pop("user_db", None)
request.session.pop("user_db_id", None)
logout_url = oauth.auth0.server_metadata.get("end_session_endpoint")
if not logout_url:
logout_url = os.getenv("OIDC_LOGOUT_URL")
return RedirectResponse(url=logout_url, status_code=303)
@router.post("/login/prepaid")
def login_prepaid(request: Request, prepaid_user_key: str = Form(...)):
t = text("SELECT id, username FROM users_prepaid WHERE user_key = :prepaid_user_key")
with engine.connect() as conn:
result = conn.execute(t, {"prepaid_user_key": prepaid_user_key}).fetchone()
if result:
user_db_id = result[0]
username = result[1]
else:
raise HTTPException(status_code=404, detail="User not found")
request.session["user_db_id"] = user_db_id
request.session[SESSION_KEY] = {"name": username, "preferred_username": username, "groups": [], "prepaid": True}
return RedirectResponse(url="/", status_code=303)

View File

@@ -24,6 +24,8 @@ DATABASE_URL = "sqlite:///./test.db"
engine = create_engine(DATABASE_URL, connect_args={"check_same_thread": False})
DRINK_COST = 100 # cent
with engine.connect() as conn:
# Create a new table for postpaid users
conn.execute(text("""
@@ -180,7 +182,7 @@ def drink_postpaid_user(user_id: int):
prev_money = get_postpaid_user(user_id)["money"]
t = text("UPDATE users_postpaid SET money = :money, last_drink = CURRENT_TIMESTAMP WHERE id = :id")
with engine.connect() as connection:
result = connection.execute(t, {"id": user_id, "money": prev_money - 100})
result = connection.execute(t, {"id": user_id, "money": prev_money - DRINK_COST})
if result.rowcount == 0:
raise HTTPException(status_code=404, detail="User not found")
connection.commit()
@@ -271,3 +273,49 @@ def create_prepaid_user(prepaid_username: str, postpaid_user_id: int, start_mone
connection.commit()
return result.lastrowid
def drink_prepaid_user(user_db_id: int):
user_dict = get_prepaid_user(user_db_id)
if not user_dict["activated"]:
raise HTTPException(status_code=403, detail="User not activated")
prev_money = user_dict["money"]
if prev_money < DRINK_COST:
raise HTTPException(status_code=403, detail="Not enough money")
t = text("UPDATE users_prepaid SET money = :money, last_drink = CURRENT_TIMESTAMP WHERE id = :id")
with engine.connect() as connection:
result = connection.execute(t, {"id": user_db_id, "money": prev_money - DRINK_COST})
if result.rowcount == 0:
raise HTTPException(status_code=404, detail="User not found")
connection.commit()
with engine.connect() as connection:
t = text("INSERT INTO drinks (prepaid_user_id, timestamp) VALUES (:prepaid_user_id, CURRENT_TIMESTAMP)")
result = connection.execute(t, {"prepaid_user_id": user_db_id})
if result.rowcount == 0:
raise HTTPException(status_code=500, detail="Failed to create drink entry")
connection.commit()
return result.rowcount
def toggle_activate_prepaid_user(user_id: int):
prev_activated = get_prepaid_user(user_id)["activated"]
t = text("UPDATE users_prepaid SET activated = :activated WHERE id = :id")
with engine.connect() as connection:
result = connection.execute(t, {"id": user_id, "activated": not prev_activated})
if result.rowcount == 0:
raise HTTPException(status_code=404, detail="User not found")
connection.commit()
return result.rowcount
def set_prepaid_user_money(user_id: int, money: int, postpaid_user_id: int):
t1 = text("UPDATE users_prepaid SET money = :money WHERE id = :id")
t2 = text("UPDATE users_prepaid SET postpaid_user_id = :postpaid_user_id WHERE id = :id")
with engine.connect() as connection:
result = connection.execute(t1, {"id": user_id, "money": money})
if result.rowcount == 0:
raise HTTPException(status_code=404, detail="User not found")
result = connection.execute(t2, {"id": user_id, "postpaid_user_id": postpaid_user_id})
if result.rowcount == 0:
raise HTTPException(status_code=404, detail="User not found")
connection.commit()
return result.rowcount

70
main.py
View File

@@ -1,4 +1,4 @@
from fastapi import FastAPI, Request, Depends, Form, HTTPException
from fastapi import FastAPI, Request, Form, HTTPException
from fastapi.responses import RedirectResponse, HTMLResponse
from fastapi.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates
@@ -16,8 +16,9 @@ from db.models import toggle_activate_postpaid_user
from db.models import get_prepaid_user
from db.models import get_prepaid_user_by_username
from db.models import create_prepaid_user
from auth.session import get_current_user
from db.models import drink_prepaid_user
from db.models import toggle_activate_prepaid_user
from db.models import set_prepaid_user_money
from auth import oidc
@@ -44,6 +45,8 @@ def home(request: Request):
user_authentik = request.session.get("user_authentik")
if not user_db_id or not user_authentik:
raise HTTPException(status_code=404, detail="User nicht gefunden")
print(f"Current user: {user_authentik}")
print(f"Current user db id: {user_db_id}")
users = None
db_users_prepaid = None
if ADMIN_GROUP in user_authentik["groups"]:
@@ -58,15 +61,22 @@ def home(request: Request):
users.append(user_db)
t = text("SELECT id FROM users_prepaid")
result = conn.execute(t).fetchall()
print(f"Result: {result}")
if result:
db_users_prepaid = []
for row in result:
prepaid_user = get_prepaid_user(row[0])
if prepaid_user:
db_users_prepaid.append(prepaid_user)
db_user = get_postpaid_user(user_db_id)
print("db_users_prepaid", db_users_prepaid)
try:
if user_authentik["prepaid"]:
print("Prepaid user")
db_user = get_prepaid_user(user_db_id)
else:
print("Postpaid user")
db_user = get_postpaid_user(user_db_id)
except KeyError:
print("Postpaid user")
db_user = get_postpaid_user(user_db_id)
return templates.TemplateResponse("index.html", {
"request": request,
"user": user_authentik,
@@ -211,6 +221,54 @@ def add_prepaid_user(request: Request, username: str = Form(...), start_money: f
return RedirectResponse(url="/", status_code=303)
@app.post("/drink_prepaid")
def drink_prepaid(request: Request):
user_db_id = request.session.get("user_db_id")
if not user_db_id:
raise HTTPException(status_code=404, detail="User nicht gefunden")
user_authentik = request.session.get("user_authentik")
if not user_authentik:
raise HTTPException(status_code=404, detail="User nicht gefunden")
if not user_authentik["prepaid"]:
raise HTTPException(status_code=403, detail="Nicht erlaubt")
drink_prepaid_user(user_db_id)
return RedirectResponse(url="/", status_code=303)
@app.post("/toggle_activated_user_prepaid")
def toggle_activated_user_prepaid(request: Request, username: str = Form(...)):
user_auth = request.session.get("user_authentik")
if not user_auth or ADMIN_GROUP not in user_auth["groups"]:
raise HTTPException(status_code=403, detail="Nicht erlaubt")
user_db_id = get_prepaid_user_by_username(username)["id"]
if not user_db_id:
raise HTTPException(status_code=404, detail="User nicht gefunden")
toggle_activate_prepaid_user(user_db_id)
return RedirectResponse(url="/", status_code=303)
@app.post("/add_money_prepaid_user")
def add_money_prepaid_user(request: Request, username: str = Form(...), money: float = Form(...)):
curr_user_auth = request.session.get("user_authentik")
if not curr_user_auth or ADMIN_GROUP not in curr_user_auth["groups"]:
raise HTTPException(status_code=403, detail="Nicht erlaubt")
curr_user_db_id = request.session.get("user_db_id")
if not curr_user_db_id:
raise HTTPException(status_code=404, detail="Logged In User not found")
prepaid_user_dict = get_prepaid_user_by_username(username)
prepaid_user_db_id = prepaid_user_dict["id"]
if not prepaid_user_db_id:
raise HTTPException(status_code=404, detail="Prepaid User not found")
curr_user_money = get_postpaid_user(curr_user_db_id)["money"]
prepaid_user_money = prepaid_user_dict["money"]
set_postpaid_user_money(curr_user_db_id, curr_user_money - money*100)
set_prepaid_user_money(prepaid_user_db_id, prepaid_user_money + money*100, curr_user_db_id)
return RedirectResponse(url="/", status_code=303)
if __name__ == "__main__":
uvicorn.run(app, host="0.0.0.0", port=8000)

View File

@@ -91,6 +91,76 @@
Add User
</button>
</form>
<p>Füge bestehendem Prepaid-User Geld hinzu:</p>
<form
method="post"
action="/add_money_prepaid_user"
style="
display: flex;
gap: 1em;
align-items: center;
margin-bottom: 1em;
background: var(--hellgrau);
padding: 1em;
border-radius: 8px;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.05);
max-width: 600px;
"
>
<label
for="addmoney-username"
style="margin: 0 0.5em 0 0; font-weight: bold"
>Username:</label
>
<select
id="addmoney-username"
name="username"
required
style="
padding: 0.5em;
border: 1px solid #ccc;
border-radius: 4px;
"
>
{% for db_user in db_users_prepaid %}
<option value="{{ db_user.username }}">
{{ db_user.username }}
</option>
{% endfor %}
</select>
<label
for="addmoney-money"
style="margin: 0 0.5em 0 0; font-weight: bold"
>Amount (€):</label
>
<input
id="addmoney-money"
type="number"
name="money"
placeholder="Money"
step="0.01"
required
style="
padding: 0.5em;
border: 1px solid #ccc;
border-radius: 4px;
width: 100px;
"
/>
<button
type="submit"
style="
padding: 0.5em 1em;
background: rgb(0, 97, 143);
color: #fff;
border: none;
border-radius: 4px;
cursor: pointer;
"
>
Add Money
</button>
</form>
{% endif %}
{% if 'Fachschaft Admins' in user.groups %}
<h2>Admin Interface</h2>
@@ -393,6 +463,57 @@
{% endif %}
</tbody>
</table>
<p>(De-)Activate User</p>
<form
method="post"
action="/toggle_activated_user_prepaid"
style="
display: flex;
gap: 1em;
align-items: center;
margin-bottom: 1em;
background: var(--hellgrau);
padding: 1em;
border-radius: 8px;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.05);
max-width: 600px;
"
>
<label
for="activate-username"
style="margin: 0 0.5em 0 0; font-weight: bold"
>Username:</label
>
<select
id="activate-username"
name="username"
required
style="
padding: 0.5em;
border: 1px solid #ccc;
border-radius: 4px;
"
>
{% for db_user in db_users_prepaid %}
<option value="{{ db_user.username }}">
{{ db_user.username }}
</option>
{% endfor %}
</select>
<button
type="submit"
style="
padding: 0.5em 1em;
background: rgb(0, 97, 143);
color: #fff;
border: none;
border-radius: 4px;
cursor: pointer;
"
>
Toggle Activation
</button>
</form>
{% endif %} {% endif %}
</main>
</body>

View File

@@ -72,4 +72,39 @@ content %}
</button>
</form>
</div>
{% endif %} {% endif %} {% endblock %}
{% endif %} {% endif %}
{% if user.prepaid %}
<div style="display: flex; justify-content: center; text-align: center">
<form method="post" action="/drink_prepaid">
<button
type="submit"
style="
background-color: rgb(165, 171, 82);
color: rgb(255, 255, 255);
font-size: 1.5em;
padding: 0.75em 2em;
border: none;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(40, 167, 69, 0.15);
cursor: pointer;
transition: background 0.2s;
"
>
Getränk abziehen
</button>
</form>
</div>
<p>Deine aktuelle Ansprechperson ist:</p>
<div
style="
text-align: center;
font-size: 2em;
color: var(--goetheblau);
margin: 0.5em 0;
"
>
ID {{ db_user.postpaid_user_id }}
</div>
{% endif %}
{% endblock %}

View File

@@ -1,7 +1,6 @@
{% extends "base.html" %} {% block title %}Login{% endblock %} {% block content
%}
<h2>Login</h2>
<h2>Postpaid-Liste:</h2>
<!-- SSO-Button -->
<div
style="
@@ -30,6 +29,52 @@
</button>
</form>
</div>
<h2>Prepaid-Liste:</h2>
<div
style="
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
"
>
<form method="post" action="/login/prepaid">
<label
for="prepaid_user_key"
style="margin: 0 0.5em 0 0; font-weight: bold"
>Prepaid Key:</label>
<input
id="prepaid-user-key"
type="text"
name="prepaid_user_key"
placeholder="Prepaid Key"
style="
padding: 12px;
border: 1px solid #ccc;
border-radius: 6px;
font-size: 1em;
width: 300px;
margin-bottom: 1em;
"
/>
<button
type="submit"
style="
padding: 12px 28px;
background-color: #1976d2;
color: #fff;
border: none;
border-radius: 6px;
font-size: 1.1em;
cursor: pointer;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.08);
transition: background 0.2s;
"
>
📜 Prepaid-Liste
</button>
</form>
</div>
<!-- WebAuthn-Button -->
<!-- <form method="get" action="/login/webauthn">

BIN
test.db

Binary file not shown.