143 lines
4.3 KiB
PHP
143 lines
4.3 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Services;
|
|
|
|
use App\Models\Tenant;
|
|
use App\Models\TenantOnboardingSession;
|
|
use App\Models\User;
|
|
use Illuminate\Database\QueryException;
|
|
use Illuminate\Support\Arr;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Support\Str;
|
|
|
|
class TenantOnboardingSessionService
|
|
{
|
|
/**
|
|
* Start a new onboarding session, or resume an existing active session.
|
|
*/
|
|
public function startOrResume(User $user, ?Tenant $tenant = null): TenantOnboardingSession
|
|
{
|
|
if ($tenant instanceof Tenant) {
|
|
$existing = TenantOnboardingSession::query()
|
|
->where('tenant_id', $tenant->getKey())
|
|
->where('status', 'active')
|
|
->first();
|
|
|
|
if ($existing instanceof TenantOnboardingSession) {
|
|
return $existing;
|
|
}
|
|
}
|
|
|
|
return TenantOnboardingSession::query()->create([
|
|
'tenant_id' => $tenant?->getKey(),
|
|
'created_by_user_id' => $user->getKey(),
|
|
'status' => 'active',
|
|
'current_step' => 'welcome',
|
|
'payload' => [],
|
|
]);
|
|
}
|
|
|
|
public function resumeById(User $user, string $sessionId): TenantOnboardingSession
|
|
{
|
|
$session = TenantOnboardingSession::query()->whereKey($sessionId)->firstOrFail();
|
|
|
|
if ((int) $session->created_by_user_id !== (int) $user->getKey()) {
|
|
abort(404);
|
|
}
|
|
|
|
return $session;
|
|
}
|
|
|
|
/**
|
|
* Persist wizard progress + non-secret payload.
|
|
*
|
|
* @param array<string, mixed> $payload
|
|
*/
|
|
public function persistProgress(TenantOnboardingSession $session, string $currentStep, array $payload, ?Tenant $tenant = null): TenantOnboardingSession
|
|
{
|
|
$payload = $this->sanitizePayload($payload);
|
|
|
|
return DB::transaction(function () use ($session, $currentStep, $payload, $tenant): TenantOnboardingSession {
|
|
$session->forceFill([
|
|
'current_step' => $currentStep,
|
|
'payload' => array_merge($session->payload ?? [], $payload),
|
|
]);
|
|
|
|
if ($tenant instanceof Tenant) {
|
|
$session->tenant()->associate($tenant);
|
|
}
|
|
|
|
try {
|
|
$session->save();
|
|
} catch (QueryException $exception) {
|
|
// If another active session already exists for the tenant, resume it.
|
|
if (($tenant instanceof Tenant) && $this->isActiveSessionUniqueViolation($exception)) {
|
|
$existing = TenantOnboardingSession::query()
|
|
->where('tenant_id', $tenant->getKey())
|
|
->where('status', 'active')
|
|
->first();
|
|
|
|
if ($existing instanceof TenantOnboardingSession) {
|
|
return $existing;
|
|
}
|
|
}
|
|
|
|
throw $exception;
|
|
}
|
|
|
|
return $session;
|
|
});
|
|
}
|
|
|
|
/**
|
|
* @param array<string, mixed> $payload
|
|
* @return array<string, mixed>
|
|
*/
|
|
public function sanitizePayload(array $payload): array
|
|
{
|
|
$forbiddenKeys = [
|
|
'app_client_secret',
|
|
'client_secret',
|
|
'secret',
|
|
'token',
|
|
'access_token',
|
|
'refresh_token',
|
|
'password',
|
|
];
|
|
|
|
return $this->forgetKeysRecursive($payload, $forbiddenKeys);
|
|
}
|
|
|
|
/**
|
|
* @param array<string, mixed> $payload
|
|
* @param array<int, string> $forbiddenKeys
|
|
* @return array<string, mixed>
|
|
*/
|
|
private function forgetKeysRecursive(array $payload, array $forbiddenKeys): array
|
|
{
|
|
foreach ($forbiddenKeys as $key) {
|
|
Arr::forget($payload, $key);
|
|
}
|
|
|
|
foreach ($payload as $key => $value) {
|
|
if (! is_array($value)) {
|
|
continue;
|
|
}
|
|
|
|
$payload[$key] = $this->forgetKeysRecursive($value, $forbiddenKeys);
|
|
}
|
|
|
|
return $payload;
|
|
}
|
|
|
|
private function isActiveSessionUniqueViolation(QueryException $exception): bool
|
|
{
|
|
$message = Str::lower($exception->getMessage());
|
|
|
|
return str_contains($message, 'tenant_onboarding_sessions_active_unique')
|
|
|| str_contains($message, 'unique') && str_contains($message, 'tenant_onboarding_sessions');
|
|
}
|
|
}
|