This commit is contained in:
2025-06-07 01:32:39 +02:00
parent f4c7b0b3cc
commit 0f7fb56b20
7 changed files with 173 additions and 8 deletions

View File

@@ -17,6 +17,7 @@ Functions:
Deducts 100 units from the specified postpaid user's balance and records a drink entry. Raises HTTPException if the user is not found or if the drink entry could not be created. Returns the number of rows affected by the drink entry insertion.
"""
import secrets
import datetime
from sqlalchemy import create_engine, text
from fastapi import HTTPException
@@ -444,3 +445,74 @@ def del_user_prepaid(user_id: int):
raise HTTPException(status_code=404, detail="User not found")
connection.commit()
return result.rowcount
def get_last_drink(user_id: int, user_is_postpaid: bool, max_since_seconds: int = 60):
if user_is_postpaid:
t = text("SELECT id, timestamp, drink_type FROM drinks WHERE postpaid_user_id = :user_id ORDER BY timestamp DESC LIMIT 1")
else:
t = text("SELECT id, timestamp, drink_type FROM drinks WHERE prepaid_user_id = :user_id ORDER BY timestamp DESC LIMIT 1")
with engine.connect() as connection:
result = connection.execute(t, {"user_id": user_id}).fetchone()
if not result:
return None
drink_id, timestamp, drink_type = result
if timestamp:
now = datetime.datetime.now(datetime.timezone.utc)
last_drink_time = datetime.datetime.fromisoformat(timestamp.replace("Z", "+00:00"))
# Ensure both are offset-aware
if last_drink_time.tzinfo is None:
last_drink_time = last_drink_time.replace(tzinfo=datetime.timezone.utc)
if (now - last_drink_time).total_seconds() > max_since_seconds:
return None
print(f"get_last_drink: user_id={user_id}, user_is_postpaid={user_is_postpaid}, drink_id={drink_id}, timestamp={timestamp}, drink_type={drink_type}")
return {"id": drink_id, "timestamp": timestamp, "drink_type": drink_type}
def revert_last_drink(user_id: int, user_is_postpaid: bool, drink_id: int, drink_cost: int = DRINK_COST):
if user_is_postpaid:
del_t = text("DELETE FROM drinks WHERE postpaid_user_id = :user_id AND id = :drink_id")
update_t = text("UPDATE users_postpaid SET money = money + :drink_cost WHERE id = :user_id")
money_t = text("SELECT money FROM users_postpaid WHERE id = :user_id")
else:
del_t = text("DELETE FROM drinks WHERE prepaid_user_id = :user_id AND id = :drink_id")
update_t = text("UPDATE users_prepaid SET money = money + :drink_cost WHERE id = :user_id")
money_t = text("SELECT money FROM users_prepaid WHERE id = :user_id")
with engine.connect() as connection:
# Check if the drink exists
print(f"revert_last_drink: user_id={user_id}, user_is_postpaid={user_is_postpaid}, drink_id={drink_id}, drink_cost={drink_cost}")
drink_exists = connection.execute(del_t, {"user_id": user_id, "drink_id": drink_id}).rowcount > 0
if not drink_exists:
raise HTTPException(status_code=404, detail="Drink not found")
# Revert the money
prev_money = connection.execute(money_t, {"user_id": user_id}).fetchone()
if not prev_money:
raise HTTPException(status_code=404, detail="User not found")
new_money = prev_money[0] + drink_cost
connection.execute(update_t, {"user_id": user_id, "drink_cost": drink_cost})
connection.commit()
_log_transaction(
user_id=user_id,
user_is_postpaid=user_is_postpaid,
previous_money_cent=prev_money[0],
new_money_cent=new_money,
delta_money_cent=drink_cost,
description="Reverted last drink"
)
def update_drink_type(user_id: int, user_is_postpaid: bool, drink_id, drink_type: str):
if user_is_postpaid:
t = text("UPDATE drinks SET drink_type = :drink_type WHERE postpaid_user_id = :user_id AND id = :drink_id")
else:
t = text("UPDATE drinks SET drink_type = :drink_type WHERE prepaid_user_id = :user_id AND id = :drink_id")
with engine.connect() as connection:
result = connection.execute(t, {"user_id": user_id, "drink_id": drink_id, "drink_type": drink_type})
if result.rowcount == 0:
raise HTTPException(status_code=404, detail="Drink not found")
connection.commit()
return result.rowcount

72
main.py
View File

@@ -23,6 +23,9 @@ from db.models import drink_prepaid_user
from db.models import toggle_activate_prepaid_user
from db.models import set_prepaid_user_money
from db.models import del_user_prepaid
from db.models import get_last_drink
from db.models import revert_last_drink
from db.models import update_drink_type
from auth import oidc
import os
@@ -95,13 +98,14 @@ def home(request: Request):
prepaid_users_from_curr_user.append(prepaid_user)
# load current user from database
try:
if user_authentik["prepaid"]:
db_user = get_prepaid_user(user_db_id)
else:
db_user = get_postpaid_user(user_db_id)
except KeyError:
user_is_postpaid = get_is_postpaid(user_authentik)
if user_is_postpaid:
db_user = get_postpaid_user(user_db_id)
else:
db_user = get_prepaid_user(user_db_id)
# get last drink for current user, if not less than 60 seconds ago
last_drink = get_last_drink(user_db_id, user_is_postpaid, 60)
return templates.TemplateResponse("index.html", {
"request": request,
@@ -110,7 +114,10 @@ def home(request: Request):
"user_db_id": user_db_id,
"db_user": db_user,
"db_users_prepaid": db_users_prepaid,
"prepaid_users_from_curr_user": prepaid_users_from_curr_user,})
"prepaid_users_from_curr_user": prepaid_users_from_curr_user,
"last_drink": last_drink,
"avail_drink_types": ["Paulaner Spezi", "Mio Mate", "Club Mate", "Sonstiges"],
})
@app.get("/login", response_class=HTMLResponse)
def login_form(request: Request):
@@ -327,6 +334,57 @@ async def popup_getraenke():
alle_getraenke = ["Wasser", "Cola", "Bier", "Mate", "Saft", "Tee", "Kaffee", "Limo"]
return JSONResponse(content={"getraenke": random.sample(alle_getraenke, 4)})
@app.post("/del_last_drink")
def del_last_drink(request: Request):
user_db_id = request.session.get("user_db_id")
if not user_db_id:
raise HTTPException(status_code=404, detail="User not found")
user_authentik = request.session.get("user_authentik")
if not user_authentik:
raise HTTPException(status_code=404, detail="User not found")
last_drink = get_last_drink(user_db_id, True, 60)
if not last_drink:
return RedirectResponse(url="/", status_code=303)
user_is_postpaid = get_is_postpaid(user_authentik)
revert_last_drink(user_db_id, user_is_postpaid, last_drink["id"])
return RedirectResponse(url="/", status_code=303)
@app.post("/update_drink_post")
def update_drink_post(request: Request, drink_type: str = Form(...)):
user_db_id = request.session.get("user_db_id")
if not user_db_id:
raise HTTPException(status_code=404, detail="User not found")
user_authentik = request.session.get("user_authentik")
if not user_authentik:
raise HTTPException(status_code=404, detail="User not found")
last_drink = get_last_drink(user_db_id, True, 60)
if not last_drink:
return RedirectResponse(url="/", status_code=303)
if not drink_type:
raise HTTPException(status_code=400, detail="Drink type is empty")
update_drink_type(user_db_id, get_is_postpaid(user_authentik), last_drink["id"], drink_type)
return RedirectResponse(url="/", status_code=303)
def get_is_postpaid(user_authentik) -> bool:
try:
if user_authentik["prepaid"]:
user_is_postpaid = False
else:
user_is_postpaid = True
except KeyError:
user_is_postpaid = True
return user_is_postpaid
if __name__ == "__main__":
uvicorn.run(app, host="0.0.0.0", port=8000)

BIN
static/drinks/Club Mate.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 137 KiB

BIN
static/drinks/Mio Mate.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 164 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 625 KiB

View File

@@ -72,7 +72,42 @@ content %}
</button>
</form>
</div>
{% endif %} {% endif %}
{% endif %}
{% if last_drink %}
<div style="margin: 1em 0; text-align: center;">
<strong>Getränk spezialisieren:</strong>
<div style="display: flex; flex-wrap: wrap; justify-content: center; gap: 1em; margin-top: 1em;">
{% for drink in avail_drink_types %}
<form method="post" action="/update_drink_post" style="display: inline-block;">
<input type="hidden" name="drink_type" value="{{ drink }}">
<button type="submit"
style="display: flex; flex-direction: column; align-items: center; background-color: #00618F; color: #fff; border: none; border-radius: 8px; padding: 0.7em 1.2em; cursor: pointer; min-width: 120px;">
<img src="/static/drinks/{{ drink|lower }}.png" alt="{{ drink }}" style="width:48px; height:48px; object-fit:contain; margin-bottom:0.5em;">
<span>{{ drink }}</span>
</button>
</form>
{% endfor %}
</div>
</div>
<div style="margin: 1em 0; text-align: center;">
<strong>Letztes Getränk:</strong>
<div style="margin: 0.5em 0;">
Typ: {{ last_drink.drink_type }}<br>
Zeit: {{ last_drink.timestamp }}<br>
ID: {{ last_drink.id }}
</div>
<form method="post" action="/del_last_drink" style="display: inline;">
<input type="hidden" name="drink_id" value="{{ last_drink.drink_id }}">
<button type="submit"
style="background-color: #c0392b; color: #fff; border: none; border-radius: 6px; padding: 0.5em 1em; cursor: pointer;">
Getränk löschen
</button>
</form>
</div>
{% endif %}
{% endif %}
{% if user.prepaid %}
<div style="display: flex; justify-content: center; text-align: center">

BIN
test.db

Binary file not shown.