presseportale/app/Services/Api/LegacyApiKeyImporter.php
Kevin Adametz 5011f28f88 legacy: API-Key-Import löst E-Mail-gemergte User über legacy_import_map auf
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>
2026-07-20 13:19:42 +00:00

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();
}
}