From 7c1f230bd34e6de3b95a5e40d1dd1a118d569681 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Sun, 7 Dec 2025 21:53:47 +0100 Subject: [PATCH] fix: remove {{port}} template variable and ensure ports are always appended to preview URLs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The {{port}} template variable was undocumented and caused a double port bug when used in preview URL templates. Since ports are always appended to the final URL anyway, we remove {{port}} substitution entirely and ensure consistent port handling across ApplicationPreview, PreviewsCompose, and the applicationParser helper. Also fix PreviewsCompose.php which wasn't preserving ports at all, and improve the Blade template formatting in previews-compose.blade.php. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .../Project/Application/PreviewsCompose.php | 3 +- app/Models/ApplicationPreview.php | 4 +- bootstrap/helpers/parsers.php | 4 +- .../application/previews-compose.blade.php | 6 +- templates/service-templates-latest.json | 34 ++--- templates/service-templates.json | 34 ++--- tests/Unit/PreviewDeploymentPortTest.php | 135 ++++++++++++++++++ 7 files changed, 179 insertions(+), 41 deletions(-) create mode 100644 tests/Unit/PreviewDeploymentPortTest.php diff --git a/app/Livewire/Project/Application/PreviewsCompose.php b/app/Livewire/Project/Application/PreviewsCompose.php index 942dfeb37..85ba2328e 100644 --- a/app/Livewire/Project/Application/PreviewsCompose.php +++ b/app/Livewire/Project/Application/PreviewsCompose.php @@ -96,8 +96,7 @@ public function generate() $preview_fqdn = str_replace('{{random}}', $random, $template); $preview_fqdn = str_replace('{{domain}}', $host, $preview_fqdn); $preview_fqdn = str_replace('{{pr_id}}', $this->preview->pull_request_id, $preview_fqdn); - $preview_fqdn = str_replace('{{port}}', $port, $preview_fqdn); - $preview_fqdns[] = "$schema://$preview_fqdn"; + $preview_fqdns[] = "$schema://$preview_fqdn{$port}"; } $preview_fqdn = implode(',', $preview_fqdns); diff --git a/app/Models/ApplicationPreview.php b/app/Models/ApplicationPreview.php index 721b22216..04ce6274a 100644 --- a/app/Models/ApplicationPreview.php +++ b/app/Models/ApplicationPreview.php @@ -145,11 +145,13 @@ public function generate_preview_fqdn_compose() $template = $this->application->preview_url_template; $host = $url->getHost(); $schema = $url->getScheme(); + $portInt = $url->getPort(); + $port = $portInt !== null ? ':'.$portInt : ''; $random = new Cuid2; $preview_fqdn = str_replace('{{random}}', $random, $template); $preview_fqdn = str_replace('{{domain}}', $host, $preview_fqdn); $preview_fqdn = str_replace('{{pr_id}}', $this->pull_request_id, $preview_fqdn); - $preview_fqdn = "$schema://$preview_fqdn"; + $preview_fqdn = "$schema://$preview_fqdn{$port}"; $preview_domains[] = $preview_fqdn; } diff --git a/bootstrap/helpers/parsers.php b/bootstrap/helpers/parsers.php index d58a4b4fe..43ba58e59 100644 --- a/bootstrap/helpers/parsers.php +++ b/bootstrap/helpers/parsers.php @@ -1145,11 +1145,13 @@ function applicationParser(Application $resource, int $pull_request_id = 0, ?int $template = $resource->preview_url_template; $host = $url->getHost(); $schema = $url->getScheme(); + $portInt = $url->getPort(); + $port = $portInt !== null ? ':'.$portInt : ''; $random = new Cuid2; $preview_fqdn = str_replace('{{random}}', $random, $template); $preview_fqdn = str_replace('{{domain}}', $host, $preview_fqdn); $preview_fqdn = str_replace('{{pr_id}}', $pullRequestId, $preview_fqdn); - $preview_fqdn = "$schema://$preview_fqdn"; + $preview_fqdn = "$schema://$preview_fqdn{$port}"; $preview->fqdn = $preview_fqdn; $preview->save(); diff --git a/resources/views/livewire/project/application/previews-compose.blade.php b/resources/views/livewire/project/application/previews-compose.blade.php index ae8d70243..8d653d950 100644 --- a/resources/views/livewire/project/application/previews-compose.blade.php +++ b/resources/views/livewire/project/application/previews-compose.blade.php @@ -1,7 +1,7 @@
- + Save Generate Domain -
\ No newline at end of file + diff --git a/templates/service-templates-latest.json b/templates/service-templates-latest.json index bc46c3c5d..db9c040ff 100644 --- a/templates/service-templates-latest.json +++ b/templates/service-templates-latest.json @@ -4298,23 +4298,6 @@ "minversion": "0.0.0", "port": "4242" }, - "unsend": { - "documentation": "https://docs.unsend.dev/get-started/self-hosting?utm_source=coolify.io", - "slogan": "Unsend is an open-source alternative to Resend, Sendgrid, Mailgun and Postmark etc.", - "compose": "c2VydmljZXM6CiAgcG9zdGdyZXM6CiAgICBpbWFnZTogJ3Bvc3RncmVzOjE2JwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gJ1BPU1RHUkVTX1VTRVI9JHtTRVJWSUNFX1VTRVJfUE9TVEdSRVN9JwogICAgICAtICdQT1NUR1JFU19QQVNTV09SRD0ke1NFUlZJQ0VfUEFTU1dPUkRfUE9TVEdSRVN9JwogICAgICAtICdQT1NUR1JFU19EQj0ke1NFUlZJQ0VfREJfUE9TVEdSRVM6LXVuc2VuZH0nCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRC1TSEVMTAogICAgICAgIC0gJ3BnX2lzcmVhZHkgLVUgJCR7UE9TVEdSRVNfVVNFUn0gLWQgJCR7UE9TVEdSRVNfREJ9JwogICAgICBpbnRlcnZhbDogNXMKICAgICAgdGltZW91dDogMjBzCiAgICAgIHJldHJpZXM6IDEwCiAgICB2b2x1bWVzOgogICAgICAtICd1bnNlbmQtcG9zdGdyZXMtZGF0YTovdmFyL2xpYi9wb3N0Z3Jlc3FsL2RhdGEnCiAgcmVkaXM6CiAgICBpbWFnZTogJ3JlZGlzOjcnCiAgICB2b2x1bWVzOgogICAgICAtICd1bnNlbmQtcmVkaXMtZGF0YTovZGF0YScKICAgIGNvbW1hbmQ6CiAgICAgIC0gcmVkaXMtc2VydmVyCiAgICAgIC0gJy0tbWF4bWVtb3J5LXBvbGljeScKICAgICAgLSBub2V2aWN0aW9uCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRAogICAgICAgIC0gcmVkaXMtY2xpCiAgICAgICAgLSBQSU5HCiAgICAgIGludGVydmFsOiA1cwogICAgICB0aW1lb3V0OiAxMHMKICAgICAgcmV0cmllczogMjAKICB1bnNlbmQ6CiAgICBpbWFnZTogJ3Vuc2VuZC91bnNlbmQ6bGF0ZXN0JwogICAgZXhwb3NlOgogICAgICAtIDMwMDAKICAgIGVudmlyb25tZW50OgogICAgICAtIFNFUlZJQ0VfVVJMX1VOU0VORF8zMDAwCiAgICAgIC0gJ0RBVEFCQVNFX1VSTD1wb3N0Z3Jlc3FsOi8vJHtTRVJWSUNFX1VTRVJfUE9TVEdSRVN9OiR7U0VSVklDRV9QQVNTV09SRF9QT1NUR1JFU31AcG9zdGdyZXM6NTQzMi8ke1NFUlZJQ0VfREJfUE9TVEdSRVM6LXVuc2VuZH0nCiAgICAgIC0gJ05FWFRBVVRIX1VSTD0ke1NFUlZJQ0VfVVJMX1VOU0VORH0nCiAgICAgIC0gJ05FWFRBVVRIX1NFQ1JFVD0ke1NFUlZJQ0VfQkFTRTY0XzY0X05FWFRBVVRIU0VDUkVUfScKICAgICAgLSAnQVdTX0FDQ0VTU19LRVk9JHtBV1NfQUNDRVNTX0tFWTo/fScKICAgICAgLSAnQVdTX1NFQ1JFVF9LRVk9JHtBV1NfU0VDUkVUX0tFWTo/fScKICAgICAgLSAnQVdTX0RFRkFVTFRfUkVHSU9OPSR7QVdTX0RFRkFVTFRfUkVHSU9OOj99JwogICAgICAtICdHSVRIVUJfSUQ9JHtHSVRIVUJfSUR9JwogICAgICAtICdHSVRIVUJfU0VDUkVUPSR7R0lUSFVCX1NFQ1JFVH0nCiAgICAgIC0gJ1JFRElTX1VSTD1yZWRpczovL3JlZGlzOjYzNzknCiAgICAgIC0gJ05FWFRfUFVCTElDX0lTX0NMT1VEPSR7TkVYVF9QVUJMSUNfSVNfQ0xPVUQ6LWZhbHNlfScKICAgICAgLSAnQVBJX1JBVEVfTElNSVQ9JHtBUElfUkFURV9MSU1JVDotMX0nCiAgICAgIC0gSE9TVE5BTUU9MC4wLjAuMAogICAgZGVwZW5kc19vbjoKICAgICAgcG9zdGdyZXM6CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX2hlYWx0aHkKICAgICAgcmVkaXM6CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX2hlYWx0aHkKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ELVNIRUxMCiAgICAgICAgLSAnd2dldCAtcU8tIGh0dHA6Ly91bnNlbmQ6MzAwMCB8fCBleGl0IDEnCiAgICAgIGludGVydmFsOiA1cwogICAgICByZXRyaWVzOiAxMAogICAgICB0aW1lb3V0OiAycwo=", - "tags": [ - "resend", - "mailer", - "marketing emails", - "transaction emails", - "self-hosting", - "postmark" - ], - "category": "messaging", - "logo": "svgs/unsend.svg", - "minversion": "0.0.0", - "port": "3000" - }, "unstructured": { "documentation": "https://github.com/Unstructured-IO/unstructured-api?tab=readme-ov-file#--general-pre-processing-pipeline-for-documents?utm_source=coolify.io", "slogan": "Unstructured provides a platform and tools to ingest and process unstructured documents for Retrieval Augmented Generation (RAG) and model fine-tuning.", @@ -4355,6 +4338,23 @@ "minversion": "0.0.0", "port": "3001" }, + "usesend": { + "documentation": "https://docs.usesend.com/self-hosting/overview?utm_source=coolify.io", + "slogan": "Usesend is an open-source alternative to Resend, Sendgrid, Mailgun and Postmark etc.", + "compose": "c2VydmljZXM6CiAgcG9zdGdyZXM6CiAgICBpbWFnZTogJ3Bvc3RncmVzOjE2JwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gJ1BPU1RHUkVTX1VTRVI9JHtTRVJWSUNFX1VTRVJfUE9TVEdSRVN9JwogICAgICAtICdQT1NUR1JFU19QQVNTV09SRD0ke1NFUlZJQ0VfUEFTU1dPUkRfUE9TVEdSRVN9JwogICAgICAtICdQT1NUR1JFU19EQj0ke1NFUlZJQ0VfREJfUE9TVEdSRVM6LXVzZXNlbmR9JwogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQtU0hFTEwKICAgICAgICAtICdwZ19pc3JlYWR5IC1VICQke1BPU1RHUkVTX1VTRVJ9IC1kICQke1BPU1RHUkVTX0RCfScKICAgICAgaW50ZXJ2YWw6IDVzCiAgICAgIHRpbWVvdXQ6IDIwcwogICAgICByZXRyaWVzOiAxMAogICAgdm9sdW1lczoKICAgICAgLSAndXNlc2VuZC1wb3N0Z3Jlcy1kYXRhOi92YXIvbGliL3Bvc3RncmVzcWwvZGF0YScKICByZWRpczoKICAgIGltYWdlOiAncmVkaXM6NycKICAgIHZvbHVtZXM6CiAgICAgIC0gJ3VzZXNlbmQtcmVkaXMtZGF0YTovZGF0YScKICAgIGNvbW1hbmQ6CiAgICAgIC0gcmVkaXMtc2VydmVyCiAgICAgIC0gJy0tbWF4bWVtb3J5LXBvbGljeScKICAgICAgLSBub2V2aWN0aW9uCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRAogICAgICAgIC0gcmVkaXMtY2xpCiAgICAgICAgLSBQSU5HCiAgICAgIGludGVydmFsOiA1cwogICAgICB0aW1lb3V0OiAxMHMKICAgICAgcmV0cmllczogMjAKICB1c2VzZW5kOgogICAgaW1hZ2U6ICd1c2VzZW5kL3VzZXNlbmQ6bGF0ZXN0JwogICAgZXhwb3NlOgogICAgICAtIDMwMDAKICAgIGVudmlyb25tZW50OgogICAgICAtIFNFUlZJQ0VfVVJMX1VTRVNFTkRfMzAwMAogICAgICAtICdEQVRBQkFTRV9VUkw9cG9zdGdyZXNxbDovLyR7U0VSVklDRV9VU0VSX1BPU1RHUkVTfToke1NFUlZJQ0VfUEFTU1dPUkRfUE9TVEdSRVN9QHBvc3RncmVzOjU0MzIvJHtTRVJWSUNFX0RCX1BPU1RHUkVTOi11c2VzZW5kfScKICAgICAgLSAnTkVYVEFVVEhfVVJMPSR7U0VSVklDRV9VUkxfVVNFU0VORH0nCiAgICAgIC0gJ05FWFRBVVRIX1NFQ1JFVD0ke1NFUlZJQ0VfQkFTRTY0XzY0X05FWFRBVVRIU0VDUkVUfScKICAgICAgLSAnQVdTX0FDQ0VTU19LRVk9JHtBV1NfQUNDRVNTX0tFWTo/fScKICAgICAgLSAnQVdTX1NFQ1JFVF9LRVk9JHtBV1NfU0VDUkVUX0tFWTo/fScKICAgICAgLSAnQVdTX0RFRkFVTFRfUkVHSU9OPSR7QVdTX0RFRkFVTFRfUkVHSU9OOj99JwogICAgICAtICdHSVRIVUJfSUQ9JHtHSVRIVUJfSUQ6P30nCiAgICAgIC0gJ0dJVEhVQl9TRUNSRVQ9JHtHSVRIVUJfU0VDUkVUOj99JwogICAgICAtICdSRURJU19VUkw9cmVkaXM6Ly9yZWRpczo2Mzc5JwogICAgICAtICdORVhUX1BVQkxJQ19JU19DTE9VRD0ke05FWFRfUFVCTElDX0lTX0NMT1VEOi1mYWxzZX0nCiAgICAgIC0gJ0FQSV9SQVRFX0xJTUlUPSR7QVBJX1JBVEVfTElNSVQ6LTF9JwogICAgICAtIEhPU1ROQU1FPTAuMC4wLjAKICAgIGRlcGVuZHNfb246CiAgICAgIHBvc3RncmVzOgogICAgICAgIGNvbmRpdGlvbjogc2VydmljZV9oZWFsdGh5CiAgICAgIHJlZGlzOgogICAgICAgIGNvbmRpdGlvbjogc2VydmljZV9oZWFsdGh5CiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRC1TSEVMTAogICAgICAgIC0gJ3dnZXQgLXFPLSBodHRwOi8vdXNlc2VuZDozMDAwIHx8IGV4aXQgMScKICAgICAgaW50ZXJ2YWw6IDVzCiAgICAgIHJldHJpZXM6IDEwCiAgICAgIHRpbWVvdXQ6IDJzCg==", + "tags": [ + "resend", + "mailer", + "marketing emails", + "transaction emails", + "self-hosting", + "postmark" + ], + "category": "messaging", + "logo": "svgs/usesend.svg", + "minversion": "0.0.0", + "port": "3000" + }, "vaultwarden": { "documentation": "https://github.com/dani-garcia/vaultwarden?utm_source=coolify.io", "slogan": "Vaultwarden is a password manager that allows you to securely store and manage your passwords.", diff --git a/templates/service-templates.json b/templates/service-templates.json index 7536800a0..0fa619192 100644 --- a/templates/service-templates.json +++ b/templates/service-templates.json @@ -4298,23 +4298,6 @@ "minversion": "0.0.0", "port": "4242" }, - "unsend": { - "documentation": "https://docs.unsend.dev/get-started/self-hosting?utm_source=coolify.io", - "slogan": "Unsend is an open-source alternative to Resend, Sendgrid, Mailgun and Postmark etc.", - "compose": "c2VydmljZXM6CiAgcG9zdGdyZXM6CiAgICBpbWFnZTogJ3Bvc3RncmVzOjE2JwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gJ1BPU1RHUkVTX1VTRVI9JHtTRVJWSUNFX1VTRVJfUE9TVEdSRVN9JwogICAgICAtICdQT1NUR1JFU19QQVNTV09SRD0ke1NFUlZJQ0VfUEFTU1dPUkRfUE9TVEdSRVN9JwogICAgICAtICdQT1NUR1JFU19EQj0ke1NFUlZJQ0VfREJfUE9TVEdSRVM6LXVuc2VuZH0nCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRC1TSEVMTAogICAgICAgIC0gJ3BnX2lzcmVhZHkgLVUgJCR7UE9TVEdSRVNfVVNFUn0gLWQgJCR7UE9TVEdSRVNfREJ9JwogICAgICBpbnRlcnZhbDogNXMKICAgICAgdGltZW91dDogMjBzCiAgICAgIHJldHJpZXM6IDEwCiAgICB2b2x1bWVzOgogICAgICAtICd1bnNlbmQtcG9zdGdyZXMtZGF0YTovdmFyL2xpYi9wb3N0Z3Jlc3FsL2RhdGEnCiAgcmVkaXM6CiAgICBpbWFnZTogJ3JlZGlzOjcnCiAgICB2b2x1bWVzOgogICAgICAtICd1bnNlbmQtcmVkaXMtZGF0YTovZGF0YScKICAgIGNvbW1hbmQ6CiAgICAgIC0gcmVkaXMtc2VydmVyCiAgICAgIC0gJy0tbWF4bWVtb3J5LXBvbGljeScKICAgICAgLSBub2V2aWN0aW9uCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRAogICAgICAgIC0gcmVkaXMtY2xpCiAgICAgICAgLSBQSU5HCiAgICAgIGludGVydmFsOiA1cwogICAgICB0aW1lb3V0OiAxMHMKICAgICAgcmV0cmllczogMjAKICB1bnNlbmQ6CiAgICBpbWFnZTogJ3Vuc2VuZC91bnNlbmQ6bGF0ZXN0JwogICAgZXhwb3NlOgogICAgICAtIDMwMDAKICAgIGVudmlyb25tZW50OgogICAgICAtIFNFUlZJQ0VfRlFETl9VTlNFTkRfMzAwMAogICAgICAtICdEQVRBQkFTRV9VUkw9cG9zdGdyZXNxbDovLyR7U0VSVklDRV9VU0VSX1BPU1RHUkVTfToke1NFUlZJQ0VfUEFTU1dPUkRfUE9TVEdSRVN9QHBvc3RncmVzOjU0MzIvJHtTRVJWSUNFX0RCX1BPU1RHUkVTOi11bnNlbmR9JwogICAgICAtICdORVhUQVVUSF9VUkw9JHtTRVJWSUNFX0ZRRE5fVU5TRU5EfScKICAgICAgLSAnTkVYVEFVVEhfU0VDUkVUPSR7U0VSVklDRV9CQVNFNjRfNjRfTkVYVEFVVEhTRUNSRVR9JwogICAgICAtICdBV1NfQUNDRVNTX0tFWT0ke0FXU19BQ0NFU1NfS0VZOj99JwogICAgICAtICdBV1NfU0VDUkVUX0tFWT0ke0FXU19TRUNSRVRfS0VZOj99JwogICAgICAtICdBV1NfREVGQVVMVF9SRUdJT049JHtBV1NfREVGQVVMVF9SRUdJT046P30nCiAgICAgIC0gJ0dJVEhVQl9JRD0ke0dJVEhVQl9JRH0nCiAgICAgIC0gJ0dJVEhVQl9TRUNSRVQ9JHtHSVRIVUJfU0VDUkVUfScKICAgICAgLSAnUkVESVNfVVJMPXJlZGlzOi8vcmVkaXM6NjM3OScKICAgICAgLSAnTkVYVF9QVUJMSUNfSVNfQ0xPVUQ9JHtORVhUX1BVQkxJQ19JU19DTE9VRDotZmFsc2V9JwogICAgICAtICdBUElfUkFURV9MSU1JVD0ke0FQSV9SQVRFX0xJTUlUOi0xfScKICAgICAgLSBIT1NUTkFNRT0wLjAuMC4wCiAgICBkZXBlbmRzX29uOgogICAgICBwb3N0Z3JlczoKICAgICAgICBjb25kaXRpb246IHNlcnZpY2VfaGVhbHRoeQogICAgICByZWRpczoKICAgICAgICBjb25kaXRpb246IHNlcnZpY2VfaGVhbHRoeQogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQtU0hFTEwKICAgICAgICAtICd3Z2V0IC1xTy0gaHR0cDovL3Vuc2VuZDozMDAwIHx8IGV4aXQgMScKICAgICAgaW50ZXJ2YWw6IDVzCiAgICAgIHJldHJpZXM6IDEwCiAgICAgIHRpbWVvdXQ6IDJzCg==", - "tags": [ - "resend", - "mailer", - "marketing emails", - "transaction emails", - "self-hosting", - "postmark" - ], - "category": "messaging", - "logo": "svgs/unsend.svg", - "minversion": "0.0.0", - "port": "3000" - }, "unstructured": { "documentation": "https://github.com/Unstructured-IO/unstructured-api?tab=readme-ov-file#--general-pre-processing-pipeline-for-documents?utm_source=coolify.io", "slogan": "Unstructured provides a platform and tools to ingest and process unstructured documents for Retrieval Augmented Generation (RAG) and model fine-tuning.", @@ -4355,6 +4338,23 @@ "minversion": "0.0.0", "port": "3001" }, + "usesend": { + "documentation": "https://docs.usesend.com/self-hosting/overview?utm_source=coolify.io", + "slogan": "Usesend is an open-source alternative to Resend, Sendgrid, Mailgun and Postmark etc.", + "compose": "c2VydmljZXM6CiAgcG9zdGdyZXM6CiAgICBpbWFnZTogJ3Bvc3RncmVzOjE2JwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gJ1BPU1RHUkVTX1VTRVI9JHtTRVJWSUNFX1VTRVJfUE9TVEdSRVN9JwogICAgICAtICdQT1NUR1JFU19QQVNTV09SRD0ke1NFUlZJQ0VfUEFTU1dPUkRfUE9TVEdSRVN9JwogICAgICAtICdQT1NUR1JFU19EQj0ke1NFUlZJQ0VfREJfUE9TVEdSRVM6LXVzZXNlbmR9JwogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQtU0hFTEwKICAgICAgICAtICdwZ19pc3JlYWR5IC1VICQke1BPU1RHUkVTX1VTRVJ9IC1kICQke1BPU1RHUkVTX0RCfScKICAgICAgaW50ZXJ2YWw6IDVzCiAgICAgIHRpbWVvdXQ6IDIwcwogICAgICByZXRyaWVzOiAxMAogICAgdm9sdW1lczoKICAgICAgLSAndXNlc2VuZC1wb3N0Z3Jlcy1kYXRhOi92YXIvbGliL3Bvc3RncmVzcWwvZGF0YScKICByZWRpczoKICAgIGltYWdlOiAncmVkaXM6NycKICAgIHZvbHVtZXM6CiAgICAgIC0gJ3VzZXNlbmQtcmVkaXMtZGF0YTovZGF0YScKICAgIGNvbW1hbmQ6CiAgICAgIC0gcmVkaXMtc2VydmVyCiAgICAgIC0gJy0tbWF4bWVtb3J5LXBvbGljeScKICAgICAgLSBub2V2aWN0aW9uCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRAogICAgICAgIC0gcmVkaXMtY2xpCiAgICAgICAgLSBQSU5HCiAgICAgIGludGVydmFsOiA1cwogICAgICB0aW1lb3V0OiAxMHMKICAgICAgcmV0cmllczogMjAKICB1c2VzZW5kOgogICAgaW1hZ2U6ICd1c2VzZW5kL3VzZXNlbmQ6bGF0ZXN0JwogICAgZXhwb3NlOgogICAgICAtIDMwMDAKICAgIGVudmlyb25tZW50OgogICAgICAtIFNFUlZJQ0VfRlFETl9VU0VTRU5EXzMwMDAKICAgICAgLSAnREFUQUJBU0VfVVJMPXBvc3RncmVzcWw6Ly8ke1NFUlZJQ0VfVVNFUl9QT1NUR1JFU306JHtTRVJWSUNFX1BBU1NXT1JEX1BPU1RHUkVTfUBwb3N0Z3Jlczo1NDMyLyR7U0VSVklDRV9EQl9QT1NUR1JFUzotdXNlc2VuZH0nCiAgICAgIC0gJ05FWFRBVVRIX1VSTD0ke1NFUlZJQ0VfRlFETl9VU0VTRU5EfScKICAgICAgLSAnTkVYVEFVVEhfU0VDUkVUPSR7U0VSVklDRV9CQVNFNjRfNjRfTkVYVEFVVEhTRUNSRVR9JwogICAgICAtICdBV1NfQUNDRVNTX0tFWT0ke0FXU19BQ0NFU1NfS0VZOj99JwogICAgICAtICdBV1NfU0VDUkVUX0tFWT0ke0FXU19TRUNSRVRfS0VZOj99JwogICAgICAtICdBV1NfREVGQVVMVF9SRUdJT049JHtBV1NfREVGQVVMVF9SRUdJT046P30nCiAgICAgIC0gJ0dJVEhVQl9JRD0ke0dJVEhVQl9JRDo/fScKICAgICAgLSAnR0lUSFVCX1NFQ1JFVD0ke0dJVEhVQl9TRUNSRVQ6P30nCiAgICAgIC0gJ1JFRElTX1VSTD1yZWRpczovL3JlZGlzOjYzNzknCiAgICAgIC0gJ05FWFRfUFVCTElDX0lTX0NMT1VEPSR7TkVYVF9QVUJMSUNfSVNfQ0xPVUQ6LWZhbHNlfScKICAgICAgLSAnQVBJX1JBVEVfTElNSVQ9JHtBUElfUkFURV9MSU1JVDotMX0nCiAgICAgIC0gSE9TVE5BTUU9MC4wLjAuMAogICAgZGVwZW5kc19vbjoKICAgICAgcG9zdGdyZXM6CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX2hlYWx0aHkKICAgICAgcmVkaXM6CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX2hlYWx0aHkKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ELVNIRUxMCiAgICAgICAgLSAnd2dldCAtcU8tIGh0dHA6Ly91c2VzZW5kOjMwMDAgfHwgZXhpdCAxJwogICAgICBpbnRlcnZhbDogNXMKICAgICAgcmV0cmllczogMTAKICAgICAgdGltZW91dDogMnMK", + "tags": [ + "resend", + "mailer", + "marketing emails", + "transaction emails", + "self-hosting", + "postmark" + ], + "category": "messaging", + "logo": "svgs/usesend.svg", + "minversion": "0.0.0", + "port": "3000" + }, "vaultwarden": { "documentation": "https://github.com/dani-garcia/vaultwarden?utm_source=coolify.io", "slogan": "Vaultwarden is a password manager that allows you to securely store and manage your passwords.", diff --git a/tests/Unit/PreviewDeploymentPortTest.php b/tests/Unit/PreviewDeploymentPortTest.php new file mode 100644 index 000000000..0dae6789a --- /dev/null +++ b/tests/Unit/PreviewDeploymentPortTest.php @@ -0,0 +1,135 @@ +getPort(); + + expect($port)->toBe(3000); +}); + +it('returns null port for domain URL without port', function () { + $domain = 'https://example.com'; + $url = Url::fromString($domain); + + $port = $url->getPort(); + + expect($port)->toBeNull(); +}); + +it('extracts port from HTTP URL with custom port', function () { + $domain = 'http://example.com:8080'; + $url = Url::fromString($domain); + + $port = $url->getPort(); + + expect($port)->toBe(8080); +}); + +it('generates preview FQDN with port preserved', function () { + $domain = 'https://example.com:3000'; + $url = Url::fromString($domain); + $template = '{{pr_id}}.{{domain}}'; + $pullRequestId = 42; + + $host = $url->getHost(); + $schema = $url->getScheme(); + $portInt = $url->getPort(); + $port = $portInt !== null ? ':'.$portInt : ''; + + $preview_fqdn = str_replace('{{random}}', 'abc123', $template); + $preview_fqdn = str_replace('{{domain}}', $host, $preview_fqdn); + $preview_fqdn = str_replace('{{pr_id}}', $pullRequestId, $preview_fqdn); + $preview_fqdn = "$schema://$preview_fqdn{$port}"; + + expect($preview_fqdn)->toBe('https://42.example.com:3000'); +}); + +it('generates preview FQDN without port when original has no port', function () { + $domain = 'https://example.com'; + $url = Url::fromString($domain); + $template = '{{pr_id}}.{{domain}}'; + $pullRequestId = 42; + + $host = $url->getHost(); + $schema = $url->getScheme(); + $portInt = $url->getPort(); + $port = $portInt !== null ? ':'.$portInt : ''; + + $preview_fqdn = str_replace('{{random}}', 'abc123', $template); + $preview_fqdn = str_replace('{{domain}}', $host, $preview_fqdn); + $preview_fqdn = str_replace('{{pr_id}}', $pullRequestId, $preview_fqdn); + $preview_fqdn = "$schema://$preview_fqdn{$port}"; + + expect($preview_fqdn)->toBe('https://42.example.com'); +}); + +it('handles multiple domains with different ports', function () { + $domains = [ + 'https://app.example.com:3000', + 'https://api.example.com:8080', + 'https://web.example.com', + ]; + + $preview_fqdns = []; + $template = 'pr-{{pr_id}}.{{domain}}'; + $pullRequestId = 123; + + foreach ($domains as $domain) { + $url = Url::fromString($domain); + $host = $url->getHost(); + $schema = $url->getScheme(); + $portInt = $url->getPort(); + $port = $portInt !== null ? ':'.$portInt : ''; + + $preview_fqdn = str_replace('{{random}}', 'xyz', $template); + $preview_fqdn = str_replace('{{domain}}', $host, $preview_fqdn); + $preview_fqdn = str_replace('{{pr_id}}', $pullRequestId, $preview_fqdn); + $preview_fqdn = "$schema://$preview_fqdn{$port}"; + $preview_fqdns[] = $preview_fqdn; + } + + expect($preview_fqdns[0])->toBe('https://pr-123.app.example.com:3000'); + expect($preview_fqdns[1])->toBe('https://pr-123.api.example.com:8080'); + expect($preview_fqdns[2])->toBe('https://pr-123.web.example.com'); +}); + +it('extracts all URL components correctly', function () { + $domain = 'https://app.example.com:3000/api/v1'; + $url = Url::fromString($domain); + + expect($url->getScheme())->toBe('https'); + expect($url->getHost())->toBe('app.example.com'); + expect($url->getPort())->toBe(3000); + expect($url->getPath())->toBe('/api/v1'); +}); + +it('formats port string correctly for URL construction', function () { + // Test port formatting logic + $testCases = [ + ['port' => 3000, 'expected' => ':3000'], + ['port' => 8080, 'expected' => ':8080'], + ['port' => 80, 'expected' => ':80'], + ['port' => 443, 'expected' => ':443'], + ['port' => null, 'expected' => ''], + ]; + + foreach ($testCases as $case) { + $portInt = $case['port']; + $port = $portInt !== null ? ':'.$portInt : ''; + + expect($port)->toBe($case['expected']); + } +});