Kunden, die in beiden Altportalen existierten, tragen nach dem Merge nur das zuletzt importierte Portal in users.legacy_portal/legacy_id — der Schlüssel des anderen Portals fand dadurch keinen Besitzer und wurde als 'Kein User-Mapping' übersprungen (Presseecho-Key → 410). Die Import-Map kennt beide Zuordnungen und wird jetzt zuerst befragt. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
113 lines
3.5 KiB
PHP
113 lines
3.5 KiB
PHP
<?php
|
|
|
|
namespace App\Services\Api;
|
|
|
|
use App\Models\LegacyImportMap;
|
|
use App\Models\User;
|
|
use Laravel\Sanctum\PersonalAccessToken;
|
|
|
|
/**
|
|
* Übernahme bestehender Legacy-API-Integrationen (User-Entscheidung
|
|
* 06.07.2026): Die api_keys der Altportale (sf_guard_user_profile) werden
|
|
* als Sanctum-Tokens mit identischem Secret importiert — Sanctum löst
|
|
* Tokens ohne "id|"-Präfix über den sha256-Hash auf, dadurch bleiben die
|
|
* bestehenden Schlüssel der Kunden unverändert gültig. Berechtigt ist,
|
|
* wer auch selbst ein Token erstellen dürfte (ApiAccessEligibilityService).
|
|
*/
|
|
class LegacyApiKeyImporter
|
|
{
|
|
public const TOKEN_NAME = 'Legacy-API-Key (importiert)';
|
|
|
|
public const ABILITIES = [
|
|
'press-releases:read',
|
|
'press-releases:write',
|
|
'press-release-images:write',
|
|
'companies:read',
|
|
'newsletter:subscribe',
|
|
];
|
|
|
|
public function __construct(private readonly ApiAccessEligibilityService $eligibility) {}
|
|
|
|
/**
|
|
* @param iterable<int, array{legacy_id: int, api_key: string}> $rows
|
|
* @return array{imported: int, already_present: int, ineligible: int, unknown_user: int, ineligible_users: list<string>}
|
|
*/
|
|
public function import(string $portal, iterable $rows, bool $dryRun = false): array
|
|
{
|
|
$result = [
|
|
'imported' => 0,
|
|
'already_present' => 0,
|
|
'ineligible' => 0,
|
|
'unknown_user' => 0,
|
|
'ineligible_users' => [],
|
|
];
|
|
|
|
foreach ($rows as $row) {
|
|
$key = trim((string) $row['api_key']);
|
|
|
|
if ($key === '') {
|
|
continue;
|
|
}
|
|
|
|
$user = $this->resolveUser($portal, (int) $row['legacy_id']);
|
|
|
|
if (! $user) {
|
|
$result['unknown_user']++;
|
|
|
|
continue;
|
|
}
|
|
|
|
$hash = hash('sha256', $key);
|
|
|
|
if (PersonalAccessToken::query()->where('token', $hash)->exists()) {
|
|
$result['already_present']++;
|
|
|
|
continue;
|
|
}
|
|
|
|
if (! $this->eligibility->canCreateToken($user)) {
|
|
$result['ineligible']++;
|
|
$result['ineligible_users'][] = (string) $user->email;
|
|
|
|
continue;
|
|
}
|
|
|
|
if (! $dryRun) {
|
|
$user->tokens()->create([
|
|
'name' => self::TOKEN_NAME,
|
|
'token' => $hash,
|
|
'abilities' => self::ABILITIES,
|
|
]);
|
|
}
|
|
|
|
$result['imported']++;
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* Löst den Token-Besitzer über die legacy_import_map auf. Die Spalten
|
|
* users.legacy_portal/legacy_id reichen nicht: Bei E-Mail-Merges (gleicher
|
|
* Kunde in beiden Altportalen) trägt der User nur das zuletzt importierte
|
|
* Portal — die Import-Map kennt dagegen beide Zuordnungen, sodass auch der
|
|
* Schlüssel des "anderen" Portals seinen Besitzer findet.
|
|
*/
|
|
private function resolveUser(string $portal, int $legacyId): ?User
|
|
{
|
|
$mappedUserId = LegacyImportMap::query()
|
|
->where('legacy_portal', $portal)
|
|
->where('legacy_table', 'sf_guard_user')
|
|
->where('legacy_id', $legacyId)
|
|
->value('target_id');
|
|
|
|
if ($mappedUserId !== null) {
|
|
return User::query()->find($mappedUserId);
|
|
}
|
|
|
|
return User::query()
|
|
->where('legacy_portal', $portal)
|
|
->where('legacy_id', $legacyId)
|
|
->first();
|
|
}
|
|
}
|