llmproxy/backend/crud.py
Oliver Hofmann cfa874a4c3 Fix medium/low priority review items; update README
Medium:
- Frontend: Error-Handling in fetchUsers/fetchApiKeys (try/catch)
- Frontend: Loading-Race behoben (Promise.all + .finally)
- Frontend: API-Keys maskiert (nur letzte 4 Zeichen sichtbar)
- Tests: Setup-Code aus test_auth.py in conftest.py konsolidiert
- Tests: Fixture-Scope vereinheitlicht (function statt session)

Low:
- bare except in database.py → except Exception
- datetime.utcnow → datetime.now(timezone.utc) durchgängig
- DateTime(timezone=True) in allen Modell-Spalten
- .gitignore hinzugefügt (.env, *.db, __pycache__, .idea, node_modules)

Docs:
- README aktualisiert (Sicherheit, Konfiguration, Projektstruktur, Tests)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-27 21:48:26 +02:00

123 lines
3.8 KiB
Python

import secrets
import hashlib
import bcrypt
import tiktoken
from datetime import datetime, timezone
from sqlalchemy.orm import Session
from models import APIKey, User, Quota, Usage
_encoder = tiktoken.get_encoding("cl100k_base")
def count_tokens(text: str) -> int:
return len(_encoder.encode(text))
def hash_password(password: str) -> str:
return bcrypt.hashpw(password.encode(), bcrypt.gensalt()).decode()
def verify_password(plain_password: str, hashed_password: str) -> bool:
return bcrypt.checkpw(plain_password.encode(), hashed_password.encode())
def _hash_api_key(key: str) -> str:
return hashlib.sha256(key.encode()).hexdigest()
def get_user_by_username(db: Session, username: str):
return db.query(User).filter(User.username == username).first()
def get_user_by_email(db: Session, email: str):
return db.query(User).filter(User.email == email).first()
def generate_api_key():
return "sk-" + secrets.token_urlsafe(32)
def create_user(db: Session, username: str, email: str, password: str, is_admin: bool = False):
db_user = User(
username=username,
email=email,
hashed_password=hash_password(password),
is_admin=is_admin,
)
db.add(db_user)
db.commit()
db.refresh(db_user)
default_quota = Quota(
user_id=db_user.id,
daily_tokens=1000000,
monthly_tokens=10000000,
daily_requests=1000,
monthly_requests=10000
)
db.add(default_quota)
db.commit()
return db_user
def create_api_key(db: Session, user_id: int, name: str) -> tuple[APIKey, str]:
raw_key = generate_api_key()
db_key = APIKey(
name=name,
key=_hash_api_key(raw_key),
user_id=user_id
)
db.add(db_key)
db.commit()
db.refresh(db_key)
return db_key, raw_key
def verify_api_key(db: Session, api_key: str):
key_hash = _hash_api_key(api_key)
return db.query(APIKey).filter(APIKey.key == key_hash, APIKey.is_active == True).first()
def get_quota(db: Session, user_id: int):
return db.query(Quota).filter(Quota.user_id == user_id).first()
def get_quota_by_user_id(db: Session, user_id: int):
return db.query(Quota).filter(Quota.user_id == user_id).first()
def get_usage(db: Session, user_id: int):
return db.query(Usage).filter(Usage.user_id == user_id).first()
def check_and_increment_quota(db: Session, user_id: int, tokens: int = 0, requests: int = 1) -> bool:
usage = (
db.query(Usage)
.filter(Usage.user_id == user_id)
.with_for_update()
.first()
)
if not usage:
usage = Usage(user_id=user_id)
db.add(usage)
db.flush()
now = datetime.now(timezone.utc)
if usage.daily_reset_at.date() < now.date():
usage.tokens_used_today = 0
usage.requests_today = 0
usage.daily_reset_at = now
if (usage.monthly_reset_at.year, usage.monthly_reset_at.month) < (now.year, now.month):
usage.tokens_used_month = 0
usage.requests_month = 0
usage.monthly_reset_at = now
quota = get_quota(db, user_id)
allowed = True
if quota:
if quota.daily_tokens and (usage.tokens_used_today + tokens) > quota.daily_tokens:
allowed = False
elif quota.monthly_tokens and (usage.tokens_used_month + tokens) > quota.monthly_tokens:
allowed = False
elif quota.daily_requests and (usage.requests_today + requests) > quota.daily_requests:
allowed = False
elif quota.monthly_requests and (usage.requests_month + requests) > quota.monthly_requests:
allowed = False
if allowed:
usage.tokens_used_today += tokens
usage.tokens_used_month += tokens
usage.requests_today += requests
usage.requests_month += requests
db.commit()
return allowed