feat(notifications): add Phase 4 Slice 2 — multi-channel notification system
Full notification infrastructure with in-app, email, Slack, and Teams channels: Backend: - NotificationConfig, NotificationLog, Notification models + migration - Notification service with event routing, channel delivery, retry logic - 9 API endpoints (config CRUD + in-app notifications) - APScheduler retry job with exponential backoff (30s, 2m, 10m) - Wired into escalation, proposal approval, and knowledge flywheel - Pydantic event key validation, cross-tenant protection on recipients Frontend: - TypeScript types + API client for all notification endpoints - NotificationsPanel: bell icon with unread badge, dropdown, mark-read - NotificationSettings: channel config, event toggles, test, delete confirm - Notifications tab on IntegrationsPage - ARIA attributes, Escape handler, settings link on panel Review fixes (13 issues resolved): - notify() no longer commits/rolls back caller's transaction (critical) - retry_failed_notifications returns count instead of None (critical) - NotificationSettings moved inside dedicated tab (critical) - target_user_ids scoped by account_id (security) - Email loop collects all failures before raising - Slack webhook validates response body - events_enabled rejects unknown event keys - link column widened to String(500) - Dead code removed from _auto_reinforce - Delete confirmation, ARIA, Escape key support Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -33,6 +33,7 @@ from app.core.rate_limit import limiter
|
||||
from app.api.router import api_router
|
||||
from app.core.scheduler import scheduler, load_all_schedules, _cleanup_expired_ai_conversations
|
||||
from app.services.retention_cleanup import cleanup_expired_chats
|
||||
from app.services.notification_service import retry_failed_notifications
|
||||
from app.core.service_account import ensure_service_account
|
||||
|
||||
# Initialize logging configuration
|
||||
@@ -61,6 +62,14 @@ async def archive_stale_ai_sessions():
|
||||
logger.info(f"[archive] Archived {result.rowcount} stale AI chat sessions")
|
||||
|
||||
|
||||
async def _process_notification_retries():
|
||||
"""Retry failed notification deliveries."""
|
||||
async with async_session_maker() as db:
|
||||
retried = await retry_failed_notifications(db)
|
||||
if retried:
|
||||
logger.info("Retried %d failed notifications", retried)
|
||||
|
||||
|
||||
def _configure_seed_module(mod: object, api_url: str, email: str, password: str) -> None:
|
||||
"""Set globals on a seed script module."""
|
||||
mod.API_BASE_URL = api_url # type: ignore[attr-defined]
|
||||
@@ -201,6 +210,16 @@ async def lifespan(app: FastAPI):
|
||||
max_instances=1,
|
||||
)
|
||||
|
||||
# Notification retry (every minute)
|
||||
scheduler.add_job(
|
||||
_process_notification_retries,
|
||||
trigger="interval",
|
||||
minutes=1,
|
||||
id="notification_retry",
|
||||
replace_existing=True,
|
||||
max_instances=1,
|
||||
)
|
||||
|
||||
# Auto-seed trees in background on PR environments
|
||||
seed_task = None
|
||||
if settings.SEED_ON_DEPLOY:
|
||||
|
||||
Reference in New Issue
Block a user