Implement Prepaid user functionality: add login, drink handling, and money management features; update templates for user interaction
This commit is contained in:
19
auth/oidc.py
19
auth/oidc.py
@@ -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)
|
||||
|
||||
50
db/models.py
50
db/models.py
@@ -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
70
main.py
@@ -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)
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 %}
|
||||
|
||||
@@ -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">
|
||||
|
||||
Reference in New Issue
Block a user