LifeRPG_v2.0/modern/backend/notifier.py
TLimoges33 7fe4ae5365
🧙‍♂️ 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.
2025-08-30 17:32:42 +00:00

150 lines
4.6 KiB
Python

"""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