feat: revue de code, doc CODE_REVIEW, forfaits 2026, traduction LLM, providers avec modèle

Made-with: Cursor
This commit is contained in:
Sepehr Ramezani
2026-03-07 11:42:58 +01:00
parent 3d37ce4582
commit 473b3e26c7
181 changed files with 30617 additions and 7170 deletions

View File

@@ -2,16 +2,18 @@
Database-backed authentication service
Replaces JSON file storage with SQLAlchemy
"""
import os
import secrets
import hashlib
from datetime import datetime, timedelta
from datetime import datetime, timedelta, timezone
from typing import Optional, Dict, Any
import logging
# Try to import optional dependencies
try:
import jwt
JWT_AVAILABLE = True
except ImportError:
JWT_AVAILABLE = False
@@ -20,6 +22,7 @@ except ImportError:
try:
from passlib.context import CryptContext
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
PASSLIB_AVAILABLE = True
except ImportError:
@@ -65,24 +68,26 @@ def verify_password(plain_password: str, hashed_password: str) -> bool:
def create_access_token(user_id: str, expires_delta: Optional[timedelta] = None) -> str:
"""Create a JWT access token"""
expire = datetime.utcnow() + (expires_delta or timedelta(hours=ACCESS_TOKEN_EXPIRE_HOURS))
expire = datetime.now(timezone.utc) + (
expires_delta or timedelta(hours=ACCESS_TOKEN_EXPIRE_HOURS)
)
if not JWT_AVAILABLE:
token_data = {"user_id": user_id, "exp": expire.isoformat(), "type": "access"}
return base64.urlsafe_b64encode(json.dumps(token_data).encode()).decode()
to_encode = {"sub": user_id, "exp": expire, "type": "access"}
return jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
def create_refresh_token(user_id: str) -> str:
"""Create a JWT refresh token"""
expire = datetime.utcnow() + timedelta(days=REFRESH_TOKEN_EXPIRE_DAYS)
expire = datetime.now(timezone.utc) + timedelta(days=REFRESH_TOKEN_EXPIRE_DAYS)
if not JWT_AVAILABLE:
token_data = {"user_id": user_id, "exp": expire.isoformat(), "type": "refresh"}
return base64.urlsafe_b64encode(json.dumps(token_data).encode()).decode()
to_encode = {"sub": user_id, "exp": expire, "type": "refresh"}
return jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
@@ -93,12 +98,12 @@ def verify_token(token: str) -> Optional[Dict[str, Any]]:
try:
data = json.loads(base64.urlsafe_b64decode(token.encode()).decode())
exp = datetime.fromisoformat(data["exp"])
if exp < datetime.utcnow():
if exp < datetime.now(timezone.utc):
return None
return {"sub": data["user_id"], "type": data.get("type", "access")}
except Exception:
return None
try:
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
return payload
@@ -112,18 +117,18 @@ def create_user(email: str, name: str, password: str) -> User:
"""Create a new user in the database"""
with get_db_session() as db:
repo = UserRepository(db)
# Check if email already exists
existing = repo.get_by_email(email)
if existing:
raise ValueError("Email already registered")
password_hash = hash_password(password)
hashed = hash_password(password)
user = repo.create(
email=email,
name=name,
password_hash=password_hash,
plan=PlanType.FREE,
hashed_password=hashed,
tier="free",
)
return user
@@ -133,15 +138,15 @@ def authenticate_user(email: str, password: str) -> Optional[User]:
with get_db_session() as db:
repo = UserRepository(db)
user = repo.get_by_email(email)
if not user:
return None
if not verify_password(password, user.password_hash):
return None
# Update last login
repo.update(user.id, last_login_at=datetime.utcnow())
repo.update(user.id, last_login_at=datetime.now(timezone.utc))
return user
@@ -181,11 +186,15 @@ def use_credits(user_id: str, credits: int) -> bool:
return repo.use_credits(user_id, credits)
def increment_usage(user_id: str, docs: int = 0, pages: int = 0, api_calls: int = 0) -> bool:
def increment_usage(
user_id: str, docs: int = 0, pages: int = 0, api_calls: int = 0
) -> bool:
"""Increment user usage counters"""
with get_db_session() as db:
repo = UserRepository(db)
result = repo.increment_usage(user_id, docs=docs, pages=pages, api_calls=api_calls)
result = repo.increment_usage(
user_id, docs=docs, pages=pages, api_calls=api_calls
)
return result is not None
@@ -194,12 +203,12 @@ def check_usage_limits(user_id: str) -> Dict[str, Any]:
with get_db_session() as db:
repo = UserRepository(db)
user = repo.get_by_id(user_id)
if not user:
return {"allowed": False, "reason": "User not found"}
plan_config = PLANS.get(user.plan, PLANS[PlanType.FREE])
# Check document limit
docs_limit = plan_config["docs_per_month"]
if docs_limit > 0 and user.docs_translated_this_month >= docs_limit:
@@ -211,10 +220,12 @@ def check_usage_limits(user_id: str) -> Dict[str, Any]:
"limit": docs_limit,
"used": user.docs_translated_this_month,
}
return {
"allowed": True,
"docs_remaining": max(0, docs_limit - user.docs_translated_this_month) if docs_limit > 0 else -1,
"docs_remaining": max(0, docs_limit - user.docs_translated_this_month)
if docs_limit > 0
else -1,
"extra_credits": user.extra_credits,
}
@@ -224,22 +235,28 @@ def get_user_usage_stats(user_id: str) -> Dict[str, Any]:
with get_db_session() as db:
repo = UserRepository(db)
user = repo.get_by_id(user_id)
if not user:
return {}
plan_config = PLANS.get(user.plan, PLANS[PlanType.FREE])
return {
"docs_used": user.docs_translated_this_month,
"docs_limit": plan_config["docs_per_month"],
"docs_remaining": max(0, plan_config["docs_per_month"] - user.docs_translated_this_month) if plan_config["docs_per_month"] > 0 else -1,
"docs_remaining": max(
0, plan_config["docs_per_month"] - user.docs_translated_this_month
)
if plan_config["docs_per_month"] > 0
else -1,
"pages_used": user.pages_translated_this_month,
"extra_credits": user.extra_credits,
"max_pages_per_doc": plan_config["max_pages_per_doc"],
"max_file_size_mb": plan_config["max_file_size_mb"],
"allowed_providers": plan_config["providers"],
"api_access": plan_config.get("api_access", False),
"api_calls_used": user.api_calls_this_month if plan_config.get("api_access") else 0,
"api_calls_used": user.api_calls_this_month
if plan_config.get("api_access")
else 0,
"api_calls_limit": plan_config.get("api_calls_per_month", 0),
}