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

@@ -21,9 +21,9 @@ logger = logging.getLogger(__name__)
class RateLimitConfig:
"""Configuration for rate limiting"""
# Requests per window
requests_per_minute: int = 30
requests_per_hour: int = 200
requests_per_day: int = 1000
requests_per_minute: int = 120
requests_per_hour: int = 1000
requests_per_day: int = 5000
# Translation-specific limits
translations_per_minute: int = 10
@@ -35,7 +35,7 @@ class RateLimitConfig:
max_total_size_per_hour_mb: int = 500
# Burst protection
burst_limit: int = 10 # Max requests in 1 second
burst_limit: int = 30 # Max requests in 1 second
# Whitelist IPs (no rate limiting)
whitelist_ips: list = field(default_factory=lambda: ["127.0.0.1", "::1"])
@@ -270,6 +270,23 @@ class RateLimitManager:
async def check_request(self, request: Request) -> tuple[bool, str, str]:
"""Check if request is allowed, return (allowed, reason, client_id)"""
client_id = self.get_client_id(request)
# Prefer user ID for authenticated users (avoids shared limits behind proxy)
# Try to extract from already-set state (auth middleware ran first)
user_id = None
if hasattr(request, "state"):
user_id = getattr(request.state, "client_id", None)
if not user_id:
# Try to get user from auth header for better per-user limiting
auth_header = request.headers.get("Authorization", "")
if auth_header.startswith("Bearer "):
# Use a hash of the token as user identifier (no decoding needed)
import hashlib
token = auth_header[7:]
user_id = f"tok:{hashlib.sha256(token.encode()).hexdigest()[:16]}"
if user_id:
client_id = user_id
self._total_requests += 1
if self.is_whitelisted(client_id):
@@ -387,6 +404,19 @@ class RateLimitMiddleware(BaseHTTPMiddleware):
if request.url.path.startswith("/static"):
return await call_next(request)
# Skip rate limiting for lightweight GET endpoints (read-only, cacheable)
# These are config/fetch endpoints that don't consume resources
if request.method == "GET":
skip_paths = (
"/api/v1/languages",
"/api/v1/providers",
"/api/v1/auth/me",
"/api/v1/auth/usage",
"/api/v1/translations/", # status polling (uses job_id suffix)
)
if any(request.url.path.startswith(p) for p in skip_paths):
return await call_next(request)
# Check rate limit
allowed, reason, client_id = await self.manager.check_request(request)