Merge pull request #3 from Moritz921/responsiveDesign
Responsive design + popup alternative
This commit is contained in:
348
db/models.py
348
db/models.py
@@ -1,27 +1,45 @@
|
||||
"""
|
||||
This module defines database models and utility functions for managing users and drinks in a beverage tracking application using SQLAlchemy and FastAPI.
|
||||
Tables:
|
||||
- users_postpaid: Stores postpaid user accounts.
|
||||
- users_prepaid: Stores prepaid user accounts, linked to postpaid users.
|
||||
- drinks: Records drink transactions for both postpaid and prepaid users.
|
||||
Database models and operations for a drink management system using SQLAlchemy and FastAPI.
|
||||
|
||||
This module provides functions to manage postpaid and prepaid users, handle drink transactions,
|
||||
log money transfers, and generate statistics. It includes table creation, user CRUD operations,
|
||||
transaction logging, drink recording, and utility functions for drink statistics.
|
||||
|
||||
Constants:
|
||||
DRINK_COST (int): Cost of a drink in cents.
|
||||
AVAILABLE_DRINKS (list): List of available drink types.
|
||||
|
||||
Functions:
|
||||
- create_postpaid_user(username: str) -> int:
|
||||
Creates a new postpaid user with the specified username. Raises HTTPException if the user already exists or if a database error occurs. Returns the ID of the newly created user.
|
||||
- get_postpaid_user(user_id: int) -> dict:
|
||||
Retrieves a postpaid user's information by their user ID. Returns a dictionary with user details. Raises HTTPException if the user is not found.
|
||||
- get_postpaid_user_by_username(username: str) -> dict:
|
||||
Retrieves a postpaid user's information by their username. Returns a dictionary with user details. Raises HTTPException if the user is not found.
|
||||
- set_postpaid_user_money(user_id: int, money: float) -> int:
|
||||
Updates the 'money' balance for a postpaid user. Raises HTTPException if the user is not found. Returns the number of rows affected.
|
||||
- drink_postpaid_user(user_id: int) -> int:
|
||||
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.
|
||||
_log_transaction(user_id, user_is_postpaid, ...): Log a user's money transaction.
|
||||
create_postpaid_user(username): Create a new postpaid user.
|
||||
get_postpaid_user(user_id): Retrieve a postpaid user's info by ID.
|
||||
get_postpaid_user_by_username(username): Retrieve a postpaid user by username.
|
||||
set_postpaid_user_money(user_id, money): Set a postpaid user's balance.
|
||||
drink_postpaid_user(user_id, drink_type): Deduct drink cost and record a drink for postpaid user
|
||||
toggle_activate_postpaid_user(user_id): Toggle activation status of a postpaid user.
|
||||
payup_postpaid_user(current_user_id, payup_user_id, money_cent): Transfer money btw. post users.
|
||||
get_prepaid_user(user_id): Retrieve a prepaid user's info by ID.
|
||||
get_prepaid_user_by_username(username): Retrieve a prepaid user by username.
|
||||
create_prepaid_user(prepaid_username, postpaid_user_id, start_money): Create a new prepaid user.
|
||||
drink_prepaid_user(user_db_id): Process a prepaid drink transaction.
|
||||
toggle_activate_prepaid_user(user_id): Toggle activation status of a prepaid user.
|
||||
set_prepaid_user_money(user_id, money, postpaid_user_id): Set prepaid user's balance and link.
|
||||
del_user_prepaid(user_id): Delete a prepaid user.
|
||||
get_last_drink(user_id, user_is_postpaid, max_since_seconds): Get last drink within time window.
|
||||
revert_last_drink(user_id, user_is_postpaid, drink_id, drink_cost): Revert a drink and refund.
|
||||
update_drink_type(user_id, user_is_postpaid, drink_id, drink_type): Update drink type for drink.
|
||||
get_most_used_drinks(user_id, user_is_postpaid, limit): Get most used drinks for a user.
|
||||
get_stats_drink_types(): Get statistics of drink types.
|
||||
|
||||
HTTPException: For database errors, not found, or forbidden actions.
|
||||
"""
|
||||
import os
|
||||
import secrets
|
||||
import datetime
|
||||
from sqlalchemy import create_engine, text
|
||||
import random
|
||||
from sqlalchemy import create_engine, text, select
|
||||
from fastapi import HTTPException
|
||||
|
||||
import os
|
||||
from dotenv import load_dotenv
|
||||
|
||||
load_dotenv()
|
||||
@@ -31,6 +49,7 @@ DATABASE_URL = "sqlite:///" + str(DATABASE_FILE)
|
||||
engine = create_engine(DATABASE_URL, connect_args={"check_same_thread": False})
|
||||
|
||||
DRINK_COST = 100 # cent
|
||||
AVAILABLE_DRINKS = ["Paulaner Spezi", "Mio Mate", "Club Mate", "Eistee Pfirsisch"]
|
||||
|
||||
with engine.connect() as conn:
|
||||
# Create a table for postpaid users
|
||||
@@ -100,22 +119,30 @@ def _log_transaction(
|
||||
description = None
|
||||
):
|
||||
"""
|
||||
Logs a transaction for a user, recording changes in their account balance.
|
||||
Depending on whether the user is postpaid or prepaid, retrieves the previous balance if not provided,
|
||||
calculates the new balance and delta if necessary, and inserts a transaction record into the database.
|
||||
Args:
|
||||
user_id (int): The ID of the user for whom the transaction is being logged.
|
||||
user_is_postpaid (bool): True if the user is postpaid, False if prepaid.
|
||||
previous_money_cent (Optional[int], default=None): The user's previous balance in cents. If None, it is fetched from the database.
|
||||
new_money_cent (Optional[int], default=None): The user's new balance in cents. If None, it is calculated using delta_money_cent.
|
||||
delta_money_cent (Optional[int], default=None): The change in balance in cents. If None, it is calculated using new_money_cent.
|
||||
description (Optional[str], default=None): A description of the transaction.
|
||||
Raises:
|
||||
HTTPException: If the user is not found, if both new_money_cent and delta_money_cent are missing,
|
||||
or if the transaction could not be logged.
|
||||
Returns:
|
||||
int: The ID of the newly created transaction record.
|
||||
"""
|
||||
Logs a transaction for a user, recording changes in their account balance.
|
||||
|
||||
Depending on whether the user is postpaid or prepaid, retrieves the previous balance if not
|
||||
provided, calculates the new balance and delta if necessary, and inserts a transaction record
|
||||
into the database.
|
||||
|
||||
Args:
|
||||
user_id (int): The ID of the user for whom the transaction is being logged.
|
||||
user_is_postpaid (bool): True if the user is postpaid, False if prepaid.
|
||||
previous_money_cent (Optional[int], default=None): The user's previous balance in cents.
|
||||
If None, it is fetched from the database.
|
||||
new_money_cent (Optional[int], default=None): The user's new balance in cents. If None,
|
||||
it is calculated using delta_money_cent.
|
||||
delta_money_cent (Optional[int], default=None): The change in balance in cents. If None,
|
||||
it is calculated using new_money_cent.
|
||||
description (Optional[str], default=None): A description of the transaction.
|
||||
|
||||
Raises:
|
||||
HTTPException: If the user is not found, if both new_money_cent and delta_money_cent are
|
||||
missing, or if the transaction could not be logged.
|
||||
|
||||
Returns:
|
||||
int: The ID of the newly created transaction record.
|
||||
"""
|
||||
if previous_money_cent is None:
|
||||
if user_is_postpaid:
|
||||
t_get_prev_money = text("SELECT money FROM users_postpaid WHERE id = :id")
|
||||
@@ -128,7 +155,13 @@ def _log_transaction(
|
||||
else:
|
||||
raise HTTPException(status_code=404, detail="User not found")
|
||||
if new_money_cent is None and delta_money_cent is None:
|
||||
raise HTTPException(status_code=400, detail="Either new_money or delta_money must be provided, not both")
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail=(
|
||||
"Either new_money or delta_money "
|
||||
"must be provided, not both"
|
||||
)
|
||||
)
|
||||
if new_money_cent is None and delta_money_cent is not None:
|
||||
new_money_cent = previous_money_cent + delta_money_cent
|
||||
elif delta_money_cent is None and new_money_cent is not None:
|
||||
@@ -246,8 +279,12 @@ def set_postpaid_user_money(user_id: int, money: float):
|
||||
int: The number of rows affected by the update operation.
|
||||
"""
|
||||
|
||||
print(f"set_postpaid_user_money: {user_id}, {money}")
|
||||
_log_transaction(user_id, user_is_postpaid=True, new_money_cent=money, description="Set money manually via Admin UI")
|
||||
_log_transaction(
|
||||
user_id,
|
||||
user_is_postpaid=True,
|
||||
new_money_cent=money,
|
||||
description="Set money manually via Admin UI"
|
||||
)
|
||||
t = text("UPDATE users_postpaid SET money = :money WHERE id = :id")
|
||||
with engine.connect() as connection:
|
||||
result = connection.execute(t, {"id": user_id, "money": money})
|
||||
@@ -300,6 +337,15 @@ def drink_postpaid_user(user_id: int, drink_type: str = ""):
|
||||
return result.rowcount
|
||||
|
||||
def toggle_activate_postpaid_user(user_id: int):
|
||||
"""
|
||||
Toggles the 'activated' status of a postpaid user in the database.
|
||||
Args:
|
||||
user_id (int): The ID of the user whose activation status should be toggled.
|
||||
Returns:
|
||||
int: The number of rows affected by the update operation.
|
||||
Raises:
|
||||
HTTPException: If no user with the given ID is found (404 error).
|
||||
"""
|
||||
prev_activated = get_postpaid_user(user_id)["activated"]
|
||||
t = text("UPDATE users_postpaid SET activated = :activated WHERE id = :id")
|
||||
with engine.connect() as connection:
|
||||
@@ -309,8 +355,71 @@ def toggle_activate_postpaid_user(user_id: int):
|
||||
connection.commit()
|
||||
return result.rowcount
|
||||
|
||||
def payup_postpaid_user(current_user_id: int, payup_user_id: int, money_cent: int):
|
||||
"""
|
||||
Transfers a specified amount of money (in cents) from one postpaid user to another.
|
||||
Args:
|
||||
current_user_id (int): ID of the user paying the money.
|
||||
payup_user_id (int): ID of the user receiving the money.
|
||||
money_cent (int): Amount of money to transfer, in cents.
|
||||
Raises:
|
||||
HTTPException: If either user is not activated or not found in the database.
|
||||
"""
|
||||
current_user = get_postpaid_user(current_user_id)
|
||||
if not current_user["activated"]:
|
||||
raise HTTPException(status_code=403, detail="Current user not activated")
|
||||
payup_user = get_postpaid_user(payup_user_id)
|
||||
if not payup_user["activated"]:
|
||||
raise HTTPException(status_code=403, detail="Payup user not activated")
|
||||
|
||||
# subtract money from current user
|
||||
t = text("UPDATE users_postpaid SET money = money - :money WHERE id = :id")
|
||||
with engine.connect() as connection:
|
||||
result = connection.execute(t, {"id": current_user_id, "money": money_cent})
|
||||
if result.rowcount == 0:
|
||||
raise HTTPException(status_code=404, detail="Current user not found")
|
||||
connection.commit()
|
||||
_log_transaction(
|
||||
user_id=current_user_id,
|
||||
user_is_postpaid=True,
|
||||
previous_money_cent=current_user["money"],
|
||||
delta_money_cent=-money_cent,
|
||||
description=f"Payup to user {payup_user_id}"
|
||||
)
|
||||
|
||||
# add money to payup user
|
||||
t = text("UPDATE users_postpaid SET money = money + :money WHERE id = :id")
|
||||
with engine.connect() as connection:
|
||||
result = connection.execute(t, {"id": payup_user_id, "money": money_cent})
|
||||
if result.rowcount == 0:
|
||||
raise HTTPException(status_code=404, detail="Payup user not found")
|
||||
connection.commit()
|
||||
_log_transaction(
|
||||
user_id=payup_user_id,
|
||||
user_is_postpaid=True,
|
||||
previous_money_cent=payup_user["money"],
|
||||
new_money_cent=payup_user["money"] + money_cent,
|
||||
delta_money_cent=money_cent,
|
||||
description=f"Received payup from user {current_user_id}"
|
||||
)
|
||||
|
||||
def get_prepaid_user(user_id: int):
|
||||
"""
|
||||
Retrieve a prepaid user from the database by user ID.
|
||||
Args:
|
||||
user_id (int): The ID of the prepaid user to retrieve.
|
||||
Returns:
|
||||
dict: A dictionary containing the user's information with keys:
|
||||
- "id": User's ID
|
||||
- "username": User's username
|
||||
- "user_key": User's unique key
|
||||
- "postpaid_user_id": Linked postpaid user ID (if any)
|
||||
- "money": User's prepaid balance
|
||||
- "activated": Activation status
|
||||
- "last_drink": Timestamp of the user's last drink
|
||||
Raises:
|
||||
HTTPException: If no user with the given ID is found (status code 404).
|
||||
"""
|
||||
t = text("SELECT id, username, user_key, postpaid_user_id, money, activated, last_drink FROM users_prepaid WHERE id = :id")
|
||||
user_db = {}
|
||||
with engine.connect() as connection:
|
||||
@@ -329,15 +438,14 @@ def get_prepaid_user(user_id: int):
|
||||
|
||||
def get_prepaid_user_by_username(username: str):
|
||||
"""
|
||||
Retrieve a prepaid user from the database by their username.
|
||||
Retrieve a prepaid user by username.
|
||||
Args:
|
||||
username (str): The username of the user to retrieve.
|
||||
username (str): The username to look up.
|
||||
Returns:
|
||||
dict: A dictionary containing the user's id, username, money, activated status, and last_drink timestamp.
|
||||
dict: User info (id, username, user_key, postpaid_user_id, money, activated, last_drink).
|
||||
Raises:
|
||||
HTTPException: If no user with the given username is found, raises a 404 HTTPException.
|
||||
HTTPException: If user not found (404).
|
||||
"""
|
||||
|
||||
t = text("SELECT id, username, user_key, postpaid_user_id, money, activated, last_drink FROM users_prepaid WHERE username = :username")
|
||||
user_db = {}
|
||||
with engine.connect() as connection:
|
||||
@@ -355,6 +463,17 @@ def get_prepaid_user_by_username(username: str):
|
||||
return user_db
|
||||
|
||||
def create_prepaid_user(prepaid_username: str, postpaid_user_id: int, start_money: int = 0):
|
||||
"""
|
||||
Create a new prepaid user in users_prepaid.
|
||||
Args:
|
||||
prepaid_username (str): Username for the new prepaid user.
|
||||
postpaid_user_id (int): Associated postpaid user ID.
|
||||
start_money (int, optional): Initial money for the prepaid user. Defaults to 0.
|
||||
Raises:
|
||||
HTTPException: If username exists (400) or user creation fails (500).
|
||||
Returns:
|
||||
int: ID of the new prepaid user.
|
||||
"""
|
||||
prepaid_key = secrets.token_urlsafe(6)
|
||||
t = text("INSERT INTO users_prepaid (username, user_key, postpaid_user_id, money) VALUES (:username, :user_key, :postpaid_user_id, :start_money)")
|
||||
with engine.connect() as connection:
|
||||
@@ -379,6 +498,24 @@ def create_prepaid_user(prepaid_username: str, postpaid_user_id: int, start_mone
|
||||
return result.lastrowid
|
||||
|
||||
def drink_prepaid_user(user_db_id: int):
|
||||
"""
|
||||
Processes a prepaid drink transaction for a user.
|
||||
This function performs the following steps:
|
||||
1. Retrieves the prepaid user's information from the database.
|
||||
2. Checks if the user is activated; raises HTTP 403 if not.
|
||||
3. Checks if the user has enough money for a drink; raises HTTP 403 if not.
|
||||
4. Logs the transaction.
|
||||
5. Deducts the drink cost from the user's balance and updates the last drink timestamp.
|
||||
6. Inserts a new entry into the drinks table for the user.
|
||||
7. Commits all changes to the database.
|
||||
Args:
|
||||
user_db_id (int): The database ID of the prepaid user.
|
||||
Returns:
|
||||
int: The number of rows affected by the drink entry insertion.
|
||||
Raises:
|
||||
HTTPException: If the user is not activated (403), does not have enough money (403),
|
||||
is not found (404), or if the drink entry could not be created (500).
|
||||
"""
|
||||
user_dict = get_prepaid_user(user_db_id)
|
||||
if not user_dict["activated"]:
|
||||
raise HTTPException(status_code=403, detail="User not activated")
|
||||
@@ -386,6 +523,7 @@ def drink_prepaid_user(user_db_id: int):
|
||||
prev_money = user_dict["money"]
|
||||
if prev_money < DRINK_COST:
|
||||
raise HTTPException(status_code=403, detail="Not enough money")
|
||||
|
||||
_log_transaction(
|
||||
user_id=user_db_id,
|
||||
user_is_postpaid=False,
|
||||
@@ -409,6 +547,15 @@ def drink_prepaid_user(user_db_id: int):
|
||||
return result.rowcount
|
||||
|
||||
def toggle_activate_prepaid_user(user_id: int):
|
||||
"""
|
||||
Toggles the 'activated' status of a prepaid user in the database.
|
||||
Args:
|
||||
user_id (int): The ID of the user whose activation status is to be toggled.
|
||||
Returns:
|
||||
int: The number of rows affected by the update operation.
|
||||
Raises:
|
||||
HTTPException: If no user with the given ID is found (404 error).
|
||||
"""
|
||||
prev_activated = get_prepaid_user(user_id)["activated"]
|
||||
t = text("UPDATE users_prepaid SET activated = :activated WHERE id = :id")
|
||||
with engine.connect() as connection:
|
||||
@@ -419,6 +566,17 @@ def toggle_activate_prepaid_user(user_id: int):
|
||||
return result.rowcount
|
||||
|
||||
def set_prepaid_user_money(user_id: int, money: int, postpaid_user_id: int):
|
||||
"""
|
||||
Updates the prepaid user's money and associated postpaid user ID in the database.
|
||||
Args:
|
||||
user_id (int): The ID of the prepaid user whose information is to be updated.
|
||||
money (int): The new amount of money (in cents) to set for the user.
|
||||
postpaid_user_id (int): The ID of the associated postpaid user.
|
||||
Raises:
|
||||
HTTPException: If the user with the given user_id is not found in the database.
|
||||
Returns:
|
||||
int: The number of rows affected by the last update operation.
|
||||
"""
|
||||
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")
|
||||
_log_transaction(
|
||||
@@ -438,6 +596,15 @@ def set_prepaid_user_money(user_id: int, money: int, postpaid_user_id: int):
|
||||
return result.rowcount
|
||||
|
||||
def del_user_prepaid(user_id: int):
|
||||
"""
|
||||
Deletes a user's prepaid entry from the 'users_prepaid' table by user ID.
|
||||
Args:
|
||||
user_id (int): The ID of the user whose prepaid entry should be deleted.
|
||||
Raises:
|
||||
HTTPException: If no entry with the given user_id is found (404 Not Found).
|
||||
Returns:
|
||||
int: The number of rows deleted (should be 1 if successful).
|
||||
"""
|
||||
t = text("DELETE FROM users_prepaid WHERE id = :id")
|
||||
with engine.connect() as connection:
|
||||
result = connection.execute(t, {"id": user_id})
|
||||
@@ -447,6 +614,18 @@ def del_user_prepaid(user_id: int):
|
||||
return result.rowcount
|
||||
|
||||
def get_last_drink(user_id: int, user_is_postpaid: bool, max_since_seconds: int = 60):
|
||||
"""
|
||||
Retrieve the most recent drink entry for a user within a specified time window.
|
||||
|
||||
Args:
|
||||
user_id (int): The ID of the user whose last drink is to be retrieved.
|
||||
user_is_postpaid (bool): True if the user is postpaid, False if prepaid.
|
||||
max_since_seconds (int, optional): Max seconds since last drink. Defaults to 60.
|
||||
|
||||
Returns:
|
||||
dict or None: Dict with 'id', 'timestamp', 'drink_type' if found within time window,
|
||||
else None.
|
||||
"""
|
||||
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:
|
||||
@@ -466,10 +645,24 @@ def get_last_drink(user_id: int, user_is_postpaid: bool, max_since_seconds: int
|
||||
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}
|
||||
|
||||
drink_obj = {"id": drink_id, "timestamp": timestamp, "drink_type": drink_type}
|
||||
return drink_obj
|
||||
|
||||
def revert_last_drink(user_id: int, user_is_postpaid: bool, drink_id: int, drink_cost: int = DRINK_COST):
|
||||
"""
|
||||
Reverts the last drink purchase for a user and refunds the drink cost.
|
||||
Args:
|
||||
user_id (int): The ID of the user whose drink is to be reverted.
|
||||
user_is_postpaid (bool): True if the user is postpaid, False if prepaid.
|
||||
drink_id (int): The ID of the drink to revert.
|
||||
drink_cost (int, optional): The cost of the drink in cents. Defaults to DRINK_COST.
|
||||
Raises:
|
||||
HTTPException: If the drink or user is not found in the database.
|
||||
Side Effects:
|
||||
- Deletes the drink record from the database.
|
||||
- Refunds the drink cost to the user's balance.
|
||||
- Logs the transaction.
|
||||
"""
|
||||
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")
|
||||
@@ -481,7 +674,6 @@ def revert_last_drink(user_id: int, user_is_postpaid: bool, drink_id: int, drink
|
||||
|
||||
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")
|
||||
@@ -490,11 +682,11 @@ def revert_last_drink(user_id: int, user_is_postpaid: bool, drink_id: int, drink
|
||||
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,
|
||||
@@ -505,6 +697,20 @@ def revert_last_drink(user_id: int, user_is_postpaid: bool, drink_id: int, drink
|
||||
)
|
||||
|
||||
def update_drink_type(user_id: int, user_is_postpaid: bool, drink_id, drink_type: str):
|
||||
"""
|
||||
Updates the drink type for a specific drink associated with a user.
|
||||
Depending on whether the user is postpaid or prepaid, the function updates the `drink_type`
|
||||
field in the `drinks` table for the drink with the given `drink_id` and user association.
|
||||
Args:
|
||||
user_id (int): The ID of the user whose drink is being updated.
|
||||
user_is_postpaid (bool): Indicates if the user is postpaid (True) or prepaid (False).
|
||||
drink_id: The ID of the drink to update.
|
||||
drink_type (str): The new type to set for the drink.
|
||||
Raises:
|
||||
HTTPException: If no drink is found with the given criteria (404 Not Found).
|
||||
Returns:
|
||||
int: The number of rows affected by the update.
|
||||
"""
|
||||
if user_is_postpaid:
|
||||
t = text("UPDATE drinks SET drink_type = :drink_type WHERE postpaid_user_id = :user_id AND id = :drink_id")
|
||||
else:
|
||||
@@ -516,3 +722,51 @@ def update_drink_type(user_id: int, user_is_postpaid: bool, drink_id, drink_type
|
||||
raise HTTPException(status_code=404, detail="Drink not found")
|
||||
connection.commit()
|
||||
return result.rowcount
|
||||
|
||||
def get_most_used_drinks(user_id: int, user_is_postpaid: bool, limit: int = 4):
|
||||
"""
|
||||
Return up to `limit` most used drinks for a user, filling with random drinks if needed.
|
||||
Args:
|
||||
user_id (int): User's ID.
|
||||
user_is_postpaid (bool): True if postpaid, else prepaid.
|
||||
limit (int): Max drinks to return.
|
||||
Returns:
|
||||
list[dict]: Each dict has 'drink_type' and 'count'.
|
||||
"""
|
||||
if user_is_postpaid:
|
||||
t = text("SELECT drink_type, count(drink_type) as count FROM drinks WHERE postpaid_user_id = :user_id AND drink_type IS NOT NULL AND drink_type != 'Sonstiges' GROUP BY drink_type ORDER BY count DESC LIMIT :limit")
|
||||
else:
|
||||
t = text("SELECT drink_type, count(drink_type) as count FROM drinks WHERE prepaid_user_id = :user_id AND drink_type IS NOT NULL AND drink_type != 'Sonstiges' GROUP BY drink_type ORDER BY count DESC LIMIT :limit")
|
||||
|
||||
with engine.connect() as connection:
|
||||
result = connection.execute(t, {"user_id": user_id, "limit": limit}).fetchall()
|
||||
if not result:
|
||||
return []
|
||||
drinks = [{"drink_type": row[0], "count": row[1]} for row in result]
|
||||
|
||||
while len(drinks) < limit:
|
||||
random_drink = random.choice(AVAILABLE_DRINKS)
|
||||
if any(drink["drink_type"] == random_drink for drink in drinks):
|
||||
continue
|
||||
drinks.append({"drink_type": random_drink, "count": 0})
|
||||
|
||||
return drinks
|
||||
|
||||
def get_stats_drink_types():
|
||||
"""
|
||||
Retrieves statistics on drink types from the database.
|
||||
Executes a SQL query to count the occurrences of each non-null drink type in the 'drinks' table,
|
||||
grouping and ordering the results by the count in descending order.
|
||||
Returns:
|
||||
list[dict]: A list of dictionaries, each containing 'drink_type' (str) and 'count' (int).
|
||||
Returns an empty list if no results are found.
|
||||
"""
|
||||
t = text("SELECT drink_type, count(drink_type) as count FROM drinks WHERE drink_type IS NOT NULL GROUP BY drink_type ORDER BY count DESC")
|
||||
|
||||
with engine.connect() as connection:
|
||||
result = connection.execute(t).fetchall()
|
||||
if not result:
|
||||
return []
|
||||
drinks = [{"drink_type": row[0], "count": row[1]} for row in result]
|
||||
|
||||
return drinks
|
||||
|
||||
262
main.py
262
main.py
@@ -1,35 +1,19 @@
|
||||
import random
|
||||
import os
|
||||
from dotenv import load_dotenv
|
||||
|
||||
from fastapi import FastAPI, Request, Form, HTTPException
|
||||
from fastapi.responses import RedirectResponse, HTMLResponse
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
from fastapi.templating import Jinja2Templates
|
||||
from fastapi.responses import JSONResponse
|
||||
from starlette.middleware.sessions import SessionMiddleware
|
||||
|
||||
import uvicorn
|
||||
from sqlalchemy import text
|
||||
|
||||
from db.models import engine
|
||||
from db.models import get_postpaid_user
|
||||
from db.models import get_postpaid_user_by_username
|
||||
from db.models import set_postpaid_user_money
|
||||
from db.models import drink_postpaid_user
|
||||
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 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
|
||||
import db.models
|
||||
|
||||
from auth import oidc
|
||||
import os
|
||||
from dotenv import load_dotenv
|
||||
|
||||
|
||||
|
||||
@@ -49,7 +33,6 @@ templates = Jinja2Templates(directory="templates")
|
||||
|
||||
@app.get("/", response_class=HTMLResponse)
|
||||
def home(request: Request):
|
||||
|
||||
# Check if user is logged in and has a valid session
|
||||
user_db_id = request.session.get("user_db_id")
|
||||
user_authentik = request.session.get("user_authentik")
|
||||
@@ -71,7 +54,7 @@ def home(request: Request):
|
||||
if result:
|
||||
users = []
|
||||
for row in result:
|
||||
user_db = get_postpaid_user(row[0])
|
||||
user_db = db.models.get_postpaid_user(row[0])
|
||||
if user_db:
|
||||
users.append(user_db)
|
||||
|
||||
@@ -84,7 +67,7 @@ def home(request: Request):
|
||||
if result:
|
||||
db_users_prepaid = []
|
||||
for row in result:
|
||||
prepaid_user = get_prepaid_user(row[0])
|
||||
prepaid_user = db.models.get_prepaid_user(row[0])
|
||||
if prepaid_user:
|
||||
db_users_prepaid.append(prepaid_user)
|
||||
# additionally load all prepaid users from the current user
|
||||
@@ -93,19 +76,22 @@ def home(request: Request):
|
||||
if result:
|
||||
prepaid_users_from_curr_user = []
|
||||
for row in result:
|
||||
prepaid_user = get_prepaid_user(row[0])
|
||||
prepaid_user = db.models.get_prepaid_user(row[0])
|
||||
if prepaid_user:
|
||||
prepaid_users_from_curr_user.append(prepaid_user)
|
||||
|
||||
# load current user from database
|
||||
user_is_postpaid = get_is_postpaid(user_authentik)
|
||||
if user_is_postpaid:
|
||||
db_user = get_postpaid_user(user_db_id)
|
||||
db_user = db.models.get_postpaid_user(user_db_id)
|
||||
else:
|
||||
db_user = get_prepaid_user(user_db_id)
|
||||
db_user = db.models.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)
|
||||
last_drink = db.models.get_last_drink(user_db_id, user_is_postpaid, 60)
|
||||
|
||||
most_used_drinks = db.models.get_most_used_drinks(user_db_id, user_is_postpaid, 3)
|
||||
most_used_drinks.append({"drink_type": "Sonstiges", "count": 0}) # ensure "Sonstiges" is in
|
||||
|
||||
return templates.TemplateResponse("index.html", {
|
||||
"request": request,
|
||||
@@ -116,7 +102,7 @@ def home(request: Request):
|
||||
"db_users_prepaid": db_users_prepaid,
|
||||
"prepaid_users_from_curr_user": prepaid_users_from_curr_user,
|
||||
"last_drink": last_drink,
|
||||
"avail_drink_types": ["Paulaner Spezi", "Mio Mate", "Club Mate", "Sonstiges"],
|
||||
"avail_drink_types": most_used_drinks,
|
||||
})
|
||||
|
||||
@app.get("/login", response_class=HTMLResponse)
|
||||
@@ -128,7 +114,6 @@ def login_form(request: Request):
|
||||
Returns:
|
||||
TemplateResponse: The rendered login.html template with the request context.
|
||||
"""
|
||||
|
||||
return templates.TemplateResponse("login.html", {"request": request})
|
||||
|
||||
@app.post("/set_money_postpaid")
|
||||
@@ -151,10 +136,10 @@ def set_money_postpaid(request: Request, username = Form(...), money: float = Fo
|
||||
if not user_authentik or ADMIN_GROUP not in user_authentik["groups"]:
|
||||
raise HTTPException(status_code=403, detail="Nicht erlaubt")
|
||||
|
||||
user = get_postpaid_user_by_username(username)
|
||||
user = db.models.get_postpaid_user_by_username(username)
|
||||
requested_user_id = user["id"]
|
||||
|
||||
set_postpaid_user_money(requested_user_id, money*100)
|
||||
db.models.set_postpaid_user_money(requested_user_id, money*100)
|
||||
return RedirectResponse(url="/", status_code=303)
|
||||
|
||||
@app.post("/drink")
|
||||
@@ -182,52 +167,93 @@ async def drink(request: Request):
|
||||
if not user_db_id:
|
||||
raise HTTPException(status_code=404, detail="User not found")
|
||||
|
||||
form = await request.form()
|
||||
getraenk = str(form.get("getraenk"))
|
||||
print(f"User {user_authentik['preferred_username']} requested drink: {getraenk}")
|
||||
|
||||
drink_postpaid_user(user_db_id, getraenk)
|
||||
db.models.drink_postpaid_user(user_db_id)
|
||||
return RedirectResponse(url="/", status_code=303)
|
||||
|
||||
@app.post("/payup")
|
||||
def payup(request: Request, username: str = Form(...), money: float = Form(...)):
|
||||
"""
|
||||
Handles the payment process for a postpaid user.
|
||||
This endpoint allows an authenticated admin user to record a payment for a specified user.
|
||||
It validates the admin's permissions, checks the validity of the username and payment amount,
|
||||
and updates the user's postpaid balance accordingly.
|
||||
Args:
|
||||
request (Request): The incoming HTTP request, containing session data.
|
||||
username (str): The username of the user whose balance is to be updated (form data).
|
||||
money (float): The amount of money to be paid (form data, must be between 0 and 1000).
|
||||
Raises:
|
||||
HTTPException: If the user is not authenticated as an admin (403).
|
||||
HTTPException: If the specified user is not found (404).
|
||||
HTTPException: If the payment amount is invalid (400).
|
||||
HTTPException: If the current user is not found in the session (404).
|
||||
Returns:
|
||||
RedirectResponse: Redirects to the home page ("/") with a 303 status code upon success.
|
||||
"""
|
||||
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="Not allowed")
|
||||
|
||||
user_db_id = get_postpaid_user_by_username(username)["id"]
|
||||
user_db_id = db.models.get_postpaid_user_by_username(username)["id"]
|
||||
if not user_db_id:
|
||||
raise HTTPException(status_code=404, detail="User not found")
|
||||
if money < 0 or money > 1000:
|
||||
raise HTTPException(status_code=400, detail="Money must be between 0 and 1000")
|
||||
|
||||
curr_user_money = get_postpaid_user(user_db_id)["money"]
|
||||
set_postpaid_user_money(user_db_id, curr_user_money + money*100)
|
||||
|
||||
current_user_db_id = request.session.get("user_db_id")
|
||||
if not current_user_db_id:
|
||||
raise HTTPException(status_code=404, detail="Current user not found")
|
||||
current_user_money = get_postpaid_user(current_user_db_id)["money"]
|
||||
set_postpaid_user_money(current_user_db_id, current_user_money - money*100)
|
||||
|
||||
db.models.payup_postpaid_user(current_user_db_id, user_db_id, int(money*100))
|
||||
|
||||
return RedirectResponse(url="/", status_code=303)
|
||||
|
||||
@app.post("/toggle_activated_user_postpaid")
|
||||
def toggle_activated_user_postpaid(request: Request, username: str = Form(...)):
|
||||
"""
|
||||
Toggles the activation status of a postpaid user account.
|
||||
Args:
|
||||
request (Request): The incoming HTTP request, containing session information.
|
||||
username (str): The username of the postpaid user to toggle, provided via form data.
|
||||
Raises:
|
||||
HTTPException: If the user is not authenticated as an admin (status code 403).
|
||||
HTTPException: If the specified user is not found (status code 404).
|
||||
Returns:
|
||||
RedirectResponse: Redirects to the homepage ("/") with a 303 status code after toggling.
|
||||
"""
|
||||
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="Not allowed")
|
||||
|
||||
user_db_id = get_postpaid_user_by_username(username)["id"]
|
||||
user_db_id = db.models.get_postpaid_user_by_username(username)["id"]
|
||||
if not user_db_id:
|
||||
raise HTTPException(status_code=404, detail="User not found")
|
||||
|
||||
toggle_activate_postpaid_user(user_db_id)
|
||||
db.models.toggle_activate_postpaid_user(user_db_id)
|
||||
|
||||
return RedirectResponse(url="/", status_code=303)
|
||||
|
||||
|
||||
@app.post("/add_prepaid_user")
|
||||
def add_prepaid_user(request: Request, username: str = Form(...), start_money: float = Form(...)):
|
||||
"""
|
||||
Handles the creation of a new prepaid user account.
|
||||
This endpoint validates the current user's authentication and group membership,
|
||||
checks for the existence of the username among prepaid and postpaid users,
|
||||
validates the input parameters, creates the prepaid user, and deducts the starting
|
||||
money from the current user's postpaid balance.
|
||||
Args:
|
||||
request (Request): The incoming HTTP request, containing session data.
|
||||
username (str): The username for the new prepaid user (form field, required).
|
||||
start_money (float): The initial balance for the new prepaid user (form field, required).
|
||||
Raises:
|
||||
HTTPException:
|
||||
- 403 if the current user is not authorized.
|
||||
- 404 if the current user is not found in the session.
|
||||
- 400 if the username is empty, already exists, or does not meet length requirements.
|
||||
- 400 if the start_money is not between 0 and 100.
|
||||
Returns:
|
||||
RedirectResponse: Redirects to the homepage ("/") with status code 303 upon successful creation.
|
||||
"""
|
||||
active_user_auth = request.session.get("user_authentik")
|
||||
active_user_db_id = request.session.get("user_db_id")
|
||||
if not active_user_auth or FS_GROUP not in active_user_auth["groups"]:
|
||||
@@ -239,9 +265,9 @@ def add_prepaid_user(request: Request, username: str = Form(...), start_money: f
|
||||
|
||||
user_exists = False
|
||||
try:
|
||||
get_postpaid_user_by_username(username)
|
||||
db.models.get_postpaid_user_by_username(username)
|
||||
user_exists = True
|
||||
get_prepaid_user_by_username(username)
|
||||
db.models.get_prepaid_user_by_username(username)
|
||||
user_exists = True
|
||||
except HTTPException:
|
||||
pass
|
||||
@@ -254,15 +280,28 @@ def add_prepaid_user(request: Request, username: str = Form(...), start_money: f
|
||||
if len(username) < 3 or len(username) > 20:
|
||||
raise HTTPException(status_code=400, detail="Username must be between 3 and 20 characters")
|
||||
|
||||
create_prepaid_user(username, active_user_db_id, int(start_money*100))
|
||||
db.models.create_prepaid_user(username, active_user_db_id, int(start_money*100))
|
||||
|
||||
prev_money = get_postpaid_user(active_user_db_id)["money"]
|
||||
set_postpaid_user_money(active_user_db_id, prev_money - int(start_money*100))
|
||||
prev_money = db.models.get_postpaid_user(active_user_db_id)["money"]
|
||||
db.models.set_postpaid_user_money(active_user_db_id, prev_money - int(start_money*100))
|
||||
|
||||
return RedirectResponse(url="/", status_code=303)
|
||||
|
||||
@app.post("/drink_prepaid")
|
||||
def drink_prepaid(request: Request):
|
||||
"""
|
||||
Handles a prepaid drink request for a user.
|
||||
This function checks if the user is authenticated and has prepaid privileges.
|
||||
If the user is not found in the session or does not have prepaid access, it raises an HTTPException.
|
||||
If the user is valid and has prepaid access, it records the prepaid drink for the user in the database
|
||||
and redirects to the home page.
|
||||
Args:
|
||||
request (Request): The incoming HTTP request containing session data.
|
||||
Raises:
|
||||
HTTPException: If the user is not found in the session or does not have prepaid privileges.
|
||||
Returns:
|
||||
RedirectResponse: Redirects the user to the home page with a 303 status code.
|
||||
"""
|
||||
user_db_id = request.session.get("user_db_id")
|
||||
if not user_db_id:
|
||||
raise HTTPException(status_code=404, detail="User not found")
|
||||
@@ -272,25 +311,52 @@ def drink_prepaid(request: Request):
|
||||
if not user_authentik["prepaid"]:
|
||||
raise HTTPException(status_code=403, detail="Not allowed")
|
||||
|
||||
drink_prepaid_user(user_db_id)
|
||||
db.models.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(...)):
|
||||
"""
|
||||
Toggle the activation status of a prepaid user account.
|
||||
This endpoint is restricted to users who are members of the ADMIN_GROUP.
|
||||
It retrieves the user by username, toggles their activation status, and redirects to the homepage.
|
||||
Args:
|
||||
request (Request): The incoming HTTP request, containing session data.
|
||||
username (str): The username of the prepaid user to toggle, provided via form data.
|
||||
Raises:
|
||||
HTTPException: If the user is not authenticated as an admin (403).
|
||||
HTTPException: If the specified user is not found (404).
|
||||
Returns:
|
||||
RedirectResponse: Redirects to the homepage with a 303 status code after toggling.
|
||||
"""
|
||||
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="Not allowed")
|
||||
|
||||
user_db_id = get_prepaid_user_by_username(username)["id"]
|
||||
user_db_id = db.models.get_prepaid_user_by_username(username)["id"]
|
||||
if not user_db_id:
|
||||
raise HTTPException(status_code=404, detail="User not found")
|
||||
|
||||
toggle_activate_prepaid_user(user_db_id)
|
||||
db.models.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(...)):
|
||||
"""
|
||||
Handles the transfer of a specified amount of money from the currently logged-in postpaid user to a prepaid user.
|
||||
Args:
|
||||
request (Request): The incoming HTTP request containing session data.
|
||||
username (str): The username of the prepaid user to whom money will be added (provided via form data).
|
||||
money (float): The amount of money to transfer (provided via form data, must be between 0 and 100).
|
||||
Raises:
|
||||
HTTPException:
|
||||
- 403 if the current user is not authorized (not in the required group).
|
||||
- 404 if the logged-in user or the prepaid user is not found.
|
||||
- 400 if the money amount is not within the allowed range.
|
||||
Returns:
|
||||
RedirectResponse: Redirects the user to the homepage ("/") with a 303 status code after a successful transfer.
|
||||
"""
|
||||
curr_user_auth = request.session.get("user_authentik")
|
||||
if not curr_user_auth or FS_GROUP not in curr_user_auth["groups"]:
|
||||
raise HTTPException(status_code=403, detail="Not allowed")
|
||||
@@ -298,7 +364,7 @@ def add_money_prepaid_user(request: Request, username: str = Form(...), money: f
|
||||
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_dict = db.models.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")
|
||||
@@ -306,36 +372,55 @@ def add_money_prepaid_user(request: Request, username: str = Form(...), money: f
|
||||
if money < 0 or money > 100:
|
||||
raise HTTPException(status_code=400, detail="Money must be between 0 and 100")
|
||||
|
||||
curr_user_money = get_postpaid_user(curr_user_db_id)["money"]
|
||||
curr_user_money = db.models.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)
|
||||
db.models.set_postpaid_user_money(curr_user_db_id, curr_user_money - money*100)
|
||||
db.models.set_prepaid_user_money(prepaid_user_db_id, prepaid_user_money + money*100, curr_user_db_id)
|
||||
|
||||
return RedirectResponse(url="/", status_code=303)
|
||||
|
||||
@app.post("/del_prepaid_user")
|
||||
def delete_prepaid_user(request: Request, username: str = Form(...)):
|
||||
"""
|
||||
Deletes a prepaid user from the system.
|
||||
This endpoint allows an admin user to delete a prepaid user by their username.
|
||||
It checks if the requester is part of the ADMIN_GROUP, retrieves the user to be deleted,
|
||||
and removes them from the database. If the user is not found or the requester is not authorized,
|
||||
an appropriate HTTPException is raised.
|
||||
Args:
|
||||
request (Request): The incoming HTTP request, containing session information.
|
||||
username (str): The username of the prepaid user to delete, provided via form data.
|
||||
Raises:
|
||||
HTTPException: If the requester is not authorized (status code 403).
|
||||
HTTPException: If the user to delete is not found (status code 404).
|
||||
Returns:
|
||||
RedirectResponse: Redirects to the homepage ("/") with status code 303 upon successful deletion.
|
||||
"""
|
||||
# check if user is in ADMIN_GROUP
|
||||
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_to_del = get_prepaid_user_by_username(username)
|
||||
user_to_del = db.models.get_prepaid_user_by_username(username)
|
||||
if not user_to_del["id"]:
|
||||
raise HTTPException(status_code=404, detail="User not found")
|
||||
|
||||
del_user_prepaid(user_to_del["id"])
|
||||
db.models.del_user_prepaid(user_to_del["id"])
|
||||
|
||||
return RedirectResponse(url="/", status_code=303)
|
||||
|
||||
@app.get("/popup_getraenke")
|
||||
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):
|
||||
"""
|
||||
Handles the deletion (reversion) of the last drink entry for the currently authenticated user.
|
||||
Args:
|
||||
request (Request): The incoming HTTP request containing session data.
|
||||
Raises:
|
||||
HTTPException: If the user is not found in the session.
|
||||
Returns:
|
||||
RedirectResponse: Redirects to the homepage ("/") after attempting to revert the last drink.
|
||||
"""
|
||||
user_db_id = request.session.get("user_db_id")
|
||||
if not user_db_id:
|
||||
raise HTTPException(status_code=404, detail="User not found")
|
||||
@@ -343,18 +428,28 @@ def del_last_drink(request: Request):
|
||||
if not user_authentik:
|
||||
raise HTTPException(status_code=404, detail="User not found")
|
||||
|
||||
last_drink = get_last_drink(user_db_id, True, 60)
|
||||
last_drink = db.models.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"])
|
||||
db.models.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(...)):
|
||||
"""
|
||||
Handles a POST request to update the type of the user's last drink.
|
||||
Args:
|
||||
request (Request): The incoming HTTP request containing session data.
|
||||
drink_type (str, optional): The new type of drink to set, provided via form data.
|
||||
Raises:
|
||||
HTTPException: If the user is not found in the session or if the drink type is empty.
|
||||
Returns:
|
||||
RedirectResponse: Redirects to the home page after updating the drink type, or if no last drink is found.
|
||||
"""
|
||||
user_db_id = request.session.get("user_db_id")
|
||||
if not user_db_id:
|
||||
raise HTTPException(status_code=404, detail="User not found")
|
||||
@@ -363,20 +458,53 @@ def update_drink_post(request: Request, drink_type: str = Form(...)):
|
||||
if not user_authentik:
|
||||
raise HTTPException(status_code=404, detail="User not found")
|
||||
|
||||
last_drink = get_last_drink(user_db_id, True, 60)
|
||||
last_drink = db.models.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)
|
||||
db.models.update_drink_type(user_db_id, get_is_postpaid(user_authentik), last_drink["id"], drink_type)
|
||||
|
||||
return RedirectResponse(url="/", status_code=303)
|
||||
|
||||
@app.get("/stats", response_class=HTMLResponse)
|
||||
def stats(request: Request):
|
||||
"""
|
||||
Handles the statistics page request for authenticated admin users.
|
||||
Args:
|
||||
request (Request): The incoming HTTP request object containing session data.
|
||||
Raises:
|
||||
HTTPException: If the user is not authenticated as an admin (403).
|
||||
HTTPException: If the user's database ID is not found in the session (404).
|
||||
Returns:
|
||||
TemplateResponse: Renders the "stats.html" template with user and drink type statistics.
|
||||
"""
|
||||
user_authentik = request.session.get("user_authentik")
|
||||
if not user_authentik or ADMIN_GROUP not in user_authentik["groups"]:
|
||||
raise HTTPException(status_code=403, detail="Not allowed")
|
||||
user_db_id = request.session.get("user_db_id")
|
||||
if not user_db_id:
|
||||
raise HTTPException(status_code=404, detail="User not found")
|
||||
|
||||
drink_types = db.models.get_stats_drink_types()
|
||||
|
||||
def get_is_postpaid(user_authentik) -> bool:
|
||||
return templates.TemplateResponse("stats.html", {
|
||||
"request": request,
|
||||
"user": user_authentik,
|
||||
"user_db_id": user_db_id,
|
||||
"stats_drink_types": drink_types,
|
||||
})
|
||||
|
||||
def get_is_postpaid(user_authentik: dict) -> bool:
|
||||
"""
|
||||
Determine if a user is postpaid based on their authentication information.
|
||||
Args:
|
||||
user_authentik (dict): A dictionary containing user authentication data, expected to have a 'prepaid' key.
|
||||
Returns:
|
||||
bool: True if the user is postpaid, False if the user is prepaid. If the 'prepaid' key is missing, defaults to True (postpaid).
|
||||
"""
|
||||
try:
|
||||
if user_authentik["prepaid"]:
|
||||
user_is_postpaid = False
|
||||
|
||||
BIN
static/drinks/eistee_pfirsisch.png
Normal file
BIN
static/drinks/eistee_pfirsisch.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 394 KiB |
BIN
static/drinks/sonstiges.png
Normal file
BIN
static/drinks/sonstiges.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 35 KiB |
@@ -38,9 +38,71 @@ header {
|
||||
background: #23272a;
|
||||
color: #e0e0e0;
|
||||
}
|
||||
input, select, button {
|
||||
input,
|
||||
select,
|
||||
button {
|
||||
background: #23272a;
|
||||
color: #e0e0e0;
|
||||
border-color: #444;
|
||||
}
|
||||
}
|
||||
|
||||
/* Responsive Design */
|
||||
@media (max-width: 600px) {
|
||||
body {
|
||||
max-width: 100vw;
|
||||
padding: 0.5em;
|
||||
}
|
||||
header,
|
||||
main {
|
||||
padding: 0.5em;
|
||||
margin-bottom: 0.5em;
|
||||
}
|
||||
h1 {
|
||||
font-size: 1.3em;
|
||||
text-align: center;
|
||||
}
|
||||
table {
|
||||
font-size: 0.95em;
|
||||
width: 100%;
|
||||
display: block;
|
||||
overflow-x: auto;
|
||||
}
|
||||
th,
|
||||
td {
|
||||
padding: 0.3em 0.5em !important;
|
||||
word-break: break-word;
|
||||
}
|
||||
form {
|
||||
flex-direction: column !important;
|
||||
gap: 0.5em !important;
|
||||
max-width: 100% !important;
|
||||
}
|
||||
input,
|
||||
select,
|
||||
button,
|
||||
label {
|
||||
width: 100% !important;
|
||||
font-size: 1em !important;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.github-icon-link {
|
||||
top: 8px;
|
||||
right: 8px;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
img[alt="Logo"] {
|
||||
height: 36px !important;
|
||||
}
|
||||
#popup {
|
||||
max-width: 98vw !important;
|
||||
left: 50% !important;
|
||||
transform: translate(-50%, -25%) !important;
|
||||
padding: 0.5em !important;
|
||||
}
|
||||
#popup-getraenke button {
|
||||
font-size: 1em !important;
|
||||
padding: 0.5em 0.7em !important;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,179 +48,6 @@
|
||||
</header>
|
||||
<main>
|
||||
{% block content %}{% endblock %}
|
||||
{% if user %}
|
||||
{% if 'Getraenkeliste Postpaid' in user.groups %}
|
||||
<p>Du bist Teil der Fachschaft Informatik.</p>
|
||||
{% if prepaid_users_from_curr_user %}
|
||||
<p>Liste deiner Prepaid-User:</p>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="padding: 0.5em 1em">ID</th>
|
||||
<th style="padding: 0.5em 1em">Username</th>
|
||||
<th style="padding: 0.5em 1em">Key</th>
|
||||
<th style="padding: 0.5em 1em">Postpaid_User ID</th>
|
||||
<th style="padding: 0.5em 1em">Money (€)</th>
|
||||
<th style="padding: 0.5em 1em">Activated</th>
|
||||
<th style="padding: 0.5em 1em">last drink</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for prepaid_user_i in prepaid_users_from_curr_user %}
|
||||
<tr{% if prepaid_user_i.money <= 0 %} style="background-color: rgba(179, 6, 44, 0.5)"{% endif %}>
|
||||
<td style="padding: 0.5em 1em">{{ prepaid_user_i.id }}</td>
|
||||
<td style="padding: 0.5em 1em">{{ prepaid_user_i.username }}</td>
|
||||
<td style="padding: 0.5em 1em">{{ prepaid_user_i.user_key }}</td>
|
||||
<td style="padding: 0.5em 1em">{{ prepaid_user_i.postpaid_user_id }}</td>
|
||||
<td style="padding: 0.5em 1em">{{ prepaid_user_i.money / 100 }}</td>
|
||||
<td style="padding: 0.5em 1em">{{ prepaid_user_i.activated }}</td>
|
||||
<td style="padding: 0.5em 1em">{{ prepaid_user_i.last_drink }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% endif %}
|
||||
<p>Füge Nutzer zur Prepaid Liste hinzu:</p>
|
||||
<form method="post" action="/add_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="username" style="margin: 0 0.5em 0 0; font-weight: bold">Username:</label>
|
||||
<input id="username" type="text" name="username" placeholder="Username" required style="padding: 0.5em; border: 1px solid #ccc; border-radius: 4px; width: 100%;" />
|
||||
<label for="start_money" style="margin: 0 0.5em 0 0; font-weight: bold">Start Money (€):</label>
|
||||
<input id="start_money" type="number" name="start_money" placeholder="Start 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 User</button>
|
||||
</form>
|
||||
<p>Füge bestehendem Prepaid-User Geld hinzu:</p>
|
||||
{% if db_users_prepaid %}
|
||||
<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>
|
||||
{% else %}
|
||||
<p>Es sind keine Prepaid-User vorhanden.</p>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% if 'Getraenkeliste Verantwortliche' in user.groups %}
|
||||
<h2>Admin Interface</h2>
|
||||
<p>Ausgleichszahlung:</p>
|
||||
<p>Der eingegebene Betrag wird vom aktuell eingeloggten Nutzer abgezogen und dem eingetragenem Nutzer gutgeschrieben.</p>
|
||||
<form method="post" action="/payup" 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="payup-username" style="margin: 0 0.5em 0 0; font-weight: bold">Username:</label>
|
||||
<select id="payup-username" name="username" required style="padding: 0.5em; border: 1px solid #ccc; border-radius: 4px;">
|
||||
{% for db_user in users %}
|
||||
<option value="{{ db_user.username }}">{{ db_user.username }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<label for="payup-money" style="margin: 0 0.5em 0 0; font-weight: bold">Amount (€):</label>
|
||||
<input id="payup-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;">Pay Up</button>
|
||||
</form>
|
||||
<h3>Postpaid Liste</h3>
|
||||
<p>Users in postpaid database:</p>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="padding: 0.5em 1em">ID</th>
|
||||
<th style="padding: 0.5em 1em">Username</th>
|
||||
<th style="padding: 0.5em 1em">Money (€)</th>
|
||||
<th style="padding: 0.5em 1em">Activated</th>
|
||||
<th style="padding: 0.5em 1em">last drink</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for db_user_i in users %}
|
||||
<tr{% if db_user_i.money <= -5000 %} style="background-color: rgba(179, 6, 44, 0.5)"{% endif %}>
|
||||
<td style="padding: 0.5em 1em">{{ db_user_i.id }}</td>
|
||||
<td style="padding: 0.5em 1em">{{ db_user_i.username }}</td>
|
||||
<td style="padding: 0.5em 1em">{{ db_user_i.money / 100 }}</td>
|
||||
<td style="padding: 0.5em 1em">{{ db_user_i.activated }}</td>
|
||||
<td style="padding: 0.5em 1em">{{ db_user_i.last_drink }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
<p>(De-)Activate User</p>
|
||||
<form method="post" action="/toggle_activated_user_postpaid" 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 users %}
|
||||
<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>
|
||||
<p>Set user money:</p>
|
||||
<form method="post" action="/set_money_postpaid" 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="setmoney-username" style="margin: 0 0.5em 0 0; font-weight: bold">Username:</label>
|
||||
<select id="setmoney-username" name="username" required style="padding: 0.5em; border: 1px solid #ccc; border-radius: 4px;">
|
||||
{% for db_user in users %}
|
||||
<option value="{{ db_user.username }}">{{ db_user.username }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<label for="setmoney-money" style="margin: 0 0.5em 0 0; font-weight: bold">Amount (€):</label>
|
||||
<input id="setmoney-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;">Set Money</button>
|
||||
</form>
|
||||
<h3>Prepaid Liste</h3>
|
||||
<p>Users in prepaid database:</p>
|
||||
{% if db_users_prepaid %}
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="padding: 0.5em 1em">ID</th>
|
||||
<th style="padding: 0.5em 1em">Username</th>
|
||||
<th style="padding: 0.5em 1em">Key</th>
|
||||
<th style="padding: 0.5em 1em">Postpaid_User ID</th>
|
||||
<th style="padding: 0.5em 1em">Money (€)</th>
|
||||
<th style="padding: 0.5em 1em">Activated</th>
|
||||
<th style="padding: 0.5em 1em">last drink</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for prepaid_user_i in db_users_prepaid %}
|
||||
<tr{% if prepaid_user_i.money <= 0 %} style="background-color: rgba(179, 6, 44, 0.5)"{% endif %}>
|
||||
<td style="padding: 0.5em 1em">{{ prepaid_user_i.id }}</td>
|
||||
<td style="padding: 0.5em 1em">{{ prepaid_user_i.username }}</td>
|
||||
<td style="padding: 0.5em 1em">{{ prepaid_user_i.user_key }}</td>
|
||||
<td style="padding: 0.5em 1em">{{ prepaid_user_i.postpaid_user_id }}</td>
|
||||
<td style="padding: 0.5em 1em">{{ prepaid_user_i.money / 100 }}</td>
|
||||
<td style="padding: 0.5em 1em">{{ prepaid_user_i.activated }}</td>
|
||||
<td style="padding: 0.5em 1em">{{ prepaid_user_i.last_drink }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</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>
|
||||
<p>Delete User</p>
|
||||
<form method="post" action="/del_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="del-username" style="margin: 0 0.5em 0 0; font-weight: bold">Username:</label>
|
||||
<select id="del-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(179, 6, 44); color: #fff; border: none; border-radius: 4px; cursor: pointer;">Delete User</button>
|
||||
{% else %}
|
||||
<tr>
|
||||
<td colspan="7" style="text-align: center">No users in prepaid database</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
{% extends "base.html" %} {% block title %}Startseite{% endblock %} {% block
|
||||
content %}
|
||||
<h2>Willkommen, {{ user.name }}!</h2>
|
||||
<p>Dies ist eine einfache geschützte Seite.</p>
|
||||
<p><strong>Aktueller Kontostand:</strong></p>
|
||||
{% if db_user.money > -5000 %}
|
||||
<div
|
||||
@@ -79,11 +78,14 @@ content %}
|
||||
<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 }}">
|
||||
<input type="hidden" name="drink_type" value="{{ drink.drink_type }}">
|
||||
<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|replace(' ', '_') }}.png" alt="{{ drink }}" style="width:48px; height:48px; object-fit:contain; margin-bottom:0.5em;">
|
||||
<span>{{ drink }}</span>
|
||||
style="display: flex; flex-direction: column; align-items: center; background-color: var(--goetheblau); color: #fff; border: none; border-radius: 8px; padding: 0.7em 1.2em; cursor: pointer; min-width: 120px;">
|
||||
<img src="/static/drinks/{{ drink.drink_type|lower|replace(' ', '_') }}.png" alt="{{ drink.drink_type }}" style="width:48px; height:48px; object-fit:contain; margin-bottom:0.5em;">
|
||||
<span>{{ drink.drink_type }}</span>
|
||||
{% if drink.count > 0 %}
|
||||
<span style="font-size:0.9em; color:#eee;">x{{ drink.count }}</span>
|
||||
{% endif %}
|
||||
</button>
|
||||
</form>
|
||||
{% endfor %}
|
||||
@@ -94,13 +96,24 @@ content %}
|
||||
<strong>Letztes Getränk:</strong>
|
||||
<div style="margin: 0.5em 0;">
|
||||
Typ: {{ last_drink.drink_type }}<br>
|
||||
Zeit: {{ last_drink.timestamp }}<br>
|
||||
Zeit: <span class="local-timestamp" data-utc="{{ last_drink.timestamp }}">{{ last_drink.timestamp }}</span><br>
|
||||
<script>
|
||||
document.addEventListener("DOMContentLoaded", function() {
|
||||
document.querySelectorAll('.local-timestamp').forEach(function(el) {
|
||||
const utc = el.getAttribute('data-utc');
|
||||
if (utc) {
|
||||
const date = new Date(utc);
|
||||
el.textContent = date.toLocaleString();
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
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;">
|
||||
style="background-color: var(--emorot); color: #fff; border: none; border-radius: 6px; padding: 0.5em 1em; cursor: pointer;">
|
||||
Getränk löschen
|
||||
</button>
|
||||
</form>
|
||||
@@ -151,55 +164,170 @@ content %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
async function showDrinkPopup() {
|
||||
const response = await fetch("/popup_getraenke");
|
||||
const data = await response.json();
|
||||
const container = document.getElementById("popup-getraenke");
|
||||
container.innerHTML = "";
|
||||
|
||||
data.getraenke.forEach(name => {
|
||||
const btn = document.createElement("button");
|
||||
btn.textContent = name;
|
||||
btn.style.margin = "0.5em";
|
||||
btn.style.padding = "0.5em 1em";
|
||||
btn.style.borderRadius = "6px";
|
||||
btn.style.border = "1px solid #00618F";
|
||||
btn.style.background = "#e0f4ff";
|
||||
btn.style.cursor = "pointer";
|
||||
btn.onclick = () => {
|
||||
document.getElementById("getraenk-auswahl").value = name;
|
||||
document.getElementById("popup").style.display = "none";
|
||||
document.getElementById("drink-form").submit();
|
||||
};
|
||||
container.appendChild(btn);
|
||||
});
|
||||
|
||||
document.getElementById("popup").style.display = "block";
|
||||
|
||||
let dauer = 3; // Sekunden
|
||||
let verbleibend = dauer;
|
||||
const bar = document.getElementById("progress-bar");
|
||||
bar.style.width = "100%";
|
||||
|
||||
const interval = setInterval(() => {
|
||||
verbleibend -= 0.1;
|
||||
bar.style.width = (verbleibend / dauer * 100) + "%";
|
||||
if (verbleibend <= 0) {
|
||||
clearInterval(interval);
|
||||
const auswahlInput = document.getElementById("getraenk-auswahl");
|
||||
if (auswahlInput) {
|
||||
auswahlInput.value = "";
|
||||
}
|
||||
document.getElementById("popup").style.display = "none";
|
||||
const drinkForm = document.getElementById("drink-form");
|
||||
if (drinkForm) {
|
||||
drinkForm.submit();
|
||||
}
|
||||
}
|
||||
}, 100);
|
||||
console.log("Popup angezeigt und Fortschrittsbalken gestartet.");
|
||||
}
|
||||
</script>
|
||||
{% if user %}
|
||||
{% if 'Getraenkeliste Postpaid' in user.groups %}
|
||||
<p>Du bist Teil der Fachschaft Informatik.</p>
|
||||
{% if prepaid_users_from_curr_user %}
|
||||
<p>Liste deiner Prepaid-User:</p>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="padding: 0.5em 1em">Username</th>
|
||||
<th style="padding: 0.5em 1em">Key</th>
|
||||
<th style="padding: 0.5em 1em">Money (€)</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for prepaid_user_i in prepaid_users_from_curr_user %}
|
||||
<tr{% if prepaid_user_i.money <= 0 %} style="background-color: rgba(179, 6, 44, 0.5)"{% endif %}>
|
||||
<td style="padding: 0.5em 1em">{{ prepaid_user_i.username }}</td>
|
||||
<td style="padding: 0.5em 1em">{{ prepaid_user_i.user_key }}</td>
|
||||
<td style="padding: 0.5em 1em">{{ prepaid_user_i.money / 100 }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% endif %}
|
||||
<p>Füge Nutzer zur Prepaid Liste hinzu:</p>
|
||||
<form method="post" action="/add_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="username" style="margin: 0 0.5em 0 0; font-weight: bold">Username:</label>
|
||||
<input id="username" type="text" name="username" placeholder="Username" required style="padding: 0.5em; border: 1px solid #ccc; border-radius: 4px; width: 100%;" />
|
||||
<label for="start_money" style="margin: 0 0.5em 0 0; font-weight: bold">Start Money (€):</label>
|
||||
<input id="start_money" type="number" name="start_money" placeholder="Start 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 User</button>
|
||||
</form>
|
||||
<p>Füge bestehendem Prepaid-User Geld hinzu:</p>
|
||||
{% if db_users_prepaid %}
|
||||
<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>
|
||||
{% else %}
|
||||
<p>Es sind keine Prepaid-User vorhanden.</p>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% if 'Getraenkeliste Verantwortliche' in user.groups %}
|
||||
<h2>Admin Interface</h2>
|
||||
<p>Ausgleichszahlung:</p>
|
||||
<p>Der eingegebene Betrag wird vom aktuell eingeloggten Nutzer abgezogen und dem eingetragenem Nutzer gutgeschrieben.</p>
|
||||
<form method="post" action="/payup" 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="payup-username" style="margin: 0 0.5em 0 0; font-weight: bold">Username:</label>
|
||||
<select id="payup-username" name="username" required style="padding: 0.5em; border: 1px solid #ccc; border-radius: 4px;">
|
||||
{% for db_user in users %}
|
||||
<option value="{{ db_user.username }}">{{ db_user.username }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<label for="payup-money" style="margin: 0 0.5em 0 0; font-weight: bold">Amount (€):</label>
|
||||
<input id="payup-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;">Pay Up</button>
|
||||
</form>
|
||||
<h3>Postpaid Liste</h3>
|
||||
<p>Users in postpaid database:</p>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="padding: 0.5em 1em">ID</th>
|
||||
<th style="padding: 0.5em 1em">Username</th>
|
||||
<th style="padding: 0.5em 1em">Money (€)</th>
|
||||
<th style="padding: 0.5em 1em">Activated</th>
|
||||
<th style="padding: 0.5em 1em">last drink</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for db_user_i in users %}
|
||||
<tr{% if db_user_i.money <= -5000 %} style="background-color: rgba(179, 6, 44, 0.5)"{% endif %}>
|
||||
<td style="padding: 0.5em 1em">{{ db_user_i.id }}</td>
|
||||
<td style="padding: 0.5em 1em">{{ db_user_i.username }}</td>
|
||||
<td style="padding: 0.5em 1em">{{ db_user_i.money / 100 }}</td>
|
||||
<td style="padding: 0.5em 1em">{{ db_user_i.activated }}</td>
|
||||
<td style="padding: 0.5em 1em">{{ db_user_i.last_drink }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
<p>(De-)Activate User</p>
|
||||
<form method="post" action="/toggle_activated_user_postpaid" 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 users %}
|
||||
<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>
|
||||
<p>Set user money:</p>
|
||||
<form method="post" action="/set_money_postpaid" 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="setmoney-username" style="margin: 0 0.5em 0 0; font-weight: bold">Username:</label>
|
||||
<select id="setmoney-username" name="username" required style="padding: 0.5em; border: 1px solid #ccc; border-radius: 4px;">
|
||||
{% for db_user in users %}
|
||||
<option value="{{ db_user.username }}">{{ db_user.username }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<label for="setmoney-money" style="margin: 0 0.5em 0 0; font-weight: bold">Amount (€):</label>
|
||||
<input id="setmoney-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;">Set Money</button>
|
||||
</form>
|
||||
<h3>Prepaid Liste</h3>
|
||||
<p>Users in prepaid database:</p>
|
||||
{% if db_users_prepaid %}
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="padding: 0.5em 1em">ID</th>
|
||||
<th style="padding: 0.5em 1em">Username</th>
|
||||
<th style="padding: 0.5em 1em">Key</th>
|
||||
<th style="padding: 0.5em 1em">Postpaid_User ID</th>
|
||||
<th style="padding: 0.5em 1em">Money (€)</th>
|
||||
<th style="padding: 0.5em 1em">Activated</th>
|
||||
<th style="padding: 0.5em 1em">last drink</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for prepaid_user_i in db_users_prepaid %}
|
||||
<tr{% if prepaid_user_i.money <= 0 %} style="background-color: rgba(179, 6, 44, 0.5)"{% endif %}>
|
||||
<td style="padding: 0.5em 1em">{{ prepaid_user_i.id }}</td>
|
||||
<td style="padding: 0.5em 1em">{{ prepaid_user_i.username }}</td>
|
||||
<td style="padding: 0.5em 1em">{{ prepaid_user_i.user_key }}</td>
|
||||
<td style="padding: 0.5em 1em">{{ prepaid_user_i.postpaid_user_id }}</td>
|
||||
<td style="padding: 0.5em 1em">{{ prepaid_user_i.money / 100 }}</td>
|
||||
<td style="padding: 0.5em 1em">{{ prepaid_user_i.activated }}</td>
|
||||
<td style="padding: 0.5em 1em">{{ prepaid_user_i.last_drink }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</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>
|
||||
<p>Delete User</p>
|
||||
<form method="post" action="/del_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="del-username" style="margin: 0 0.5em 0 0; font-weight: bold">Username:</label>
|
||||
<select id="del-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(179, 6, 44); color: #fff; border: none; border-radius: 4px; cursor: pointer;">Delete User</button>
|
||||
{% else %}
|
||||
<tr>
|
||||
<td colspan="7" style="text-align: center">No users in prepaid database</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
{% endblock %}
|
||||
|
||||
36
templates/stats.html
Normal file
36
templates/stats.html
Normal file
@@ -0,0 +1,36 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}Statistik{% endblock %}
|
||||
{% block content %}
|
||||
|
||||
<!-- Chart.js einbinden -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
||||
|
||||
<h3>Verteilung der Getränkesorten</h3>
|
||||
<canvas id="pieChart" width="400" height="200"></canvas>
|
||||
|
||||
<script>
|
||||
// Pie Chart für Getränketypen
|
||||
const pieChartLabels = {{ stats_drink_types | map(attribute='drink_type') | list | tojson }};
|
||||
const pieChartData = {{ stats_drink_types | map(attribute='count') | list | tojson }};
|
||||
const pieCtx = document.getElementById('pieChart').getContext('2d');
|
||||
new Chart(pieCtx, {
|
||||
type: 'doughnut',
|
||||
data: {
|
||||
labels: pieChartLabels,
|
||||
datasets: [{
|
||||
data: pieChartData,
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
plugins: {
|
||||
legend: { position: 'top' },
|
||||
title: {
|
||||
display: true,
|
||||
text: 'Verteilung der Getränkesorten'
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user