diff --git a/app/controllers/api/vcs.php b/app/controllers/api/vcs.php index 84ab2eb86c..867f9bd3be 100644 --- a/app/controllers/api/vcs.php +++ b/app/controllers/api/vcs.php @@ -9,8 +9,8 @@ use Appwrite\SDK\ContentType; use Appwrite\SDK\Method; use Appwrite\SDK\MethodType; use Appwrite\SDK\Response as SDKResponse; -use Appwrite\Transformation\Adapter\BranchDomain; -use Appwrite\Transformation\Transformation; +use Appwrite\Filter\Filter; +use Appwrite\Filter\Adapter\BranchDomain as FilterBranchDomain; use Appwrite\Utopia\Database\Validator\Queries\Installations; use Appwrite\Utopia\Request; use Appwrite\Utopia\Response; @@ -371,17 +371,16 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId // VCS branch preview if (!empty($providerBranch)) { - $transformation = new Transformation([new BranchDomain()]); - $transformation + $filter = new Filter([new FilterBranchDomain()]); + $filter ->setInput([ 'branch' => $providerBranch, 'resourceId' => $resource->getId(), 'projectId' => $project->getId(), 'sitesDomain' => $sitesDomain, ]) - ->setTraits([]) - ->transform(); - $domain = $transformation->getOutput(); + ->filter(); + $domain = $filter->getOutput(); $ruleId = md5($domain); try { $authorization->skip( diff --git a/src/Appwrite/Filter/Adapter.php b/src/Appwrite/Filter/Adapter.php new file mode 100644 index 0000000000..c80f6245ca --- /dev/null +++ b/src/Appwrite/Filter/Adapter.php @@ -0,0 +1,29 @@ +input = $input; + return $this; + } + + public function getOutput(): mixed + { + return $this->output; + } + + abstract public function isValid(mixed $input): bool; + + abstract public function filter(): self; +} \ No newline at end of file diff --git a/src/Appwrite/Transformation/Adapter/BranchDomain.php b/src/Appwrite/Filter/Adapter/BranchDomain.php similarity index 83% rename from src/Appwrite/Transformation/Adapter/BranchDomain.php rename to src/Appwrite/Filter/Adapter/BranchDomain.php index c0efd3196f..97701beeff 100644 --- a/src/Appwrite/Transformation/Adapter/BranchDomain.php +++ b/src/Appwrite/Filter/Adapter/BranchDomain.php @@ -1,8 +1,8 @@ $traits - */ - public function isValid(array $traits): bool + public function isValid(mixed $input): bool { + $branch = $input['branch'] ?? ''; + $resourceId = $input['resourceId'] ?? ''; + $projectId = $input['projectId'] ?? ''; + $sitesDomain = $input['sitesDomain'] ?? ''; + + if (empty($branch) || empty($resourceId) || empty($projectId) || empty($sitesDomain)) { + return false; + } + return true; } /** - * Transform branch name into a valid domain name. - * + * Pre-process branch name to a valid domain name. + * * Input should be an array with: * - 'branch' (string): The branch name * - 'resourceId' (string): The resource ID (site or function) * - 'projectId' (string): The project ID * - 'sitesDomain' (string): The base sites domain */ - public function transform(): void + public function filter(): self { $branch = $this->input['branch'] ?? ''; $resourceId = $this->input['resourceId'] ?? ''; @@ -44,6 +50,7 @@ class BranchDomain extends Adapter $branchPrefix = $this->generateBranchPrefix($branch); $resourceProjectHash = substr(hash('sha256', $resourceId . $projectId), 0, self::HASH_SUFFIX_LENGTH); $this->output = strtolower("branch-{$branchPrefix}-{$resourceProjectHash}.{$sitesDomain}"); + return $this; } /** @@ -92,7 +99,7 @@ class BranchDomain extends Adapter if (isset($allowedCharsFlip[$char])) { $sanitized .= $char; } else { - // Prevents two -- or more in row + // Prevents two -- or more in a row if (strlen($sanitized) > 0 && $sanitized[strlen($sanitized) - 1] !== '-') { $sanitized .= '-'; } @@ -101,4 +108,4 @@ class BranchDomain extends Adapter return trim($sanitized, '-'); } -} +} \ No newline at end of file diff --git a/src/Appwrite/Filter/Filter.php b/src/Appwrite/Filter/Filter.php new file mode 100644 index 0000000000..0532728ea2 --- /dev/null +++ b/src/Appwrite/Filter/Filter.php @@ -0,0 +1,62 @@ + $adapters + */ + protected array $adapters; + + protected mixed $input; + + protected mixed $output; + + /** + * @param array $adapters + */ + public function __construct(array $adapters = []) + { + $this->adapters = $adapters; + } + + public function setInput(mixed $input): self + { + $this->input = $input; + return $this; + } + + public function getOutput(): mixed + { + return $this->output; + } + + public function addAdapter(Adapter $adapter): self + { + $this->adapters[] = $adapter; + return $this; + } + + public function filter(): bool + { + foreach ($this->adapters as $adapter) { + if (!$adapter->isValid($this->input)) { + return false; + } + } + + $output = $this->input; + + foreach ($this->adapters as $adapter) { + $output = $adapter + ->setInput($output) + ->filter() + ->getOutput(); + } + + $this->output = $output; + + return true; + } +} \ No newline at end of file diff --git a/src/Appwrite/Platform/Modules/Compute/Base.php b/src/Appwrite/Platform/Modules/Compute/Base.php index a5d1b94039..8435159541 100644 --- a/src/Appwrite/Platform/Modules/Compute/Base.php +++ b/src/Appwrite/Platform/Modules/Compute/Base.php @@ -6,8 +6,8 @@ use Appwrite\Event\Build; use Appwrite\Extend\Exception; use Appwrite\Platform\Action; use Appwrite\Platform\Modules\Compute\Validator\Specification as SpecificationValidator; -use Appwrite\Transformation\Adapter\BranchDomain; -use Appwrite\Transformation\Transformation; +use Appwrite\Filter\Filter; +use Appwrite\Filter\Adapter\BranchDomain as FilterBranchDomain; use Utopia\Config\Config; use Utopia\Database\Database; use Utopia\Database\Document; @@ -298,17 +298,16 @@ class Base extends Action // VCS branch preview if (!empty($providerBranch)) { - $transformation = new Transformation([new BranchDomain()]); - $transformation + $filter = new Filter([new FilterBranchDomain()]); + $filter ->setInput([ 'branch' => $providerBranch, 'resourceId' => $site->getId(), 'projectId' => $project->getId(), 'sitesDomain' => $sitesDomain, ]) - ->setTraits([]) - ->transform(); - $domain = $transformation->getOutput(); + ->filter(); + $domain = $filter->getOutput(); $ruleId = md5($domain); try { $authorization->skip( diff --git a/src/Appwrite/Platform/Modules/Functions/Workers/Builds.php b/src/Appwrite/Platform/Modules/Functions/Workers/Builds.php index 9c6f304aa6..06234496e5 100644 --- a/src/Appwrite/Platform/Modules/Functions/Workers/Builds.php +++ b/src/Appwrite/Platform/Modules/Functions/Workers/Builds.php @@ -9,8 +9,8 @@ use Appwrite\Event\Realtime; use Appwrite\Event\Screenshot; use Appwrite\Event\StatsUsage; use Appwrite\Event\Webhook; -use Appwrite\Transformation\Adapter\BranchDomain; -use Appwrite\Transformation\Transformation; +use Appwrite\Filter\Filter; +use Appwrite\Filter\Adapter\BranchDomain as FilterBranchDomain; use Appwrite\Utopia\Response\Model\Deployment; use Appwrite\Vcs\Comment; use Exception; @@ -1039,17 +1039,16 @@ class Builds extends Action // VCS branch $branchName = $deployment->getAttribute('providerBranch'); if (!empty($branchName)) { - $transformation = new Transformation([new BranchDomain()]); - $transformation + $filter = new Filter([new FilterBranchDomain()]); + $filter ->setInput([ 'branch' => $branchName, 'resourceId' => $resource->getId(), 'projectId' => $project->getId(), 'sitesDomain' => $platform['sitesDomain'], ]) - ->setTraits([]) - ->transform(); - $domain = $transformation->getOutput(); + ->filter(); + $domain = $filter->getOutput(); $ruleId = md5($domain); try { diff --git a/tests/unit/Filter/FilterTest.php b/tests/unit/Filter/FilterTest.php new file mode 100644 index 0000000000..a0908cd33e --- /dev/null +++ b/tests/unit/Filter/FilterTest.php @@ -0,0 +1,189 @@ +setInput([ + 'branch' => 'feature/test', + 'resourceId' => 'site123', + 'projectId' => 'proj456', + 'sitesDomain' => 'appwrite.network' + ]) + ->filter(); + $this->assertTrue($success); + $domain = $filter->getOutput(); + $this->assertStringNotContainsString('/', $domain); + $this->assertStringStartsWith('branch-feature-test-', $domain); + $this->assertStringEndsWith('.appwrite.network', $domain); + + // Branch domain consistency + $success = $filter + ->setInput([ + 'branch' => 'feature/test', + 'resourceId' => 'site123', + 'projectId' => 'proj456', + 'sitesDomain' => 'appwrite.network' + ]) + ->filter(); + $this->assertTrue($success); + $domain2 = $filter->getOutput(); + $this->assertEquals($domain, $domain2); + + // Different resources should produce different domains + $success = $filter + ->setInput([ + 'branch' => 'feature/test', + 'resourceId' => 'site789', + 'projectId' => 'proj456', + 'sitesDomain' => 'appwrite.network' + ]) + ->filter(); + $this->assertTrue($success); + $domain2 = $filter->getOutput(); + $this->assertNotEquals($domain, $domain2); + + // Different projects should produce different domains + $success = $filter + ->setInput([ + 'branch' => 'feature/test', + 'resourceId' => 'site123', + 'projectId' => 'proj789', + 'sitesDomain' => 'appwrite.network' + ]) + ->filter(); + $this->assertTrue($success); + $domain2 = $filter->getOutput(); + $this->assertNotEquals($domain, $domain2); + + // Some real-world branch names + $success = $filter + ->setInput([ + 'branch' => 'feature/SER-1234', + 'resourceId' => 'site123', + 'projectId' => 'proj456', + 'sitesDomain' => 'appwrite.network' + ]) + ->filter(); + $this->assertTrue($success); + $domain = $filter->getOutput(); + $this->assertStringStartsWith('branch-feature-ser-1234-', $domain); + $this->assertStringEndsWith('.appwrite.network', $domain); + + $success = $filter + ->setInput([ + 'branch' => 'bugfix/fix-login', + 'resourceId' => 'site123', + 'projectId' => 'proj456', + 'sitesDomain' => 'appwrite.network' + ]) + ->filter(); + $this->assertTrue($success); + $domain = $filter->getOutput(); + $this->assertStringStartsWith('branch-bugfix-fix-login-', $domain); + $this->assertStringEndsWith('.appwrite.network', $domain); + + $success = $filter + ->setInput([ + 'branch' => 'hotfix/v1.2.3', + 'resourceId' => 'site123', + 'projectId' => 'proj456', + 'sitesDomain' => 'appwrite.network' + ]) + ->filter(); + $this->assertTrue($success); + $domain = $filter->getOutput(); + $this->assertStringStartsWith('branch-hotfix-v1-2-3-', $domain); + $this->assertStringEndsWith('.appwrite.network', $domain); + + $success = $filter + ->setInput([ + 'branch' => 'release/2024.01', + 'resourceId' => 'site123', + 'projectId' => 'proj456', + 'sitesDomain' => 'appwrite.network' + ]) + ->filter(); + $this->assertTrue($success); + $domain = $filter->getOutput(); + $this->assertStringStartsWith('branch-release-2024-01-', $domain); + $this->assertStringEndsWith('.appwrite.network', $domain); + + $success = $filter + ->setInput([ + 'branch' => 'user/john/experiment', + 'resourceId' => 'site123', + 'projectId' => 'proj456', + 'sitesDomain' => 'appwrite.network' + ]) + ->filter(); + $this->assertTrue($success); + $domain = $filter->getOutput(); + $this->assertStringStartsWith('branch-user-john-experi-', $domain); + $this->assertStringEndsWith('.appwrite.network', $domain); + + $success = $filter + ->setInput([ + 'branch' => 'dependabot/npm_and_yarn/lodash-4.17.21', + 'resourceId' => 'site123', + 'projectId' => 'proj456', + 'sitesDomain' => 'appwrite.network' + ]) + ->filter(); + $this->assertTrue($success); + $domain = $filter->getOutput(); + $this->assertStringStartsWith('branch-dependabot-npm-a-', $domain); + $this->assertStringEndsWith('.appwrite.network', $domain); + + // Invalid inputs + $success = $filter + ->setInput([ + 'branch' => '', + 'resourceId' => 'site123', + 'projectId' => 'proj456', + 'sitesDomain' => 'appwrite.network' + ]) + ->filter(); + $this->assertFalse($success); + + $success = $filter + ->setInput([ + 'branch' => 'feature/test', + 'resourceId' => '', + 'projectId' => 'proj456', + 'sitesDomain' => 'appwrite.network' + ]) + ->filter(); + $this->assertFalse($success); + + $success = $filter + ->setInput([ + 'branch' => 'feature/test', + 'resourceId' => 'site123', + 'projectId' => '', + 'sitesDomain' => 'appwrite.network' + ]) + ->filter(); + $this->assertFalse($success); + + $success = $filter + ->setInput([ + 'branch' => 'feature/test', + 'resourceId' => 'site123', + 'projectId' => 'proj456', + 'sitesDomain' => '' + ]) + ->filter(); + $this->assertFalse($success); + } +} \ No newline at end of file diff --git a/tests/unit/Transformation/TransformationTest.php b/tests/unit/Transformation/TransformationTest.php index a2bda227ce..a3169026a3 100644 --- a/tests/unit/Transformation/TransformationTest.php +++ b/tests/unit/Transformation/TransformationTest.php @@ -2,7 +2,6 @@ namespace Tests\Unit\Transformation; -use Appwrite\Transformation\Adapter\BranchDomain; use Appwrite\Transformation\Adapter\Mock; use Appwrite\Transformation\Adapter\Preview; use Appwrite\Transformation\Transformation; @@ -41,142 +40,4 @@ class TransformationTest extends TestCase $this->assertStringContainsString("Preview by", $transformer->getOutput()); $this->assertStringContainsString("Mock:", $transformer->getOutput()); } - - public function testBranchDomain(): void - { - $transformer = new Transformation([new BranchDomain()]); - - // Branch name with slash - $transformer - ->setInput([ - 'branch' => 'feature/test', - 'resourceId' => 'site123', - 'projectId' => 'proj456', - 'sitesDomain' => 'appwrite.network' - ]) - ->setTraits([]) - ->transform(); - $domain = $transformer->getOutput(); - $this->assertStringNotContainsString('/', $domain); - $this->assertStringStartsWith('branch-feature-test-', $domain); - $this->assertStringEndsWith('.appwrite.network', $domain); - - // Branch domain consistency - $transformer - ->setInput([ - 'branch' => 'feature/test', - 'resourceId' => 'site123', - 'projectId' => 'proj456', - 'sitesDomain' => 'appwrite.network' - ]) - ->setTraits([]) - ->transform(); - $domain2 = $transformer->getOutput(); - $this->assertEquals($domain, $domain2); - - // Different resources should produce different domains - $transformer - ->setInput([ - 'branch' => 'feature/test', - 'resourceId' => 'site789', - 'projectId' => 'proj456', - 'sitesDomain' => 'appwrite.network' - ]) - ->setTraits([]) - ->transform(); - $domain2 = $transformer->getOutput(); - $this->assertNotEquals($domain, $domain2); - - // Different projects should produce different domains - $transformer - ->setInput([ - 'branch' => 'feature/test', - 'resourceId' => 'site123', - 'projectId' => 'proj789', - 'sitesDomain' => 'appwrite.network' - ]) - ->setTraits([]) - ->transform(); - $domain2 = $transformer->getOutput(); - $this->assertNotEquals($domain, $domain2); - - // Some real-world branch names - $transformer - ->setInput([ - 'branch' => 'feature/SER-1234', - 'resourceId' => 'site123', - 'projectId' => 'proj456', - 'sitesDomain' => 'appwrite.network' - ]) - ->setTraits([]) - ->transform(); - $domain = $transformer->getOutput(); - $this->assertStringStartsWith('branch-feature-ser-1234-', $domain); - $this->assertStringEndsWith('.appwrite.network', $domain); - - $transformer - ->setInput([ - 'branch' => 'bugfix/fix-login', - 'resourceId' => 'site123', - 'projectId' => 'proj456', - 'sitesDomain' => 'appwrite.network' - ]) - ->setTraits([]) - ->transform(); - $domain = $transformer->getOutput(); - $this->assertStringStartsWith('branch-bugfix-fix-login-', $domain); - $this->assertStringEndsWith('.appwrite.network', $domain); - - $transformer - ->setInput([ - 'branch' => 'hotfix/v1.2.3', - 'resourceId' => 'site123', - 'projectId' => 'proj456', - 'sitesDomain' => 'appwrite.network' - ]) - ->setTraits([]) - ->transform(); - $domain = $transformer->getOutput(); - $this->assertStringStartsWith('branch-hotfix-v1-2-3-', $domain); - $this->assertStringEndsWith('.appwrite.network', $domain); - - $transformer - ->setInput([ - 'branch' => 'release/2024.01', - 'resourceId' => 'site123', - 'projectId' => 'proj456', - 'sitesDomain' => 'appwrite.network' - ]) - ->setTraits([]) - ->transform(); - $domain = $transformer->getOutput(); - $this->assertStringStartsWith('branch-release-2024-01-', $domain); - $this->assertStringEndsWith('.appwrite.network', $domain); - - $transformer - ->setInput([ - 'branch' => 'user/john/experiment', - 'resourceId' => 'site123', - 'projectId' => 'proj456', - 'sitesDomain' => 'appwrite.network' - ]) - ->setTraits([]) - ->transform(); - $domain = $transformer->getOutput(); - $this->assertStringStartsWith('branch-user-john-experi-', $domain); - $this->assertStringEndsWith('.appwrite.network', $domain); - - $transformer - ->setInput([ - 'branch' => 'dependabot/npm_and_yarn/lodash-4.17.21', - 'resourceId' => 'site123', - 'projectId' => 'proj456', - 'sitesDomain' => 'appwrite.network' - ]) - ->setTraits([]) - ->transform(); - $domain = $transformer->getOutput(); - $this->assertStringStartsWith('branch-dependabot-npm-a-', $domain); - $this->assertStringEndsWith('.appwrite.network', $domain); - } }