feat: homelab deployment - NPM + IONOS DNS + monitoring + NAS backup

- Restructured docker-compose for Nginx Proxy Manager (no custom nginx)
- Added domain wordly.art configuration
- Added Prometheus + Grafana monitoring stack with pre-configured dashboards
- Added PostgreSQL backup script to NAS (daily/weekly/monthly rotation)
- Added alert rules for backend, system, and Docker metrics
- Updated deployment guide for NPM + IONOS DNS homelab setup
- Added marketing plan document
- PDF translator and watermark support
- Enhanced middleware, routes, and translator modules

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-10 11:43:28 +02:00
parent 16ac7ca2b9
commit ce8e150a61
110 changed files with 6935 additions and 4301 deletions

View File

@@ -11,17 +11,15 @@ from typing import Optional
logger = logging.getLogger(__name__)
from fastapi import APIRouter, Depends, Request, HTTPException
from fastapi import APIRouter, Depends, Request
from fastapi.responses import JSONResponse
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
from pydantic import BaseModel, Field
from services.auth_service import verify_token, get_user_by_id
from routes.deps import ProUser, require_pro_user
from database.connection import get_sync_session
from database.models import ApiKey
router = APIRouter(prefix="/api/v1/api-keys", tags=["API Keys v1"])
security = HTTPBearer(auto_error=False)
MAX_API_KEYS_PER_USER = 10
@@ -38,95 +36,6 @@ class ApiKeyResponse(BaseModel):
created_at: str
class ProUser:
"""Wrapper for authenticated Pro user with tier info"""
def __init__(self, user):
self._user = user
self.id = user.id
self.email = getattr(user, "email", None)
self._tier = None
@property
def tier(self) -> str:
if self._tier is None:
user_tier = getattr(self._user, "tier", None)
if user_tier:
self._tier = user_tier
else:
plan_value = getattr(self._user, "plan", None)
if plan_value and hasattr(plan_value, "value"):
if plan_value.value in ("pro", "business", "enterprise"):
self._tier = "pro"
else:
self._tier = "free"
else:
self._tier = "free"
return self._tier
def _require_auth(
credentials: Optional[HTTPAuthorizationCredentials] = Depends(security),
):
"""Dependency that requires a valid JWT token (any authenticated user)"""
if not credentials:
raise HTTPException(
status_code=401,
detail={
"error": "UNAUTHORIZED",
"message": "Authentification requise",
},
)
payload = verify_token(credentials.credentials)
if not payload:
raise HTTPException(
status_code=401,
detail={
"error": "UNAUTHORIZED",
"message": "Token invalide ou expiré",
},
)
sub = payload.get("sub")
if not sub or not isinstance(sub, str):
raise HTTPException(
status_code=401,
detail={
"error": "UNAUTHORIZED",
"message": "Token invalide",
},
)
user = get_user_by_id(sub)
if not user:
raise HTTPException(
status_code=401,
detail={
"error": "UNAUTHORIZED",
"message": "Utilisateur non trouvé",
},
)
return user
def _require_pro_user(user=Depends(_require_auth)) -> ProUser:
"""Dependency that requires a valid Pro user JWT token"""
pro_user = ProUser(user)
if pro_user.tier != "pro":
raise HTTPException(
status_code=403,
detail={
"error": "PRO_FEATURE_REQUIRED",
"message": "Cette fonctionnalité nécessite un abonnement Pro",
},
)
return pro_user
def _generate_api_key() -> tuple[str, str, str]:
"""
Generate a secure API key with sk_live_ prefix.
@@ -146,7 +55,7 @@ def _generate_api_key() -> tuple[str, str, str]:
async def create_api_key(
request: Request,
body: Optional[ApiKeyCreateRequest] = None,
user: ProUser = Depends(_require_pro_user),
user: ProUser = Depends(require_pro_user),
):
"""
Create a new API key for the authenticated Pro user.
@@ -213,7 +122,7 @@ async def create_api_key(
@router.get("")
async def list_api_keys(
request: Request,
user: ProUser = Depends(_require_pro_user),
user: ProUser = Depends(require_pro_user),
):
"""
List all API keys for the authenticated Pro user.
@@ -260,7 +169,7 @@ async def list_api_keys(
@router.delete("/{key_id}")
async def revoke_api_key(
key_id: str,
user: ProUser = Depends(_require_pro_user),
user: ProUser = Depends(require_pro_user),
):
"""
Revoke an API key for the authenticated Pro user.