Add module event producer coverage
This commit is contained in:
@@ -7,7 +7,15 @@ from sqlalchemy.orm import Session
|
||||
|
||||
from govoplan_core.auth import ApiPrincipal
|
||||
from govoplan_core.core.change_sequence import record_change
|
||||
from govoplan_core.core.events import current_event_trace, normalize_trace_id
|
||||
from govoplan_core.core.events import (
|
||||
EventActorRef,
|
||||
EventObjectRef,
|
||||
EventTenantRef,
|
||||
PlatformEvent,
|
||||
current_event_trace,
|
||||
normalize_trace_id,
|
||||
publish_platform_event,
|
||||
)
|
||||
from govoplan_core.privacy.retention import sanitize_audit_details_for_policy
|
||||
from govoplan_audit.backend.db.models import AuditLog
|
||||
|
||||
@@ -34,6 +42,27 @@ SENSITIVE_DETAIL_KEYS = {
|
||||
"build_token",
|
||||
}
|
||||
|
||||
_ACTION_MODULE_PREFIXES = {
|
||||
"api_key": "access",
|
||||
"configuration_change": "access",
|
||||
"configuration_package": "access",
|
||||
"group": "access",
|
||||
"profile": "access",
|
||||
"role": "access",
|
||||
"system_access": "access",
|
||||
"system_account": "access",
|
||||
"system_memberships": "access",
|
||||
"system_role": "access",
|
||||
"user": "access",
|
||||
"tenant": "tenancy",
|
||||
"privacy_retention": "policy",
|
||||
"retention_policy": "policy",
|
||||
"files": "files",
|
||||
"mail": "mail",
|
||||
"campaign": "campaigns",
|
||||
"report": "campaigns",
|
||||
}
|
||||
|
||||
|
||||
def _optional_text(value: object | None) -> str | None:
|
||||
if value is None:
|
||||
@@ -135,6 +164,34 @@ def _restore_trace_after_policy(details: dict[str, Any], trace: dict[str, str])
|
||||
return restored
|
||||
|
||||
|
||||
def _module_id_for_audit_action(action: str) -> str:
|
||||
prefix = action.split(".", 1)[0].strip().casefold()
|
||||
return _ACTION_MODULE_PREFIXES.get(prefix, prefix or AUDIT_MODULE_ID)
|
||||
|
||||
|
||||
def _publish_audit_platform_event(item: AuditLog) -> None:
|
||||
trace = _compact_trace(item.details.get("_trace") if isinstance(item.details, Mapping) else None)
|
||||
publish_platform_event(
|
||||
PlatformEvent(
|
||||
type=item.action,
|
||||
module_id=_module_id_for_audit_action(item.action),
|
||||
payload={
|
||||
"audit_log_id": item.id,
|
||||
"scope": item.scope,
|
||||
"details": dict(item.details or {}),
|
||||
},
|
||||
correlation_id=trace.get("correlation_id"),
|
||||
causation_id=trace.get("causation_id"),
|
||||
actor=EventActorRef(type="user", id=item.user_id) if item.user_id else (
|
||||
EventActorRef(type="api_key", id=item.api_key_id) if item.api_key_id else None
|
||||
),
|
||||
tenant=EventTenantRef(id=item.tenant_id) if item.tenant_id else None,
|
||||
resource=EventObjectRef(type=item.object_type, id=item.object_id) if item.object_type else None,
|
||||
classification="internal",
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def audit_event(
|
||||
session: Session,
|
||||
*,
|
||||
@@ -194,6 +251,7 @@ def audit_event(
|
||||
actor_id=user_id or api_key_id,
|
||||
payload={"scope": scope, "action": action, "object_type": object_type, "object_id": object_id},
|
||||
)
|
||||
_publish_audit_platform_event(item)
|
||||
if commit:
|
||||
session.commit()
|
||||
return item
|
||||
|
||||
@@ -6,6 +6,7 @@ from typing import Any, Iterable, Sequence
|
||||
from sqlalchemy import BigInteger, DateTime, Index, Integer, JSON, String, UniqueConstraint, func
|
||||
from sqlalchemy.orm import Mapped, Session, mapped_column
|
||||
|
||||
from govoplan_core.core.events import EventActorRef, EventObjectRef, EventTenantRef, PlatformEvent, publish_platform_event
|
||||
from govoplan_core.db.base import Base, utcnow
|
||||
|
||||
WATERMARK_PREFIX = "seq:"
|
||||
@@ -95,9 +96,42 @@ def record_change(
|
||||
payload=payload or {},
|
||||
)
|
||||
session.add(entry)
|
||||
_publish_change_event(entry)
|
||||
return entry
|
||||
|
||||
|
||||
def _publish_change_event(entry: ChangeSequenceEntry) -> None:
|
||||
event_type = _change_event_type(entry.module_id, entry.resource_type, entry.operation)
|
||||
publish_platform_event(
|
||||
PlatformEvent(
|
||||
type=event_type,
|
||||
module_id=entry.module_id,
|
||||
payload={
|
||||
"collection": entry.collection,
|
||||
"operation": entry.operation,
|
||||
"payload": dict(entry.payload or {}),
|
||||
},
|
||||
actor=EventActorRef(type=entry.actor_type, id=entry.actor_id) if entry.actor_type else None,
|
||||
tenant=EventTenantRef(id=entry.tenant_id) if entry.tenant_id else None,
|
||||
resource=EventObjectRef(type=entry.resource_type, id=entry.resource_id),
|
||||
classification="internal",
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def _change_event_type(module_id: str, resource_type: str, operation: str) -> str:
|
||||
resource = _event_segment(resource_type)
|
||||
module = _event_segment(module_id)
|
||||
if resource.startswith(f"{module}."):
|
||||
resource = resource[len(module) + 1:]
|
||||
return ".".join(item for item in (module, resource, _event_segment(operation)) if item)
|
||||
|
||||
|
||||
def _event_segment(value: str) -> str:
|
||||
compact = "".join(character if character.isalnum() else "." for character in value.casefold())
|
||||
return ".".join(part for part in compact.split(".") if part)
|
||||
|
||||
|
||||
def max_sequence_id(
|
||||
session: Session,
|
||||
*,
|
||||
|
||||
@@ -168,5 +168,26 @@ class EventBus:
|
||||
handler(traced)
|
||||
|
||||
|
||||
_default_event_bus = EventBus()
|
||||
_current_event_bus: ContextVar[EventBus | None] = ContextVar("govoplan_event_bus", default=None)
|
||||
|
||||
|
||||
def platform_event_bus() -> EventBus:
|
||||
return _current_event_bus.get() or _default_event_bus
|
||||
|
||||
|
||||
@contextmanager
|
||||
def event_bus_context(bus: EventBus):
|
||||
token = _current_event_bus.set(bus)
|
||||
try:
|
||||
yield bus
|
||||
finally:
|
||||
_current_event_bus.reset(token)
|
||||
|
||||
|
||||
def publish_platform_event(event: PlatformEvent) -> None:
|
||||
platform_event_bus().publish(event)
|
||||
|
||||
|
||||
def _compact_dict(value: Mapping[str, Any]) -> dict[str, Any]:
|
||||
return {key: item for key, item in value.items() if item is not None}
|
||||
|
||||
Reference in New Issue
Block a user