value, ?string $reasonCode = null ): bool { try { return $this->userLifecycleAuditRepository->create($this->buildBaseRow( $runUuid, UserLifecycleAction::Deactivate->value, $triggerType, $status, $reasonCode, $policy, $actorUserId, $targetUser )) !== false; } catch (\Throwable $exception) { return false; } } public function logDeleteWithSnapshot( string $runUuid, string $triggerType, array $policy, ?int $actorUserId, array $targetUser ): int|false { try { $snapshot = $this->buildSnapshot($targetUser); $json = json_encode($snapshot, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); if (!is_string($json)) { return false; } $row = $this->buildBaseRow( $runUuid, UserLifecycleAction::Delete->value, $triggerType, UserLifecycleStatus::Success->value, null, $policy, $actorUserId, $targetUser ); $row['snapshot_enc'] = Crypto::encryptString($json); $row['snapshot_version'] = self::SNAPSHOT_VERSION; return $this->userLifecycleAuditRepository->create($row); } catch (\Throwable $exception) { return false; } } public function logDeleteFailure( string $runUuid, string $triggerType, array $policy, ?int $actorUserId, array $targetUser, string $reasonCode ): bool { try { return $this->userLifecycleAuditRepository->create($this->buildBaseRow( $runUuid, UserLifecycleAction::Delete->value, $triggerType, UserLifecycleStatus::Failed->value, $reasonCode, $policy, $actorUserId, $targetUser )) !== false; } catch (\Throwable $exception) { return false; } } public function logRestore( string $runUuid, string $triggerType, array $policy, ?int $actorUserId, array $targetUser, string $status = UserLifecycleStatus::Success->value, ?string $reasonCode = null ): bool { try { return $this->userLifecycleAuditRepository->create($this->buildBaseRow( $runUuid, UserLifecycleAction::Restore->value, $triggerType, $status, $reasonCode, $policy, $actorUserId, $targetUser )) !== false; } catch (\Throwable $exception) { return false; } } public function markDeleteEventRestored(int $auditId, int $restoredByUserId, int $restoredUserId): bool { try { return $this->userLifecycleAuditRepository->markRestored($auditId, $restoredByUserId, $restoredUserId); } catch (\Throwable $exception) { return false; } } public function listPaged(array $filters): array { return $this->userLifecycleAuditRepository->listPaged($filters); } public function find(int $id): ?array { return $this->userLifecycleAuditRepository->find($id); } public function findDeleteEventForRestore(int $id, bool $forUpdate = false): ?array { return $this->userLifecycleAuditRepository->findDeleteEventForRestore($id, $forUpdate); } public function decryptSnapshot(array $event): ?array { $enc = trim((string) ($event['snapshot_enc'] ?? '')); if ($enc === '') { return null; } try { $json = Crypto::decryptString($enc); $decoded = json_decode($json, true); return is_array($decoded) ? $decoded : null; } catch (\Throwable $exception) { return null; } } public function updateStatus(int $id, string $status, ?string $reasonCode = null): bool { try { return $this->userLifecycleAuditRepository->updateStatus($id, $status, $reasonCode); } catch (\Throwable $exception) { return false; } } public function purgeExpired(): int { return $this->userLifecycleAuditRepository->purgeOlderThanDays(self::RETENTION_DAYS); } private function buildBaseRow( string $runUuid, string $action, string $triggerType, string $status, ?string $reasonCode, array $policy, ?int $actorUserId, array $targetUser ): array { $action = UserLifecycleAction::normalizeOr($action, UserLifecycleAction::Deactivate)->value; $triggerType = UserLifecycleTriggerType::normalizeOr($triggerType, UserLifecycleTriggerType::System)->value; $status = UserLifecycleStatus::normalizeOr($status, UserLifecycleStatus::Failed)->value; return [ 'run_uuid' => trim($runUuid), 'action' => $action, 'trigger_type' => $triggerType, 'status' => $status, 'reason_code' => $reasonCode !== null ? trim($reasonCode) : null, 'policy_deactivate_days' => max(0, (int) ($policy['deactivate_days'] ?? 0)), 'policy_delete_days' => max(0, (int) ($policy['delete_days'] ?? 0)), 'actor_user_id' => ($actorUserId ?? 0) > 0 ? (int) $actorUserId : null, 'target_user_id' => ((int) ($targetUser['id'] ?? 0)) > 0 ? (int) $targetUser['id'] : null, 'target_user_uuid' => $this->stringOrNull($targetUser['uuid'] ?? null), 'target_user_email' => $this->stringOrNull($targetUser['email'] ?? null), 'snapshot_enc' => null, 'snapshot_version' => self::SNAPSHOT_VERSION, ]; } private function buildSnapshot(array $targetUser): array { $snapshot = []; foreach (self::SNAPSHOT_FIELDS as $field) { $snapshot[$field] = $targetUser[$field] ?? null; } return $snapshot; } private function stringOrNull(mixed $value): ?string { $value = trim((string) $value); return $value !== '' ? $value : null; } }