Lightweight containerized prosody tooling + moved auth scripts + xmpp reconnect/auth stabilization
This commit is contained in:
108
core/events/ledger.py
Normal file
108
core/events/ledger.py
Normal file
@@ -0,0 +1,108 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import time
|
||||
|
||||
from asgiref.sync import sync_to_async
|
||||
from django.conf import settings
|
||||
|
||||
from core.models import ConversationEvent
|
||||
from core.observability.tracing import ensure_trace_id
|
||||
|
||||
|
||||
def event_ledger_enabled() -> bool:
|
||||
return bool(getattr(settings, "EVENT_LEDGER_DUAL_WRITE", False))
|
||||
|
||||
|
||||
def event_ledger_status() -> dict:
|
||||
return {
|
||||
"event_ledger_dual_write": bool(getattr(settings, "EVENT_LEDGER_DUAL_WRITE", False)),
|
||||
"event_primary_write_path": bool(getattr(settings, "EVENT_PRIMARY_WRITE_PATH", False)),
|
||||
}
|
||||
|
||||
|
||||
def _normalize_direction(value: str) -> str:
|
||||
direction = str(value or "system").strip().lower()
|
||||
if direction not in {"in", "out", "system"}:
|
||||
return "system"
|
||||
return direction
|
||||
|
||||
|
||||
def _safe_ts(value: int | None) -> int:
|
||||
if value is None:
|
||||
return int(time.time() * 1000)
|
||||
try:
|
||||
parsed = int(value)
|
||||
except Exception:
|
||||
return int(time.time() * 1000)
|
||||
if parsed <= 0:
|
||||
return int(time.time() * 1000)
|
||||
return parsed
|
||||
|
||||
|
||||
def append_event_sync(
|
||||
*,
|
||||
user,
|
||||
session,
|
||||
event_type: str,
|
||||
direction: str,
|
||||
actor_identifier: str = "",
|
||||
origin_transport: str = "",
|
||||
origin_message_id: str = "",
|
||||
origin_chat_id: str = "",
|
||||
payload: dict | None = None,
|
||||
raw_payload: dict | None = None,
|
||||
trace_id: str = "",
|
||||
ts: int | None = None,
|
||||
):
|
||||
if not event_ledger_enabled():
|
||||
return None
|
||||
|
||||
normalized_type = str(event_type or "").strip().lower()
|
||||
if not normalized_type:
|
||||
raise ValueError("event_type is required")
|
||||
|
||||
candidates = {
|
||||
str(choice[0]) for choice in ConversationEvent.EVENT_TYPE_CHOICES
|
||||
}
|
||||
if normalized_type not in candidates:
|
||||
raise ValueError(f"unsupported event_type: {normalized_type}")
|
||||
|
||||
normalized_direction = _normalize_direction(direction)
|
||||
normalized_trace = ensure_trace_id(trace_id, payload or {})
|
||||
|
||||
transport = str(origin_transport or "").strip().lower()
|
||||
message_id = str(origin_message_id or "").strip()
|
||||
dedup_row = None
|
||||
if transport and message_id:
|
||||
dedup_row = (
|
||||
ConversationEvent.objects.filter(
|
||||
user=user,
|
||||
session=session,
|
||||
event_type=normalized_type,
|
||||
origin_transport=transport,
|
||||
origin_message_id=message_id,
|
||||
)
|
||||
.order_by("-created_at")
|
||||
.first()
|
||||
)
|
||||
if dedup_row is not None:
|
||||
return dedup_row
|
||||
|
||||
return ConversationEvent.objects.create(
|
||||
user=user,
|
||||
session=session,
|
||||
ts=_safe_ts(ts),
|
||||
event_type=normalized_type,
|
||||
direction=normalized_direction,
|
||||
actor_identifier=str(actor_identifier or "").strip(),
|
||||
origin_transport=transport,
|
||||
origin_message_id=message_id,
|
||||
origin_chat_id=str(origin_chat_id or "").strip(),
|
||||
payload=dict(payload or {}),
|
||||
raw_payload=dict(raw_payload or {}),
|
||||
trace_id=normalized_trace,
|
||||
)
|
||||
|
||||
|
||||
async def append_event(**kwargs):
|
||||
return await sync_to_async(append_event_sync, thread_sensitive=True)(**kwargs)
|
||||
Reference in New Issue
Block a user