From 5982165128a8e363240c9e0653714f99ea528af8 Mon Sep 17 00:00:00 2001 From: isaacclad Date: Sat, 27 Jun 2026 15:26:27 +0000 Subject: [PATCH] Fix ladl.link 404s for QR ecosystem slugs. Only direct Link redirects use destination_url; catalog QR slugs proxy through the platform resolver instead of malformed summary URLs. Co-authored-by: Cursor --- .../Public/LinkRedirectController.php | 2 +- app/Models/ShortLink.php | 6 ++ app/Services/Link/LinkCatalogSyncService.php | 2 +- app/Services/Link/LinkPlatformProxy.php | 5 +- tests/Feature/LinkRedirectTest.php | 63 +++++++++++++++++++ 5 files changed, 75 insertions(+), 3 deletions(-) create mode 100644 tests/Feature/LinkRedirectTest.php diff --git a/app/Http/Controllers/Public/LinkRedirectController.php b/app/Http/Controllers/Public/LinkRedirectController.php index 9ea7237..52fc6a9 100644 --- a/app/Http/Controllers/Public/LinkRedirectController.php +++ b/app/Http/Controllers/Public/LinkRedirectController.php @@ -21,7 +21,7 @@ class LinkRedirectController extends Controller { if ($path === null || $path === '') { $link = $this->links->resolve($request, $slug); - if ($link !== null) { + if ($link !== null && $link->isDirectRedirect()) { return redirect()->away($link->destination_url, 302); } } diff --git a/app/Models/ShortLink.php b/app/Models/ShortLink.php index 55121e7..a254d45 100644 --- a/app/Models/ShortLink.php +++ b/app/Models/ShortLink.php @@ -64,6 +64,12 @@ class ShortLink extends Model return $this->expires_at !== null && $this->expires_at->isPast(); } + /** Simple URL redirects created in Link; QR ecosystem slugs resolve via the platform proxy. */ + public function isDirectRedirect(): bool + { + return $this->is_managed_here && $this->source_kind === 'redirect'; + } + public function sourceAppLabel(): string { return match ($this->source_app) { diff --git a/app/Services/Link/LinkCatalogSyncService.php b/app/Services/Link/LinkCatalogSyncService.php index 49e16fa..7f9a058 100644 --- a/app/Services/Link/LinkCatalogSyncService.php +++ b/app/Services/Link/LinkCatalogSyncService.php @@ -40,7 +40,7 @@ class LinkCatalogSyncService [ 'user_id' => $user->id, 'label' => $row['label'] ?? null, - 'destination_url' => (string) ($row['destination_url'] ?? LadillLink::url($slug)), + 'destination_url' => (string) ($row['destination_summary'] ?? $row['destination_url'] ?? LadillLink::url($slug)), 'source_app' => (string) ($row['source_app'] ?? 'platform'), 'source_kind' => (string) ($row['source_kind'] ?? 'qr'), 'source_ref' => isset($row['source_ref']) ? (string) $row['source_ref'] : null, diff --git a/app/Services/Link/LinkPlatformProxy.php b/app/Services/Link/LinkPlatformProxy.php index 2cce2c4..3c911ff 100644 --- a/app/Services/Link/LinkPlatformProxy.php +++ b/app/Services/Link/LinkPlatformProxy.php @@ -24,8 +24,11 @@ class LinkPlatformProxy $target .= '?'.$qs; } - $client = Http::withOptions(['allow_redirects' => false])->timeout(15); $method = strtoupper($request->method()); + $followRedirects = $method === 'GET' || $method === 'HEAD'; + $client = Http::withOptions([ + 'allow_redirects' => $followRedirects ? ['max' => 5] : false, + ])->timeout(15); $pending = match ($method) { 'POST' => $client->withHeaders($this->forwardHeaders($request))->asForm()->post($target, $request->post()), diff --git a/tests/Feature/LinkRedirectTest.php b/tests/Feature/LinkRedirectTest.php new file mode 100644 index 0000000..8374b09 --- /dev/null +++ b/tests/Feature/LinkRedirectTest.php @@ -0,0 +1,63 @@ + (string) Str::uuid(), + 'name' => 'Test', + 'email' => 'test+'.uniqid().'@example.com', + ]); + } + + public function test_direct_redirect_links_go_to_destination_url(): void + { + $user = $this->user(); + ShortLink::create([ + 'user_id' => $user->id, + 'slug' => 'direct1', + 'source_app' => 'link', + 'source_kind' => 'redirect', + 'is_managed_here' => true, + 'destination_url' => 'https://example.com/target', + 'is_active' => true, + ]); + + $this->get('https://ladl.link/direct1') + ->assertRedirect('https://example.com/target'); + } + + public function test_catalog_qr_links_proxy_to_platform_instead_of_bad_destination(): void + { + $user = $this->user(); + ShortLink::create([ + 'user_id' => $user->id, + 'slug' => 'qrpage', + 'source_app' => 'qrplus', + 'source_kind' => 'qr', + 'is_managed_here' => false, + 'destination_url' => 'https://ladl.link/qrpage ยท Link list page', + 'is_active' => true, + ]); + + Http::fake([ + 'https://ladill.com/q/qrpage' => Http::response('QR page', 200), + ]); + + $this->get('https://ladl.link/qrpage') + ->assertOk() + ->assertSee('QR page'); + } +}