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:
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user