From 5011f28f88f6ddf43b53ad7444f2a048eebddc18 Mon Sep 17 00:00:00 2001 From: Kevin Adametz Date: Mon, 20 Jul 2026 13:19:42 +0000 Subject: [PATCH] =?UTF-8?q?legacy:=20API-Key-Import=20l=C3=B6st=20E-Mail-g?= =?UTF-8?q?emergte=20User=20=C3=BCber=20legacy=5Fimport=5Fmap=20auf?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- app/Services/Api/LegacyApiKeyImporter.php | 31 ++++++++++++++++--- .../Api/LegacyApiCompatibilityTest.php | 29 +++++++++++++++++ 2 files changed, 56 insertions(+), 4 deletions(-) diff --git a/app/Services/Api/LegacyApiKeyImporter.php b/app/Services/Api/LegacyApiKeyImporter.php index 3cfce50d..c4daad38 100644 --- a/app/Services/Api/LegacyApiKeyImporter.php +++ b/app/Services/Api/LegacyApiKeyImporter.php @@ -2,6 +2,7 @@ namespace App\Services\Api; +use App\Models\LegacyImportMap; use App\Models\User; use Laravel\Sanctum\PersonalAccessToken; @@ -48,10 +49,7 @@ class LegacyApiKeyImporter continue; } - $user = User::query() - ->where('legacy_portal', $portal) - ->where('legacy_id', (int) $row['legacy_id']) - ->first(); + $user = $this->resolveUser($portal, (int) $row['legacy_id']); if (! $user) { $result['unknown_user']++; @@ -87,4 +85,29 @@ class LegacyApiKeyImporter 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(); + } } diff --git a/tests/Feature/Api/LegacyApiCompatibilityTest.php b/tests/Feature/Api/LegacyApiCompatibilityTest.php index 1fdedab3..cde6122b 100644 --- a/tests/Feature/Api/LegacyApiCompatibilityTest.php +++ b/tests/Feature/Api/LegacyApiCompatibilityTest.php @@ -4,6 +4,7 @@ use App\Enums\Portal; use App\Enums\UserPaymentOptionStatus; use App\Models\Category; use App\Models\Company; +use App\Models\LegacyImportMap; use App\Models\PaymentOption; use App\Models\PressRelease; use App\Models\User; @@ -80,6 +81,34 @@ test('the importer turns legacy api keys into sanctum tokens idempotently', func expect(PersonalAccessToken::query()->where('token', hash('sha256', 'legacy-key-abc123'))->count())->toBe(1); }); +test('the importer resolves email-merged users via the legacy import map', function () { + // E-Mail-Merge-Fall: Der Kunde existierte in beiden Altportalen; der + // User trägt nur das zuletzt importierte Portal (businessportal24) in + // legacy_portal/legacy_id. Der Presseecho-Schlüssel muss trotzdem über + // die legacy_import_map seinen Besitzer finden. + $user = eligibleLegacyApiUser(); + + LegacyImportMap::query()->create([ + 'legacy_portal' => 'presseecho', + 'legacy_table' => 'sf_guard_user', + 'legacy_id' => 555, + 'target_table' => 'users', + 'target_id' => $user->id, + 'imported_at' => now(), + ]); + + $result = app(LegacyApiKeyImporter::class)->import('presseecho', [ + ['legacy_id' => 555, 'api_key' => 'legacy-key-presseecho'], + ]); + + expect($result['imported'])->toBe(1); + expect($result['unknown_user'])->toBe(0); + + $token = PersonalAccessToken::findToken('legacy-key-presseecho'); + expect($token)->not->toBeNull(); + expect($token->tokenable_id)->toBe($user->id); +}); + test('an imported legacy key authenticates only via legacy headers', function () { /** @var TestCase $this */ eligibleLegacyApiUser();