Добавлены уведомления

This commit is contained in:
mi
2026-07-27 17:36:53 +03:00
parent a072005164
commit 958fba5f3e
149 changed files with 6371 additions and 110 deletions
+23 -4
View File
@@ -50,6 +50,8 @@ from app.integrations import (
S3Client,
SafetyClient,
)
from app.notification_routes import router as notification_router
from app.notification_service import synchronize_source_tokens
from app.realtime import RealtimeFanout
from app.schemas import (
AttachmentCompleteRequest,
@@ -128,6 +130,7 @@ async def lifespan(app: FastAPI):
try:
async with app.state.db.sessions() as db:
app.state.snapshot = await load_settings(db)
await synchronize_source_tokens(db)
except Exception:
structlog.get_logger().warning("settings.warmup_failed")
settings_task = asyncio.create_task(refresh_settings_cache(app))
@@ -153,6 +156,7 @@ app = FastAPI(
redoc_url=None,
lifespan=lifespan,
)
app.include_router(notification_router)
log = structlog.get_logger()
@@ -243,7 +247,7 @@ async def request_context(request: Request, call_next: Any) -> Response:
response.headers["Access-Control-Allow-Headers"] = (
"Authorization,Content-Type,Idempotency-Key,X-Request-ID,X-Ux-Session-Id"
)
response.headers["Access-Control-Allow-Methods"] = "GET,POST,OPTIONS"
response.headers["Access-Control-Allow-Methods"] = "GET,POST,DELETE,OPTIONS"
response.headers["Vary"] = "Origin"
response.headers["X-Request-ID"] = request_id
response.headers["X-Content-Type-Options"] = "nosniff"
@@ -416,7 +420,7 @@ async def ready(request: Request, db: Session):
try:
await db.execute(text("SELECT 1"))
revision = await db.scalar(text("SELECT version_num FROM han_app.alembic_version LIMIT 1"))
if revision != "0005_otp_settings":
if revision != "0008_notifications_v1":
raise RuntimeError("unexpected database revision")
await load_settings(db)
components["postgres"] = "ok"
@@ -498,6 +502,14 @@ async def app_config(request: Request, response: Response, settings: SnapshotDep
"allowed_mime_types": settings.strings("chat.attachments.allowed_mime_types"),
"max_size_mb": settings.integer("chat.attachments.max_size_mb"),
},
"notification": {
"carousel_autoplay_enabled": settings.boolean(
"notification.carousel.autoplay_enabled"
),
"carousel_autoplay_interval_ms": settings.integer(
"notification.carousel.autoplay_interval_ms"
),
},
"ux": {"idle_timeout_minutes": settings.integer("ux.session.idle_timeout_minutes")},
}
@@ -1062,6 +1074,7 @@ async def realtime(websocket: WebSocket):
await websocket.close(code=4400)
return
ids = {uuid.UUID(value) for value in payload.get("dialog_ids", [])[:100]}
notifications = bool(payload.get("notifications", False))
count = await db.scalar(
select(func.count(Dialog.id)).where(
Dialog.id.in_(ids),
@@ -1076,10 +1089,16 @@ async def realtime(websocket: WebSocket):
event_task.cancel()
if event_stream:
await event_stream.aclose()
event_stream = websocket.app.state.realtime.events(ids)
event_stream = websocket.app.state.realtime.events(
ids, user.id, notifications
)
event_task = asyncio.create_task(anext(event_stream))
await websocket.send_json(
{"type": "subscribed", "dialog_ids": [str(value) for value in ids]}
{
"type": "subscribed",
"dialog_ids": [str(value) for value in ids],
"notifications": notifications,
}
)
except (AuthError, DomainError, ValueError):
await websocket.close(code=4401)