Implement task 006 site config versioning and audit events

This commit is contained in:
2026-05-21 19:13:19 +03:00
parent cf84400118
commit ff1a2f78c0
23 changed files with 1878 additions and 32 deletions
+108 -1
View File
@@ -37,6 +37,7 @@ class BackendRepository:
self.target_sites = TargetSitesRepository(self)
self.articles = ArticlesRepository(self)
self.script_config_versions = ScriptConfigVersionsRepository(self)
self.script_config_version_events = ScriptConfigVersionAuditEventsRepository(self)
self.schema = SchemaRepository(self)
def setup(self) -> None:
@@ -719,6 +720,7 @@ class ScriptConfigVersionsRepository:
target_site_id: UUID,
version_id: UUID,
activated_at: datetime,
rollback_target_version_id: UUID | None = None,
) -> dict[str, Any]:
placeholder = self._repository.placeholder()
with self._repository.connection() as connection:
@@ -756,13 +758,15 @@ class ScriptConfigVersionsRepository:
SET
status = {placeholder},
activated_at = {placeholder},
updated_at = {placeholder}
updated_at = {placeholder},
rollback_target_version_id = COALESCE({placeholder}, rollback_target_version_id)
WHERE id = {placeholder}
""",
(
ScriptConfigVersionStatus.ACTIVE.value,
_datetime_value(activated_at),
_datetime_value(activated_at),
_uuid_value(rollback_target_version_id),
str(version_id),
),
)
@@ -795,6 +799,97 @@ class ScriptConfigVersionsRepository:
return [_plain_row(row) for row in rows]
class ScriptConfigVersionAuditEventsRepository:
def __init__(self, repository: BackendRepository) -> None:
self._repository = repository
def create(
self,
*,
target_site_id: UUID,
version_id: UUID,
event_type: str,
actor_user_id: UUID | None,
payload: JsonObject,
created_at: datetime,
) -> dict[str, Any]:
placeholder = self._repository.placeholder()
sql = f"""
INSERT INTO script_config_version_events (
id,
target_site_id,
version_id,
event_type,
actor_user_id,
payload,
created_at
)
VALUES (
{placeholder},
{placeholder},
{placeholder},
{placeholder},
{placeholder},
{placeholder},
{placeholder}
)
"""
params = (
str(uuid4()),
str(target_site_id),
str(version_id),
event_type,
_uuid_value(actor_user_id),
_json_value(payload),
_datetime_value(created_at),
)
with self._repository.connection() as connection:
connection.execute(sql, params)
return self.get(event_type=event_type, target_site_id=target_site_id, version_id=version_id)
def list_for_site(self, target_site_id: UUID) -> list[dict[str, Any]]:
placeholder = self._repository.placeholder()
with self._repository.connection() as connection:
rows = connection.execute(
f"""
SELECT id, target_site_id, version_id, event_type, actor_user_id, payload, created_at
FROM script_config_version_events
WHERE target_site_id = {placeholder}
ORDER BY created_at
""",
(str(target_site_id),),
).fetchall()
return [_script_config_version_event_from_row(row) for row in rows]
def get(
self,
*,
target_site_id: UUID,
version_id: UUID,
event_type: str,
) -> dict[str, Any]:
placeholder = self._repository.placeholder()
with self._repository.connection() as connection:
row = connection.execute(
f"""
SELECT id, target_site_id, version_id, event_type, actor_user_id, payload, created_at
FROM script_config_version_events
WHERE target_site_id = {placeholder}
AND version_id = {placeholder}
AND event_type = {placeholder}
""",
(str(target_site_id), str(version_id), event_type),
).fetchone()
if row is None:
raise LookupError(
f"Script config version event not found: {target_site_id} {version_id} {event_type}"
)
return _script_config_version_event_from_row(row)
class SchemaRepository:
def __init__(self, repository: BackendRepository) -> None:
self._repository = repository
@@ -920,6 +1015,18 @@ def _workflow_event_from_row(row: Any) -> WorkflowEventSummary:
)
def _script_config_version_event_from_row(row: Any) -> dict[str, Any]:
return {
"id": _row_value(row, "id"),
"target_site_id": _row_value(row, "target_site_id"),
"version_id": _row_value(row, "version_id"),
"event_type": _row_value(row, "event_type"),
"actor_user_id": _row_value(row, "actor_user_id"),
"payload": _json_from_row(row, "payload"),
"created_at": _row_value(row, "created_at"),
}
def _plain_row(row: Any) -> dict[str, Any]:
if isinstance(row, sqlite3.Row):
result = dict(row)