🧙‍♂️ Transform LifeRPG into The Wizard's Grimoire - Production-Ready Application

 Major Features Added:
- Complete magical theming and rebranding from LifeRPG to The Wizard's Grimoire
- Production-grade React frontend with Tailwind CSS v4 and magical aesthetics
- Comprehensive analytics dashboard with Recharts integration (ScryingPortal)
- Push notifications system with PWA service worker support
- Drag & drop functionality using @dnd-kit for habit reordering
- Social features with friends system and leaderboards
- Performance optimization tools and monitoring
- Mobile app enhancement with PWA installation support

🏗️ Technical Infrastructure:
- Advanced service worker with offline support and background sync
- Zustand state management for scalable application state
- Production-ready UI component system with enhanced Button, Card, Input
- Progressive Web App (PWA) with manifest and app installation
- FastAPI backend with comprehensive API endpoints
- Docker containerization and CI/CD pipeline setup

📱 Progressive Web App Features:
- Offline functionality with intelligent caching
- Push notification support for habit reminders
- App installation on mobile and desktop platforms
- Background sync for offline data management
- Performance monitoring and optimization tools

🎨 User Experience:
- Magical wizard/grimoire theming throughout application
- Responsive design optimized for all device sizes
- Drag & drop habit management with smooth animations
- Interactive analytics with multiple chart types
- Social connectivity with friends and competitive features
- Comprehensive notification and performance settings

🔧 Developer Experience:
- Modern development stack with Vite and React
- Comprehensive testing setup and CI/CD pipelines
- Code quality tools with pre-commit hooks
- Docker development environment
- Detailed documentation and implementation guides

This represents a complete transformation from prototype to production-ready application with enterprise-grade features and magical user experience.
This commit is contained in:
TLimoges33
2025-08-30 17:32:42 +00:00
committed by GitHub
parent 00ad1bd8d4
commit 7fe4ae5365
270 changed files with 46366 additions and 7824 deletions
+153
View File
@@ -0,0 +1,153 @@
import time
from typing import Dict, Tuple, Optional
from starlette.middleware.base import BaseHTTPMiddleware
from starlette.requests import Request
from starlette.responses import JSONResponse, Response
from config import settings
class BodySizeLimitMiddleware(BaseHTTPMiddleware):
def __init__(self, app, max_body_bytes: int):
super().__init__(app)
self.max_body_bytes = max_body_bytes
async def dispatch(self, request: Request, call_next):
# Skip when no body (GET/DELETE/etc.)
if request.method in {"GET", "DELETE", "OPTIONS", "HEAD"}:
return await call_next(request)
cl = request.headers.get("content-length")
try:
if cl and int(cl) > self.max_body_bytes:
return JSONResponse({"detail": "request entity too large"}, status_code=413)
except Exception:
pass
# Read body once and reuse cached body downstream
body = await request.body()
if len(body) > self.max_body_bytes:
return JSONResponse({"detail": "request entity too large"}, status_code=413)
# Starlette caches body in request, so downstream can still call .json()/.form()
return await call_next(request)
class RateLimitMiddleware(BaseHTTPMiddleware):
"""Per-IP rate limiter (windowed per minute).
Uses Redis when REDIS_URL is configured; falls back to in-memory otherwise.
"""
def __init__(self, app, requests_per_minute: int):
super().__init__(app)
self.rpm = max(1, int(requests_per_minute))
self._counts: Dict[Tuple[str, int], int] = {}
self._redis = self._init_redis()
def _init_redis(self):
import os
url = os.getenv('REDIS_URL')
if not url:
return None
try:
from redis import Redis
return Redis.from_url(url)
except Exception:
return None
def _client_ip(self, request: Request) -> str:
# Prefer X-Forwarded-For first value if provided by a trusted proxy
xff = request.headers.get("x-forwarded-for")
if xff:
return xff.split(",")[0].strip()
client = request.client
return client.host if client else "unknown"
async def dispatch(self, request: Request, call_next):
# Don't limit CORS preflights
if request.method == "OPTIONS":
return await call_next(request)
now = int(time.time())
window = now // 60
ip = self._client_ip(request)
if self._redis is not None:
# Use a single Redis counter per ip+window
rkey = f"rl:{ip}:{window}"
try:
current = self._redis.incr(rkey)
if current == 1:
# Set TTL to end of current minute
self._redis.expire(rkey, 60 - (now % 60))
if current > self.rpm:
retry_after = 60 - (now % 60)
return JSONResponse(
{"detail": "rate limit exceeded"},
status_code=429,
headers={
"Retry-After": str(retry_after),
"X-RateLimit-Limit": str(self.rpm),
"X-RateLimit-Remaining": "0",
},
)
resp: Response = await call_next(request)
remaining = max(0, self.rpm - int(current))
resp.headers.setdefault("X-RateLimit-Limit", str(self.rpm))
resp.headers.setdefault("X-RateLimit-Remaining", str(remaining))
return resp
except Exception:
# If Redis fails, fall back to memory
pass
# In-memory windowing fallback
key = (ip, window)
count = self._counts.get(key, 0)
if count >= self.rpm:
retry_after = 60 - (now % 60)
return JSONResponse(
{"detail": "rate limit exceeded"},
status_code=429,
headers={
"Retry-After": str(retry_after),
"X-RateLimit-Limit": str(self.rpm),
"X-RateLimit-Remaining": "0",
},
)
self._counts[key] = count + 1
resp: Response = await call_next(request)
remaining = max(0, self.rpm - self._counts.get(key, 0))
resp.headers.setdefault("X-RateLimit-Limit", str(self.rpm))
resp.headers.setdefault("X-RateLimit-Remaining", str(remaining))
return resp
class CSRFMiddleware(BaseHTTPMiddleware):
"""Double-submit cookie CSRF protection for cookie-authenticated, state-changing requests.
Enforced when settings.CSRF_ENABLE is true and request has a session cookie and no Bearer token.
Excludes safe methods and OPTIONS.
"""
SAFE_METHODS = {"GET", "HEAD", "OPTIONS"}
def __init__(self, app):
super().__init__(app)
async def dispatch(self, request: Request, call_next):
if not settings.CSRF_ENABLE:
return await call_next(request)
if request.method in self.SAFE_METHODS:
return await call_next(request)
# If using Bearer token, skip CSRF (not cookie-based auth)
auth = request.headers.get('authorization') or request.headers.get('Authorization')
if auth and auth.lower().startswith('bearer '):
return await call_next(request)
# Only enforce if session cookie present
if request.cookies.get('session'):
header = request.headers.get(settings.CSRF_HEADER_NAME) or request.headers.get(settings.CSRF_HEADER_NAME.upper())
cookie = request.cookies.get(settings.CSRF_COOKIE_NAME)
if not header or not cookie or header != cookie:
return JSONResponse({"detail": "CSRF token missing or invalid"}, status_code=403)
return await call_next(request)