userLifecycleAuditRepository->create($this->buildBaseRow( $runUuid, 'deactivate', $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, 'delete', $triggerType, 'success', 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, 'delete', $triggerType, 'failed', $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 = 'success', ?string $reasonCode = null ): bool { try { return $this->userLifecycleAuditRepository->create($this->buildBaseRow( $runUuid, 'restore', $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 { $triggerType = strtolower(trim($triggerType)); if (!in_array($triggerType, ['manual', 'cron', 'system'], true)) { $triggerType = 'system'; } $status = strtolower(trim($status)); if (!in_array($status, ['success', 'skipped', 'failed'], true)) { $status = 'failed'; } 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; } }