diff --git a/app/controllers/api/transfers.php b/app/controllers/api/transfers.php index 160979c0c9..7b3821346a 100644 --- a/app/controllers/api/transfers.php +++ b/app/controllers/api/transfers.php @@ -23,7 +23,6 @@ use Utopia\Transfer\Sources\Appwrite as SourcesAppwrite; use Utopia\Transfer\Sources\Firebase; use Utopia\Transfer\Sources\NHost; use Utopia\Transfer\Sources\Supabase; -use Utopia\Transfer\Transfer as TransferTransfer; use Utopia\Validator\ArrayList; use Utopia\Validator\Integer; use Utopia\Validator\JSON; @@ -285,7 +284,7 @@ App::post('/v1/transfers/sources/:sourceId/validate') ->label('sdk.description', '/docs/references/transfers/validate-source.md') ->label('sdk.response.code', Response::STATUS_CODE_OK) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) - ->label('sdk.response.model', Response::MODEL_ANY) + ->label('sdk.response.model', Response::MODEL_SOURCE_VALIDATION) ->param('sourceId', '', new UID(), 'Source unique ID.') ->param('resources', TRANSFER_RESOURCES, new ArrayList(new WhiteList(TRANSFER_RESOURCES)), 'List of resources to test. If none are sent then all resources are tested. [A list of resources can be found here.](https://appwrite.io/docs/transfers#resources)', true) ->inject('response') @@ -328,7 +327,13 @@ App::post('/v1/transfers/sources/:sourceId/validate') return $response->json($result); } catch (Throwable $e) { - throw new Exception(Exception::TRANSFER_SOURCE_FAILED, $e->getMessage(), 400); + return $response->setStatusCode(401)->dynamic(new Document([ + 'success' => false, + 'message' => 'Missing Permissions', + 'errors' => [ + 'Databases' => [$e->getMessage()], + ], + ]), Response::MODEL_DESTINATION_VALIDATION); } }); @@ -467,18 +472,17 @@ App::post('/v1/transfers/destinations/:destinationId/validate') $result = $testAdapter->check($resources); $result = array_filter($result, function ($value) { - return $value !== true; + return $value !== []; }); - + if (count($result) == 0) { return $response->dynamic(new Document([ - 'status' => 'success', - 'message' => 'Destination is valid', - 'errors' => $result + 'success' => true, + 'message' => 'Destination is valid' ]), Response::MODEL_DESTINATION_VALIDATION); } else { return $response->setStatusCode(401)->dynamic(new Document([ - 'status' => 'failed', + 'success' => false, 'message' => 'Missing Permissions', 'errors' => $result ]), Response::MODEL_DESTINATION_VALIDATION); @@ -567,8 +571,6 @@ App::post('/v1/transfers/sources/appwrite/validate') ->groups(['api', 'transfers']) ->desc('Validate Appwrite Source') ->label('scope', 'transfers.write') - ->label('event', 'transfers.[sourceId].validateAppwriteSource') - ->label('audits.event', 'transfers.validateAppwriteSource') ->label('audits.resource', 'sources/{response.$id}') ->label('sdk.auth', [APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'transfers') @@ -586,9 +588,17 @@ App::post('/v1/transfers/sources/appwrite/validate') try { $testAdapter->check(); // Throws exception on failure } catch (Exception $e) { - throw new Exception($e->getMessage(), 400); + return $response->setStatusCode(401)->dynamic(new Document([ + 'success' => false, + 'message' => 'Missing Permissions', + 'errors' => [ + 'Databases' => [$e->getMessage()], + ], + ]), Response::MODEL_DESTINATION_VALIDATION); } + + return $response->json(TRANSFER_RESOURCES); }); @@ -641,8 +651,6 @@ App::post('/v1/transfers/sources/firebase/validate') ->groups(['api', 'transfers']) ->desc('Validate Firebase Source') ->label('scope', 'transfers.write') - ->label('event', 'transfers.validateFirebaseSource') - ->label('audits.event', 'transfers.validateFirebaseSource') ->label('audits.resource', 'sources/{response.$id}') ->label('sdk.auth', [APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'transfers') @@ -722,8 +730,6 @@ App::post('/v1/transfers/sources/supabase/validate') ->groups(['api', 'transfers']) ->desc('Validate Supabase Source') ->label('scope', 'transfers.write') - ->label('event', 'transfers.validateSupabaseSource') - ->label('audits.event', 'transfers.validateSupabaseSource') ->label('audits.resource', 'sources/{response.$id}') ->label('sdk.auth', [APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'transfers') @@ -744,21 +750,21 @@ App::post('/v1/transfers/sources/supabase/validate') $result = $testAdapter->check(); $result = array_filter($result, function ($value) { - return $value !== true; + return $value !== []; }); if (count($result) == 0) { return $response->dynamic(new Document([ - 'status' => 'success', - 'message' => 'Destination is valid', + 'success' => true, + 'message' => 'Source is valid', 'errors' => $result - ]), Response::MODEL_DESTINATION_VALIDATION); + ]), Response::MODEL_SOURCE_VALIDATION); } else { return $response->setStatusCode(401)->dynamic(new Document([ - 'status' => 'failed', + 'success' => false, 'message' => 'Missing Permissions', 'errors' => $result - ]), Response::MODEL_DESTINATION_VALIDATION); + ]), Response::MODEL_SOURCE_VALIDATION); } }); @@ -819,8 +825,6 @@ App::post('/v1/transfers/sources/nhost/validate') ->groups(['api', 'transfers']) ->desc('Validate Nhost Source') ->label('scope', 'transfers.write') - ->label('event', 'transfers.validateNhostSource') - ->label('audits.event', 'transfers.validateNhostSource') ->label('audits.resource', 'sources/{response.$id}') ->label('sdk.auth', [APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'transfers') @@ -836,24 +840,36 @@ App::post('/v1/transfers/sources/nhost/validate') ->param('port', '5432', new Integer(true), 'Nhost Database Port. The port of the database to validate.', true) ->inject('response') ->action(function (string $url, string $database, string $username, string $password, string $port, Response $response) { - $testAdapter = new NHost($url, $database, $username, $password, $port); + try { + $testAdapter = new NHost($url, $database, $username, $password, $port); + } catch (Throwable $e) { + return $response->setStatusCode(401)->dynamic(new Document([ + 'success' => false, + 'message' => 'Invalid Nhost Source', + 'errors' => [ + 'Databases' => [$e->getMessage()], + ] + ]), Response::MODEL_SOURCE_VALIDATION); + }; + + $result = $testAdapter->check(); $result = array_filter($result, function ($value) { - return $value !== true; + return $value !== []; }); if (count($result) == 0) { return $response->dynamic(new Document([ - 'status' => 'success', - 'message' => 'Destination is valid', + 'success' => true, + 'message' => 'Source is valid', 'errors' => $result - ]), Response::MODEL_DESTINATION_VALIDATION); + ]), Response::MODEL_SOURCE_VALIDATION); } else { return $response->setStatusCode(401)->dynamic(new Document([ - 'status' => 'failed', + 'success' => false, 'message' => 'Missing Permissions', 'errors' => $result - ]), Response::MODEL_DESTINATION_VALIDATION); + ]), Response::MODEL_SOURCE_VALIDATION); } }); @@ -888,7 +904,7 @@ App::post('/v1/transfers/destinations/appwrite') $result = $testAdapter->check(); $result = array_filter($result, function ($value) { - return $value !== true; + return $value !== []; }); if (count($result) > 0) { @@ -936,18 +952,18 @@ App::post('/v1/transfers/destinations/appwrite/validate') $result = $testAdapter->check(); $result = array_filter($result, function ($value) { - return $value !== true; + return $value !== []; }); if (count($result) == 0) { return $response->dynamic(new Document([ - 'status' => 'success', + 'success' => true, 'message' => 'Destination is valid', 'errors' => $result ]), Response::MODEL_DESTINATION_VALIDATION); } else { return $response->setStatusCode(401)->dynamic(new Document([ - 'status' => 'failed', + 'success' => false, 'message' => 'Missing Permissions', 'errors' => $result ]), Response::MODEL_DESTINATION_VALIDATION); diff --git a/composer.lock b/composer.lock index d45e87fadb..4a6c64775e 100644 --- a/composer.lock +++ b/composer.lock @@ -734,16 +734,16 @@ }, { "name": "guzzlehttp/psr7", - "version": "2.4.3", + "version": "2.4.4", "source": { "type": "git", "url": "https://github.com/guzzle/psr7.git", - "reference": "67c26b443f348a51926030c83481b85718457d3d" + "reference": "3cf1b6d4f0c820a2cf8bcaec39fc698f3443b5cf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/psr7/zipball/67c26b443f348a51926030c83481b85718457d3d", - "reference": "67c26b443f348a51926030c83481b85718457d3d", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/3cf1b6d4f0c820a2cf8bcaec39fc698f3443b5cf", + "reference": "3cf1b6d4f0c820a2cf8bcaec39fc698f3443b5cf", "shasum": "" }, "require": { @@ -833,7 +833,7 @@ ], "support": { "issues": "https://github.com/guzzle/psr7/issues", - "source": "https://github.com/guzzle/psr7/tree/2.4.3" + "source": "https://github.com/guzzle/psr7/tree/2.4.4" }, "funding": [ { @@ -849,7 +849,7 @@ "type": "tidelift" } ], - "time": "2022-10-26T14:07:24+00:00" + "time": "2023-03-09T13:19:02+00:00" }, { "name": "influxdb/influxdb-php", @@ -1748,16 +1748,16 @@ }, { "name": "symfony/deprecation-contracts", - "version": "v3.2.0", + "version": "v3.2.1", "source": { "type": "git", "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "1ee04c65529dea5d8744774d474e7cbd2f1206d3" + "reference": "e2d1534420bd723d0ef5aec58a22c5fe60ce6f5e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/1ee04c65529dea5d8744774d474e7cbd2f1206d3", - "reference": "1ee04c65529dea5d8744774d474e7cbd2f1206d3", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/e2d1534420bd723d0ef5aec58a22c5fe60ce6f5e", + "reference": "e2d1534420bd723d0ef5aec58a22c5fe60ce6f5e", "shasum": "" }, "require": { @@ -1795,7 +1795,7 @@ "description": "A generic function and convention to trigger deprecation notices", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v3.2.0" + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.2.1" }, "funding": [ { @@ -1811,7 +1811,7 @@ "type": "tidelift" } ], - "time": "2022-11-25T10:21:52+00:00" + "time": "2023-03-01T10:25:55+00:00" }, { "name": "symfony/polyfill-php80", @@ -2948,12 +2948,12 @@ "source": { "type": "git", "url": "https://github.com/utopia-php/transfer.git", - "reference": "84b543d9c9c344986930821be234adf9ee8adace" + "reference": "fae74e218c5deaa8ec13a7e85921791f8974948f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/transfer/zipball/84b543d9c9c344986930821be234adf9ee8adace", - "reference": "84b543d9c9c344986930821be234adf9ee8adace", + "url": "https://api.github.com/repos/utopia-php/transfer/zipball/fae74e218c5deaa8ec13a7e85921791f8974948f", + "reference": "fae74e218c5deaa8ec13a7e85921791f8974948f", "shasum": "" }, "require": { @@ -2998,7 +2998,7 @@ "source": "https://github.com/utopia-php/transfer/tree/feat-database-and-documents", "issues": "https://github.com/utopia-php/transfer/issues" }, - "time": "2023-02-24T06:12:39+00:00" + "time": "2023-02-24T16:24:24+00:00" }, { "name": "utopia-php/websocket", @@ -3431,16 +3431,16 @@ }, { "name": "myclabs/deep-copy", - "version": "1.11.0", + "version": "1.11.1", "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "14daed4296fae74d9e3201d2c4925d1acb7aa614" + "reference": "7284c22080590fb39f2ffa3e9057f10a4ddd0e0c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/14daed4296fae74d9e3201d2c4925d1acb7aa614", - "reference": "14daed4296fae74d9e3201d2c4925d1acb7aa614", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/7284c22080590fb39f2ffa3e9057f10a4ddd0e0c", + "reference": "7284c22080590fb39f2ffa3e9057f10a4ddd0e0c", "shasum": "" }, "require": { @@ -3478,7 +3478,7 @@ ], "support": { "issues": "https://github.com/myclabs/DeepCopy/issues", - "source": "https://github.com/myclabs/DeepCopy/tree/1.11.0" + "source": "https://github.com/myclabs/DeepCopy/tree/1.11.1" }, "funding": [ { @@ -3486,20 +3486,20 @@ "type": "tidelift" } ], - "time": "2022-03-03T13:19:32+00:00" + "time": "2023-03-08T13:26:56+00:00" }, { "name": "nikic/php-parser", - "version": "v4.15.3", + "version": "v4.15.4", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "570e980a201d8ed0236b0a62ddf2c9cbb2034039" + "reference": "6bb5176bc4af8bcb7d926f88718db9b96a2d4290" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/570e980a201d8ed0236b0a62ddf2c9cbb2034039", - "reference": "570e980a201d8ed0236b0a62ddf2c9cbb2034039", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/6bb5176bc4af8bcb7d926f88718db9b96a2d4290", + "reference": "6bb5176bc4af8bcb7d926f88718db9b96a2d4290", "shasum": "" }, "require": { @@ -3540,9 +3540,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v4.15.3" + "source": "https://github.com/nikic/PHP-Parser/tree/v4.15.4" }, - "time": "2023-01-16T22:05:37+00:00" + "time": "2023-03-05T19:49:14+00:00" }, { "name": "phar-io/manifest", @@ -3890,23 +3890,23 @@ }, { "name": "phpunit/php-code-coverage", - "version": "9.2.24", + "version": "9.2.26", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "2cf940ebc6355a9d430462811b5aaa308b174bed" + "reference": "443bc6912c9bd5b409254a40f4b0f4ced7c80ea1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/2cf940ebc6355a9d430462811b5aaa308b174bed", - "reference": "2cf940ebc6355a9d430462811b5aaa308b174bed", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/443bc6912c9bd5b409254a40f4b0f4ced7c80ea1", + "reference": "443bc6912c9bd5b409254a40f4b0f4ced7c80ea1", "shasum": "" }, "require": { "ext-dom": "*", "ext-libxml": "*", "ext-xmlwriter": "*", - "nikic/php-parser": "^4.14", + "nikic/php-parser": "^4.15", "php": ">=7.3", "phpunit/php-file-iterator": "^3.0.3", "phpunit/php-text-template": "^2.0.2", @@ -3921,8 +3921,8 @@ "phpunit/phpunit": "^9.3" }, "suggest": { - "ext-pcov": "*", - "ext-xdebug": "*" + "ext-pcov": "PHP extension that provides line coverage", + "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage" }, "type": "library", "extra": { @@ -3955,7 +3955,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.24" + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.26" }, "funding": [ { @@ -3963,7 +3963,7 @@ "type": "github" } ], - "time": "2023-01-26T08:26:55+00:00" + "time": "2023-03-06T12:58:08+00:00" }, { "name": "phpunit/php-file-iterator", diff --git a/src/Appwrite/Event/Transfer.php b/src/Appwrite/Event/Transfer.php index 745b0bfd66..aa1155d466 100644 --- a/src/Appwrite/Event/Transfer.php +++ b/src/Appwrite/Event/Transfer.php @@ -150,4 +150,24 @@ class Transfer extends Event 'payload' => $this->payload ]); } + + /** + * {@inheritdoc} + */ + public function setUser(Document $user): self + { + parent::setUser($user); + + return $this; + } + + /** + * {@inheritdoc} + */ + public function setProject(Document $project): self + { + parent::setProject($project); + + return $this; + } } diff --git a/src/Appwrite/Utopia/Response.php b/src/Appwrite/Utopia/Response.php index 9d159d9894..37ee889e1b 100644 --- a/src/Appwrite/Utopia/Response.php +++ b/src/Appwrite/Utopia/Response.php @@ -88,6 +88,7 @@ use Appwrite\Utopia\Response\Model\Source; use Appwrite\Utopia\Response\Model\Destination; use Appwrite\Utopia\Response\Model\DestinationValidation; use Appwrite\Utopia\Response\Model\SourceValidation; +use Appwrite\Utopia\Response\Model\TransferValidationError; /** * @method int getStatusCode() @@ -221,6 +222,7 @@ class Response extends SwooleResponse // Transfers public const MODEL_TRANSFER = 'transfer'; public const MODEL_TRANSFER_LIST = 'transferList'; + public const MODEL_TRANSFER_VALIDATION_ERROR = 'transferValidationError'; public const MODEL_SOURCE = 'source'; public const MODEL_SOURCE_LIST = 'sourceList'; public const MODEL_SOURCE_VALIDATION = 'sourceValidation'; @@ -364,6 +366,7 @@ class Response extends SwooleResponse ->setModel(new DestinationValidation()) ->setModel(new Source()) ->setModel(new SourceValidation()) + ->setModel(new TransferValidationError()) // Verification // Recovery // Tests (keep last) diff --git a/src/Appwrite/Utopia/Response/Model/DestinationValidation.php b/src/Appwrite/Utopia/Response/Model/DestinationValidation.php index ea85934473..e9e51572cb 100644 --- a/src/Appwrite/Utopia/Response/Model/DestinationValidation.php +++ b/src/Appwrite/Utopia/Response/Model/DestinationValidation.php @@ -10,24 +10,27 @@ class DestinationValidation extends Model public function __construct() { $this - ->addRule('status', [ - 'type' => self::TYPE_STRING, - 'description' => 'Success status. "success" or "failed".', - 'default' => "failed", - 'example' => "success", + ->addRule('success', [ + 'type' => self::TYPE_BOOLEAN, + 'description' => 'If the validation was successful or not.', + 'default' => false, + 'example' => true, ]) ->addRule('message', [ 'type' => self::TYPE_STRING, - 'description' => 'Extra details about the status.', + 'description' => 'Validation message.', 'default' => '', - 'example' => 'Source is valid.', + 'example' => 'Validation completed successfully', ]) ->addRule('errors', [ - 'type' => self::TYPE_STRING, - 'description' => 'Missing roles.', - 'default' => '', - 'example' => '[]', - 'array' => true + 'type' => Response::MODEL_TRANSFER_VALIDATION_ERROR, + 'description' => 'A key-value array of all the validation errors.', + 'default' => [], + 'example' => [ + 'Users' => ['Access to table "public.users" is denied.'], + 'Databases' => ['Failed to access database. Please check your connection.'], + ], + 'requried' => false ]) ; } @@ -51,4 +54,4 @@ class DestinationValidation extends Model { return Response::MODEL_DESTINATION_VALIDATION; } -} \ No newline at end of file +} diff --git a/src/Appwrite/Utopia/Response/Model/Source.php b/src/Appwrite/Utopia/Response/Model/Source.php index 5c3ad3aac6..2455575e8a 100644 --- a/src/Appwrite/Utopia/Response/Model/Source.php +++ b/src/Appwrite/Utopia/Response/Model/Source.php @@ -40,6 +40,15 @@ class Source extends Model 'default' => '', 'example' => 'Appwrite', ]) + ->addRule('lastCheck', [ + 'type' => self::TYPE_JSON, + 'description' => 'A JSON Object with the result of the last source check.', + 'default' => '', + 'example' => [ + 'success' => false, + 'message' => 'Transfer completed successfully' + ], + ]) ; } diff --git a/src/Appwrite/Utopia/Response/Model/SourceValidation.php b/src/Appwrite/Utopia/Response/Model/SourceValidation.php index 2c43983336..2ce5d920a3 100644 --- a/src/Appwrite/Utopia/Response/Model/SourceValidation.php +++ b/src/Appwrite/Utopia/Response/Model/SourceValidation.php @@ -10,24 +10,27 @@ class SourceValidation extends Model public function __construct() { $this - ->addRule('status', [ - 'type' => self::TYPE_STRING, - 'description' => 'Success status. "success" or "failed".', - 'default' => "failed", - 'example' => "success", + ->addRule('success', [ + 'type' => self::TYPE_BOOLEAN, + 'description' => 'If the validation was successful or not.', + 'default' => false, + 'example' => true, ]) ->addRule('message', [ 'type' => self::TYPE_STRING, - 'description' => 'Extra details about the status.', + 'description' => 'Validation message.', 'default' => '', - 'example' => 'Source is valid.', + 'example' => 'Validation completed successfully', ]) ->addRule('errors', [ - 'type' => self::TYPE_STRING, - 'description' => 'Missing roles.', - 'default' => '', - 'example' => '[]', - 'array' => true + 'type' => Response::MODEL_TRANSFER_VALIDATION_ERROR, + 'description' => 'A key-value array of all the validation errors.', + 'default' => [], + 'example' => [ + 'Users' => ['Access to table "public.users" is denied.'], + 'Documents' => ['Failed to access database. Please check your connection.'], + ], + 'requried' => false ]) ; } diff --git a/src/Appwrite/Utopia/Response/Model/ValidationError.php b/src/Appwrite/Utopia/Response/Model/ValidationError.php new file mode 100644 index 0000000000..a9cb8eb28a --- /dev/null +++ b/src/Appwrite/Utopia/Response/Model/ValidationError.php @@ -0,0 +1,28 @@ +