Add sql syntax and refactor postpaid users and databases
This commit is contained in:
14
.vscode/settings.json
vendored
Normal file
14
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||
"editor.formatOnSave": true,
|
||||
"python.analysis.typeCheckingMode": "standard",
|
||||
"[html]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||
"editor.formatOnSave": false
|
||||
},
|
||||
"[python]": {
|
||||
"editor.defaultFormatter": "ms-python.python",
|
||||
"editor.formatOnSave": true
|
||||
},
|
||||
"cSpell.enabled": false
|
||||
}
|
||||
121
auth/oidc.py
121
auth/oidc.py
@@ -1,31 +1,49 @@
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
"""
|
||||
This module handles OpenID Connect (OIDC) authentication using FastAPI and Authlib.
|
||||
Routes:
|
||||
/login/oidc (GET): Initiates the OIDC login flow by redirecting the user to the identity provider's authorization endpoint.
|
||||
/authorize (ANY): Handles the callback from the identity provider, exchanges the authorization code for tokens, retrieves user profile information, stores it in the session, and ensures the user exists in the database.
|
||||
/logout (GET): Logs the user out by clearing session data and redirecting to the identity provider's logout endpoint.
|
||||
Environment Variables:
|
||||
OIDC_CLIENT_ID: The client ID for the OIDC application.
|
||||
OIDC_CLIENT_SECRET: The client secret for the OIDC application.
|
||||
OIDC_CONFIG_URL: The OIDC discovery document URL.
|
||||
OIDC_REDIRECT_URL: The redirect URI registered with the OIDC provider.
|
||||
OIDC_LOGOUT_URL: The logout endpoint URL (optional fallback).
|
||||
Dependencies:
|
||||
- FastAPI
|
||||
- Authlib
|
||||
- SQLAlchemy
|
||||
- python-dotenv
|
||||
- Starlette
|
||||
Session Keys:
|
||||
SESSION_KEY: Key for storing user profile information in the session ("user_authentik").
|
||||
"user_db_id": Key for storing the user's database ID in the session.
|
||||
Database:
|
||||
Uses SQLAlchemy to check for and create users in the 'users_postpaid' table.
|
||||
Functions:
|
||||
login(request): Starts the OIDC login process.
|
||||
authorize(request): Handles the OIDC callback, processes user info, and manages user records.
|
||||
logout(request): Logs the user out and redirects to the identity provider's logout endpoint.
|
||||
"""
|
||||
import os
|
||||
from fastapi import APIRouter
|
||||
from fastapi.responses import RedirectResponse
|
||||
from authlib.integrations.starlette_client import OAuth
|
||||
from starlette.requests import Request
|
||||
|
||||
from db.models import User, SessionLocal
|
||||
from sqlalchemy import text
|
||||
|
||||
from dotenv import load_dotenv
|
||||
import os
|
||||
|
||||
from db.models import (
|
||||
engine,
|
||||
create_postpaid_user,
|
||||
)
|
||||
|
||||
oauth = OAuth()
|
||||
router = APIRouter()
|
||||
SESSION_KEY = "user_authentik"
|
||||
|
||||
# OIDC-Provider konfigurieren (hier als Beispiel Auth0)
|
||||
# oauth.register(
|
||||
# name="auth0",
|
||||
# client_id="ZUZYpdYmqjMwdBDb2GdWWX4xkASNe2gsYqLlF9dy",
|
||||
# client_secret="o6LXTspeaiAMhvPyX2vplQ0RUsRGhthFadg1M5LOJylpQXm9A0d8YJ4CeNwq0kAg2BrdCM7UyfZFOlnVjrJS2o4fBvvhLWDfbd7LhScCzde4Heh5P3C26ZWCRGQppJhb",
|
||||
# authorize_url="https://login.fs.cs.uni-frankfurt.de/application/o/authorize/",
|
||||
# authorize_params=None,
|
||||
# access_token_url="https://login.fs.cs.uni-frankfurt.de/application/o/token/",
|
||||
# access_token_params=None,
|
||||
# client_kwargs={"scope": "openid profile email"},
|
||||
# server_metadata_url="https://login.fs.cs.uni-frankfurt.de/application/o/testkicker/.well-known/openid-configuration",
|
||||
# api_base_url="https://login.fs.cs.uni-frankfurt.de/application/o/testkicker/",
|
||||
# jwks_uri="https://login.fs.cs.uni-frankfurt.de/application/o/testkicker/jwks/",
|
||||
# userinfo_endpoint="https://login.fs.cs.uni-frankfurt.de/application/o/userinfo/",
|
||||
# )
|
||||
|
||||
load_dotenv()
|
||||
|
||||
@@ -41,43 +59,74 @@ oauth.register(
|
||||
|
||||
@router.get("/login/oidc")
|
||||
async def login(request: Request):
|
||||
"""
|
||||
Initiates the OAuth2 login flow using Auth0 and redirects the user to the Auth0 authorization endpoint.
|
||||
Args:
|
||||
request (Request): The incoming HTTP request object.
|
||||
Returns:
|
||||
Response: A redirect response to the Auth0 authorization URL.
|
||||
Raises:
|
||||
Exception: If the Auth0 client cannot be created or the redirect fails.
|
||||
Environment Variables:
|
||||
OIDC_REDIRECT_URL: The URL to which Auth0 should redirect after authentication.
|
||||
"""
|
||||
|
||||
auth0_client = oauth.create_client("auth0")
|
||||
redirect_uri = os.getenv("OIDC_REDIRECT_URL")
|
||||
return await auth0_client.authorize_redirect(request, redirect_uri)
|
||||
|
||||
@router.route("/authorize")
|
||||
async def authorize(request: Request):
|
||||
"""
|
||||
Handles the OAuth2 authorization callback, retrieves the user's profile from the identity provider,
|
||||
stores user information in the session, checks if the user exists in the database (and creates them if not),
|
||||
and redirects to the home page.
|
||||
Args:
|
||||
request (Request): The incoming HTTP request containing the OAuth2 callback.
|
||||
Returns:
|
||||
RedirectResponse: A redirect response to the home page after successful authorization and user handling.
|
||||
Side Effects:
|
||||
- Stores user profile and database user ID in the session.
|
||||
- May create a new user in the database if not already present.
|
||||
"""
|
||||
|
||||
token = await oauth.auth0.authorize_access_token(request)
|
||||
userinfo_endpoint = oauth.auth0.server_metadata.get("userinfo_endpoint")
|
||||
resp = await oauth.auth0.get(userinfo_endpoint, token=token)
|
||||
resp.raise_for_status()
|
||||
profile = resp.json()
|
||||
print("Profile:", profile)
|
||||
|
||||
# save user info in session
|
||||
request.session["user"] = profile
|
||||
request.session[SESSION_KEY] = profile
|
||||
|
||||
# check if user is already in the database
|
||||
db = SessionLocal()
|
||||
user_db = db.query(User).filter(User.username == profile["preferred_username"]).first()
|
||||
if not user_db:
|
||||
print("Create User in DB")
|
||||
user_db = User(
|
||||
username=profile["preferred_username"],
|
||||
role="user" # Default role
|
||||
)
|
||||
db.add(user_db)
|
||||
db.commit()
|
||||
db.refresh(user_db)
|
||||
db.close()
|
||||
|
||||
print("User in DB:", user_db)
|
||||
with engine.connect() as conn:
|
||||
t = text("SELECT id FROM users_postpaid WHERE username = :username")
|
||||
result = conn.execute(t, {"username": profile["preferred_username"]}).fetchone()
|
||||
if result:
|
||||
user_db_id = result[0]
|
||||
else:
|
||||
print("Create User in DB")
|
||||
user_db_id = create_postpaid_user(profile["preferred_username"])
|
||||
|
||||
request.session["user_db_id"] = user_db_id
|
||||
return RedirectResponse(url="/", status_code=303)
|
||||
|
||||
@router.get("/logout")
|
||||
async def logout(request: Request):
|
||||
request.session.pop("user", None)
|
||||
"""
|
||||
Logs out the current user by clearing session data and redirecting to the OIDC provider's logout endpoint.
|
||||
Args:
|
||||
request (Request): The incoming HTTP request containing the user's session.
|
||||
Returns:
|
||||
RedirectResponse: A response that redirects the user to the OIDC provider's logout URL with a 303 status code.
|
||||
Notes:
|
||||
- Removes the authentication session key and user database information from the session.
|
||||
- Determines the logout URL from the OIDC provider's metadata or an environment variable.
|
||||
"""
|
||||
|
||||
request.session.pop(SESSION_KEY, None)
|
||||
request.session.pop("user_db", None)
|
||||
logout_url = oauth.auth0.server_metadata.get("end_session_endpoint")
|
||||
if not logout_url:
|
||||
logout_url = os.getenv("OIDC_LOGOUT_URL")
|
||||
|
||||
@@ -1,31 +1,78 @@
|
||||
from fastapi import Depends, HTTPException
|
||||
"""
|
||||
This module provides session management utilities for postpaid users in a FastAPI application.
|
||||
It includes functions to log in a user by username, log out the current user, and retrieve the
|
||||
currently logged-in user from the session.
|
||||
|
||||
Functions:
|
||||
login_postpaid_user(request: Request, username: str):
|
||||
Raises HTTPException if the user is not found.
|
||||
|
||||
logout_user(request: Request):
|
||||
|
||||
get_current_user(request: Request):
|
||||
Retrieves the current user from the session, returning the user object if present.
|
||||
"""
|
||||
from fastapi import HTTPException
|
||||
from starlette.requests import Request
|
||||
from db.models import User, SessionLocal
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy import text
|
||||
|
||||
from starlette.requests import Request
|
||||
from db.models import User, get_db
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
SESSION_KEY = "user"
|
||||
from db.models import engine, get_postpaid_user
|
||||
|
||||
|
||||
def login_user(request: Request, db: Session, username: str):
|
||||
user = db.query(User).filter(User.username == username).first()
|
||||
if user:
|
||||
request.session[SESSION_KEY] = user.id
|
||||
return user
|
||||
SESSION_KEY = "user_db_id"
|
||||
|
||||
|
||||
def login_postpaid_user(request: Request, username: str):
|
||||
"""
|
||||
Logs in a postpaid user by their username and stores their user ID in the session.
|
||||
Args:
|
||||
request (Request): The incoming HTTP request object, which contains the session.
|
||||
username (str): The username of the postpaid user to log in.
|
||||
Returns:
|
||||
int or None: The user ID if the user is found and logged in; otherwise, None.
|
||||
Raises:
|
||||
HTTPException: If the user with the given username is not found (404 error).
|
||||
"""
|
||||
|
||||
t = text("SELECT id FROM users_postpaid WHERE username = :username")
|
||||
with engine.connect() as conn:
|
||||
result = conn.execute(t, {"username": username}).fetchone()
|
||||
if result:
|
||||
user_id = result[0]
|
||||
else:
|
||||
raise HTTPException(status_code=404, detail="User nicht gefunden")
|
||||
if user_id:
|
||||
request.session[SESSION_KEY] = user_id
|
||||
return user_id
|
||||
return None
|
||||
|
||||
|
||||
def logout_user(request: Request):
|
||||
"""
|
||||
Logs out the current user by removing their session data.
|
||||
This function removes the user's session key and any associated user database information
|
||||
from the session, effectively logging the user out.
|
||||
Args:
|
||||
request (Request): The incoming request object containing the session.
|
||||
Returns:
|
||||
None
|
||||
"""
|
||||
|
||||
request.session.pop(SESSION_KEY, None)
|
||||
request.session.pop("user_db", None)
|
||||
|
||||
|
||||
def get_current_user(request: Request, db: Session = Depends(get_db)):
|
||||
def get_current_user(request: Request):
|
||||
"""
|
||||
Retrieve the current user from the session in the given request.
|
||||
Args:
|
||||
request (Request): The incoming HTTP request containing the session.
|
||||
Returns:
|
||||
User or None: The user object associated with the session if present, otherwise None.
|
||||
"""
|
||||
|
||||
user_id = request.session.get(SESSION_KEY)
|
||||
# if user_id is None:
|
||||
# raise HTTPException(status_code=401, detail="Nicht eingeloggt")
|
||||
# user = db.query(User).filter(User.id == user_id).first()
|
||||
user = user_id
|
||||
if not user_id:
|
||||
return None
|
||||
user = get_postpaid_user(user_id)
|
||||
return user
|
||||
|
||||
203
db/models.py
203
db/models.py
@@ -1,38 +1,189 @@
|
||||
from sqlalchemy import Column, Integer, String, Float
|
||||
from sqlalchemy.orm import declarative_base, sessionmaker, Session
|
||||
from sqlalchemy import create_engine
|
||||
from fastapi import Depends
|
||||
"""
|
||||
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.
|
||||
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.
|
||||
"""
|
||||
from sqlalchemy import create_engine, text
|
||||
from fastapi import HTTPException
|
||||
|
||||
DATABASE_URL = "sqlite:///./test.db"
|
||||
|
||||
engine = create_engine(DATABASE_URL, connect_args={"check_same_thread": False})
|
||||
SessionLocal = sessionmaker(bind=engine)
|
||||
|
||||
Base = declarative_base()
|
||||
with engine.connect() as conn:
|
||||
# Create a new table for postpaid users
|
||||
conn.execute(text("""
|
||||
CREATE TABLE IF NOT EXISTS users_postpaid (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
username TEXT NOT NULL UNIQUE,
|
||||
money INT DEFAULT 0,
|
||||
activated BOOLEAN DEFAULT 0,
|
||||
last_drink TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
)
|
||||
"""))
|
||||
|
||||
conn.execute(text("""
|
||||
CREATE TABLE IF NOT EXISTS users_prepaid (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
username TEXT NOT NULL UNIQUE,
|
||||
user_key TEXT NOT NULL UNIQUE,
|
||||
postpaid_user_id INTEGER NOT NULL,
|
||||
money INT DEFAULT 0,
|
||||
activated BOOLEAN DEFAULT 0,
|
||||
last_drink TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (postpaid_user_id) REFERENCES users_postpaid(id)
|
||||
)
|
||||
"""))
|
||||
|
||||
conn.execute(text("""
|
||||
CREATE TABLE IF NOT EXISTS drinks (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
postpaid_user_id INTEGER,
|
||||
prepaid_user_id INTEGER,
|
||||
timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
drink_type TEXT,
|
||||
FOREIGN KEY (postpaid_user_id) REFERENCES users_postpaid(id),
|
||||
FOREIGN KEY (prepaid_user_id) REFERENCES users_prepaid(id)
|
||||
)
|
||||
"""))
|
||||
conn.commit()
|
||||
|
||||
|
||||
class User(Base):
|
||||
__tablename__ = "users"
|
||||
|
||||
id = Column(Integer, primary_key=True)
|
||||
username = Column(String, unique=True, index=True)
|
||||
role = Column(String) # z. B. "admin" oder "user"
|
||||
money = Column(Float, default=0) # money of user
|
||||
def create_postpaid_user(username: str):
|
||||
"""
|
||||
Creates a new postpaid user with the given username in the users_postpaid table.
|
||||
Args:
|
||||
username (str): The username of the user to be created.
|
||||
Raises:
|
||||
HTTPException: If the user already exists (status_code=400).
|
||||
HTTPException: If the user could not be created due to a database error (status_code=500).
|
||||
Returns:
|
||||
int: The ID of the newly created user.
|
||||
"""
|
||||
|
||||
def create_user(db: Session, username: str, role: str):
|
||||
db_user = User(username=username, role=role)
|
||||
db.add(db_user)
|
||||
db.commit()
|
||||
db.refresh(db_user)
|
||||
return db_user
|
||||
t = text("INSERT INTO users_postpaid (username) VALUES (:username)")
|
||||
with engine.connect() as connection:
|
||||
t = text("SELECT * FROM users_postpaid WHERE username = :username")
|
||||
if connection.execute(t, {"username": username}).fetchone():
|
||||
raise HTTPException(status_code=400, detail="User already exists")
|
||||
try:
|
||||
res = connection.execute(t, {"username": username})
|
||||
if res.rowcount == 0:
|
||||
raise HTTPException(status_code=500, detail="Failed to create user")
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=f"Database error: {str(e)}") from e
|
||||
connection.commit()
|
||||
|
||||
def get_user(db: Session, user_id: int):
|
||||
return db.query(User).filter(User.id == user_id).first()
|
||||
return res.lastrowid
|
||||
|
||||
def get_postpaid_user(user_id: int):
|
||||
"""
|
||||
Retrieve a postpaid user's information from the database by user ID.
|
||||
Args:
|
||||
user_id (int): The unique identifier of the user to retrieve.
|
||||
Returns:
|
||||
dict: A dictionary containing the user's id, username, money, activated status, and last_drink timestamp.
|
||||
Raises:
|
||||
HTTPException: If no user with the given ID is found, raises a 404 HTTPException.
|
||||
"""
|
||||
|
||||
t = text("SELECT id, username, money, activated, last_drink FROM users_postpaid WHERE id = :id")
|
||||
user_db = {}
|
||||
with engine.connect() as connection:
|
||||
result = connection.execute(t, {"id": user_id}).fetchone()
|
||||
if result:
|
||||
user_db["id"] = result[0]
|
||||
user_db["username"] = result[1]
|
||||
user_db["money"] = result[2]
|
||||
user_db["activated"] = result[3]
|
||||
user_db["last_drink"] = result[4]
|
||||
else:
|
||||
raise HTTPException(status_code=404, detail="User not found")
|
||||
return user_db
|
||||
|
||||
def get_db():
|
||||
db = SessionLocal()
|
||||
try:
|
||||
yield db
|
||||
finally:
|
||||
db.close()
|
||||
def get_postpaid_user_by_username(username: str):
|
||||
"""
|
||||
Retrieve a postpaid user from the database by their username.
|
||||
Args:
|
||||
username (str): The username of the user to retrieve.
|
||||
Returns:
|
||||
dict: A dictionary containing the user's id, username, money, activated status, and last_drink timestamp.
|
||||
Raises:
|
||||
HTTPException: If no user with the given username is found, raises a 404 HTTPException.
|
||||
"""
|
||||
|
||||
t = text("SELECT id, username, money, activated, last_drink FROM users_postpaid WHERE username = :username")
|
||||
user_db = {}
|
||||
with engine.connect() as connection:
|
||||
result = connection.execute(t, {"username": username}).fetchone()
|
||||
if result:
|
||||
user_db["id"] = result[0]
|
||||
user_db["username"] = result[1]
|
||||
user_db["money"] = result[2]
|
||||
user_db["activated"] = result[3]
|
||||
user_db["last_drink"] = result[4]
|
||||
else:
|
||||
raise HTTPException(status_code=404, detail="User not found")
|
||||
return user_db
|
||||
|
||||
def set_postpaid_user_money(user_id: int, money: float):
|
||||
"""
|
||||
Updates the 'money' value for a postpaid user in the database.
|
||||
Args:
|
||||
user_id (int): The ID of the user whose balance is to be updated.
|
||||
money (float): The new balance to set for the user.
|
||||
Raises:
|
||||
HTTPException: If no user with the given ID is found (404 error).
|
||||
Returns:
|
||||
int: The number of rows affected by the update operation.
|
||||
"""
|
||||
|
||||
print(f"set_postpaid_user_money: {user_id}, {money}")
|
||||
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})
|
||||
if result.rowcount == 0:
|
||||
raise HTTPException(status_code=404, detail="User not found")
|
||||
connection.commit()
|
||||
return result.rowcount
|
||||
|
||||
def drink_postpaid_user(user_id: int):
|
||||
"""
|
||||
Deducts 100 units from the specified postpaid user's balance and records a drink entry.
|
||||
Args:
|
||||
user_id (int): The ID of the postpaid user.
|
||||
Raises:
|
||||
HTTPException: If the user is not found (404) or if the drink entry could not be created (500).
|
||||
Returns:
|
||||
int: The number of rows affected by the drink entry insertion (should be 1 on success).
|
||||
"""
|
||||
|
||||
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})
|
||||
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 (postpaid_user_id, timestamp) VALUES (:postpaid_user_id, CURRENT_TIMESTAMP)")
|
||||
result = connection.execute(t, {"postpaid_user_id": user_id})
|
||||
if result.rowcount == 0:
|
||||
raise HTTPException(status_code=500, detail="Failed to create drink entry")
|
||||
connection.commit()
|
||||
return result.rowcount
|
||||
|
||||
136
main.py
136
main.py
@@ -4,14 +4,19 @@ from fastapi.staticfiles import StaticFiles
|
||||
from fastapi.templating import Jinja2Templates
|
||||
from starlette.middleware.sessions import SessionMiddleware
|
||||
|
||||
from db.models import Base, engine, get_db, User
|
||||
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 auth.session import get_current_user
|
||||
|
||||
from auth import oidc
|
||||
|
||||
import uvicorn
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
|
||||
ADMIN_GROUP = "Fachschaft Admins"
|
||||
@@ -23,60 +28,121 @@ app.include_router(oidc.router)
|
||||
app.mount("/static", StaticFiles(directory="static"), name="static")
|
||||
templates = Jinja2Templates(directory="templates")
|
||||
|
||||
# DB
|
||||
Base.metadata.create_all(bind=engine)
|
||||
|
||||
@app.get("/", response_class=HTMLResponse)
|
||||
def home(request: Request, user: User = Depends(get_current_user), db: Session = Depends(get_db)):
|
||||
if not user:
|
||||
def home(request: Request):
|
||||
user_db_id = request.session.get("user_db_id")
|
||||
user_authentik = request.session.get("user_authentik")
|
||||
if not user_db_id or not user_authentik:
|
||||
return RedirectResponse(url="/login", status_code=303)
|
||||
db_user = db.query(User).filter_by(username=user["preferred_username"]).first()
|
||||
if not db_user:
|
||||
|
||||
user_db_id = request.session.get("user_db_id")
|
||||
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")
|
||||
users = None
|
||||
if ADMIN_GROUP in user["groups"]:
|
||||
users = db.query(User).all()
|
||||
return templates.TemplateResponse("index.html", {"request": request, "user": user, "users": users, "db_user": db_user})
|
||||
if ADMIN_GROUP in user_authentik["groups"]:
|
||||
with engine.connect() as conn:
|
||||
t = text("SELECT id FROM users_postpaid")
|
||||
result = conn.execute(t).fetchall()
|
||||
if result:
|
||||
users = []
|
||||
for row in result:
|
||||
user_db = get_postpaid_user(row[0])
|
||||
if user_db:
|
||||
users.append(user_db)
|
||||
db_user = get_postpaid_user(user_db_id)
|
||||
return templates.TemplateResponse("index.html", {"request": request, "user": user_authentik, "users": users, "user_db_id": user_db_id, "db_user": db_user})
|
||||
|
||||
@app.get("/login", response_class=HTMLResponse)
|
||||
def login_form(request: Request):
|
||||
"""
|
||||
Renders the login form template.
|
||||
Args:
|
||||
request (Request): The incoming HTTP request object.
|
||||
Returns:
|
||||
TemplateResponse: The rendered login.html template with the request context.
|
||||
"""
|
||||
|
||||
return templates.TemplateResponse("login.html", {"request": request})
|
||||
|
||||
@app.post("/set_money")
|
||||
def set_money(request: Request, username: str = Form(...), money: float = Form(...), db: Session = Depends(get_db), user: User = Depends(get_current_user)):
|
||||
if not user or ADMIN_GROUP not in user["groups"]:
|
||||
@app.post("/set_money_postpaid")
|
||||
def set_money_postpaid(request: Request, username = Form(...), money: float = Form(...)):
|
||||
"""
|
||||
Handles a POST request to set the postpaid money balance for a specified user.
|
||||
Args:
|
||||
request (Request): The incoming HTTP request, containing session information for authentication.
|
||||
username (str, Form): The username of the user whose postpaid balance is to be set (provided via form data).
|
||||
money (float, Form): The new balance amount to set for the user (provided via form data).
|
||||
Raises:
|
||||
HTTPException:
|
||||
- 403 if the current user is not authenticated as an admin.
|
||||
- 404 if the specified user does not exist.
|
||||
Returns:
|
||||
RedirectResponse: Redirects to the home page ("/") with a 303 status code upon successful update.
|
||||
"""
|
||||
|
||||
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="Nicht erlaubt")
|
||||
db_user = db.query(User).filter_by(username=username).first()
|
||||
if not db_user:
|
||||
raise HTTPException(status_code=404, detail="User nicht gefunden")
|
||||
db_user.money = money*100
|
||||
db.commit()
|
||||
|
||||
with engine.connect() as conn:
|
||||
t = text("SELECT id FROM users_postpaid WHERE username = :username")
|
||||
result = conn.execute(t, {"username": username}).fetchone()
|
||||
if result:
|
||||
requested_user_id = result[0]
|
||||
else:
|
||||
raise HTTPException(status_code=404, detail="User nicht gefunden")
|
||||
|
||||
set_postpaid_user_money(requested_user_id, money*100)
|
||||
return RedirectResponse(url="/", status_code=303)
|
||||
|
||||
@app.post("/drink")
|
||||
def drink(request: Request, db: Session = Depends(get_db), user: User = Depends(get_current_user)):
|
||||
if not user or ADMIN_GROUP not in user["groups"]:
|
||||
def drink(request: Request):
|
||||
"""
|
||||
Handles a drink purchase request for a user.
|
||||
Checks if the user is authenticated and belongs to the admin group. If not, raises a 403 error.
|
||||
Verifies that the user's database ID is present in the session; if not, raises a 404 error.
|
||||
Retrieves the current user's balance and processes the drink purchase.
|
||||
Redirects the user to the homepage after processing.
|
||||
Args:
|
||||
request (Request): The incoming HTTP request 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:
|
||||
RedirectResponse: Redirects to the homepage after processing the drink purchase.
|
||||
"""
|
||||
|
||||
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="Nicht erlaubt")
|
||||
db_user = db.query(User).filter_by(username=user["preferred_username"]).first()
|
||||
if not db_user:
|
||||
|
||||
user_db_id = request.session.get("user_db_id")
|
||||
if not user_db_id:
|
||||
raise HTTPException(status_code=404, detail="User nicht gefunden")
|
||||
db_user.money -= 100
|
||||
db.commit()
|
||||
|
||||
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(...), db: Session = Depends(get_db), user: User = Depends(get_current_user)):
|
||||
if not user or ADMIN_GROUP not in user["groups"]:
|
||||
def payup(request: Request, username: str = Form(...), money: float = 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")
|
||||
db_user = db.query(User).filter_by(username=username).first()
|
||||
if not db_user:
|
||||
|
||||
user_db_id = get_postpaid_user_by_username(username)["id"]
|
||||
if not user_db_id:
|
||||
raise HTTPException(status_code=404, detail="User nicht gefunden")
|
||||
db_user.money += money*100
|
||||
current_user = db.query(User).filter_by(username=user["preferred_username"]).first()
|
||||
if not current_user:
|
||||
|
||||
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="Aktueller User nicht gefunden")
|
||||
current_user.money -= money*100
|
||||
db.commit()
|
||||
current_user_money = get_postpaid_user(current_user_db_id)["money"]
|
||||
set_postpaid_user_money(current_user_db_id, current_user_money - money*100)
|
||||
return RedirectResponse(url="/", status_code=303)
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -15,8 +15,7 @@
|
||||
<h1>Getränkeliste</h1>
|
||||
{% if user %}
|
||||
<p>
|
||||
Angemeldet als {{ user.preferred_username }}{% if 'Fachschaft
|
||||
Admins' in user.groups %} (Admin){% endif %} –
|
||||
Angemeldet als {{ user.preferred_username }}{% if 'Fachschaft Admins' in user.groups %} (Admin){% endif %} –
|
||||
<a href="/logout">Logout</a>
|
||||
</p>
|
||||
{% endif %}
|
||||
@@ -105,18 +104,20 @@
|
||||
<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">Role</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 in users %}
|
||||
{% for db_user_i in users %}
|
||||
<tr
|
||||
{%
|
||||
if
|
||||
db_user.money
|
||||
<="-5000"
|
||||
db_user_i.money
|
||||
<=-5000
|
||||
%}
|
||||
style="background-color: rgba(179, 6, 44, 0.5)"
|
||||
{%
|
||||
@@ -124,11 +125,19 @@
|
||||
%}
|
||||
>
|
||||
<td style="padding: 0.5em 1em">
|
||||
{{ db_user.username }}
|
||||
{{ db_user_i.id }}
|
||||
</td>
|
||||
<td style="padding: 0.5em 1em">{{ db_user.role }}</td>
|
||||
<td style="padding: 0.5em 1em">
|
||||
{{ db_user.money / 100 }}
|
||||
{{ 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 %}
|
||||
@@ -137,7 +146,7 @@
|
||||
<p>Set user money:</p>
|
||||
<form
|
||||
method="post"
|
||||
action="/set_money"
|
||||
action="/set_money_postpaid"
|
||||
style="
|
||||
display: flex;
|
||||
gap: 1em;
|
||||
|
||||
Reference in New Issue
Block a user