forked from fa/breadcrumb-the-shire
179 lines
6.7 KiB
PHP
179 lines
6.7 KiB
PHP
<?php
|
|
|
|
namespace MintyPHP\Service\User;
|
|
|
|
use MintyPHP\Repository\Support\DatabaseSessionRepository;
|
|
use MintyPHP\Repository\Support\RepoQuery;
|
|
use MintyPHP\Repository\User\UserReadRepositoryInterface;
|
|
use MintyPHP\Repository\User\UserWriteRepositoryInterface;
|
|
use MintyPHP\Service\Audit\UserLifecycleAuditService;
|
|
|
|
class UserLifecycleRestoreService
|
|
{
|
|
public function __construct(
|
|
private readonly UserReadRepositoryInterface $userReadRepository,
|
|
private readonly UserWriteRepositoryInterface $userWriteRepository,
|
|
private readonly UserLifecycleAuditService $userLifecycleAuditService,
|
|
private readonly DatabaseSessionRepository $databaseSessionRepository
|
|
) {
|
|
}
|
|
|
|
public function restoreFromLifecycleAudit(int $auditId, int $actorUserId): array
|
|
{
|
|
if ($auditId <= 0 || $actorUserId <= 0) {
|
|
return ['ok' => false, 'error' => 'invalid_request'];
|
|
}
|
|
|
|
$transactionStarted = false;
|
|
try {
|
|
$this->databaseSessionRepository->beginTransaction();
|
|
$transactionStarted = true;
|
|
|
|
$event = $this->userLifecycleAuditService->findDeleteEventForRestore($auditId, true);
|
|
if (!$event) {
|
|
$this->rollbackQuietly();
|
|
return ['ok' => false, 'error' => 'audit_event_not_found'];
|
|
}
|
|
if (!empty($event['restored_at'])) {
|
|
$this->rollbackQuietly();
|
|
return ['ok' => false, 'error' => 'audit_event_already_restored'];
|
|
}
|
|
|
|
$snapshot = $this->userLifecycleAuditService->decryptSnapshot($event);
|
|
if (!$snapshot) {
|
|
$this->rollbackQuietly();
|
|
return ['ok' => false, 'error' => 'snapshot_unavailable'];
|
|
}
|
|
|
|
$uuid = trim((string) ($snapshot['uuid'] ?? ''));
|
|
$email = trim((string) ($snapshot['email'] ?? ''));
|
|
if ($uuid === '' || $email === '') {
|
|
$this->rollbackQuietly();
|
|
return ['ok' => false, 'error' => 'snapshot_invalid'];
|
|
}
|
|
|
|
if ($this->userReadRepository->findByUuid($uuid)) {
|
|
$this->rollbackQuietly();
|
|
return ['ok' => false, 'error' => 'restore_uuid_exists'];
|
|
}
|
|
if ($this->userReadRepository->findByEmail($email)) {
|
|
$this->rollbackQuietly();
|
|
return ['ok' => false, 'error' => 'restore_email_exists'];
|
|
}
|
|
|
|
$createdId = $this->userWriteRepository->create([
|
|
'uuid' => $uuid,
|
|
'first_name' => trim((string) ($snapshot['first_name'] ?? '')),
|
|
'last_name' => trim((string) ($snapshot['last_name'] ?? '')),
|
|
'email' => $email,
|
|
'profile_description' => $this->nullableText($snapshot['profile_description'] ?? null),
|
|
'job_title' => $this->nullableText($snapshot['job_title'] ?? null),
|
|
'phone' => $this->nullableText($snapshot['phone'] ?? null),
|
|
'mobile' => $this->nullableText($snapshot['mobile'] ?? null),
|
|
'short_dial' => $this->nullableText($snapshot['short_dial'] ?? null),
|
|
'address' => $this->nullableText($snapshot['address'] ?? null),
|
|
'postal_code' => $this->nullableText($snapshot['postal_code'] ?? null),
|
|
'city' => $this->nullableText($snapshot['city'] ?? null),
|
|
'country' => $this->nullableText($snapshot['country'] ?? null),
|
|
'region' => $this->nullableText($snapshot['region'] ?? null),
|
|
'hire_date' => $this->nullableDate($snapshot['hire_date'] ?? null),
|
|
'password' => $this->randomPassword(),
|
|
'locale' => $this->nullableText($snapshot['locale'] ?? null),
|
|
'totp_secret' => '',
|
|
'theme' => $this->normalizeTheme($snapshot['theme'] ?? null),
|
|
'primary_tenant_id' => null,
|
|
'current_tenant_id' => null,
|
|
'created_by' => $actorUserId,
|
|
'active' => 0,
|
|
'active_changed_at' => gmdate('Y-m-d H:i:s'),
|
|
'active_changed_by' => $actorUserId,
|
|
]);
|
|
if (!$createdId) {
|
|
$this->rollbackQuietly();
|
|
return ['ok' => false, 'error' => 'restore_create_failed'];
|
|
}
|
|
$restoredUserId = (int) $createdId;
|
|
|
|
$marked = $this->userLifecycleAuditService->markDeleteEventRestored(
|
|
$auditId,
|
|
$actorUserId,
|
|
$restoredUserId
|
|
);
|
|
if (!$marked) {
|
|
$this->rollbackQuietly();
|
|
return ['ok' => false, 'error' => 'restore_mark_failed'];
|
|
}
|
|
|
|
$this->userLifecycleAuditService->logRestore(
|
|
RepoQuery::uuidV4(),
|
|
'manual',
|
|
[
|
|
'deactivate_days' => (int) ($event['policy_deactivate_days'] ?? 0),
|
|
'delete_days' => (int) ($event['policy_delete_days'] ?? 0),
|
|
],
|
|
$actorUserId,
|
|
[
|
|
'id' => $restoredUserId,
|
|
'uuid' => $uuid,
|
|
'email' => $email,
|
|
],
|
|
'success',
|
|
null
|
|
);
|
|
|
|
$this->databaseSessionRepository->commitTransaction();
|
|
$transactionStarted = false;
|
|
return [
|
|
'ok' => true,
|
|
'restored_user_id' => $restoredUserId,
|
|
'restored_user_uuid' => $uuid,
|
|
'audit_id' => $auditId,
|
|
];
|
|
} catch (\Throwable $exception) {
|
|
if ($transactionStarted) {
|
|
$this->rollbackQuietly();
|
|
}
|
|
return ['ok' => false, 'error' => 'restore_unexpected_error'];
|
|
}
|
|
}
|
|
|
|
private function rollbackQuietly(): void
|
|
{
|
|
try {
|
|
$this->databaseSessionRepository->rollbackTransaction();
|
|
} catch (\Throwable $exception) {
|
|
// no-op
|
|
}
|
|
}
|
|
|
|
private function nullableText(mixed $value): ?string
|
|
{
|
|
$value = trim((string) $value);
|
|
return $value !== '' ? $value : null;
|
|
}
|
|
|
|
private function nullableDate(mixed $value): ?string
|
|
{
|
|
$value = trim((string) $value);
|
|
if ($value === '') {
|
|
return null;
|
|
}
|
|
if (!preg_match('/^\d{4}-\d{2}-\d{2}$/', $value)) {
|
|
return null;
|
|
}
|
|
return $value;
|
|
}
|
|
|
|
private function randomPassword(): string
|
|
{
|
|
return bin2hex(random_bytes(24));
|
|
}
|
|
|
|
private function normalizeTheme(mixed $value): string
|
|
{
|
|
$theme = strtolower(trim((string) $value));
|
|
$themes = appThemes();
|
|
return isset($themes[$theme]) ? $theme : appDefaultTheme();
|
|
}
|
|
}
|