chore: snapshot notifications hardening and css/docs alignment

This commit is contained in:
2026-03-20 00:05:03 +01:00
parent fb6e30a062
commit 60c27e50cb
41 changed files with 1862 additions and 254 deletions

View File

@@ -103,10 +103,8 @@ class UserAccountService
}
// Capture tenant associations before CASCADE deletes them
$preDeletionTenantIds = array_column(
$this->userAssignmentService->buildAssignmentsForUser($userId)['tenants'] ?? [],
'id'
);
$preDeletionAssignments = $this->safeAssignmentsForUser($userId);
$preDeletionTenantIds = $this->extractTenantIdsFromAssignments($preDeletionAssignments);
$deleted = $this->userWriteRepository->delete($userId);
if (!$deleted) {
@@ -156,6 +154,27 @@ class UserAccountService
}
}
$deletedUsers = [];
foreach ($uuids as $deleteUuid) {
$user = $this->userReadRepository->findByUuid((string) $deleteUuid);
if (!$user || !isset($user['id'])) {
continue;
}
$deleteUserId = (int) $user['id'];
if ($deleteUserId <= 0) {
continue;
}
$assignments = $this->safeAssignmentsForUser($deleteUserId);
$deletedUsers[] = [
'id' => $deleteUserId,
'uuid' => (string) ($user['uuid'] ?? ''),
'display_name' => trim((string) ($user['display_name'] ?? '')),
'tenant_ids' => $this->extractTenantIdsFromAssignments($assignments),
];
}
$deleted = $this->userWriteRepository->deleteByUuids($uuids);
if (!$deleted) {
return ['ok' => false, 'error' => 'delete_failed'];
@@ -171,6 +190,16 @@ class UserAccountService
],
]);
foreach ($deletedUsers as $deletedUser) {
$this->eventDispatcher?->dispatch('user.deleted', [
'user_id' => (int) ($deletedUser['id'] ?? 0),
'uuid' => (string) ($deletedUser['uuid'] ?? ''),
'actor_user_id' => $currentUserId,
'display_name' => (string) ($deletedUser['display_name'] ?? ''),
'tenant_ids' => is_array($deletedUser['tenant_ids'] ?? null) ? $deletedUser['tenant_ids'] : [],
]);
}
return ['ok' => true, 'count' => count($uuids)];
}
@@ -434,6 +463,20 @@ class UserAccountService
],
]);
if ($activeChanged) {
$tenantIds = $this->extractTenantIdsFromAssignments(
$this->safeAssignmentsForUser($userId)
);
$eventType = ((int) $form['active'] === 1) ? 'user.activated' : 'user.deactivated';
$this->eventDispatcher?->dispatch($eventType, [
'user_id' => $userId,
'uuid' => (string) ($existing['uuid'] ?? ''),
'display_name' => trim((string) ($existing['display_name'] ?? '')),
'actor_user_id' => $currentUserId,
'tenant_ids' => $tenantIds,
]);
}
return ['ok' => true, 'form' => $form];
}
@@ -475,6 +518,17 @@ class UserAccountService
'after' => ['active' => $active ? 1 : 0],
]);
$tenantIds = $this->extractTenantIdsFromAssignments(
$this->safeAssignmentsForUser($userId)
);
$this->eventDispatcher?->dispatch($active ? 'user.activated' : 'user.deactivated', [
'user_id' => $userId,
'uuid' => (string) ($user['uuid'] ?? ''),
'display_name' => trim((string) ($user['display_name'] ?? '')),
'actor_user_id' => $currentUserId,
'tenant_ids' => $tenantIds,
]);
return ['ok' => true, 'user' => $user];
}
@@ -519,6 +573,29 @@ class UserAccountService
],
]);
foreach ($uuids as $uuid) {
$user = $this->userReadRepository->findByUuid((string) $uuid);
if (!$user || !isset($user['id'])) {
continue;
}
$targetUserId = (int) ($user['id'] ?? 0);
if ($targetUserId <= 0) {
continue;
}
$tenantIds = $this->extractTenantIdsFromAssignments(
$this->safeAssignmentsForUser($targetUserId)
);
$this->eventDispatcher?->dispatch($active ? 'user.activated' : 'user.deactivated', [
'user_id' => $targetUserId,
'uuid' => (string) ($user['uuid'] ?? ''),
'display_name' => trim((string) ($user['display_name'] ?? '')),
'actor_user_id' => $currentUserId,
'tenant_ids' => $tenantIds,
]);
}
return ['ok' => true, 'count' => count($uuids)];
}
@@ -695,6 +772,49 @@ class UserAccountService
return ['ok' => true, 'form' => $form];
}
public function dispatchAssignmentChangedIfNeeded(
int $userId,
array $beforeAssignments,
array $afterAssignments,
int $currentUserId = 0
): void {
if ($userId <= 0) {
return;
}
$beforeTenantIds = $this->extractTenantIdsFromAssignments($beforeAssignments);
$afterTenantIds = $this->extractTenantIdsFromAssignments($afterAssignments);
$beforeRoleIds = $this->extractAssignmentIds($beforeAssignments, 'roles');
$afterRoleIds = $this->extractAssignmentIds($afterAssignments, 'roles');
$beforeDepartmentIds = $this->extractAssignmentIds($beforeAssignments, 'departments');
$afterDepartmentIds = $this->extractAssignmentIds($afterAssignments, 'departments');
$tenantsChanged = $beforeTenantIds !== $afterTenantIds;
$rolesChanged = $beforeRoleIds !== $afterRoleIds;
$departmentsChanged = $beforeDepartmentIds !== $afterDepartmentIds;
if (!$tenantsChanged && !$rolesChanged && !$departmentsChanged) {
return;
}
$user = $this->userReadRepository->find($userId);
if (!$user) {
return;
}
$this->eventDispatcher?->dispatch('user.assignment_changed', [
'user_id' => $userId,
'uuid' => (string) ($user['uuid'] ?? ''),
'display_name' => trim((string) ($user['display_name'] ?? '')),
'actor_user_id' => $currentUserId,
'tenant_ids' => array_values(array_unique(array_merge($beforeTenantIds, $afterTenantIds))),
'changes' => [
'tenants' => ['before' => $beforeTenantIds, 'after' => $afterTenantIds],
'roles' => ['before' => $beforeRoleIds, 'after' => $afterRoleIds],
'departments' => ['before' => $beforeDepartmentIds, 'after' => $afterDepartmentIds],
],
]);
}
private function filterUuidsByTenantScope(array $uuids, int $currentUserId): array
{
$allowed = [];
@@ -715,6 +835,37 @@ class UserAccountService
return array_values(array_unique($allowed));
}
private function extractTenantIdsFromAssignments(array $assignments): array
{
return $this->extractAssignmentIds($assignments, 'tenants');
}
private function safeAssignmentsForUser(int $userId): array
{
$assignments = $this->userAssignmentService->buildAssignmentsForUser($userId);
return is_array($assignments) ? $assignments : [];
}
private function extractAssignmentIds(array $assignments, string $key): array
{
$rows = is_array($assignments[$key] ?? null) ? $assignments[$key] : [];
$ids = [];
foreach ($rows as $row) {
if (!is_array($row)) {
continue;
}
$id = (int) ($row['id'] ?? 0);
if ($id > 0) {
$ids[] = $id;
}
}
$ids = array_values(array_unique($ids));
sort($ids);
return $ids;
}
private function sanitizeBase(array $input): array
{
return [