🧙♂️ 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:
@@ -0,0 +1,150 @@
|
||||
"""Lightweight notifier for backend events (sync success/failure).
|
||||
|
||||
Currently supports Slack via incoming webhook stored as an OAuthToken on a
|
||||
Slack integration for the same user. Best-effort; failures are logged but do
|
||||
not raise.
|
||||
"""
|
||||
from typing import Dict, Any, List
|
||||
import requests
|
||||
import smtplib
|
||||
from email.message import EmailMessage
|
||||
|
||||
|
||||
def _slack_webhooks_for_user(db, user_id: int) -> List[str]:
|
||||
from . import models
|
||||
from .crypto import decrypt_text
|
||||
out: List[str] = []
|
||||
rows = db.query(models.Integration).filter_by(user_id=user_id, provider='slack').all()
|
||||
for integ in rows:
|
||||
tok = (
|
||||
db.query(models.OAuthToken)
|
||||
.filter_by(integration_id=integ.id)
|
||||
.order_by(models.OAuthToken.id.desc())
|
||||
.first()
|
||||
)
|
||||
if tok and tok.access_token:
|
||||
try:
|
||||
url = decrypt_text(tok.access_token)
|
||||
if url:
|
||||
out.append(url)
|
||||
except Exception:
|
||||
continue
|
||||
return out
|
||||
|
||||
|
||||
def emit_sync_event(db, integration_id: int, event: str, payload: Dict[str, Any]):
|
||||
"""Emit a sync event notification to configured channels.
|
||||
|
||||
For now, if the owning user has a Slack integration, post a message.
|
||||
"""
|
||||
from . import models
|
||||
from .metrics import log_job_event
|
||||
|
||||
integ = db.query(models.Integration).filter_by(id=integration_id).first()
|
||||
if not integ:
|
||||
return
|
||||
user_id = integ.user_id
|
||||
text = f"LifeRPG: {event} for {payload.get('provider','?')} (integration {integration_id})"
|
||||
try:
|
||||
res = payload.get('summary')
|
||||
if isinstance(res, dict):
|
||||
count = res.get('count')
|
||||
if count is not None:
|
||||
text += f" — items: {count}"
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
for hook in _slack_webhooks_for_user(db, user_id):
|
||||
try:
|
||||
requests.post(hook, json={"text": text}, timeout=5)
|
||||
except Exception as e:
|
||||
try:
|
||||
log_job_event('notify_fail', integration_id=integration_id, channel='slack', error=str(e))
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
def send_webhook(url: str, body: Dict[str, Any], headers: Dict[str, str] | None = None):
|
||||
headers = headers or {}
|
||||
requests.post(url, json=body, headers=headers, timeout=5)
|
||||
|
||||
|
||||
def send_email(to: str, subject: str, body: str):
|
||||
"""Send an email via configured transport.
|
||||
|
||||
Transports:
|
||||
- console (default): log intent only
|
||||
- smtp: use SMTP settings from environment
|
||||
- disabled: no-op
|
||||
"""
|
||||
try:
|
||||
from .metrics import log_job_event
|
||||
except Exception:
|
||||
log_job_event = None
|
||||
try:
|
||||
from .config import settings
|
||||
except Exception:
|
||||
settings = None
|
||||
|
||||
transport = (settings.EMAIL_TRANSPORT if settings else 'console') if settings else 'console'
|
||||
if transport == 'disabled':
|
||||
if log_job_event:
|
||||
try:
|
||||
log_job_event('email_disabled', to=to)
|
||||
except Exception:
|
||||
pass
|
||||
return
|
||||
if transport == 'console' or settings is None:
|
||||
if log_job_event:
|
||||
try:
|
||||
log_job_event('email_console', to=to, subject=subject)
|
||||
except Exception:
|
||||
pass
|
||||
return
|
||||
|
||||
# SMTP path
|
||||
host = settings.SMTP_HOST
|
||||
port = settings.SMTP_PORT
|
||||
user = settings.SMTP_USERNAME
|
||||
pwd = settings.SMTP_PASSWORD
|
||||
use_tls = settings.SMTP_USE_TLS
|
||||
sender = settings.SMTP_FROM or user or 'no-reply@liferpg.local'
|
||||
if not host:
|
||||
# fallback to console if missing configuration
|
||||
if log_job_event:
|
||||
try:
|
||||
log_job_event('email_console', to=to, subject=subject, reason='smtp_not_configured')
|
||||
except Exception:
|
||||
pass
|
||||
return
|
||||
msg = EmailMessage()
|
||||
msg['From'] = sender
|
||||
msg['To'] = to
|
||||
msg['Subject'] = subject
|
||||
msg.set_content(body)
|
||||
try:
|
||||
if use_tls:
|
||||
server = smtplib.SMTP(host, port, timeout=10)
|
||||
server.starttls()
|
||||
else:
|
||||
server = smtplib.SMTP(host, port, timeout=10)
|
||||
try:
|
||||
if user and pwd:
|
||||
server.login(user, pwd)
|
||||
server.send_message(msg)
|
||||
finally:
|
||||
try:
|
||||
server.quit()
|
||||
except Exception:
|
||||
pass
|
||||
if log_job_event:
|
||||
try:
|
||||
log_job_event('email_sent', to=to)
|
||||
except Exception:
|
||||
pass
|
||||
except Exception as e:
|
||||
if log_job_event:
|
||||
try:
|
||||
log_job_event('email_fail', to=to, error=str(e))
|
||||
except Exception:
|
||||
pass
|
||||
Reference in New Issue
Block a user