From e578aa6b9800e755d0adef11e5347e1e834ed736 Mon Sep 17 00:00:00 2001 From: Bradley Schofield Date: Thu, 5 Jan 2023 15:44:43 +0000 Subject: [PATCH 01/70] Initial Push --- .gitignore | 7 + CODE_OF_CONDUCT.md | 76 ++++++++ LICENSE | 2 +- README.md | 40 ++++- composer.json | 34 ++++ phpunit.xml | 16 ++ psalm.xml | 15 ++ src/Transfer/Destination.php | 224 +++++++++++++++++++++++ src/Transfer/Destinations/Appwrite.php | 142 +++++++++++++++ src/Transfer/Hash.php | 220 +++++++++++++++++++++++ src/Transfer/Log.php | 81 +++++++++ src/Transfer/Resource.php | 37 ++++ src/Transfer/Resources/Project.php | 29 +++ src/Transfer/Resources/User.php | 166 +++++++++++++++++ src/Transfer/Source.php | 238 +++++++++++++++++++++++++ src/Transfer/Sources/Firebase.php | 174 ++++++++++++++++++ src/Transfer/Transfer.php | 77 ++++++++ tests/Transfer/FBTest2.php | 21 +++ tests/Transfer/FirebaseTest.php | 21 +++ tests/Transfer/TransferTest1.php | 21 +++ 20 files changed, 1639 insertions(+), 2 deletions(-) create mode 100644 .gitignore create mode 100644 CODE_OF_CONDUCT.md create mode 100644 composer.json create mode 100644 phpunit.xml create mode 100644 psalm.xml create mode 100644 src/Transfer/Destination.php create mode 100644 src/Transfer/Destinations/Appwrite.php create mode 100644 src/Transfer/Hash.php create mode 100644 src/Transfer/Log.php create mode 100644 src/Transfer/Resource.php create mode 100644 src/Transfer/Resources/Project.php create mode 100644 src/Transfer/Resources/User.php create mode 100644 src/Transfer/Source.php create mode 100644 src/Transfer/Sources/Firebase.php create mode 100644 src/Transfer/Transfer.php create mode 100644 tests/Transfer/FBTest2.php create mode 100644 tests/Transfer/FirebaseTest.php create mode 100644 tests/Transfer/TransferTest1.php diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0fdb91f --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +composer.lock +/vendor/ +/.idea/ +*.cache +.env +test-service-account.json +.DS_Store \ No newline at end of file diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..adc8fec --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,76 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to make participation in our project and +our community a harassment-free experience for everyone, regardless of age, body +size, disability, ethnicity, sex characteristics, gender identity, expression, +level of experience, education, socio-economic status, nationality, personal +appearance, race, religion, or sexual identity and orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment +include: + +- Using welcoming and inclusive language +- Being respectful of differing viewpoints and experiences +- Gracefully accepting constructive criticism +- Focusing on what is best for the community +- Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +- The use of sexualized language or imagery and unwelcome sexual attention or + advances +- Trolling, insulting/derogatory comments, and personal or political attacks +- Public or private harassment +- Publishing others' private information, such as a physical or electronic + address, without explicit permission +- Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or +reject comments, commits, code, wiki edits, issues, and other contributions +that are not aligned to this Code of Conduct, or to ban temporarily or +permanently any contributor for other behaviors that they deem inappropriate, +threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. Examples of +representing a project or community include using an official project e-mail +address, posting via an official social media account, or acting as an appointed +representative at an online or offline event. Representation of a project may be +further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting the project team at team@appwrite.io. All +complaints will be reviewed and investigated and will result in a response that +is deemed necessary and appropriate to the circumstances. The project team is +obligated to maintain confidentiality with regard to the reporter of an incident. +Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good +faith may face temporary or permanent repercussions as determined by other +members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, +available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html + +[homepage]: https://www.contributor-covenant.org + +For answers to common questions about this code of conduct, see +https://www.contributor-covenant.org/faq \ No newline at end of file diff --git a/LICENSE b/LICENSE index bb8c408..42cb9d6 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2023 Utopia +Copyright (c) 2022 Bradley Schofield Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index d35d021..644c2f5 100644 --- a/README.md +++ b/README.md @@ -1 +1,39 @@ -# transfer \ No newline at end of file +# Utopia Transfer + +[![Build Status](https://travis-ci.com/utopia-php/transfer.svg?branch=main)](https://travis-ci.com/utopia-php/transfer) +![Total Downloads](https://img.shields.io/packagist/dt/utopia-php/transfer.svg) +[![Discord](https://img.shields.io/discord/564160730845151244?label=discord)](https://appwrite.io/discord) + +Utopia Transfer is a simple and lite library to transfer and translate resources inbetween services. This library is aiming to be as simple and easy to learn and use. This library is maintained by the [Appwrite team](https://appwrite.io). + +Although this library is part of the [Utopia Framework](https://github.com/utopia-php/framework) project it is dependency free and can be used as standalone with any other PHP project or framework. + +## Getting Started + +Install using composer: +```bash +composer require utopia-php/transfer +``` + +Init in your application: +```php +=8.0", + "utopia-php/cli": "^0.14.0", + "google/apiclient": "^2.12.1", + "kreait/firebase-php": "^7.0", + "appwrite/appwrite": "^7.2" + }, + "require-dev": { + "phpunit/phpunit": "^9.3", + "squizlabs/php_codesniffer": "^3.6", + "vimeo/psalm": "^5.4" + } +} diff --git a/phpunit.xml b/phpunit.xml new file mode 100644 index 0000000..ec39a7e --- /dev/null +++ b/phpunit.xml @@ -0,0 +1,16 @@ + + + + ./tests/ + + + \ No newline at end of file diff --git a/psalm.xml b/psalm.xml new file mode 100644 index 0000000..b5d5891 --- /dev/null +++ b/psalm.xml @@ -0,0 +1,15 @@ + + + + + + + + + \ No newline at end of file diff --git a/src/Transfer/Destination.php b/src/Transfer/Destination.php new file mode 100644 index 0000000..7af9c73 --- /dev/null +++ b/src/Transfer/Destination.php @@ -0,0 +1,224 @@ + '', + ]; + + /** + * Logs + * + * @var array $logs + */ + protected $logs = []; + + /** + * Internal Adapter State + * + * @var array $state + */ + protected $state = []; + + /** + * Constructor, mainly handles state initialization. + * + * Automatically detects if we are running within Swoole and uses a Swoole table instead of a PHP array. + */ + public function __construct(protected string $endpoint, protected string $projectID, protected string $key) + { + $this->state = []; + } + + /** + * Gets the name of the adapter. + * + * @return string + */ + abstract public function getName(): string; + + /** + * Get Supported Resources + * + * @return array + */ + abstract public function getSupportedResources(): array; + + /** + * Register Logs Array + * + * @param array &$logs + */ + public function registerLogs(array &$logs): void { + $this->logs = &$logs; + } + + /** + * Transfer Resources between adapters + * + * @param array $resources + * @param callable $callback + */ + public function run(array $resources, callable $callback, Source $source): void { + foreach ($resources as $resource) { + if (!in_array($resource, $this->getSupportedResources())) { + throw new \Exception("Cannot Transfer unsupported resource: '".$resource."'"); + } + + $source->run($resources, function (Log $currentLog, string $resource, array &$resourceCache) { + switch ($resource) { + case Transfer::RESOURCE_USERS: { + $this->importUsers($resourceCache); + $resourceCache = []; + break; + } + } + }); + } + } + + /** + * Check Requirements + * Performs a suite of API Checks, Resource Checks, etc... to ensure the adapter is ready to be used. + * This is highly recommended to be called before any other method after initialization. + * + * If no resources are provided, the method should check all resources. + * + * @array $resources + * + * @return bool + */ + abstract public function check(array $resources = []): bool; + + /** + * Call + * + * Make an API call + * + * @param string $method + * @param string $path + * @param array $params + * @param array $headers + * @return array|string + * @throws \Exception + */ + public function call(string $method, string $path = '', array $headers = array(), array $params = array()): array|string + { + $headers = array_merge($this->headers, $headers); + $ch = curl_init((str_contains($path, 'http') ? $path : $this->endpoint . $path . (($method == 'GET' && !empty($params)) ? '?' . http_build_query($params) : ''))); + $responseHeaders = []; + $responseStatus = -1; + $responseType = ''; + $responseBody = ''; + + switch ($headers['Content-Type']) { + case 'application/json': + $query = json_encode($params); + break; + + case 'multipart/form-data': + $query = $this->flatten($params); + break; + + default: + $query = http_build_query($params); + break; + } + + foreach ($headers as $i => $header) { + $headers[] = $i . ':' . $header; + unset($headers[$i]); + } + + curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($ch, CURLOPT_USERAGENT, php_uname('s') . '-' . php_uname('r') . ':php-' . phpversion()); + curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); + curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); + curl_setopt($ch, CURLOPT_HEADERFUNCTION, function ($curl, $header) use (&$responseHeaders) { + $len = strlen($header); + $header = explode(':', strtolower($header), 2); + + if (count($header) < 2) { // ignore invalid headers + return $len; + } + + $responseHeaders[strtolower(trim($header[0]))] = trim($header[1]); + + return $len; + }); + + if ($method != 'GET') { + curl_setopt($ch, CURLOPT_POSTFIELDS, $query); + } + + $responseBody = curl_exec($ch); + + $responseType = $responseHeaders['Content-Type'] ?? ''; + $responseStatus = curl_getinfo($ch, CURLINFO_HTTP_CODE); + + switch (substr($responseType, 0, strpos($responseType, ';'))) { + case 'application/json': + $responseBody = json_decode($responseBody, true); + break; + } + + if (curl_errno($ch)) { + throw new \Exception(curl_error($ch)); + } + + curl_close($ch); + + if ($responseStatus >= 400) { + if (is_array($responseBody)) { + throw new \Exception(json_encode($responseBody)); + } else { + throw new \Exception($responseStatus . ': ' . $responseBody); + } + } + + return $responseBody; + } + + /** + * Flatten params array to PHP multiple format + * + * @param array $data + * @param string $prefix + * @return array + */ + protected function flatten(array $data, string $prefix = ''): array + { + $output = []; + + foreach ($data as $key => $value) { + $finalKey = $prefix ? "{$prefix}[{$key}]" : $key; + + if (is_array($value)) { + $output += $this->flatten($value, $finalKey); // @todo: handle name collision here if needed + } else { + $output[$finalKey] = $value; + } + } + + return $output; + } + + /** + * Import Users + * + * @param array $users + */ + protected function importUsers(array $users): void { + throw new \Exception("Not Implemented"); + } +} diff --git a/src/Transfer/Destinations/Appwrite.php b/src/Transfer/Destinations/Appwrite.php new file mode 100644 index 0000000..de33a6d --- /dev/null +++ b/src/Transfer/Destinations/Appwrite.php @@ -0,0 +1,142 @@ +client = new Client(); + $this->client->setEndpoint($endpoint); + $this->client->setProject($projectID); + $this->client->setKey($apiKey); + } + + /** + * Get Name + * + * @return string + */ + public function getName(): string { + return 'Appwrite'; + } + + /** + * Get Supported Resources + * + * @return array + */ + public function getSupportedResources(): array { + return [ + Transfer::RESOURCE_USERS, + Transfer::RESOURCE_DATABASES, + Transfer::RESOURCE_COLLECTIONS, + Transfer::RESOURCE_FILES, + Transfer::RESOURCE_FUNCTIONS + ]; + } + + public function check(array $resources = []): bool + { + //TODO: Implement check() method. + return true; + } + + public function importPasswordUser(User $user): void + { + $authentication = new Users($this->client); + $hash = $user->getPasswordHash(); + $result = null; + + switch ($hash->getAlgorithm()) { + case Hash::SCRYPT_MODIFIED: + $result = $authentication->createScryptModifiedUser( + $user->getId(), + $user->getEmail(), + $hash->getHash(), + $hash->getSalt(), + $hash->getSeparator(), + $hash->getSigningKey(), + $user->getEmail() + ); + break; + case Hash::BCRYPT: + $result = $authentication->createBcryptUser( + $user->getId(), + $user->getEmail(), + $hash->getHash(), + $user->getEmail() + ); + break; + case Hash::ARGON2: + $result = $authentication->createArgon2User( + $user->getId(), + $user->getEmail(), + $hash->getHash(), + $user->getEmail() + ); + break; + case Hash::SHA: + $result = $authentication->createShaUser( + $user->getId(), + $user->getEmail(), + $hash->getHash(), + $user->getEmail() + ); + break; + case Hash::PHPASS: + $result = $authentication->createPHPassUser( + $user->getId(), + $user->getEmail(), + $hash->getHash(), + $user->getEmail() + ); + break; + case Hash::SCRYPT: + $result = $authentication->createScryptUser( + $user->getId(), + $user->getEmail(), + $hash->getHash(), + $hash->getSalt(), + $hash->getPasswordCpu(), + $hash->getPasswordMemory(), + $hash->getPasswordParallel(), + $hash->getPasswordLength(), + $user->getEmail() + ); + break; + } + + if (!$result) { + throw new \Exception('Failed to import user: "'.$user->getId().'"'); + } + } + + public function importUsers(array $users): void + { + foreach ($users as $user) { + /** @var \Utopia\Transfer\Resources\User $user */ + try { + switch ($user->getType()) { + case User::AUTH_EMAIL: + $this->importPasswordUser($user); + break; + default: + $this->logs[] = new Log(Log::WARNING, 'Not copying user: "'.$user->getId().'" due to it being an account type: "'.$user->getType().'".', \time(), $user); + //TODO: Implement other auth types, talk to Eldadfux about API's requried (Might have to resort to using Console SDK). + } + } catch (\Exception $e) { + $this->logs[] = new Log(Log::ERROR, $e->getMessage(), \time(), $user); + } + } + } +} \ No newline at end of file diff --git a/src/Transfer/Hash.php b/src/Transfer/Hash.php new file mode 100644 index 0000000..eff30a5 --- /dev/null +++ b/src/Transfer/Hash.php @@ -0,0 +1,220 @@ +hash; + } + + /** + * Set Hash + * + * @param string $hash + * @returns self + */ + public function setHash(string $hash): self + { + $this->hash = $hash; + return $this; + } + + /** + * Get Salt + * + * @returns string + */ + public function getSalt(): string + { + return $this->salt; + } + + /** + * Set Salt + * + * @param string $salt + * @returns self + */ + public function setSalt(string $salt): self + { + $this->salt = $salt; + return $this; + } + + /** + * Get Algorithm + * + * @returns string + */ + public function getAlgorithm(): string + { + return $this->algorithm; + } + + /** + * Set Algorithm + * + * @param string $algorithm + * @returns self + */ + public function setAlgorithm(string $algorithm): self + { + $this->algorithm = $algorithm; + return $this; + } + + /** + * Get Separator + * + * @returns string + */ + public function getSeparator(): string + { + return $this->separator; + } + + /** + * Set Separator + * + * @param string $separator + * @returns self + */ + public function setSeparator(string $separator): self + { + $this->separator = $separator; + return $this; + } + + /** + * Get Signing Key + * + * @returns string + */ + public function getSigningKey(): string + { + return $this->signingKey; + } + + /** + * Set Signing Key + * + * @param string $signingKey + * @returns self + */ + public function setSigningKey(string $signingKey): self + { + $this->signingKey = $signingKey; + return $this; + } + + /** + * Get Password CPU + * + * @returns int + */ + public function getPasswordCpu(): int + { + return $this->passwordCpu; + } + + /** + * Set Password CPU + * + * @param int $passwordCpu + * @returns self + */ + public function setPasswordCpu(int $passwordCpu): self + { + $this->passwordCpu = $passwordCpu; + return $this; + } + + /** + * Get Password Memory + * + * @returns int + */ + public function getPasswordMemory(): int + { + return $this->passwordMemory; + } + + /** + * Set Password Memory + * + * @param int $passwordMemory + * @returns self + */ + public function setPasswordMemory(int $passwordMemory): self + { + $this->passwordMemory = $passwordMemory; + return $this; + } + + /** + * Get Password Parallel + * + * @returns int + */ + public function getPasswordParallel(): int + { + return $this->passwordParallel; + } + + /** + * Set Password Parallel + * + * @param int $passwordParallel + * @returns self + */ + public function setPasswordParallel(int $passwordParallel): self + { + $this->passwordParallel = $passwordParallel; + return $this; + } + + /** + * Get Password Length + * + * @returns int + */ + public function getPasswordLength(): int + { + return $this->passwordLength; + } + + /** + * Set Password Length + * + * @param int $passwordLength + * @returns self + */ + public function setPasswordLength(int $passwordLength): self + { + $this->passwordLength = $passwordLength; + return $this; + } +} \ No newline at end of file diff --git a/src/Transfer/Log.php b/src/Transfer/Log.php new file mode 100644 index 0000000..95047df --- /dev/null +++ b/src/Transfer/Log.php @@ -0,0 +1,81 @@ +level; + } + + /** + * Set Level + * + * @param string $level + * @returns self + */ + public function setLevel(string $level) + { + $this->level = $level; + return $this; + } + + /** + * Get Message + * + * @returns string + */ + public function getMessage() + { + return $this->message; + } + + /** + * Set Message + * + * @param string $message + * @returns self + */ + public function setMessage(string $message) + { + $this->message = $message; + return $this; + } + + /** + * Get Timestamp + * + * @returns string + */ + public function getTimestamp() + { + return $this->timestamp; + } + + /** + * Set Timestamp + * + * @param string $timestamp + * @returns self + */ + public function setTimestamp(string $timestamp) + { + $this->timestamp = $timestamp; + return $this; + } +} \ No newline at end of file diff --git a/src/Transfer/Resource.php b/src/Transfer/Resource.php new file mode 100644 index 0000000..21f8c55 --- /dev/null +++ b/src/Transfer/Resource.php @@ -0,0 +1,37 @@ +id; + } + + /** + * Set ID + * + * @param string $id + * @return self + */ + public function setId(string $id): self + { + $this->id = $id; + return $this; + } +} \ No newline at end of file diff --git a/src/Transfer/Resources/Project.php b/src/Transfer/Resources/Project.php new file mode 100644 index 0000000..0d76785 --- /dev/null +++ b/src/Transfer/Resources/Project.php @@ -0,0 +1,29 @@ +id; + } + + public function setId(string $id): self + { + $this->id = $id; + return $this; + } +} \ No newline at end of file diff --git a/src/Transfer/Resources/User.php b/src/Transfer/Resources/User.php new file mode 100644 index 0000000..b18cefe --- /dev/null +++ b/src/Transfer/Resources/User.php @@ -0,0 +1,166 @@ +id; + } + + /** + * Set ID + * + * @param string $id + * @returns self + */ + public function setId(string $id): self + { + $this->id = $id; + return $this; + } + + /** + * Get Email + * + * @returns string + */ + public function getEmail(): string + { + return $this->email; + } + + /** + * Set Email + * + * @param string $email + * @returns self + */ + public function setEmail(string $email): self + { + $this->email = $email; + return $this; + } + + /** + * Get Password Hash + * + * @returns Hash + */ + public function getPasswordHash(): Hash + { + return $this->passwordHash; + } + + /** + * Set Password Hash + * + * @param Hash $passwordHash + * @returns self + */ + public function setPasswordHash(Hash $passwordHash): self + { + $this->passwordHash = $passwordHash; + return $this; + } + + /** + * Get Phone + * + * @returns string + */ + public function getPhone(): string + { + return $this->phone; + } + + /** + * Set Phone + * + * @param string $phone + * @returns self + */ + public function setPhone(string $phone): self + { + $this->phone = $phone; + return $this; + } + + /** + * Get Type + * + * @returns string + */ + public function getType(): string + { + return $this->type; + } + + /** + * Set Type + * + * @param string $type + * @returns self + */ + public function setType(string $type): self + { + $this->type = $type; + return $this; + } + + /** + * Get OAuth Provider + * + * @returns string + */ + public function getOAuthProvider(): string + { + return $this->oauthProvider; + } + + /** + * Set OAuth Provider + * + * @param string $oauthProvider + * @returns self + */ + public function setOAuthProvider(string $oauthProvider): self + { + $this->oauthProvider = $oauthProvider; + return $this; + } +} \ No newline at end of file diff --git a/src/Transfer/Source.php b/src/Transfer/Source.php new file mode 100644 index 0000000..80fcc64 --- /dev/null +++ b/src/Transfer/Source.php @@ -0,0 +1,238 @@ + '', + ]; + + /** + * Logs + * + * @var array $logs + */ + protected $logs = []; + + /** + * Internal Adapter State + * + * @var array $state + */ + protected $state = []; + + /** + * Endpoint + * + * @var string $endpoint + */ + protected $endpoint = ''; + + /** + * Gets the name of the adapter. + * + * @return string + */ + abstract public function getName(): string; + + /** + * Get Supported Resources + * + * @return array + */ + abstract public function getSupportedResources(): array; + + /** + * Register Logs Array + * + * @param array &$logs + */ + public function registerLogs(array &$logs): void { + $this->logs = &$logs; + } + + /** + * Transfer Resources into destination + * + * @param array $resources + * @param callable $callback + */ + public function run(array $resources, callable $callback): void { + foreach ($resources as $resource) { + if (!in_array($resource, $this->getSupportedResources())) { + throw new \Exception("Cannot Transfer unsupported resource: '".$resource."'"); + } + + switch ($resource) { + case Transfer::RESOURCE_USERS: { + $resourceCache = []; + + while (true) { + $users = $this->exportUsers(1000); + $resourceCache = array_merge($resourceCache, $users); + $callback(new Log(Log::INFO, 'Exporting Users...'), Transfer::RESOURCE_USERS, $resourceCache); + + if (count($users) < 1000) { + break; + } + } + break; + } + } + } + } + + /** + * Get Projects + * Get all the currently accessible projects. + * + * @return array + */ + abstract public function getProjects(): array; + + /** + * Check Requirements + * Performs a suite of API Checks, Resource Checks, etc... to ensure the adapter is ready to be used. + * This is highly recommended to be called before any other method after initialization. + * + * If no resources are provided, the method should check all resources. + * + * @array $resources + * + * @return bool + */ + abstract public function check(array $resources = []): bool; + + /** + * Call + * + * Make an API call + * + * @param string $method + * @param string $path + * @param array $params + * @param array $headers + * @return array|string + * @throws \Exception + */ + public function call(string $method, string $path = '', array $headers = array(), array $params = array()): array|string + { + $headers = array_merge($this->headers, $headers); + $ch = curl_init((str_contains($path, 'http') ? $path : $this->endpoint . $path . (($method == 'GET' && !empty($params)) ? '?' . http_build_query($params) : ''))); + $responseHeaders = []; + $responseStatus = -1; + $responseType = ''; + $responseBody = ''; + + switch ($headers['Content-Type']) { + case 'application/json': + $query = json_encode($params); + break; + + case 'multipart/form-data': + $query = $this->flatten($params); + break; + + default: + $query = http_build_query($params); + break; + } + + foreach ($headers as $i => $header) { + $headers[] = $i . ':' . $header; + unset($headers[$i]); + } + + curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($ch, CURLOPT_USERAGENT, php_uname('s') . '-' . php_uname('r') . ':php-' . phpversion()); + curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); + curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); + curl_setopt($ch, CURLOPT_HEADERFUNCTION, function ($curl, $header) use (&$responseHeaders) { + $len = strlen($header); + $header = explode(':', strtolower($header), 2); + + if (count($header) < 2) { // ignore invalid headers + return $len; + } + + $responseHeaders[strtolower(trim($header[0]))] = trim($header[1]); + + return $len; + }); + + if ($method != 'GET') { + curl_setopt($ch, CURLOPT_POSTFIELDS, $query); + } + + $responseBody = curl_exec($ch); + + $responseType = $responseHeaders['Content-Type'] ?? ''; + $responseStatus = curl_getinfo($ch, CURLINFO_HTTP_CODE); + + switch (substr($responseType, 0, strpos($responseType, ';'))) { + case 'application/json': + $responseBody = json_decode($responseBody, true); + break; + } + + if (curl_errno($ch)) { + throw new \Exception(curl_error($ch)); + } + + curl_close($ch); + + if ($responseStatus >= 400) { + if (is_array($responseBody)) { + throw new \Exception(json_encode($responseBody)); + } else { + throw new \Exception($responseStatus . ': ' . $responseBody); + } + } + + return $responseBody; + } + + /** + * Flatten params array to PHP multiple format + * + * @param array $data + * @param string $prefix + * @return array + */ + protected function flatten(array $data, string $prefix = ''): array + { + $output = []; + + foreach ($data as $key => $value) { + $finalKey = $prefix ? "{$prefix}[{$key}]" : $key; + + if (is_array($value)) { + $output += $this->flatten($value, $finalKey); // @todo: handle name collision here if needed + } else { + $output[$finalKey] = $value; + } + } + + return $output; + } + + /** + * Export Users + * + * @param int $chunk + * @returns User[] + */ + public function exportUsers(int $chunk = 1000) + { + throw new Exception('Unimplemented, Please check if your source adapter supports this method.'); + } +} diff --git a/src/Transfer/Sources/Firebase.php b/src/Transfer/Sources/Firebase.php new file mode 100644 index 0000000..1d6e998 --- /dev/null +++ b/src/Transfer/Sources/Firebase.php @@ -0,0 +1,174 @@ + self::AUTH_SERVICEACCOUNT, + 'object' => [] + ]; + + /** + * @var Client|null + */ + protected $googleClient = null; + + /** + * @var Factory|null + */ + protected $firebaseAdminClient = null; + + /** + * Constructor + * + * @param array $authObject Service Account Credentials for AUTH_SERVICEACCOUNT + * @param string $authType Can be either Firebase::AUTH_OAUTH or Firebase::AUTH_APIKEY + */ + function __construct(array $authObject = [], string $authType = self::AUTH_SERVICEACCOUNT) + { + if (!in_array($authType, [self::AUTH_OAUTH, self::AUTH_SERVICEACCOUNT])) { + throw new \Exception('Invalid authentication type'); + } + + $this->googleClient = new Client(); + + if ($authType === self::AUTH_OAUTH) { + $this->googleClient->setAccessToken($authObject); + } else if ($authType === self::AUTH_SERVICEACCOUNT) { + $this->googleClient->setAuthConfig($authObject); + } + + $this->googleClient->addScope('https://www.googleapis.com/auth/firebase'); + $this->googleClient->addScope('https://www.googleapis.com/auth/cloud-platform'); + + $this->firebaseAdminClient = (new Factory)->withServiceAccount($authObject); + } + + function getName(): string + { + return 'Firebase'; + } + + function getSupportedResources(): array + { + return [ + Transfer::RESOURCE_USERS + ]; + } + + function getProjects(): array + { + $projects = []; + + $firebase = new \Google\Service\FirebaseManagement($this->googleClient); + + $request = $firebase->projects->listProjects(); + + if ($request['results']) { + foreach ($request['results'] as $project) { + $projects[] = new Project( + $project['displayName'], + $project['projectId'] + ); + } + } + + return $projects; + } + + /** + * Export Users + * + * @param int $chunk + * @returns list + */ + public function exportUsers(int $chunk = 1000) + { + $users = []; + + $auth = $this->firebaseAdminClient->createAuth(); + + $result = $auth->listUsers($chunk); + + // Fetch our hash config + + $httpClient = $this->googleClient->authorize(); + + $hashConfig = json_decode($httpClient->request('GET', 'https://identitytoolkit.googleapis.com/admin/v2/projects/amadeus-a5bcc/config')->getBody()->getContents(), true)["signIn"]["hashConfig"]; + + foreach($result as $user) { + /** @var \Kreait\Firebase\Auth\UserRecord $user */ + + // Figure out what type of user it is. + $type = $user->providerData[0]->providerId ?? 'Anonymous'; + + switch ($type) { + case 'password': { + $users[] = new User( + $user->uid, + $user->email, + new Hash($user->passwordHash, $user->passwordSalt, Hash::SCRYPT_MODIFIED, $hashConfig["saltSeparator"], $hashConfig["signerKey"]), + '', + User::AUTH_EMAIL + ); + break; + } + case 'phone': { + $users[] = new User( + $user->uid, + '', + new Hash(''), + $user->phoneNumber, + User::AUTH_PHONE + ); + break; + } + case 'Anonymous': { + $users[] = new User( + $user->uid, + '', + new Hash(''), + '', + User::AUTH_ANONYMOUS + ); + break; + } + default: { + $users[] = new User( + $user->uid, + '', + new Hash(''), + '', + User::AUTH_OAUTH, + $type + ); + + break; + } + } + } + + return $users; + + } + + function check(array $resources = []): bool + { + return true; + } +} \ No newline at end of file diff --git a/src/Transfer/Transfer.php b/src/Transfer/Transfer.php new file mode 100644 index 0000000..672bfe4 --- /dev/null +++ b/src/Transfer/Transfer.php @@ -0,0 +1,77 @@ +source = $source; + $this->destination = $destination; + + $this->source->registerLogs($this->logs); + $this->destination->registerLogs($this->logs); + + return $this; + } + + /** + * @var Source + */ + protected Source $source; + + /** + * @var Destination + */ + protected Destination $destination; + + /** + * @var array + */ + protected array $resources = []; + + /** + * @var array + */ + protected array $options = []; + + /** + * @var array + */ + protected array $logs = []; + + /** + * @var array + */ + protected array $callbacks = []; + + /** + * @var array + */ + protected array $events = []; + + /** + * Transfer Resources between adapters + * + * @param array $resources + * @param callable $callback + */ + public function run(array $resources, callable $callback): void { + $this->destination->run($resources, function ($data) use ($resources) { + var_dump($data); + }, $this->source); + } +} \ No newline at end of file diff --git a/tests/Transfer/FBTest2.php b/tests/Transfer/FBTest2.php new file mode 100644 index 0000000..107bdb1 --- /dev/null +++ b/tests/Transfer/FBTest2.php @@ -0,0 +1,21 @@ +getProjects()); + + +Console::log('Grabbing Users...'); +var_dump($source->exportUsers()); \ No newline at end of file diff --git a/tests/Transfer/FirebaseTest.php b/tests/Transfer/FirebaseTest.php new file mode 100644 index 0000000..def1a50 --- /dev/null +++ b/tests/Transfer/FirebaseTest.php @@ -0,0 +1,21 @@ + + * @version 1.0 RC1 + * @license The MIT License (MIT) + */ + +namespace Utopia\Tests; + +use PHPUnit\Framework\TestCase; + +class FirebaseTest extends TestCase +{ +} \ No newline at end of file diff --git a/tests/Transfer/TransferTest1.php b/tests/Transfer/TransferTest1.php new file mode 100644 index 0000000..d35b0a0 --- /dev/null +++ b/tests/Transfer/TransferTest1.php @@ -0,0 +1,21 @@ +run([ + Transfer::RESOURCE_USERS +], function($data) { + Console::log('Got More Data!'); +}); \ No newline at end of file From 2d467d07c8a05a150d929a458eed603a82c2af9d Mon Sep 17 00:00:00 2001 From: Bradley Schofield Date: Tue, 17 Jan 2023 15:13:53 +0000 Subject: [PATCH 02/70] Initial Push 2 + Completed first implementation of both Appwrite and Firebase adapters with the core Transfer class logic ready --- .env.example | 6 + LICENSE | 2 +- composer.json | 1 - phpunit.xml | 4 +- src/Transfer/Destination.php | 27 ++- src/Transfer/Destinations/Appwrite.php | 61 ++++-- src/Transfer/Log.php | 62 +++--- src/Transfer/Resource.php | 7 + src/Transfer/{ => Resources}/Hash.php | 35 ++- src/Transfer/Resources/Project.php | 8 + src/Transfer/Resources/User.php | 165 ++++++++++++-- src/Transfer/Source.php | 37 ++-- src/Transfer/Sources/Firebase.php | 203 ++++++++++++------ src/Transfer/Transfer.php | 60 +++++- tests/Transfer/Destinations/AppwriteTest.php | 86 ++++++++ tests/Transfer/FBTest2.php | 21 -- tests/Transfer/FirebaseTest.php | 21 -- tests/Transfer/Sources/FirebaseTest.php | 99 +++++++++ tests/Transfer/TransferTest1.php | 21 -- .../Transfers/FirebaseToAppwriteTest.php | 128 +++++++++++ 20 files changed, 820 insertions(+), 234 deletions(-) create mode 100644 .env.example rename src/Transfer/{ => Resources}/Hash.php (79%) create mode 100644 tests/Transfer/Destinations/AppwriteTest.php delete mode 100644 tests/Transfer/FBTest2.php delete mode 100644 tests/Transfer/FirebaseTest.php create mode 100644 tests/Transfer/Sources/FirebaseTest.php delete mode 100644 tests/Transfer/TransferTest1.php create mode 100644 tests/Transfer/Transfers/FirebaseToAppwriteTest.php diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..b43bdec --- /dev/null +++ b/.env.example @@ -0,0 +1,6 @@ +APPWRITE_TEST_PROJECT=testProject +APPWRITE_TEST_ENDPONT=http://localhost/v1 +APPWRITE_TEST_KEY=xxxxxxxxxxxxxxxxxx + +FIREBASE_TEST_PROJECT=testProject +FIREBASE_TEST_ACCOUNT='{type: "service_account", ...}' \ No newline at end of file diff --git a/LICENSE b/LICENSE index 42cb9d6..caa59ba 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2022 Bradley Schofield +Copyright (c) 2022 Utopia Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/composer.json b/composer.json index 4d7e346..ec62258 100644 --- a/composer.json +++ b/composer.json @@ -23,7 +23,6 @@ "php": ">=8.0", "utopia-php/cli": "^0.14.0", "google/apiclient": "^2.12.1", - "kreait/firebase-php": "^7.0", "appwrite/appwrite": "^7.2" }, "require-dev": { diff --git a/phpunit.xml b/phpunit.xml index ec39a7e..d686f8a 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -10,7 +10,9 @@ > - ./tests/ + ./tests/Transfer/Sources + ./tests/Transfer/Destinations + ./tests/Transfer/Transfers \ No newline at end of file diff --git a/src/Transfer/Destination.php b/src/Transfer/Destination.php index 7af9c73..a58ee26 100644 --- a/src/Transfer/Destination.php +++ b/src/Transfer/Destination.php @@ -22,6 +22,13 @@ abstract class Destination */ protected $logs = []; + /** + * Resource Cache + * + * @var array $resourceCache + */ + protected $resourceCache = []; + /** * Internal Adapter State * @@ -63,7 +70,16 @@ public function registerLogs(array &$logs): void { } /** - * Transfer Resources between adapters + * Register Resource Cache + * + * @param array &$cache + */ + public function registerResourceCache(array &$cache): void { + $this->resourceCache = &$cache; + } + + /** + * Transfer Resources to Destination from Source callback * * @param array $resources * @param callable $callback @@ -71,17 +87,18 @@ public function registerLogs(array &$logs): void { public function run(array $resources, callable $callback, Source $source): void { foreach ($resources as $resource) { if (!in_array($resource, $this->getSupportedResources())) { + $this->logs[Log::FATAL] = new Log("Cannot Transfer unsupported resource: '".$resource."'"); throw new \Exception("Cannot Transfer unsupported resource: '".$resource."'"); } - $source->run($resources, function (Log $currentLog, string $resource, array &$resourceCache) { - switch ($resource) { + $source->run($resources, function (Log $currentLog, string $resourceType, array $resource) use ($callback) { + switch ($resourceType) { case Transfer::RESOURCE_USERS: { - $this->importUsers($resourceCache); - $resourceCache = []; + $this->importUsers($resource); break; } } + $callback($currentLog, $resourceType, $resource); }); } } diff --git a/src/Transfer/Destinations/Appwrite.php b/src/Transfer/Destinations/Appwrite.php index de33a6d..bfbc326 100644 --- a/src/Transfer/Destinations/Appwrite.php +++ b/src/Transfer/Destinations/Appwrite.php @@ -5,7 +5,7 @@ use Appwrite\Client; use Appwrite\Services\Users; use Utopia\Transfer\Destination; -use Utopia\Transfer\Hash; +use Utopia\Transfer\Resources\Hash; use Utopia\Transfer\Log; use Utopia\Transfer\Resources\User; use Utopia\Transfer\Transfer; @@ -47,19 +47,31 @@ public function getSupportedResources(): array { public function check(array $resources = []): bool { - //TODO: Implement check() method. + $auth = new Users($this->client); + + try { + $auth->list(); + } catch (\Exception $e) { + $this->logs[Log::ERROR] = new Log($e->getMessage()); + return false; + } + return true; } - public function importPasswordUser(User $user): void + public function importPasswordUser(User $user): array|null { - $authentication = new Users($this->client); + $auth = new Users($this->client); $hash = $user->getPasswordHash(); $result = null; + if (empty($hash->getHash()) || empty($hash->getSalt())) { + throw new \Exception('User password hash is empty'); + } + switch ($hash->getAlgorithm()) { case Hash::SCRYPT_MODIFIED: - $result = $authentication->createScryptModifiedUser( + $result = $auth->createScryptModifiedUser( $user->getId(), $user->getEmail(), $hash->getHash(), @@ -70,7 +82,7 @@ public function importPasswordUser(User $user): void ); break; case Hash::BCRYPT: - $result = $authentication->createBcryptUser( + $result = $auth->createBcryptUser( $user->getId(), $user->getEmail(), $hash->getHash(), @@ -78,23 +90,24 @@ public function importPasswordUser(User $user): void ); break; case Hash::ARGON2: - $result = $authentication->createArgon2User( + $result = $auth->createArgon2User( $user->getId(), $user->getEmail(), $hash->getHash(), $user->getEmail() ); break; - case Hash::SHA: - $result = $authentication->createShaUser( + case Hash::SHA256: + $result = $auth->createShaUser( $user->getId(), $user->getEmail(), $hash->getHash(), + 'sha256', $user->getEmail() ); break; case Hash::PHPASS: - $result = $authentication->createPHPassUser( + $result = $auth->createPHPassUser( $user->getId(), $user->getEmail(), $hash->getHash(), @@ -102,7 +115,7 @@ public function importPasswordUser(User $user): void ); break; case Hash::SCRYPT: - $result = $authentication->createScryptUser( + $result = $auth->createScryptUser( $user->getId(), $user->getEmail(), $hash->getHash(), @@ -116,26 +129,30 @@ public function importPasswordUser(User $user): void break; } - if (!$result) { - throw new \Exception('Failed to import user: "'.$user->getId().'"'); - } + return $result; } public function importUsers(array $users): void { + $auth = new Users($this->client); + foreach ($users as $user) { /** @var \Utopia\Transfer\Resources\User $user */ try { - switch ($user->getType()) { - case User::AUTH_EMAIL: - $this->importPasswordUser($user); - break; - default: - $this->logs[] = new Log(Log::WARNING, 'Not copying user: "'.$user->getId().'" due to it being an account type: "'.$user->getType().'".', \time(), $user); - //TODO: Implement other auth types, talk to Eldadfux about API's requried (Might have to resort to using Console SDK). + $createdUser = in_array(User::TYPE_EMAIL, $user->getTypes()) ? $this->importPasswordUser($user) : $auth->create($user->getId(), $user->getEmail(), $user->getPhone(), null, $user->getName()); + + if (!$createdUser) { + $this->logs[Log::ERROR][] = new Log('Failed to import user', \time(), $user); + } else { + // Add more data to the user + $auth->updateName($user->getId(), $user->getUsername()); + $auth->updatePhone($user->getId(), $user->getPhone()); + $auth->updateEmailVerification($user->getId(), $user->getEmailVerified()); + $auth->updatePhoneVerification($user->getId(), $user->getPhoneVerified()); + $auth->updateStatus($user->getId(), !$user->getDisabled()); } } catch (\Exception $e) { - $this->logs[] = new Log(Log::ERROR, $e->getMessage(), \time(), $user); + $this->logs[Log::ERROR][] = new Log($e->getMessage(), \time(), $user); } } } diff --git a/src/Transfer/Log.php b/src/Transfer/Log.php index 95047df..2d145a9 100644 --- a/src/Transfer/Log.php +++ b/src/Transfer/Log.php @@ -6,41 +6,19 @@ class Log { const INFO = 'info'; const WARNING = 'warning'; const ERROR = 'error'; + const FATAL = 'fatal'; const DEBUG = 'debug'; - public function __construct(private string $level = Log::INFO, private string $message = '', private int $timestamp = 0, protected Resource $resource = null) + public function __construct(private string $message = '', private int $timestamp = 0, protected Resource|null $resource = null) { $timestamp = \time(); } - - /** - * Get Level - * - * @returns string - */ - public function getLevel(): string - { - return $this->level; - } - - /** - * Set Level - * - * @param string $level - * @returns self - */ - public function setLevel(string $level) - { - $this->level = $level; - return $this; - } - /** * Get Message * * @returns string */ - public function getMessage() + public function getMessage(): string { return $this->message; } @@ -51,7 +29,7 @@ public function getMessage() * @param string $message * @returns self */ - public function setMessage(string $message) + public function setMessage(string $message): self { $this->message = $message; return $this; @@ -60,9 +38,9 @@ public function setMessage(string $message) /** * Get Timestamp * - * @returns string + * @returns int */ - public function getTimestamp() + public function getTimestamp(): int { return $this->timestamp; } @@ -70,12 +48,36 @@ public function getTimestamp() /** * Set Timestamp * - * @param string $timestamp + * @param int $timestamp * @returns self */ - public function setTimestamp(string $timestamp) + public function setTimestamp(int $timestamp): self { $this->timestamp = $timestamp; return $this; } + + /** + * Get Resource + * + * @returns Resource|null + */ + public function getResource(): ?Resource + { + return $this->resource; + } + + /** + * As Array + * + * @returns array + */ + public function asArray(): array + { + return [ + 'message' => $this->message, + 'timestamp' => $this->timestamp, + 'resource' => $this->resource ? $this->resource->asArray() : null, + ]; + } } \ No newline at end of file diff --git a/src/Transfer/Resource.php b/src/Transfer/Resource.php index 21f8c55..1218dd7 100644 --- a/src/Transfer/Resource.php +++ b/src/Transfer/Resource.php @@ -34,4 +34,11 @@ public function setId(string $id): self $this->id = $id; return $this; } + + /** + * As Array + * + * @returns array + */ + abstract public function asArray(): array; } \ No newline at end of file diff --git a/src/Transfer/Hash.php b/src/Transfer/Resources/Hash.php similarity index 79% rename from src/Transfer/Hash.php rename to src/Transfer/Resources/Hash.php index eff30a5..b792bb3 100644 --- a/src/Transfer/Hash.php +++ b/src/Transfer/Resources/Hash.php @@ -1,25 +1,32 @@ passwordLength = $passwordLength; return $this; } + + /** + * As Array + * + * @returns array + */ + public function asArray(): array + { + return [ + 'hash' => $this->hash, + 'salt' => $this->salt, + 'algorithm' => $this->algorithm, + 'separator' => $this->separator, + 'signingKey' => $this->signingKey, + 'passwordCpu' => $this->passwordCpu, + 'passwordMemory' => $this->passwordMemory, + 'passwordParallel' => $this->passwordParallel, + 'passwordLength' => $this->passwordLength, + ]; + } } \ No newline at end of file diff --git a/src/Transfer/Resources/Project.php b/src/Transfer/Resources/Project.php index 0d76785..12a2cfb 100644 --- a/src/Transfer/Resources/Project.php +++ b/src/Transfer/Resources/Project.php @@ -26,4 +26,12 @@ public function setId(string $id): self $this->id = $id; return $this; } + + public function asArray(): array + { + return [ + 'name' => $this->name, + 'id' => $this->id, + ]; + } } \ No newline at end of file diff --git a/src/Transfer/Resources/User.php b/src/Transfer/Resources/User.php index b18cefe..c2c8a50 100644 --- a/src/Transfer/Resources/User.php +++ b/src/Transfer/Resources/User.php @@ -3,23 +3,28 @@ namespace Utopia\Transfer\Resources; use Utopia\Transfer\Resource; -use Utopia\Transfer\Hash; +use Utopia\Transfer\Resources\Hash; class User extends Resource { - const AUTH_EMAIL = 'email'; - const AUTH_PHONE = 'phone'; - const AUTH_ANONYMOUS = 'anonymous'; - const AUTH_MAGIC = 'magic'; - const AUTH_OAUTH = 'oauth'; + const TYPE_EMAIL = 'email'; + const TYPE_PHONE = 'phone'; + const TYPE_ANONYMOUS = 'anonymous'; + const TYPE_MAGIC = 'magic'; + const TYPE_OAUTH = 'oauth'; public function __construct( protected string $id = '', protected string $email = '', + protected string $username = '', protected Hash $passwordHash = new Hash(''), protected string $phone = '', - protected string $type = self::AUTH_ANONYMOUS, - protected string $oauthProvider = '' + protected array $types = [Self::TYPE_ANONYMOUS], + protected string $oauthProvider = '', + protected bool $emailVerified = false, + protected bool $phoneVerified = false, + protected bool $disabled = false, + protected array $preferences = [] ){} /** @@ -76,6 +81,28 @@ public function setEmail(string $email): self return $this; } + /** + * Get Username + * + * @returns string + */ + public function getUsername(): string + { + return $this->username; + } + + /** + * Set Username + * + * @param string $username + * @returns self + */ + public function setUsername(string $username): self + { + $this->username = $username; + return $this; + } + /** * Get Password Hash * @@ -123,22 +150,22 @@ public function setPhone(string $phone): self /** * Get Type * - * @returns string + * @returns array */ - public function getType(): string + public function getTypes(): array { - return $this->type; + return $this->types; } /** - * Set Type + * Set Types * - * @param string $type + * @param string $types * @returns self */ - public function setType(string $type): self + public function setTypes(string $types): self { - $this->type = $type; + $this->types = $types; return $this; } @@ -163,4 +190,112 @@ public function setOAuthProvider(string $oauthProvider): self $this->oauthProvider = $oauthProvider; return $this; } + + /** + * Get Email Verified + * + * @returns bool + */ + public function getEmailVerified(): bool + { + return $this->emailVerified; + } + + /** + * Set Email Verified + * + * @param bool $verified + * @returns self + */ + public function setEmailVerified(bool $verified): self + { + $this->emailVerified = $verified; + return $this; + } + + /** + * Get Email Verified + * + * @returns bool + */ + public function getPhoneVerified(): bool + { + return $this->phoneVerified; + } + + /** + * Set Phone Verified + * + * @param bool $verified + * @returns self + */ + public function setPhoneVerified(bool $verified): self + { + $this->phoneVerified = $verified; + return $this; + } + + /** + * Get Disabled + * + * @returns bool + */ + public function getDisabled(): bool + { + return $this->disabled; + } + + /** + * Set Disabled + * + * @param bool $disabled + * @returns self + */ + public function setDisabled(bool $disabled): self + { + $this->disabled = $disabled; + return $this; + } + + /** + * Get Preferences + * + * @returns array + */ + public function getPreferences(): array + { + return $this->preferences; + } + + /** + * Set Preferences + * + * @param array $preferences + * @returns self + */ + public function setPreferences(array $preferences): self + { + $this->preferences = $preferences; + return $this; + } + + /** + * As Array + * + * @returns array + */ + public function asArray(): array + { + return [ + 'id' => $this->id, + 'email' => $this->email, + 'username' => $this->username, + 'passwordHash' => $this->passwordHash->asArray(), + 'phone' => $this->phone, + 'types' => $this->types, + 'oauthProvider' => $this->oauthProvider, + 'emailVerified' => $this->emailVerified, + 'phoneVerified' => $this->phoneVerified, + ]; + } } \ No newline at end of file diff --git a/src/Transfer/Source.php b/src/Transfer/Source.php index 80fcc64..3d62661 100644 --- a/src/Transfer/Source.php +++ b/src/Transfer/Source.php @@ -22,6 +22,13 @@ abstract class Source */ protected $logs = []; + /** + * Resource Cache + * + * @var array $resourceCache + */ + protected $resourceCache = []; + /** * Internal Adapter State * @@ -59,6 +66,15 @@ public function registerLogs(array &$logs): void { $this->logs = &$logs; } + /** + * Register Resource Cache + * + * @param array &$cache + */ + public function registerResourceCache(array &$cache): void { + $this->resourceCache = &$cache; + } + /** * Transfer Resources into destination * @@ -73,17 +89,10 @@ public function run(array $resources, callable $callback): void { switch ($resource) { case Transfer::RESOURCE_USERS: { - $resourceCache = []; - - while (true) { - $users = $this->exportUsers(1000); - $resourceCache = array_merge($resourceCache, $users); - $callback(new Log(Log::INFO, 'Exporting Users...'), Transfer::RESOURCE_USERS, $resourceCache); - - if (count($users) < 1000) { - break; - } - } + $this->exportUsers(500, function (array $users) use ($callback) { + $this->resourceCache = array_merge($this->resourceCache, $users); + $callback(new Log('Exporting Users...'), Transfer::RESOURCE_USERS, $users); + }); break; } } @@ -228,10 +237,12 @@ protected function flatten(array $data, string $prefix = ''): array /** * Export Users * - * @param int $chunk + * @param int $batchSize + * @param callable $callback Callback function to be called after each batch, $callback(user[] $batch); + * * @returns User[] */ - public function exportUsers(int $chunk = 1000) + public function exportUsers(int $batchSize, callable $callback): array { throw new Exception('Unimplemented, Please check if your source adapter supports this method.'); } diff --git a/src/Transfer/Sources/Firebase.php b/src/Transfer/Sources/Firebase.php index 1d6e998..f522d34 100644 --- a/src/Transfer/Sources/Firebase.php +++ b/src/Transfer/Sources/Firebase.php @@ -7,12 +7,12 @@ use Utopia\Transfer\Resources\Project; use Utopia\Transfer\Resources\User; use Utopia\Transfer\Transfer; -use Kreait\Firebase\Factory; -use Utopia\Transfer\Hash; +use Utopia\Transfer\Log; +use Utopia\Transfer\Resources\Hash; class Firebase extends Source { - const AUTH_OAUTH = 'oauth'; + const TYPE_OAUTH = 'oauth'; const AUTH_SERVICEACCOUNT = 'serviceaccount'; /** @@ -29,25 +29,25 @@ class Firebase extends Source protected $googleClient = null; /** - * @var Factory|null + * @var Project|null */ - protected $firebaseAdminClient = null; + protected $project; /** * Constructor * * @param array $authObject Service Account Credentials for AUTH_SERVICEACCOUNT - * @param string $authType Can be either Firebase::AUTH_OAUTH or Firebase::AUTH_APIKEY + * @param string $authType Can be either Firebase::TYPE_OAUTH or Firebase::AUTH_APIKEY */ function __construct(array $authObject = [], string $authType = self::AUTH_SERVICEACCOUNT) { - if (!in_array($authType, [self::AUTH_OAUTH, self::AUTH_SERVICEACCOUNT])) { + if (!in_array($authType, [self::TYPE_OAUTH, self::AUTH_SERVICEACCOUNT])) { throw new \Exception('Invalid authentication type'); } $this->googleClient = new Client(); - if ($authType === self::AUTH_OAUTH) { + if ($authType === self::TYPE_OAUTH) { $this->googleClient->setAccessToken($authObject); } else if ($authType === self::AUTH_SERVICEACCOUNT) { $this->googleClient->setAuthConfig($authObject); @@ -55,10 +55,8 @@ function __construct(array $authObject = [], string $authType = self::AUTH_SERVI $this->googleClient->addScope('https://www.googleapis.com/auth/firebase'); $this->googleClient->addScope('https://www.googleapis.com/auth/cloud-platform'); - - $this->firebaseAdminClient = (new Factory)->withServiceAccount($authObject); } - + function getName(): string { return 'Firebase'; @@ -75,6 +73,10 @@ function getProjects(): array { $projects = []; + if (!$this->googleClient) { + throw new \Exception('Google Client not initialized'); + } + $firebase = new \Google\Service\FirebaseManagement($this->googleClient); $request = $firebase->projects->listProjects(); @@ -92,83 +94,144 @@ function getProjects(): array } /** - * Export Users + * Set Project * - * @param int $chunk - * @returns list + * @param Project|string $project */ - public function exportUsers(int $chunk = 1000) + function setProject(Project|string $project): void { - $users = []; + if (is_string($project)) { + $project = new Project($project, $project); + } - $auth = $this->firebaseAdminClient->createAuth(); + $this->project = $project; + } - $result = $auth->listUsers($chunk); + /** + * Get Project + * + * @returns Project|null + */ + function getProject(): Project|null + { + return $this->project; + } - // Fetch our hash config + /** + * Export Users + * + * @param int $batchSize Max 500 + * @param callable $callback Callback function to be called after each batch, $callback(user[] $batch); + * + * @returns User[] + */ + public function exportUsers(int $batchSize, callable $callback): array + { + if (!$this->project || !$this->project->getId()) { + $this->logs[Log::FATAL][] = new Log('Project not set'); + throw new \Exception('Project not set'); + } + if ($batchSize > 500) { + $this->logs[Log::FATAL][] = new Log('Batch size cannot be greater than 500'); + throw new \Exception('Batch size cannot be greater than 500'); + } + + // Fetch our hash config $httpClient = $this->googleClient->authorize(); - $hashConfig = json_decode($httpClient->request('GET', 'https://identitytoolkit.googleapis.com/admin/v2/projects/amadeus-a5bcc/config')->getBody()->getContents(), true)["signIn"]["hashConfig"]; - - foreach($result as $user) { - /** @var \Kreait\Firebase\Auth\UserRecord $user */ - - // Figure out what type of user it is. - $type = $user->providerData[0]->providerId ?? 'Anonymous'; - - switch ($type) { - case 'password': { - $users[] = new User( - $user->uid, - $user->email, - new Hash($user->passwordHash, $user->passwordSalt, Hash::SCRYPT_MODIFIED, $hashConfig["saltSeparator"], $hashConfig["signerKey"]), - '', - User::AUTH_EMAIL - ); - break; - } - case 'phone': { - $users[] = new User( - $user->uid, - '', - new Hash(''), - $user->phoneNumber, - User::AUTH_PHONE - ); - break; - } - case 'Anonymous': { - $users[] = new User( - $user->uid, - '', - new Hash(''), - '', - User::AUTH_ANONYMOUS - ); - break; - } - default: { - $users[] = new User( - $user->uid, - '', - new Hash(''), - '', - User::AUTH_OAUTH, - $type - ); + $hashConfig = json_decode($httpClient->request('GET', 'https://identitytoolkit.googleapis.com/admin/v2/projects/' . $this->project->getId() . '/config')->getBody()->getContents(), true)["signIn"]["hashConfig"]; - break; - } + if (!$hashConfig) { + $this->logs[Log::FATAL][] = new Log('Unable to fetch hash config'); + throw new \Exception('Unable to fetch hash config'); + } + + $count = 0; + $nextPageToken = null; + + while (true) { + $users = []; + + $request = [ + "targetProjectId" => $this->project->getId(), + "maxResults" => $batchSize, + ]; + + if ($nextPageToken) { + $request["nextPageToken"] = $nextPageToken; + } + + $response = json_decode($httpClient->request('POST', 'https://identitytoolkit.googleapis.com/identitytoolkit/v3/relyingparty/downloadAccount', [ + 'json' => $request + ])->getBody()->getContents(), true); + + if (!$response) { + $this->logs[Log::FATAL][] = new Log('Unable to fetch users'); + throw new \Exception('Unable to fetch users'); + } + + $result = $response["users"]; + $nextPageToken = $response["nextPageToken"] ?? null; + + foreach ($result as $user) { + /** @var array $user */ + + // Figure out what type of user it is. + $types = $this->calculateUserType($user['providerUserInfo'] ?? []); + + $users[] = new User( + $user["localId"] ?? '', + $user["email"] ?? '', + $user["displayName"] ?? $user["email"] ?? '', + new Hash($user["passwordHash"] ?? '', $user["salt"] ?? '', Hash::SCRYPT_MODIFIED, $hashConfig["saltSeparator"], $hashConfig["signerKey"]), + $user["phoneNumber"] ?? '', + $types, + '', + $user["emailVerified"], + false, // Can't get phone number status on firebase :/ + $user["disabled"] + ); + + $count++; + } + + $callback($users); + + if (count($result) < $batchSize) { + break; } } return $users; + } + + function calculateUserType(array $providerData): array { + if (count($providerData) === 0) { + return [User::TYPE_ANONYMOUS]; + } + + $types = []; + + foreach ($providerData as $provider) { + switch ($provider["providerId"]) { + case 'password': + $types[] = User::TYPE_EMAIL; + break; + case 'phone': + $types[] = User::TYPE_PHONE; + break; + default: + $types[] = User::TYPE_OAUTH; + break; + } + } + return $types; } function check(array $resources = []): bool { return true; } -} \ No newline at end of file +} diff --git a/src/Transfer/Transfer.php b/src/Transfer/Transfer.php index 672bfe4..04dfa8f 100644 --- a/src/Transfer/Transfer.php +++ b/src/Transfer/Transfer.php @@ -5,7 +5,8 @@ use Utopia\Transfer\Destination; use Utopia\Transfer\Source; -class Transfer { +class Transfer +{ const RESOURCE_USERS = 'users'; const RESOURCE_FILES = 'files'; const RESOURCE_FUNCTIONS = 'functions'; @@ -18,12 +19,15 @@ class Transfer { * * @return Transfer */ - function __construct(Source $source, Destination $destination) { + function __construct(Source $source, Destination $destination) + { $this->source = $source; $this->destination = $destination; $this->source->registerLogs($this->logs); + $this->source->registerResourceCache($this->resources); $this->destination->registerLogs($this->logs); + $this->destination->registerResourceCache($this->resources); return $this; } @@ -39,9 +43,17 @@ function __construct(Source $source, Destination $destination) { protected Destination $destination; /** + * A local cache of resources that were transferred. + * * @var array */ - protected array $resources = []; + protected array $resources = [ + self::RESOURCE_COLLECTIONS => [], + self::RESOURCE_DATABASES => [], + self::RESOURCE_FILES => [], + self::RESOURCE_FUNCTIONS => [], + self::RESOURCE_USERS => [] + ]; /** * @var array @@ -51,7 +63,12 @@ function __construct(Source $source, Destination $destination) { /** * @var array */ - protected array $logs = []; + protected array $logs = [ + Log::ERROR => [], + Log::WARNING => [], + Log::INFO => [], + Log::FATAL => [] + ]; /** * @var array @@ -69,9 +86,34 @@ function __construct(Source $source, Destination $destination) { * @param array $resources * @param callable $callback */ - public function run(array $resources, callable $callback): void { - $this->destination->run($resources, function ($data) use ($resources) { - var_dump($data); - }, $this->source); + public function run(array $resources, callable $callback): void + { + $this->destination->run($resources, $callback, $this->source); + } + + /** + * Get Logs + * + * If no level is provided then the function returns all logs combined ordered by timestamp. + * + * @param string $level + * + * @return array + */ + public function getLogs($level = ''): array + { + if (!empty($level)) { + return $this->logs[$level]; + } + + $mergedLogs = array_merge($this->logs[Log::ERROR], $this->logs[Log::WARNING], $this->logs[Log::INFO], $this->logs[Log::FATAL]); + + $timestamps = []; + foreach ($mergedLogs as $key => $log) { + $timestamps[$key] = $log->getTimestamp(); + } + array_multisort($timestamps, SORT_ASC, $mergedLogs); + + return $mergedLogs; } -} \ No newline at end of file +} diff --git a/tests/Transfer/Destinations/AppwriteTest.php b/tests/Transfer/Destinations/AppwriteTest.php new file mode 100644 index 0000000..69dcbd5 --- /dev/null +++ b/tests/Transfer/Destinations/AppwriteTest.php @@ -0,0 +1,86 @@ + + * @version 1.0 RC1 + * @license The MIT License (MIT) + */ + +namespace Utopia\Tests; + +use PHPUnit\Framework\TestCase; +use Utopia\Transfer\Destinations\Appwrite; +use Utopia\Transfer\Resources\Hash; +use Utopia\Transfer\Resources\User; +use Appwrite\Client; +use Appwrite\Services\Users; + +class AppwriteTest extends TestCase +{ + /** + * @var Appwrite + */ + public $appwrite; + + /** + * @var Client + */ + public $client; + + public function setUp(): void + { + $this->appwrite = new Appwrite( + getenv("APPWRITE_TEST_PROJECT"), + getenv("APPWRITE_TEST_ENDPOINT"), + getenv("APPWRITE_TEST_KEY") + ); + + $this->client = new Client(); + $this->client + ->setEndpoint(getenv("APPWRITE_TEST_ENDPOINT")) + ->setProject(getenv("APPWRITE_TEST_PROJECT")) + ->setKey(getenv("APPWRITE_TEST_KEY")); + } + + public function testGetSupportedResources(): void + { + $this->assertIsArray($this->appwrite->getSupportedResources()); + $this->assertNotEmpty($this->appwrite->getSupportedResources()); + } + + public function testImportUserPassword(): void + { + $users = new Users($this->client); + + /** + * Hash: SHA256, + * Password: 'password' + */ + $user = new User( + '123456789', + 'test@user.com', + "Walter O'brien", + new Hash('5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8'), + '', + [User::TYPE_EMAIL] + ); + + $this->appwrite->importPasswordUser($user); + + // Check User Exists in Appwrite + $response = $users->get($user->getId()); + $this->assertEquals($user->getId(), $response['$id']); + $this->assertEquals($user->getEmail(), $response['email']); + $this->assertEquals($user->getPasswordHash()->getHash(), $response['password']); + + + // Cleanup + $users->delete($user->getId()); + } +} diff --git a/tests/Transfer/FBTest2.php b/tests/Transfer/FBTest2.php deleted file mode 100644 index 107bdb1..0000000 --- a/tests/Transfer/FBTest2.php +++ /dev/null @@ -1,21 +0,0 @@ -getProjects()); - - -Console::log('Grabbing Users...'); -var_dump($source->exportUsers()); \ No newline at end of file diff --git a/tests/Transfer/FirebaseTest.php b/tests/Transfer/FirebaseTest.php deleted file mode 100644 index def1a50..0000000 --- a/tests/Transfer/FirebaseTest.php +++ /dev/null @@ -1,21 +0,0 @@ - - * @version 1.0 RC1 - * @license The MIT License (MIT) - */ - -namespace Utopia\Tests; - -use PHPUnit\Framework\TestCase; - -class FirebaseTest extends TestCase -{ -} \ No newline at end of file diff --git a/tests/Transfer/Sources/FirebaseTest.php b/tests/Transfer/Sources/FirebaseTest.php new file mode 100644 index 0000000..18a99ef --- /dev/null +++ b/tests/Transfer/Sources/FirebaseTest.php @@ -0,0 +1,99 @@ + + * @version 1.0 RC1 + * @license The MIT License (MIT) + */ + +namespace Utopia\Tests; + +use PHPUnit\Framework\TestCase; +use Utopia\App; +use Utopia\Transfer\Sources\Firebase; +use Utopia\Transfer\Resources\Project; + +class FirebaseTest extends TestCase +{ + /** + * @var Firebase + */ + public $firebase; + + /** + * @var array + */ + public $serviceAccount; + + public function setUp(): void + { + $this->serviceAccount = json_decode(App::getEnv("FIREBASE_TEST_ACCOUNT"), true); + + $this->firebase = new Firebase( + $this->serviceAccount, + Firebase::AUTH_SERVICEACCOUNT + ); + } + + public function testGetProjects(): void + { + $projects = $this->firebase->getProjects(); + + $this->assertIsArray($projects); + $this->assertNotEmpty($projects); + } + + public function testSetProject(): void + { + $projects = $this->firebase->getProjects(); + + /** + * @var Project $testProject + */ + $testProject = null; + + foreach($projects as $project) { + /** @var Project $project */ + if($project->getId() == $this->serviceAccount['project_id']) { + $testProject = $project; + break; + } + } + + $this->assertIsObject($testProject); + + $this->firebase->setProject($testProject); + + $this->assertEquals($projects[0], $this->firebase->getProject()); + } + + + public function testGetUsers(): void + { + $projects = $this->firebase->getProjects(); + + $this->firebase->setProject($projects[0]); + + $result = []; + + $this->firebase->exportUsers(500, function (array $users) use (&$result) { + $result = array_merge($result, $users); + }); + + foreach ($result as $user) { + /** @var User $user */ + $this->assertIsObject($user); + $this->assertNotEmpty($user->getPasswordHash()); + $this->assertNotEmpty($user->getPasswordHash()->getHash()); + } + + $this->assertIsArray($result); + $this->assertNotEmpty($result); + } +} \ No newline at end of file diff --git a/tests/Transfer/TransferTest1.php b/tests/Transfer/TransferTest1.php deleted file mode 100644 index d35b0a0..0000000 --- a/tests/Transfer/TransferTest1.php +++ /dev/null @@ -1,21 +0,0 @@ -run([ - Transfer::RESOURCE_USERS -], function($data) { - Console::log('Got More Data!'); -}); \ No newline at end of file diff --git a/tests/Transfer/Transfers/FirebaseToAppwriteTest.php b/tests/Transfer/Transfers/FirebaseToAppwriteTest.php new file mode 100644 index 0000000..5240cee --- /dev/null +++ b/tests/Transfer/Transfers/FirebaseToAppwriteTest.php @@ -0,0 +1,128 @@ + + * @version 1.0 RC1 + * @license The MIT License (MIT) + */ + +namespace Utopia\Tests; + +use PHPUnit\Framework\TestCase; +use Utopia\Transfer\Destinations\Appwrite; +use Utopia\Transfer\Log; +use Utopia\Transfer\Sources\Firebase; +use Utopia\Transfer\Transfer; +use Utopia\Transfer\Resources\User; +use Appwrite\Client as AppwriteClient; +use Appwrite\Services\Users; + +class FirebaseToAppwriteTest extends TestCase +{ + /** + * @var Firebase + */ + public $firebase; + + /** + * @var Appwrite + */ + public $appwrite; + + /** + * @var AppwriteClient + */ + public $appwriteClient; + + /** + * @var Transfer + */ + public $transfer; + + public function setUp(): void + { + $serviceAccount = json_decode(getenv("FIREBASE_TEST_ACCOUNT"), true); + + $this->firebase = new Firebase( + $serviceAccount, + Firebase::AUTH_SERVICEACCOUNT + ); + + $this->firebase->setProject($serviceAccount['project_id']); + + $this->appwrite = new Appwrite( + getenv("APPWRITE_TEST_PROJECT"), + getenv("APPWRITE_TEST_ENDPOINT"), + getenv("APPWRITE_TEST_KEY") + ); + + $this->appwriteClient = new AppwriteClient(); + $this->appwriteClient + ->setEndpoint(getenv("APPWRITE_TEST_ENDPOINT")) + ->setProject(getenv("APPWRITE_TEST_PROJECT")) + ->setKey(getenv("APPWRITE_TEST_KEY")); + + $this->transfer = new Transfer($this->firebase, $this->appwrite); + } + + public function testTransferUsers(): void + { + $this->transfer->run([Transfer::RESOURCE_USERS], function () { + }); + + // Check for Fatal Errors in Transfer Log + $this->assertEmpty($this->transfer->getLogs(Log::FATAL)); + } + + /** + * @depends testTransferUsers + */ + public function testVerifyUsers(): void + { + $userClient = new Users($this->appwriteClient); + + $assertedUsers = false; + + $this->firebase->exportUsers(500, function (array $users) use ($userClient, &$assertedUsers) { + foreach ($users as $user) { + /** @var User $user */ + if (in_array(User::TYPE_ANONYMOUS, $user->getTypes())) { + continue; + } + + try { + $userFound = $userClient->get($user->getId()); + } catch (\Exception $e) { + throw $e; + } + $this->assertNotEmpty($userFound); + + $this->assertEquals($user->getEmail(), $userFound['email']); + $this->assertEquals($user->getPhone(), $userFound['phone']); + $assertedUsers = true; + } + }); + + $this->assertTrue($assertedUsers); + } + + public function testCleanupUsers(): void + { + $userClient = new Users($this->appwriteClient); + $appwriteUsers = $userClient->list(); + + $deletedUsers = 0; + + foreach ($appwriteUsers["users"] as $user) { + $userClient->delete($user['$id']); + $this->assertTrue(true); + $deletedUsers++; + } + } +} From 8f769c324e00f922f24fadcb31414018af7a0b08 Mon Sep 17 00:00:00 2001 From: Bradley Schofield Date: Wed, 18 Jan 2023 12:48:36 +0000 Subject: [PATCH 03/70] Implement Supabase and improve Firebase and Appwrite adapters --- LICENSE | 2 +- src/Transfer/Destinations/Appwrite.php | 40 ++++-- src/Transfer/Log.php | 1 + src/Transfer/Source.php | 10 +- src/Transfer/Sources/Firebase.php | 55 ++++++-- src/Transfer/Sources/Supabase.php | 125 +++++++++++++++++ src/Transfer/Transfer.php | 3 +- tests/Transfer/Sources/FirebaseTest.php | 2 +- tests/Transfer/Sources/SupabaseTest.php | 92 ++++++++++++ .../Transfers/SupabaseToAppwriteTest.php | 131 ++++++++++++++++++ 10 files changed, 428 insertions(+), 33 deletions(-) create mode 100644 src/Transfer/Sources/Supabase.php create mode 100644 tests/Transfer/Sources/SupabaseTest.php create mode 100644 tests/Transfer/Transfers/SupabaseToAppwriteTest.php diff --git a/LICENSE b/LICENSE index caa59ba..bb8c408 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2022 Utopia +Copyright (c) 2023 Utopia Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/src/Transfer/Destinations/Appwrite.php b/src/Transfer/Destinations/Appwrite.php index bfbc326..571b2e5 100644 --- a/src/Transfer/Destinations/Appwrite.php +++ b/src/Transfer/Destinations/Appwrite.php @@ -65,7 +65,7 @@ public function importPasswordUser(User $user): array|null $hash = $user->getPasswordHash(); $result = null; - if (empty($hash->getHash()) || empty($hash->getSalt())) { + if (empty($hash->getHash())) { throw new \Exception('User password hash is empty'); } @@ -78,7 +78,7 @@ public function importPasswordUser(User $user): array|null $hash->getSalt(), $hash->getSeparator(), $hash->getSigningKey(), - $user->getEmail() + $user->getUsername() ); break; case Hash::BCRYPT: @@ -86,7 +86,7 @@ public function importPasswordUser(User $user): array|null $user->getId(), $user->getEmail(), $hash->getHash(), - $user->getEmail() + $user->getUsername() ); break; case Hash::ARGON2: @@ -94,7 +94,7 @@ public function importPasswordUser(User $user): array|null $user->getId(), $user->getEmail(), $hash->getHash(), - $user->getEmail() + $user->getUsername() ); break; case Hash::SHA256: @@ -103,7 +103,7 @@ public function importPasswordUser(User $user): array|null $user->getEmail(), $hash->getHash(), 'sha256', - $user->getEmail() + $user->getUsername() ); break; case Hash::PHPASS: @@ -111,7 +111,7 @@ public function importPasswordUser(User $user): array|null $user->getId(), $user->getEmail(), $hash->getHash(), - $user->getEmail() + $user->getUsername() ); break; case Hash::SCRYPT: @@ -124,7 +124,7 @@ public function importPasswordUser(User $user): array|null $hash->getPasswordMemory(), $hash->getPasswordParallel(), $hash->getPasswordLength(), - $user->getEmail() + $user->getUsername() ); break; } @@ -145,11 +145,27 @@ public function importUsers(array $users): void $this->logs[Log::ERROR][] = new Log('Failed to import user', \time(), $user); } else { // Add more data to the user - $auth->updateName($user->getId(), $user->getUsername()); - $auth->updatePhone($user->getId(), $user->getPhone()); - $auth->updateEmailVerification($user->getId(), $user->getEmailVerified()); - $auth->updatePhoneVerification($user->getId(), $user->getPhoneVerified()); - $auth->updateStatus($user->getId(), !$user->getDisabled()); + if ($user->getUsername()) { + $auth->updateName($user->getId(), $user->getUsername()); + } + + if ($user->getPhone()) { + $auth->updatePhone($user->getId(), $user->getPhone()); + } + + if ($user->getEmailVerified()) { + $auth->updateEmailVerification($user->getId(), $user->getEmailVerified()); + } + + if ($user->getPhoneVerified()) { + $auth->updatePhoneVerification($user->getId(), $user->getPhoneVerified()); + } + + if ($user->getDisabled()) { + $auth->updateStatus($user->getId(), !$user->getDisabled()); + } + + $this->logs[Log::SUCCESS][] = new Log('User imported successfully', \time(), $user); } } catch (\Exception $e) { $this->logs[Log::ERROR][] = new Log($e->getMessage(), \time(), $user); diff --git a/src/Transfer/Log.php b/src/Transfer/Log.php index 2d145a9..b9cb2c8 100644 --- a/src/Transfer/Log.php +++ b/src/Transfer/Log.php @@ -7,6 +7,7 @@ class Log { const WARNING = 'warning'; const ERROR = 'error'; const FATAL = 'fatal'; + const SUCCESS = 'success'; const DEBUG = 'debug'; public function __construct(private string $message = '', private int $timestamp = 0, protected Resource|null $resource = null) diff --git a/src/Transfer/Source.php b/src/Transfer/Source.php index 3d62661..3835cb7 100644 --- a/src/Transfer/Source.php +++ b/src/Transfer/Source.php @@ -99,14 +99,6 @@ public function run(array $resources, callable $callback): void { } } - /** - * Get Projects - * Get all the currently accessible projects. - * - * @return array - */ - abstract public function getProjects(): array; - /** * Check Requirements * Performs a suite of API Checks, Resource Checks, etc... to ensure the adapter is ready to be used. @@ -242,7 +234,7 @@ protected function flatten(array $data, string $prefix = ''): array * * @returns User[] */ - public function exportUsers(int $batchSize, callable $callback): array + public function exportUsers(int $batchSize, callable $callback): void { throw new Exception('Unimplemented, Please check if your source adapter supports this method.'); } diff --git a/src/Transfer/Sources/Firebase.php b/src/Transfer/Sources/Firebase.php index f522d34..a48ce22 100644 --- a/src/Transfer/Sources/Firebase.php +++ b/src/Transfer/Sources/Firebase.php @@ -8,6 +8,7 @@ use Utopia\Transfer\Resources\User; use Utopia\Transfer\Transfer; use Utopia\Transfer\Log; +use Utopia\Transfer\Resource; use Utopia\Transfer\Resources\Hash; class Firebase extends Source @@ -123,9 +124,9 @@ function getProject(): Project|null * @param int $batchSize Max 500 * @param callable $callback Callback function to be called after each batch, $callback(user[] $batch); * - * @returns User[] + * @returns void */ - public function exportUsers(int $batchSize, callable $callback): array + public function exportUsers(int $batchSize, callable $callback): void { if (!$this->project || !$this->project->getId()) { $this->logs[Log::FATAL][] = new Log('Project not set'); @@ -177,16 +178,13 @@ public function exportUsers(int $batchSize, callable $callback): array foreach ($result as $user) { /** @var array $user */ - // Figure out what type of user it is. - $types = $this->calculateUserType($user['providerUserInfo'] ?? []); - $users[] = new User( $user["localId"] ?? '', $user["email"] ?? '', $user["displayName"] ?? $user["email"] ?? '', new Hash($user["passwordHash"] ?? '', $user["salt"] ?? '', Hash::SCRYPT_MODIFIED, $hashConfig["saltSeparator"], $hashConfig["signerKey"]), $user["phoneNumber"] ?? '', - $types, + $this->calculateTypes($user['providerUserInfo'] ?? []), '', $user["emailVerified"], false, // Can't get phone number status on firebase :/ @@ -202,11 +200,9 @@ public function exportUsers(int $batchSize, callable $callback): array break; } } - - return $users; } - function calculateUserType(array $providerData): array { + function calculateTypes(array $providerData): array { if (count($providerData) === 0) { return [User::TYPE_ANONYMOUS]; } @@ -232,6 +228,47 @@ function calculateUserType(array $providerData): array { function check(array $resources = []): bool { + if (!$this->googleClient) { + $this->logs[Log::FATAL][] = new Log('Google Client not initialized'); + return false; + } + + if (!$this->project || !$this->project->getId()) { + $this->logs[Log::FATAL][] = new Log('Project not set'); + return false; + } + + foreach ($resources as $resource) { + switch ($resource) + { + case Transfer::RESOURCE_USERS: + $firebase = new \Google\Service\FirebaseManagement($this->googleClient); + + $request = $firebase->projects->listProjects(); + + if (!$request['results']) { + $this->logs[Log::FATAL][] = new Log('Unable to fetch projects'); + return false; + } + + $found = false; + + foreach ($request['results'] as $project) { + if ($project['projectId'] === $this->project->getId()) { + $found = true; + break; + } + } + + if (!$found) { + $this->logs[Log::FATAL][] = new Log('Project not found'); + return false; + } + + break; + } + } + return true; } } diff --git a/src/Transfer/Sources/Supabase.php b/src/Transfer/Sources/Supabase.php new file mode 100644 index 0000000..6d76493 --- /dev/null +++ b/src/Transfer/Sources/Supabase.php @@ -0,0 +1,125 @@ +pdo = new \PDO("pgsql:host={$this->host};port={$this->port};dbname={$this->databaseName}", $this->username, $this->password); + } + + function getName(): string + { + return 'Supabase'; + } + + function getSupportedResources(): array + { + return [ + Transfer::RESOURCE_USERS + ]; + } + + /** + * Export Users + * + * @param int $batchSize Max 500 + * @param callable $callback Callback function to be called after each batch, $callback(user[] $batch); + * + * @returns User[] + */ + public function exportUsers(int $batchSize, callable $callback): void + { + $total = $this->pdo->query('SELECT COUNT(*) FROM auth.users')->fetchColumn(); + + $offset = 0; + + while ($offset < $total) { + $statement = $this->pdo->prepare('SELECT * FROM auth.users order by created_at LIMIT :limit OFFSET :offset'); + $statement->bindValue(':limit', $batchSize, \PDO::PARAM_INT); + $statement->bindValue(':offset', $offset, \PDO::PARAM_INT); + $statement->execute(); + + $users = $statement->fetchAll(\PDO::FETCH_ASSOC); + + $offset += $batchSize; + + $transferUsers = []; + + foreach ($users as $user) { + $transferUsers[] = new User( + $user['id'], + $user['email'] ?? '', + '', + new Hash($user['encrypted_password'], '', Hash::BCRYPT), + $user['phone'] ?? '', + $this->calculateTypes($user), + '', + !empty($user['email_confirmed_at']), + !empty($user['phone_confirmed_at']), + false, + [] + ); + } + + $callback($transferUsers); + } + } + + private function calculateTypes(array $user): array + { + if (empty($user['encrypted_password']) && empty($user['phone'])) + { + return [User::TYPE_ANONYMOUS]; + } + + $types = []; + + if (!empty($user['encrypted_password'])) + { + $types[] = User::TYPE_EMAIL; + } + + if (!empty($user['phone'])) + { + $types[] = User::TYPE_PHONE; + } + + return $types; + } + + function check(array $resources = []): bool + { + if ($this->pdo->errorCode() !== '00000') { + $this->logs[Log::FATAL] = new Log('Failed to connect to database. Error: ' . $this->pdo->errorInfo()[2]); + } + + foreach ($resources as $resource) + { + switch ($resource) { + case Transfer::RESOURCE_USERS: + $statement = $this->pdo->prepare('SELECT COUNT(*) FROM auth.users'); + $statement->execute(); + + if ($statement->errorCode() !== '00000') { + $this->logs[Log::FATAL] = new Log('Failed to access users table. Error: ' . $statement->errorInfo()[2]); + } + break; + } + } + + return true; + } +} \ No newline at end of file diff --git a/src/Transfer/Transfer.php b/src/Transfer/Transfer.php index 04dfa8f..a904441 100644 --- a/src/Transfer/Transfer.php +++ b/src/Transfer/Transfer.php @@ -67,7 +67,8 @@ function __construct(Source $source, Destination $destination) Log::ERROR => [], Log::WARNING => [], Log::INFO => [], - Log::FATAL => [] + Log::FATAL => [], + Log::SUCCESS => [] ]; /** diff --git a/tests/Transfer/Sources/FirebaseTest.php b/tests/Transfer/Sources/FirebaseTest.php index 18a99ef..e783513 100644 --- a/tests/Transfer/Sources/FirebaseTest.php +++ b/tests/Transfer/Sources/FirebaseTest.php @@ -33,7 +33,7 @@ class FirebaseTest extends TestCase public function setUp(): void { - $this->serviceAccount = json_decode(App::getEnv("FIREBASE_TEST_ACCOUNT"), true); + $this->serviceAccount = json_decode(getEnv("FIREBASE_TEST_ACCOUNT"), true); $this->firebase = new Firebase( $this->serviceAccount, diff --git a/tests/Transfer/Sources/SupabaseTest.php b/tests/Transfer/Sources/SupabaseTest.php new file mode 100644 index 0000000..a9e2708 --- /dev/null +++ b/tests/Transfer/Sources/SupabaseTest.php @@ -0,0 +1,92 @@ + + * @version 1.0 RC1 + * @license The MIT License (MIT) + */ + +namespace Utopia\Tests; + +use PHPUnit\Framework\TestCase; +use Utopia\App; +use Utopia\Transfer\Sources\Supabase; +use Utopia\Transfer\Resources\Project; +use Utopia\Transfer\Resources\User; + +class SupabaseTest extends TestCase +{ + /** + * @var Supabase + */ + public $supabase; + + public function setUp(): void + { + $this->supabase = new Supabase( + getEnv("SUPABASE_TEST_HOST") ?? '', + getEnv("SUPABASE_TEST_DATABASE") ?? '', + getEnv("SUPABASE_TEST_USERNAME") ?? '', + getEnv("SUPABASE_TEST_PASSWORD") ?? '', + ); + } + + public function testGetUsers(): array + { + $result = []; + + $this->supabase->exportUsers(500, function (array $users) use (&$result) { + $result = array_merge($result, $users); + }); + + foreach ($result as $user) { + /** @var User $user */ + $this->assertIsObject($user); + $this->assertNotEmpty($user->getPasswordHash()); + $this->assertNotEmpty($user->getPasswordHash()->getHash()); + } + + $this->assertIsArray($result); + $this->assertNotEmpty($result); + + return $result; + } + + /** + * @depends testGetUsers + */ + public function testVerifyUsers(array $users): void + { + $assertedUsers = 0; + + foreach ($users as $user) { + /** @var User $user */ + if (in_array(User::TYPE_ANONYMOUS, $user->getTypes())) { + continue; + } + + try { + $userFound = $this->supabase->pdo->query('SELECT * FROM auth.users WHERE id = \'' . $user->getId() . '\'')->fetch(); + $assertedUsers++; + + $this->assertNotEmpty($userFound); + $this->assertEquals($user->getId(), $userFound['id']); + $this->assertEquals($user->getEmail(), $userFound['email']); + $this->assertEquals($user->getPhone(), $userFound['phone']); + $this->assertEquals($user->getPasswordHash()->getHash(), $userFound['encrypted_password']); + $this->assertEquals($user->getEmailVerified(), !empty($userFound['email_confirmed_at'])); + $this->assertEquals($user->getPhoneVerified(), !empty($userFound['phone_confirmed_at'])); + } catch (\Exception $e) { + throw $e; + } + } + + $this->assertGreaterThan(1, $assertedUsers); + } +} \ No newline at end of file diff --git a/tests/Transfer/Transfers/SupabaseToAppwriteTest.php b/tests/Transfer/Transfers/SupabaseToAppwriteTest.php new file mode 100644 index 0000000..eb9d78a --- /dev/null +++ b/tests/Transfer/Transfers/SupabaseToAppwriteTest.php @@ -0,0 +1,131 @@ + + * @version 1.0 RC1 + * @license The MIT License (MIT) + */ + +namespace Utopia\Tests; + +use PHPUnit\Framework\TestCase; +use Utopia\Transfer\Destinations\Appwrite; +use Utopia\Transfer\Log; +use Utopia\Transfer\Sources\Supabase; +use Utopia\Transfer\Transfer; +use Utopia\Transfer\Resources\User; +use Appwrite\Client as AppwriteClient; +use Appwrite\Services\Users; + +class SupabaseToAppwriteTest extends TestCase +{ + /** + * @var Supabase + */ + public $supabase; + + /** + * @var Appwrite + */ + public $appwrite; + + /** + * @var AppwriteClient + */ + public $appwriteClient; + + /** + * @var Transfer + */ + public $transfer; + + function setUp(): void + { + $this->supabase = new Supabase( + getEnv("SUPABASE_TEST_HOST"), + getEnv("SUPABASE_TEST_DATABASE"), + getEnv("SUPABASE_TEST_USERNAME"), + getEnv("SUPABASE_TEST_PASSWORD"), + ); + + $this->appwrite = new Appwrite( + getenv("APPWRITE_TEST_PROJECT"), + getenv("APPWRITE_TEST_ENDPOINT"), + getenv("APPWRITE_TEST_KEY") + ); + + $this->appwriteClient = new AppwriteClient(); + $this->appwriteClient + ->setEndpoint(getenv("APPWRITE_TEST_ENDPOINT")) + ->setProject(getenv("APPWRITE_TEST_PROJECT")) + ->setKey(getenv("APPWRITE_TEST_KEY")); + + $this->transfer = new Transfer($this->supabase, $this->appwrite); + } + + public function testTransferUsers(): void + { + $this->transfer->run([Transfer::RESOURCE_USERS], function () { + }); + + // Check for Fatal Errors in Transfer Log + $this->assertEmpty($this->transfer->getLogs(Log::FATAL)); + } + + + /** + * @depends testTransferUsers + */ + public function testVerifyUsers(): void + { + $userClient = new Users($this->appwriteClient); + + $assertedUsers = false; + + $this->supabase->exportUsers(500, function (array $users) use ($userClient, &$assertedUsers) { + foreach ($users as $user) { + /** @var User $user */ + if (in_array(User::TYPE_ANONYMOUS, $user->getTypes())) { + continue; + } + + try { + $userFound = $userClient->get($user->getId()); + } catch (\Exception $e) { + throw $e; + } + $this->assertNotEmpty($userFound); + + $this->assertEquals($user->getId(), $userFound['$id']); + $this->assertEquals($user->getEmail(), $userFound['email']); + $this->assertEquals($user->getPhone(), $userFound['phone']); + $this->assertEquals($user->getPasswordHash()->getHash(), $userFound['password']); + $this->assertEquals($user->getEmailVerified(), $userFound['emailVerification']); + $this->assertEquals($user->getPhoneVerified(), $userFound['phoneVerification']); + $assertedUsers = true; + } + }); + + $this->assertTrue($assertedUsers); + } + + public function testCleanupUsers(): void + { + $userClient = new Users($this->appwriteClient); + $appwriteUsers = $userClient->list(); + + $deletedUsers = 0; + + foreach ($appwriteUsers["users"] as $user) { + $userClient->delete($user['$id']); + $this->assertTrue(true); + $deletedUsers++; + } + } +} From 649bffa1b57fe5ebbcb18c690424fffc7208dabb Mon Sep 17 00:00:00 2001 From: Bradley Schofield Date: Thu, 19 Jan 2023 11:00:20 +0000 Subject: [PATCH 04/70] Add NHost and Tests --- src/Transfer/Sources/NHost.php | 125 +++++++++++++++++++++++++++ tests/Transfer/Sources/NHostTest.php | 92 ++++++++++++++++++++ 2 files changed, 217 insertions(+) create mode 100644 src/Transfer/Sources/NHost.php create mode 100644 tests/Transfer/Sources/NHostTest.php diff --git a/src/Transfer/Sources/NHost.php b/src/Transfer/Sources/NHost.php new file mode 100644 index 0000000..09a3d74 --- /dev/null +++ b/src/Transfer/Sources/NHost.php @@ -0,0 +1,125 @@ +pdo = new \PDO("pgsql:host={$this->host};port={$this->port};dbname={$this->databaseName}", $this->username, $this->password); + } + + function getName(): string + { + return 'NHost'; + } + + function getSupportedResources(): array + { + return [ + Transfer::RESOURCE_USERS + ]; + } + + /** + * Export Users + * + * @param int $batchSize Max 500 + * @param callable $callback Callback function to be called after each batch, $callback(user[] $batch); + * + * @returns User[] + */ + public function exportUsers(int $batchSize, callable $callback): void + { + $total = $this->pdo->query('SELECT COUNT(*) FROM auth.users')->fetchColumn(); + + $offset = 0; + + while ($offset < $total) { + $statement = $this->pdo->prepare('SELECT * FROM auth.users order by created_at LIMIT :limit OFFSET :offset'); + $statement->bindValue(':limit', $batchSize, \PDO::PARAM_INT); + $statement->bindValue(':offset', $offset, \PDO::PARAM_INT); + $statement->execute(); + + $users = $statement->fetchAll(\PDO::FETCH_ASSOC); + + $offset += $batchSize; + + $transferUsers = []; + + foreach ($users as $user) { + $transferUsers[] = new User( + $user['id'], + $user['email'] ?? '', + $user['display_name'] ?? '', + new Hash($user['password_hash'], '', Hash::BCRYPT), + $user['phone_number'] ?? '', + $this->calculateTypes($user), + '', + $user['email_verified'], + $user['phone_number_verified'], + $user['disabled'], + [] + ); + } + + $callback($transferUsers); + } + } + + private function calculateTypes(array $user): array + { + if (empty($user['password_hash']) && empty($user['phone_number'])) + { + return [User::TYPE_ANONYMOUS]; + } + + $types = []; + + if (!empty($user['password_hash'])) + { + $types[] = User::TYPE_EMAIL; + } + + if (!empty($user['phone_number'])) + { + $types[] = User::TYPE_PHONE; + } + + return $types; + } + + function check(array $resources = []): bool + { + if ($this->pdo->errorCode() !== '00000') { + $this->logs[Log::FATAL] = new Log('Failed to connect to database. Error: ' . $this->pdo->errorInfo()[2]); + } + + foreach ($resources as $resource) + { + switch ($resource) { + case Transfer::RESOURCE_USERS: + $statement = $this->pdo->prepare('SELECT COUNT(*) FROM auth.users'); + $statement->execute(); + + if ($statement->errorCode() !== '00000') { + $this->logs[Log::FATAL] = new Log('Failed to access users table. Error: ' . $statement->errorInfo()[2]); + } + break; + } + } + + return true; + } +} \ No newline at end of file diff --git a/tests/Transfer/Sources/NHostTest.php b/tests/Transfer/Sources/NHostTest.php new file mode 100644 index 0000000..feca9b1 --- /dev/null +++ b/tests/Transfer/Sources/NHostTest.php @@ -0,0 +1,92 @@ + + * @version 1.0 RC1 + * @license The MIT License (MIT) + */ + +namespace Utopia\Tests; + +use PHPUnit\Framework\TestCase; +use Utopia\App; +use Utopia\Transfer\Sources\NHost; +use Utopia\Transfer\Resources\Project; +use Utopia\Transfer\Resources\User; + +class NHostTest extends TestCase +{ + /** + * @var NHost + */ + public $nhost; + + public function setUp(): void + { + $this->nhost = new NHost( + getEnv("NHOST_TEST_HOST") ?? '', + getEnv("NHOST_TEST_DATABASE") ?? '', + getEnv("NHOST_TEST_USERNAME") ?? '', + getEnv("NHOST_TEST_PASSWORD") ?? '', + ); + } + + public function testGetUsers(): array + { + $result = []; + + $this->nhost->exportUsers(500, function (array $users) use (&$result) { + $result = array_merge($result, $users); + }); + + foreach ($result as $user) { + /** @var User $user */ + $this->assertIsObject($user); + $this->assertNotEmpty($user->getPasswordHash()); + $this->assertNotEmpty($user->getPasswordHash()->getHash()); + } + + $this->assertIsArray($result); + $this->assertNotEmpty($result); + + return $result; + } + + /** + * @depends testGetUsers + */ + public function testVerifyUsers(array $users): void + { + $assertedUsers = 0; + + foreach ($users as $user) { + /** @var User $user */ + if (in_array(User::TYPE_ANONYMOUS, $user->getTypes())) { + continue; + } + + try { + $userFound = $this->nhost->pdo->query('SELECT * FROM auth.users WHERE id = \'' . $user->getId() . '\'')->fetch(); + $assertedUsers++; + + $this->assertNotEmpty($userFound); + $this->assertEquals($user->getId(), $userFound['id']); + $this->assertEquals($user->getEmail(), $userFound['email']); + $this->assertEquals($user->getPhone(), $userFound['phone_number']); + $this->assertEquals($user->getPasswordHash()->getHash(), $userFound['password_hash']); + $this->assertEquals($user->getEmailVerified(), $userFound['email_verified']); + $this->assertEquals($user->getPhoneVerified(), $userFound['phone_number_verified']); + } catch (\Exception $e) { + throw $e; + } + } + + $this->assertGreaterThan(1, $assertedUsers); + } +} \ No newline at end of file From 07702882f002a31d6d8dc053efdee5be4f073c1d Mon Sep 17 00:00:00 2001 From: Bradley Schofield Date: Tue, 24 Jan 2023 21:37:04 +0000 Subject: [PATCH 05/70] Continue code cleanup --- src/Transfer/Destination.php | 81 ++++++++++++++++---- src/Transfer/Destinations/Appwrite.php | 22 +++++- src/Transfer/Resources/Progress.php | 15 ++++ src/Transfer/Source.php | 40 ++++++++-- src/Transfer/Sources/NHost.php | 4 +- src/Transfer/Sources/Supabase.php | 4 +- src/Transfer/Transfer.php | 54 ++++++++++++- tests/Transfer/Destinations/AppwriteTest.php | 1 - 8 files changed, 189 insertions(+), 32 deletions(-) create mode 100644 src/Transfer/Resources/Progress.php diff --git a/src/Transfer/Destination.php b/src/Transfer/Destination.php index a58ee26..1340d39 100644 --- a/src/Transfer/Destination.php +++ b/src/Transfer/Destination.php @@ -30,21 +30,23 @@ abstract class Destination protected $resourceCache = []; /** - * Internal Adapter State + * @var string + */ + protected string $endpoint = ''; + + /** + * Counters * - * @var array $state + * @var array $counter */ - protected $state = []; + protected $counters = []; /** - * Constructor, mainly handles state initialization. + * Source * - * Automatically detects if we are running within Swoole and uses a Swoole table instead of a PHP array. + * @var Source $source */ - public function __construct(protected string $endpoint, protected string $projectID, protected string $key) - { - $this->state = []; - } + protected Source $source; /** * Gets the name of the adapter. @@ -60,6 +62,27 @@ abstract public function getName(): string; */ abstract public function getSupportedResources(): array; + /** + * Get Source + * + * @return Source + */ + public function getSource(): Source { + return $this->source; + } + + /** + * Set Soruce + * + * @param Source $source + * + * @returns self + */ + public function setSource(Source $source): self { + $this->source = $source; + return $this; + } + /** * Register Logs Array * @@ -70,12 +93,38 @@ public function registerLogs(array &$logs): void { } /** - * Register Resource Cache + * Get Resource Counters + * + * @param string $resource + * + * @returns array + */ + public function &getCounter(string $resource): array { + if ($this->counters[$resource]) { + return $this->counters[$resource]; + } else { + $this->counters[$resource] = [ + 'total' => 0, + 'current' => 0, + 'failed' => 0, + 'skipped' => 0, + ]; + + return $this->counters[$resource]; + } + } + + /** + * Register Transfer Hooks * * @param array &$cache + * @param array &$counters + * + * @return void */ - public function registerResourceCache(array &$cache): void { + public function registerTransferHooks(array &$cache, array &$counters): void { $this->resourceCache = &$cache; + $this->counters = &$counters; } /** @@ -85,20 +134,21 @@ public function registerResourceCache(array &$cache): void { * @param callable $callback */ public function run(array $resources, callable $callback, Source $source): void { + $this->source = $source; + foreach ($resources as $resource) { if (!in_array($resource, $this->getSupportedResources())) { $this->logs[Log::FATAL] = new Log("Cannot Transfer unsupported resource: '".$resource."'"); throw new \Exception("Cannot Transfer unsupported resource: '".$resource."'"); } - $source->run($resources, function (Log $currentLog, string $resourceType, array $resource) use ($callback) { + $source->run($resources, function (string $resourceType, array $resource) use ($callback) { switch ($resourceType) { case Transfer::RESOURCE_USERS: { - $this->importUsers($resource); + $this->importUsers($resource, $callback); break; } } - $callback($currentLog, $resourceType, $resource); }); } } @@ -234,8 +284,9 @@ protected function flatten(array $data, string $prefix = ''): array * Import Users * * @param array $users + * @param callable $callback (Progress $progress) */ - protected function importUsers(array $users): void { + protected function importUsers(array $users, callable $callback): void { throw new \Exception("Not Implemented"); } } diff --git a/src/Transfer/Destinations/Appwrite.php b/src/Transfer/Destinations/Appwrite.php index 571b2e5..94b7acd 100644 --- a/src/Transfer/Destinations/Appwrite.php +++ b/src/Transfer/Destinations/Appwrite.php @@ -7,13 +7,14 @@ use Utopia\Transfer\Destination; use Utopia\Transfer\Resources\Hash; use Utopia\Transfer\Log; +use Utopia\Transfer\Progress; use Utopia\Transfer\Resources\User; use Utopia\Transfer\Transfer; class Appwrite extends Destination { protected Client $client; - public function __construct(protected string $projectID, protected string $endpoint, private string $apiKey) + public function __construct(protected string $projectID, string $endpoint, private string $apiKey) { $this->client = new Client(); $this->client->setEndpoint($endpoint); @@ -132,7 +133,7 @@ public function importPasswordUser(User $user): array|null return $result; } - public function importUsers(array $users): void + public function importUsers(array $users, callable $callback): void { $auth = new Users($this->client); @@ -143,6 +144,8 @@ public function importUsers(array $users): void if (!$createdUser) { $this->logs[Log::ERROR][] = new Log('Failed to import user', \time(), $user); + $userCounters = &$this->getCounter(Transfer::RESOURCE_USERS); + $userCounters['failed']++; } else { // Add more data to the user if ($user->getUsername()) { @@ -166,9 +169,24 @@ public function importUsers(array $users): void } $this->logs[Log::SUCCESS][] = new Log('User imported successfully', \time(), $user); + $userCounters = &$this->getCounter(Transfer::RESOURCE_USERS); + $userCounters['current']++; + + $callback( + new Progress( + Transfer::RESOURCE_USERS, + time(), + $userCounters['total'], + $userCounters['current'], + $userCounters['failed'], + $userCounters['skipped'] + ) + ); } } catch (\Exception $e) { $this->logs[Log::ERROR][] = new Log($e->getMessage(), \time(), $user); + $counter = &$this->getCounter(Transfer::RESOURCE_USERS); + $counter['failed']++; } } } diff --git a/src/Transfer/Resources/Progress.php b/src/Transfer/Resources/Progress.php new file mode 100644 index 0000000..a02c097 --- /dev/null +++ b/src/Transfer/Resources/Progress.php @@ -0,0 +1,15 @@ +counters[$resource]) { + return $this->counters[$resource]; + } else { + $this->counters[$resource] = [ + 'total' => 0, + 'current' => 0, + 'failed' => 0, + 'skipped' => 0, + ]; + + return $this->counters[$resource]; + } + } + /** * Gets the name of the adapter. * @@ -67,12 +89,16 @@ public function registerLogs(array &$logs): void { } /** - * Register Resource Cache + * Register Transfer Hooks * * @param array &$cache + * @param array &$counters + * + * @return void */ - public function registerResourceCache(array &$cache): void { + public function registerTransferHooks(array &$cache, array &$counters): void { $this->resourceCache = &$cache; + $this->counters = &$counters; } /** @@ -89,9 +115,9 @@ public function run(array $resources, callable $callback): void { switch ($resource) { case Transfer::RESOURCE_USERS: { - $this->exportUsers(500, function (array $users) use ($callback) { + $this->exportUsers(100, function (array $users) use ($callback) { $this->resourceCache = array_merge($this->resourceCache, $users); - $callback(new Log('Exporting Users...'), Transfer::RESOURCE_USERS, $users); + $callback(Transfer::RESOURCE_USERS, $users); }); break; } diff --git a/src/Transfer/Sources/NHost.php b/src/Transfer/Sources/NHost.php index 09a3d74..40f98a1 100644 --- a/src/Transfer/Sources/NHost.php +++ b/src/Transfer/Sources/NHost.php @@ -65,7 +65,7 @@ public function exportUsers(int $batchSize, callable $callback): void $user['display_name'] ?? '', new Hash($user['password_hash'], '', Hash::BCRYPT), $user['phone_number'] ?? '', - $this->calculateTypes($user), + $this->calculateUserTypes($user), '', $user['email_verified'], $user['phone_number_verified'], @@ -78,7 +78,7 @@ public function exportUsers(int $batchSize, callable $callback): void } } - private function calculateTypes(array $user): array + private function calculateUserTypes(array $user): array { if (empty($user['password_hash']) && empty($user['phone_number'])) { diff --git a/src/Transfer/Sources/Supabase.php b/src/Transfer/Sources/Supabase.php index 6d76493..710f865 100644 --- a/src/Transfer/Sources/Supabase.php +++ b/src/Transfer/Sources/Supabase.php @@ -65,7 +65,7 @@ public function exportUsers(int $batchSize, callable $callback): void '', new Hash($user['encrypted_password'], '', Hash::BCRYPT), $user['phone'] ?? '', - $this->calculateTypes($user), + $this->calculateAuthTypes($user), '', !empty($user['email_confirmed_at']), !empty($user['phone_confirmed_at']), @@ -78,7 +78,7 @@ public function exportUsers(int $batchSize, callable $callback): void } } - private function calculateTypes(array $user): array + private function calculateAuthTypes(array $user): array { if (empty($user['encrypted_password']) && empty($user['phone'])) { diff --git a/src/Transfer/Transfer.php b/src/Transfer/Transfer.php index a904441..278a8f0 100644 --- a/src/Transfer/Transfer.php +++ b/src/Transfer/Transfer.php @@ -25,9 +25,9 @@ function __construct(Source $source, Destination $destination) $this->destination = $destination; $this->source->registerLogs($this->logs); - $this->source->registerResourceCache($this->resources); + $this->source->registerTransferHooks($this->resources, $this->counters); $this->destination->registerLogs($this->logs); - $this->destination->registerResourceCache($this->resources); + $this->destination->registerTransferHooks($this->resources, $this->counters); return $this; } @@ -42,6 +42,44 @@ function __construct(Source $source, Destination $destination) */ protected Destination $destination; + /** + * Counters + * + * @var array $counter + */ + protected $counters = [ + Transfer::RESOURCE_USERS => [ + 'total' => 0, + 'current' => 0, + 'failed' => 0, + 'skipped' => 0, + ], + Transfer::RESOURCE_FILES => [ + 'total' => 0, + 'current' => 0, + 'failed' => 0, + 'skipped' => 0, + ], + Transfer::RESOURCE_FUNCTIONS => [ + 'total' => 0, + 'current' => 0, + 'failed' => 0, + 'skipped' => 0, + ], + Transfer::RESOURCE_DATABASES => [ + 'total' => 0, + 'current' => 0, + 'failed' => 0, + 'skipped' => 0, + ], + Transfer::RESOURCE_COLLECTIONS => [ + 'total' => 0, + 'current' => 0, + 'failed' => 0, + 'skipped' => 0, + ] + ]; + /** * A local cache of resources that were transferred. * @@ -85,7 +123,7 @@ function __construct(Source $source, Destination $destination) * Transfer Resources between adapters * * @param array $resources - * @param callable $callback + * @param callable $callback (Progress $progress) */ public function run(array $resources, callable $callback): void { @@ -117,4 +155,14 @@ public function getLogs($level = ''): array return $mergedLogs; } + + /** + * Get Resource Cache + * + * @returns array + */ + public function getResourceCache(): array + { + return $this->resources; + } } diff --git a/tests/Transfer/Destinations/AppwriteTest.php b/tests/Transfer/Destinations/AppwriteTest.php index 69dcbd5..66085c6 100644 --- a/tests/Transfer/Destinations/AppwriteTest.php +++ b/tests/Transfer/Destinations/AppwriteTest.php @@ -79,7 +79,6 @@ public function testImportUserPassword(): void $this->assertEquals($user->getEmail(), $response['email']); $this->assertEquals($user->getPasswordHash()->getHash(), $response['password']); - // Cleanup $users->delete($user->getId()); } From d57cd4f36c872b267e3f9580036e662449c42105 Mon Sep 17 00:00:00 2001 From: Bradley Schofield Date: Wed, 25 Jan 2023 10:37:03 +0000 Subject: [PATCH 06/70] Add Appwrite Source Adapter --- .env.example | 10 +- src/Transfer/Sources/Appwrite.php | 156 ++++++++++++++++++ src/Transfer/Sources/Firebase.php | 3 - tests/Transfer/Destinations/AppwriteTest.php | 8 +- tests/Transfer/Sources/AppwriteTest.php | 63 +++++++ .../Transfers/FirebaseToAppwriteTest.php | 8 +- .../Transfers/SupabaseToAppwriteTest.php | 8 +- 7 files changed, 238 insertions(+), 18 deletions(-) create mode 100644 src/Transfer/Sources/Appwrite.php create mode 100644 tests/Transfer/Sources/AppwriteTest.php diff --git a/.env.example b/.env.example index b43bdec..bd9358d 100644 --- a/.env.example +++ b/.env.example @@ -1,6 +1,10 @@ -APPWRITE_TEST_PROJECT=testProject -APPWRITE_TEST_ENDPONT=http://localhost/v1 -APPWRITE_TEST_KEY=xxxxxxxxxxxxxxxxxx +DESTINATION_APPWRITE_TEST_PROJECT=testProject +DESTINATION_APPWRITE_TEST_ENDPONT=http://localhost/v1 +DESTINATION_APPWRITE_TEST_KEY=xxxxxxxxxxxxxxxxxx + +SOURCE_APPWRITE_TEST_PROJECT=testProject +SOURCE_APPWRITE_TEST_ENDPONT=http://localhost/v1 +SOURCE_APPWRITE_TEST_KEY=xxxxxxxxxxxxxxxxxx FIREBASE_TEST_PROJECT=testProject FIREBASE_TEST_ACCOUNT='{type: "service_account", ...}' \ No newline at end of file diff --git a/src/Transfer/Sources/Appwrite.php b/src/Transfer/Sources/Appwrite.php new file mode 100644 index 0000000..978f434 --- /dev/null +++ b/src/Transfer/Sources/Appwrite.php @@ -0,0 +1,156 @@ +appwriteClient = new Client(); + $this->appwriteClient + ->setEndpoint($endpoint) + ->setProject($project) + ->setKey($key); + } + + /** + * Get Name + * + * @returns string + */ + public function getName(): string + { + return 'Appwrite'; + } + + /** + * Get Supported Resources + * + * @returns array + */ + public function getSupportedResources(): array + { + return [ + Transfer::RESOURCE_USERS, + ]; + } + + /** + * Check + * + * @param array $resources + * + * @returns bool + */ + public function check(array $resources = []): bool + { + return true; + } + + /** + * Export Users + * + * @param int $batchSize Max 500 + * @param callable $callback Callback function to be called after each batch, $callback(user[] $batch); + * + * @returns void + */ + public function exportUsers(int $batchSize, callable $callback): void + { + $usersClient = new \Appwrite\Services\Users($this->appwriteClient); + + $lastDocument = null; + + while (true) { + $users = []; + + $queries = [ + Query::limit($batchSize) + ]; + + if ($lastDocument) { + $queries[] = Query::cursorAfter($lastDocument); + } + + + $response = $usersClient->list($queries); + + foreach ($response['users'] as $user) { + $users[] = new User( + $user['$id'], + $user['email'], + $user['name'], + new Hash($user['password'], $user['hash']), + $user['phone'], + $this->calculateTypes($user), + '', + $user['emailVerification'], + $user['phoneVerification'], + !$user['status'], + $user['prefs'] + ); + + $lastDocument = $user['$id']; + } + + $callback($users); + + if (count($users) < $batchSize) { + break; + } + } + } + + /** + * Calculate Types + * + * @param array $user + * + * @returns array + */ + protected function calculateTypes(array $user): array + { + if (empty($user['email']) && empty($user['phone'])) + { + return [User::TYPE_ANONYMOUS]; + } + + $types = []; + + if (!empty($user['email'])) + { + $types[] = User::TYPE_EMAIL; + } + + if (!empty($user['phone'])) + { + $types[] = User::TYPE_PHONE; + } + + return $types; + } +} diff --git a/src/Transfer/Sources/Firebase.php b/src/Transfer/Sources/Firebase.php index a48ce22..dc0ff9e 100644 --- a/src/Transfer/Sources/Firebase.php +++ b/src/Transfer/Sources/Firebase.php @@ -148,7 +148,6 @@ public function exportUsers(int $batchSize, callable $callback): void throw new \Exception('Unable to fetch hash config'); } - $count = 0; $nextPageToken = null; while (true) { @@ -190,8 +189,6 @@ public function exportUsers(int $batchSize, callable $callback): void false, // Can't get phone number status on firebase :/ $user["disabled"] ); - - $count++; } $callback($users); diff --git a/tests/Transfer/Destinations/AppwriteTest.php b/tests/Transfer/Destinations/AppwriteTest.php index 66085c6..7f31ff9 100644 --- a/tests/Transfer/Destinations/AppwriteTest.php +++ b/tests/Transfer/Destinations/AppwriteTest.php @@ -36,16 +36,16 @@ class AppwriteTest extends TestCase public function setUp(): void { $this->appwrite = new Appwrite( - getenv("APPWRITE_TEST_PROJECT"), + getenv("DESTINATION_APPWRITE_TEST_PROJECT"), getenv("APPWRITE_TEST_ENDPOINT"), - getenv("APPWRITE_TEST_KEY") + getenv("DESTINATION_APPWRITE_TEST_KEY") ); $this->client = new Client(); $this->client ->setEndpoint(getenv("APPWRITE_TEST_ENDPOINT")) - ->setProject(getenv("APPWRITE_TEST_PROJECT")) - ->setKey(getenv("APPWRITE_TEST_KEY")); + ->setProject(getenv("DESTINATION_APPWRITE_TEST_PROJECT")) + ->setKey(getenv("DESTINATION_APPWRITE_TEST_KEY")); } public function testGetSupportedResources(): void diff --git a/tests/Transfer/Sources/AppwriteTest.php b/tests/Transfer/Sources/AppwriteTest.php new file mode 100644 index 0000000..81174fd --- /dev/null +++ b/tests/Transfer/Sources/AppwriteTest.php @@ -0,0 +1,63 @@ + + * @version 1.0 RC1 + * @license The MIT License (MIT) + */ + +namespace Utopia\Tests; + +use PHPUnit\Framework\TestCase; +use Utopia\App; +use Utopia\Transfer\Sources\Appwrite; +use Utopia\Transfer\Resources\Project; + +class AppwriteTest extends TestCase +{ + /** + * @var Appwrite + */ + public $appwrite; + + /** + * @var array + */ + public $serviceAccount; + + public function setUp(): void + { + $this->appwrite = new Appwrite( + getenv('SOURCE_APPWRITE_TEST_ENDPOINT'), + getenv('SOURCE_APPWRITE_TEST_PROJECT'), + getenv('SOURCE_APPWRITE_TEST_KEY') + ); + } + + + public function testGetUsers(): void + { + $result = []; + + $this->appwrite->exportUsers(100, function (array $users) use (&$result) { + $result = array_merge($result, $users); + }); + + + foreach ($result as $user) { + /** @var User $user */ + $this->assertIsObject($user); + $this->assertNotEmpty($user->getPasswordHash()); + $this->assertNotEmpty($user->getPasswordHash()->getHash()); + } + + $this->assertIsArray($result); + $this->assertNotEmpty($result); + } +} \ No newline at end of file diff --git a/tests/Transfer/Transfers/FirebaseToAppwriteTest.php b/tests/Transfer/Transfers/FirebaseToAppwriteTest.php index 5240cee..8a2eec4 100644 --- a/tests/Transfer/Transfers/FirebaseToAppwriteTest.php +++ b/tests/Transfer/Transfers/FirebaseToAppwriteTest.php @@ -57,16 +57,16 @@ public function setUp(): void $this->firebase->setProject($serviceAccount['project_id']); $this->appwrite = new Appwrite( - getenv("APPWRITE_TEST_PROJECT"), + getenv("DESTINATION_APPWRITE_TEST_PROJECT"), getenv("APPWRITE_TEST_ENDPOINT"), - getenv("APPWRITE_TEST_KEY") + getenv("DESTINATION_APPWRITE_TEST_KEY") ); $this->appwriteClient = new AppwriteClient(); $this->appwriteClient ->setEndpoint(getenv("APPWRITE_TEST_ENDPOINT")) - ->setProject(getenv("APPWRITE_TEST_PROJECT")) - ->setKey(getenv("APPWRITE_TEST_KEY")); + ->setProject(getenv("DESTINATION_APPWRITE_TEST_PROJECT")) + ->setKey(getenv("DESTINATION_APPWRITE_TEST_KEY")); $this->transfer = new Transfer($this->firebase, $this->appwrite); } diff --git a/tests/Transfer/Transfers/SupabaseToAppwriteTest.php b/tests/Transfer/Transfers/SupabaseToAppwriteTest.php index eb9d78a..f80930c 100644 --- a/tests/Transfer/Transfers/SupabaseToAppwriteTest.php +++ b/tests/Transfer/Transfers/SupabaseToAppwriteTest.php @@ -55,16 +55,16 @@ function setUp(): void ); $this->appwrite = new Appwrite( - getenv("APPWRITE_TEST_PROJECT"), + getenv("DESTINATION_APPWRITE_TEST_PROJECT"), getenv("APPWRITE_TEST_ENDPOINT"), - getenv("APPWRITE_TEST_KEY") + getenv("DESTINATION_APPWRITE_TEST_KEY") ); $this->appwriteClient = new AppwriteClient(); $this->appwriteClient ->setEndpoint(getenv("APPWRITE_TEST_ENDPOINT")) - ->setProject(getenv("APPWRITE_TEST_PROJECT")) - ->setKey(getenv("APPWRITE_TEST_KEY")); + ->setProject(getenv("DESTINATION_APPWRITE_TEST_PROJECT")) + ->setKey(getenv("DESTINATION_APPWRITE_TEST_KEY")); $this->transfer = new Transfer($this->supabase, $this->appwrite); } From cb533f59e22f87f875364762032119e2ddf546dd Mon Sep 17 00:00:00 2001 From: Bradley Schofield Date: Mon, 13 Feb 2023 15:53:47 +0000 Subject: [PATCH 07/70] Implement Database Support --- .env.example | 4 +- composer.json | 3 +- src/Transfer/Destination.php | 18 +- src/Transfer/Destinations/Appwrite.php | 205 ++++++++++++++++-- src/Transfer/Destinations/Local.php | 132 +++++++++++ src/Transfer/Log.php | 12 +- src/Transfer/{Resources => }/Progress.php | 0 src/Transfer/Resource.php | 2 +- src/Transfer/Resources/Attribute.php | 76 +++++++ .../Resources/Attributes/BoolAttribute.php | 39 ++++ .../Attributes/DateTimeAttribute.php | 32 +++ .../Resources/Attributes/EmailAttribute.php | 32 +++ .../Resources/Attributes/EnumAttribute.php | 51 +++++ .../Resources/Attributes/FloatAttribute.php | 66 ++++++ .../Resources/Attributes/IPAttribute.php | 32 +++ .../Resources/Attributes/IntAttribute.php | 66 ++++++ .../Resources/Attributes/StringAttribute.php | 51 +++++ .../Resources/Attributes/URLAttribute.php | 32 +++ src/Transfer/Resources/Collection.php | 94 ++++++++ src/Transfer/Resources/Database.php | 74 +++++++ src/Transfer/Resources/Hash.php | 38 ++-- src/Transfer/Resources/Index.php | 82 +++++++ src/Transfer/Resources/User.php | 48 ++-- src/Transfer/Source.php | 24 +- src/Transfer/Sources/Appwrite.php | 136 +++++++++++- src/Transfer/Sources/Firebase.php | 193 ++++++++++++++++- src/Transfer/Sources/NHost.php | 168 +++++++++++++- src/Transfer/Sources/Supabase.php | 145 ++++++++++++- src/Transfer/Transfer.php | 8 +- tests/Transfer/Sources/AppwriteTest.php | 10 +- tests/tmp/databaseDMP.json | 102 +++++++++ tests/tmp/playground.php | 79 +++++++ 32 files changed, 1944 insertions(+), 110 deletions(-) create mode 100644 src/Transfer/Destinations/Local.php rename src/Transfer/{Resources => }/Progress.php (100%) create mode 100644 src/Transfer/Resources/Attribute.php create mode 100644 src/Transfer/Resources/Attributes/BoolAttribute.php create mode 100644 src/Transfer/Resources/Attributes/DateTimeAttribute.php create mode 100644 src/Transfer/Resources/Attributes/EmailAttribute.php create mode 100644 src/Transfer/Resources/Attributes/EnumAttribute.php create mode 100644 src/Transfer/Resources/Attributes/FloatAttribute.php create mode 100644 src/Transfer/Resources/Attributes/IPAttribute.php create mode 100644 src/Transfer/Resources/Attributes/IntAttribute.php create mode 100644 src/Transfer/Resources/Attributes/StringAttribute.php create mode 100644 src/Transfer/Resources/Attributes/URLAttribute.php create mode 100644 src/Transfer/Resources/Collection.php create mode 100644 src/Transfer/Resources/Database.php create mode 100644 src/Transfer/Resources/Index.php create mode 100644 tests/tmp/databaseDMP.json create mode 100644 tests/tmp/playground.php diff --git a/.env.example b/.env.example index bd9358d..45bc350 100644 --- a/.env.example +++ b/.env.example @@ -1,9 +1,9 @@ DESTINATION_APPWRITE_TEST_PROJECT=testProject -DESTINATION_APPWRITE_TEST_ENDPONT=http://localhost/v1 +DESTINATION_APPWRITE_TEST_ENDPOINT=http://localhost/v1 DESTINATION_APPWRITE_TEST_KEY=xxxxxxxxxxxxxxxxxx SOURCE_APPWRITE_TEST_PROJECT=testProject -SOURCE_APPWRITE_TEST_ENDPONT=http://localhost/v1 +SOURCE_APPWRITE_TEST_ENDPOINT=http://localhost/v1 SOURCE_APPWRITE_TEST_KEY=xxxxxxxxxxxxxxxxxx FIREBASE_TEST_PROJECT=testProject diff --git a/composer.json b/composer.json index ec62258..c95186d 100644 --- a/composer.json +++ b/composer.json @@ -28,6 +28,7 @@ "require-dev": { "phpunit/phpunit": "^9.3", "squizlabs/php_codesniffer": "^3.6", - "vimeo/psalm": "^5.4" + "vimeo/psalm": "^5.4", + "vlucas/phpdotenv": "^5.5" } } diff --git a/src/Transfer/Destination.php b/src/Transfer/Destination.php index 1340d39..6ba802e 100644 --- a/src/Transfer/Destination.php +++ b/src/Transfer/Destination.php @@ -76,7 +76,7 @@ public function getSource(): Source { * * @param Source $source * - * @returns self + * @return self */ public function setSource(Source $source): self { $this->source = $source; @@ -97,7 +97,7 @@ public function registerLogs(array &$logs): void { * * @param string $resource * - * @returns array + * @return array */ public function &getCounter(string $resource): array { if ($this->counters[$resource]) { @@ -148,6 +148,10 @@ public function run(array $resources, callable $callback, Source $source): void $this->importUsers($resource, $callback); break; } + case Transfer::RESOURCE_DATABASES: { + $this->importDatabases($resource, $callback); + break; + } } }); } @@ -289,4 +293,14 @@ protected function flatten(array $data, string $prefix = ''): array protected function importUsers(array $users, callable $callback): void { throw new \Exception("Not Implemented"); } + + /** + * Import Database + * + * @param array $databases + * @param callable $callback (Progress $progress) + */ + protected function importDatabases(array $databases, callable $callback): void { + throw new \Exception("Not Implemented"); + } } diff --git a/src/Transfer/Destinations/Appwrite.php b/src/Transfer/Destinations/Appwrite.php index 94b7acd..f77340b 100644 --- a/src/Transfer/Destinations/Appwrite.php +++ b/src/Transfer/Destinations/Appwrite.php @@ -2,24 +2,39 @@ namespace Utopia\Transfer\Destinations; +use Appwrite\AppwriteException; use Appwrite\Client; use Appwrite\Services\Users; +use Appwrite\Services\Databases as DatabasesService; use Utopia\Transfer\Destination; use Utopia\Transfer\Resources\Hash; use Utopia\Transfer\Log; use Utopia\Transfer\Progress; +use Utopia\Transfer\Resources\Attribute; use Utopia\Transfer\Resources\User; use Utopia\Transfer\Transfer; +use Utopia\Transfer\Resources\Database; +use Utopia\Transfer\Resources\Collection; +use Utopia\Transfer\Resources\Attributes\BoolAttribute; +use Utopia\Transfer\Resources\Attributes\DateTimeAttribute; +use Utopia\Transfer\Resources\Attributes\EmailAttribute; +use Utopia\Transfer\Resources\Attributes\EnumAttribute; +use Utopia\Transfer\Resources\Attributes\FloatAttribute; +use Utopia\Transfer\Resources\Attributes\IntAttribute; +use Utopia\Transfer\Resources\Attributes\IPAttribute; +use Utopia\Transfer\Resources\Attributes\StringAttribute; +use Utopia\Transfer\Resources\Attributes\URLAttribute; +use Utopia\Transfer\Resources\Index; class Appwrite extends Destination { protected Client $client; - public function __construct(protected string $projectID, string $endpoint, private string $apiKey) + public function __construct(protected string $project, string $endpoint, private string $key) { - $this->client = new Client(); - $this->client->setEndpoint($endpoint); - $this->client->setProject($projectID); - $this->client->setKey($apiKey); + $this->client = (new Client()) + ->setEndpoint($endpoint) + ->setProject($project) + ->setKey($key); } /** @@ -40,7 +55,7 @@ public function getSupportedResources(): array { return [ Transfer::RESOURCE_USERS, Transfer::RESOURCE_DATABASES, - Transfer::RESOURCE_COLLECTIONS, + Transfer::RESOURCE_DOCUMENTS, Transfer::RESOURCE_FILES, Transfer::RESOURCE_FUNCTIONS ]; @@ -135,6 +150,7 @@ public function importPasswordUser(User $user): array|null public function importUsers(array $users, callable $callback): void { + $userCounters = &$this->getCounter(Transfer::RESOURCE_USERS); $auth = new Users($this->client); foreach ($users as $user) { @@ -169,19 +185,7 @@ public function importUsers(array $users, callable $callback): void } $this->logs[Log::SUCCESS][] = new Log('User imported successfully', \time(), $user); - $userCounters = &$this->getCounter(Transfer::RESOURCE_USERS); $userCounters['current']++; - - $callback( - new Progress( - Transfer::RESOURCE_USERS, - time(), - $userCounters['total'], - $userCounters['current'], - $userCounters['failed'], - $userCounters['skipped'] - ) - ); } } catch (\Exception $e) { $this->logs[Log::ERROR][] = new Log($e->getMessage(), \time(), $user); @@ -189,5 +193,170 @@ public function importUsers(array $users, callable $callback): void $counter['failed']++; } } + + $callback( + new Progress( + Transfer::RESOURCE_USERS, + time(), + $userCounters['total'], + $userCounters['current'], + $userCounters['failed'], + $userCounters['skipped'] + ) + ); + } + + public function createAttribute(Attribute $attribute, Collection $collection, Database $database): void + { + $databaseService = new DatabasesService($this->client); + + try { + switch ($attribute->getName()) { + case Attribute::TYPE_STRING: + /** @var StringAttribute $attribute */ + $databaseService->createStringAttribute($database->getId(), $collection->getId(), $attribute->getKey(), $attribute->getSize(), $attribute->getRequired(), $attribute->getDefault(), $attribute->getArray()); + break; + case Attribute::TYPE_INTEGER: + /** @var IntAttribute $attribute */ + $databaseService->createIntegerAttribute($database->getId(), $collection->getId(), $attribute->getKey(), $attribute->getRequired(), $attribute->getMin(), $attribute->getMax(), $attribute->getDefault(), $attribute->getArray()); + break; + case Attribute::TYPE_FLOAT: + /** @var FloatAttribute $attribute */ + $databaseService->createFloatAttribute($database->getId(), $collection->getId(), $attribute->getKey(), $attribute->getRequired(), $attribute->getDefault(), $attribute->getArray()); + break; + case Attribute::TYPE_BOOLEAN: + /** @var BoolAttribute $attribute */ + $databaseService->createBooleanAttribute($database->getId(), $collection->getId(), $attribute->getKey(), $attribute->getRequired(), $attribute->getDefault(), $attribute->getArray()); + break; + case Attribute::TYPE_DATETIME: + /** @var DateTimeAttribute $attribute */ + $databaseService->createDateTimeAttribute($database->getId(), $collection->getId(), $attribute->getKey(), $attribute->getRequired(), $attribute->getDefault(), $attribute->getArray()); + break; + case Attribute::TYPE_EMAIL: + /** @var EmailAttribute $attribute */ + $databaseService->createEmailAttribute($database->getId(), $collection->getId(), $attribute->getKey(), $attribute->getRequired(), $attribute->getDefault(), $attribute->getArray()); + break; + case Attribute::TYPE_IP: + /** @var IPAttribute $attribute */ + $databaseService->createIPAttribute($database->getId(), $collection->getId(), $attribute->getKey(), $attribute->getRequired(), $attribute->getDefault(), $attribute->getArray()); + break; + case Attribute::TYPE_URL: + /** @var URLAttribute $attribute */ + $databaseService->createUrlAttribute($database->getId(), $collection->getId(), $attribute->getKey(), $attribute->getRequired(), $attribute->getDefault(), $attribute->getArray()); + break; + case Attribute::TYPE_ENUM: + /** @var EnumAttribute $attribute */ + $databaseService->createEnumAttribute($database->getId(), $collection->getId(), $attribute->getKey(), $attribute->getElements(), $attribute->getRequired(), $attribute->getDefault(), $attribute->getArray()); + break; + } + } catch (\Exception $e) { + $this->logs[Log::ERROR][] = new Log($e->getMessage(), \time(), $attribute); + } + } + + /** + * Validate Attributes Creation + * + * @param Attribute[] $attributes + * @param Collection $collection + * @param Database $database + * + * @return bool + */ + public function validateAttributesCreation(array $attributes, Collection $collection, Database $database): bool + { + $databaseService = new DatabasesService($this->client); + $destinationAttributes = $databaseService->listAttributes($database->getId(), $collection->getId())['attributes']; + + foreach ($attributes as $attribute) { + /** @var Attribute $attribute */ + $foundAttribute = null; + + foreach ($destinationAttributes as $destinationAttribute) { + if ($destinationAttribute['key'] === $attribute->getKey()) { + $foundAttribute = $destinationAttribute; + break; + } + } + + if ($foundAttribute) { + if ($foundAttribute['status'] !== 'available') { + return false; + } else { + continue; + } + } else { + return false; + } + } + + return true; + } + + /** + * Import Databases + * + * @param array $databases + * @param callable $callback + * + * @return void + */ + public function importDatabases(array $databases, callable $callback): void + { + $databaseCounters = &$this->getCounter(Transfer::RESOURCE_DATABASES); + $databaseService = new DatabasesService($this->client); + + foreach ($databases as $database) { + /** @var Database $database */ + try { + $databaseService->create($database->getId(), $database->getDBName()); + + foreach ($database->getCollections() as $collection) { + /** @var Collection $collection */ + var_dump($collection->getCollectionName()); + var_dump(preg_replace('/[^a-zA-Z0-9_ -]/s','_', $collection->getCollectionName())); + $databaseService->createCollection($database->getId(), $collection->getId(), preg_replace('/[^a-zA-Z0-9_ -]/s','_', $collection->getCollectionName())); + + foreach ($collection->getAttributes() as $attribute) { + /** @var Attribute $attribute */ + $this->createAttribute($attribute, $collection, $database); + } + + // We need to wait for all the attributes to be created before creating the indexes. + $timeout = 0; + + while (!$this->validateAttributesCreation($collection->getAttributes(), $collection, $database)) { + if ($timeout > 10) { + throw new AppwriteException('Timeout while waiting for attributes to be created'); + } + + $timeout++; + \sleep(1); + } + + foreach ($collection->getIndexes() as $index) { + /** @var Index $index */ + $databaseService->createIndex($database->getId(), $collection->getId(), $index->getKey(), $index->getType(), $index->getAttributes(), $index->getOrders()); + } + } + + $this->logs[Log::SUCCESS][] = new Log('Database imported successfully', \time(), $database); + $databaseCounters['current']++; + } catch (AppwriteException $e) { + $this->logs[Log::ERROR][] = new Log($e->getMessage(), \time(), $database); + $databaseCounters['failed']++; + } + } + + $callback( + new Progress( + Transfer::RESOURCE_DATABASES, + time(), + $databaseCounters['total'], + $databaseCounters['current'], + $databaseCounters['failed'], + $databaseCounters['skipped'] + ) + ); } } \ No newline at end of file diff --git a/src/Transfer/Destinations/Local.php b/src/Transfer/Destinations/Local.php new file mode 100644 index 0000000..a85128b --- /dev/null +++ b/src/Transfer/Destinations/Local.php @@ -0,0 +1,132 @@ +path, \json_encode($this->data, JSON_PRETTY_PRINT)); + } + + /** + * Import Users + * + * @param array $users + * @param callable $callback + * + * @return void + */ + public function importUsers(array $users, callable $callback): void + { + $userCounters = &$this->getCounter(Transfer::RESOURCE_USERS); + + foreach ($users as $user) { + /** @var User $user */ + $this->data['users'][] = $user->asArray(); + $this->logs[Log::SUCCESS][] = new Log('Users imported successfully', \time(), $user); + $userCounters['current']++; + } + + $callback( + new Progress( + Transfer::RESOURCE_USERS, + time(), + $userCounters['total'], + $userCounters['current'], + $userCounters['failed'], + $userCounters['skipped'] + ) + ); + + $this->syncFile(); + } + + /** + * Import Databases + * + * @param array $databases + * @param callable $callback + * + * @return void + */ + public function importDatabases(array $databases, callable $callback): void + { + $databaseCounters = &$this->getCounter(Transfer::RESOURCE_DATABASES); + + foreach ($databases as $database) { + /** @var Database $database */ + $this->data['databases'][] = $database->asArray(); + $this->logs[Log::SUCCESS][] = new Log('Database imported successfully', \time(), $database); + $databaseCounters['current']++; + } + + $callback( + new Progress( + Transfer::RESOURCE_DATABASES, + time(), + $databaseCounters['total'], + $databaseCounters['current'], + $databaseCounters['failed'], + $databaseCounters['skipped'] + ) + ); + + $this->syncFile(); + } +} \ No newline at end of file diff --git a/src/Transfer/Log.php b/src/Transfer/Log.php index b9cb2c8..8133fae 100644 --- a/src/Transfer/Log.php +++ b/src/Transfer/Log.php @@ -17,7 +17,7 @@ public function __construct(private string $message = '', private int $timestamp /** * Get Message * - * @returns string + * @return string */ public function getMessage(): string { @@ -28,7 +28,7 @@ public function getMessage(): string * Set Message * * @param string $message - * @returns self + * @return self */ public function setMessage(string $message): self { @@ -39,7 +39,7 @@ public function setMessage(string $message): self /** * Get Timestamp * - * @returns int + * @return int */ public function getTimestamp(): int { @@ -50,7 +50,7 @@ public function getTimestamp(): int * Set Timestamp * * @param int $timestamp - * @returns self + * @return self */ public function setTimestamp(int $timestamp): self { @@ -61,7 +61,7 @@ public function setTimestamp(int $timestamp): self /** * Get Resource * - * @returns Resource|null + * @return Resource|null */ public function getResource(): ?Resource { @@ -71,7 +71,7 @@ public function getResource(): ?Resource /** * As Array * - * @returns array + * @return array */ public function asArray(): array { diff --git a/src/Transfer/Resources/Progress.php b/src/Transfer/Progress.php similarity index 100% rename from src/Transfer/Resources/Progress.php rename to src/Transfer/Progress.php diff --git a/src/Transfer/Resource.php b/src/Transfer/Resource.php index 1218dd7..769dec1 100644 --- a/src/Transfer/Resource.php +++ b/src/Transfer/Resource.php @@ -38,7 +38,7 @@ public function setId(string $id): self /** * As Array * - * @returns array + * @return array */ abstract public function asArray(): array; } \ No newline at end of file diff --git a/src/Transfer/Resources/Attribute.php b/src/Transfer/Resources/Attribute.php new file mode 100644 index 0000000..25cd18e --- /dev/null +++ b/src/Transfer/Resources/Attribute.php @@ -0,0 +1,76 @@ +key; + } + + function setKey(string $key): self + { + $this->key = $key; + return $this; + } + + function getRequired(): bool + { + return $this->required; + } + + function setRequired(bool $required): self + { + $this->required = $required; + return $this; + } + + function getArray(): bool + { + return $this->array; + } + + function setArray(bool $array): self + { + $this->array = $array; + return $this; + } + + function asArray(): array + { + return [ + 'key' => $this->key, + 'required' => $this->required, + 'array' => $this->array, + 'type' => $this->getName(), + ]; + } +} \ No newline at end of file diff --git a/src/Transfer/Resources/Attributes/BoolAttribute.php b/src/Transfer/Resources/Attributes/BoolAttribute.php new file mode 100644 index 0000000..e3e76ef --- /dev/null +++ b/src/Transfer/Resources/Attributes/BoolAttribute.php @@ -0,0 +1,39 @@ +default; + } + + function setDefault(bool $default): void + { + $this->default = $default; + } + + function asArray(): array + { + return array_merge(parent::asArray(), [ + 'default' => $this->default, + ]); + } +} \ No newline at end of file diff --git a/src/Transfer/Resources/Attributes/DateTimeAttribute.php b/src/Transfer/Resources/Attributes/DateTimeAttribute.php new file mode 100644 index 0000000..94ee7c9 --- /dev/null +++ b/src/Transfer/Resources/Attributes/DateTimeAttribute.php @@ -0,0 +1,32 @@ +default; + } + + function setDefault(string $default): void + { + $this->default = $default; + } + + function getName(): string + { + return 'dateTimeAttribute'; + } +} \ No newline at end of file diff --git a/src/Transfer/Resources/Attributes/EmailAttribute.php b/src/Transfer/Resources/Attributes/EmailAttribute.php new file mode 100644 index 0000000..aa714e2 --- /dev/null +++ b/src/Transfer/Resources/Attributes/EmailAttribute.php @@ -0,0 +1,32 @@ +default; + } + + function setDefault(string $default): void + { + $this->default = $default; + } + + function getName(): string + { + return 'emailAttribute'; + } +} \ No newline at end of file diff --git a/src/Transfer/Resources/Attributes/EnumAttribute.php b/src/Transfer/Resources/Attributes/EnumAttribute.php new file mode 100644 index 0000000..acb8c1c --- /dev/null +++ b/src/Transfer/Resources/Attributes/EnumAttribute.php @@ -0,0 +1,51 @@ +elements; + } + + function setElements(array $elements): self + { + $this->elements = $elements; + return $this; + } + + function getDefault(): ?string + { + return $this->default; + } + + function setDefault(string $default): void + { + $this->default = $default; + } + + function asArray(): array + { + return array_merge(parent::asArray(), [ + 'elements' => $this->elements, + ]); + } +} \ No newline at end of file diff --git a/src/Transfer/Resources/Attributes/FloatAttribute.php b/src/Transfer/Resources/Attributes/FloatAttribute.php new file mode 100644 index 0000000..7dec074 --- /dev/null +++ b/src/Transfer/Resources/Attributes/FloatAttribute.php @@ -0,0 +1,66 @@ +min; + } + + function getMax(): float + { + return $this->max; + } + + function setMin(float $min): self + { + $this->min = $min; + return $this; + } + + function setMax(float $max): self + { + $this->max = $max; + return $this; + } + + function getDefault(): ?float + { + return $this->default; + } + + function setDefault(float $default): self + { + $this->default = $default; + return $this; + } + + function asArray(): array + { + return array_merge(parent::asArray(), [ + 'min' => $this->getMin(), + 'max' => $this->getMax(), + 'default' => $this->getDefault(), + ]); + } +} \ No newline at end of file diff --git a/src/Transfer/Resources/Attributes/IPAttribute.php b/src/Transfer/Resources/Attributes/IPAttribute.php new file mode 100644 index 0000000..19ae736 --- /dev/null +++ b/src/Transfer/Resources/Attributes/IPAttribute.php @@ -0,0 +1,32 @@ +default; + } + + function setDefault(string $default): void + { + $this->default = $default; + } + + function getName(): string + { + return 'IPAttribute'; + } +} \ No newline at end of file diff --git a/src/Transfer/Resources/Attributes/IntAttribute.php b/src/Transfer/Resources/Attributes/IntAttribute.php new file mode 100644 index 0000000..10c9379 --- /dev/null +++ b/src/Transfer/Resources/Attributes/IntAttribute.php @@ -0,0 +1,66 @@ +min; + } + + function getMax(): int + { + return $this->max; + } + + function setMin(int $min): self + { + $this->min = $min; + return $this; + } + + function setMax(int $max): self + { + $this->max = $max; + return $this; + } + + function getDefault(): ?int + { + return $this->default; + } + + function setDefault(int $default): self + { + $this->default = $default; + return $this; + } + + function asArray(): array + { + return array_merge(parent::asArray(), [ + 'min' => $this->getMin(), + 'max' => $this->getMax(), + 'default' => $this->getDefault(), + ]); + } +} \ No newline at end of file diff --git a/src/Transfer/Resources/Attributes/StringAttribute.php b/src/Transfer/Resources/Attributes/StringAttribute.php new file mode 100644 index 0000000..b96b328 --- /dev/null +++ b/src/Transfer/Resources/Attributes/StringAttribute.php @@ -0,0 +1,51 @@ +size; + } + + function setSize(int $size): self + { + $this->size = $size; + return $this; + } + + function getDefault(): ?string + { + return $this->default; + } + + function setDefault(string $default): void + { + $this->default = $default; + } + + function asArray(): array + { + return array_merge(parent::asArray(), [ + 'size' => $this->size, + ]); + } +} \ No newline at end of file diff --git a/src/Transfer/Resources/Attributes/URLAttribute.php b/src/Transfer/Resources/Attributes/URLAttribute.php new file mode 100644 index 0000000..c3b2c76 --- /dev/null +++ b/src/Transfer/Resources/Attributes/URLAttribute.php @@ -0,0 +1,32 @@ +default; + } + + function setDefault(string $default): void + { + $this->default = $default; + } + + function getName(): string + { + return 'URLAttribute'; + } +} \ No newline at end of file diff --git a/src/Transfer/Resources/Collection.php b/src/Transfer/Resources/Collection.php new file mode 100644 index 0000000..6b11e3f --- /dev/null +++ b/src/Transfer/Resources/Collection.php @@ -0,0 +1,94 @@ + $columns + */ + + private array $columns = []; + + /** + * @var list $indexes + */ + private array $indexes = []; + + public function __construct(protected string $name, protected string $id) + { + } + + public function getName(): string + { + return 'collection'; + } + + public function getCollectionName(): string + { + return $this->name; + } + + public function setCollectionName(string $name): self + { + $this->name = $name; + return $this; + } + + public function getId(): string + { + return $this->id; + } + + public function setId(string $id): self + { + $this->id = $id; + return $this; + } + + public function getAttributes(): array + { + return $this->columns; + } + + /** + * @param list $columns + * @return self + */ + public function setAttributes(array $columns): self + { + $this->columns = $columns; + return $this; + } + + public function getIndexes(): array + { + return $this->indexes; + } + + /** + * @param list $indexes + * @return self + */ + public function setIndexes(array $indexes): self + { + $this->indexes = $indexes; + return $this; + } + + public function asArray(): array + { + return [ + 'name' => $this->name, + 'id' => $this->id, + 'columns' => array_map(function ($column) { + return $column->asArray(); + }, $this->columns), + 'indexes' => array_map(function ($index) { + return $index->asArray(); + }, $this->indexes), + ]; + } +} \ No newline at end of file diff --git a/src/Transfer/Resources/Database.php b/src/Transfer/Resources/Database.php new file mode 100644 index 0000000..9989c03 --- /dev/null +++ b/src/Transfer/Resources/Database.php @@ -0,0 +1,74 @@ + $collections + */ + private array $collections = []; + + public function __construct(protected string $name, protected string $id) + { + + } + + public function getName(): string + { + return 'database'; + } + + public function getDBName(): string + { + return $this->name; + } + + public function getId(): string + { + return $this->id; + } + + public function setId(string $id): self + { + $this->id = $id; + return $this; + } + + public function getCollections(): array + { + return $this->collections; + } + + /** + * @param list $collections + * @return self + */ + public function setCollections(array $collections): self + { + $this->collections = $collections; + + return $this; + } + + public function asArray(): array + { + return [ + 'name' => $this->name, + 'id' => $this->id, + 'collections' => array_map(function ($collection) { + return $collection->asArray(); + }, $this->collections) + ]; + } +} \ No newline at end of file diff --git a/src/Transfer/Resources/Hash.php b/src/Transfer/Resources/Hash.php index b792bb3..4c39223 100644 --- a/src/Transfer/Resources/Hash.php +++ b/src/Transfer/Resources/Hash.php @@ -30,7 +30,7 @@ public function getName(): string /** * Get Hash * - * @returns string + * @return string */ public function getHash(): string { @@ -41,7 +41,7 @@ public function getHash(): string * Set Hash * * @param string $hash - * @returns self + * @return self */ public function setHash(string $hash): self { @@ -52,7 +52,7 @@ public function setHash(string $hash): self /** * Get Salt * - * @returns string + * @return string */ public function getSalt(): string { @@ -63,7 +63,7 @@ public function getSalt(): string * Set Salt * * @param string $salt - * @returns self + * @return self */ public function setSalt(string $salt): self { @@ -74,7 +74,7 @@ public function setSalt(string $salt): self /** * Get Algorithm * - * @returns string + * @return string */ public function getAlgorithm(): string { @@ -85,7 +85,7 @@ public function getAlgorithm(): string * Set Algorithm * * @param string $algorithm - * @returns self + * @return self */ public function setAlgorithm(string $algorithm): self { @@ -96,7 +96,7 @@ public function setAlgorithm(string $algorithm): self /** * Get Separator * - * @returns string + * @return string */ public function getSeparator(): string { @@ -107,7 +107,7 @@ public function getSeparator(): string * Set Separator * * @param string $separator - * @returns self + * @return self */ public function setSeparator(string $separator): self { @@ -118,7 +118,7 @@ public function setSeparator(string $separator): self /** * Get Signing Key * - * @returns string + * @return string */ public function getSigningKey(): string { @@ -129,7 +129,7 @@ public function getSigningKey(): string * Set Signing Key * * @param string $signingKey - * @returns self + * @return self */ public function setSigningKey(string $signingKey): self { @@ -140,7 +140,7 @@ public function setSigningKey(string $signingKey): self /** * Get Password CPU * - * @returns int + * @return int */ public function getPasswordCpu(): int { @@ -151,7 +151,7 @@ public function getPasswordCpu(): int * Set Password CPU * * @param int $passwordCpu - * @returns self + * @return self */ public function setPasswordCpu(int $passwordCpu): self { @@ -162,7 +162,7 @@ public function setPasswordCpu(int $passwordCpu): self /** * Get Password Memory * - * @returns int + * @return int */ public function getPasswordMemory(): int { @@ -173,7 +173,7 @@ public function getPasswordMemory(): int * Set Password Memory * * @param int $passwordMemory - * @returns self + * @return self */ public function setPasswordMemory(int $passwordMemory): self { @@ -184,7 +184,7 @@ public function setPasswordMemory(int $passwordMemory): self /** * Get Password Parallel * - * @returns int + * @return int */ public function getPasswordParallel(): int { @@ -195,7 +195,7 @@ public function getPasswordParallel(): int * Set Password Parallel * * @param int $passwordParallel - * @returns self + * @return self */ public function setPasswordParallel(int $passwordParallel): self { @@ -206,7 +206,7 @@ public function setPasswordParallel(int $passwordParallel): self /** * Get Password Length * - * @returns int + * @return int */ public function getPasswordLength(): int { @@ -217,7 +217,7 @@ public function getPasswordLength(): int * Set Password Length * * @param int $passwordLength - * @returns self + * @return self */ public function setPasswordLength(int $passwordLength): self { @@ -228,7 +228,7 @@ public function setPasswordLength(int $passwordLength): self /** * As Array * - * @returns array + * @return array */ public function asArray(): array { diff --git a/src/Transfer/Resources/Index.php b/src/Transfer/Resources/Index.php new file mode 100644 index 0000000..7d31379 --- /dev/null +++ b/src/Transfer/Resources/Index.php @@ -0,0 +1,82 @@ + $attributes + * @param array $orders + */ + + public function __construct(protected string $key, protected string $type, protected array $attributes, protected array $orders) + { + } + + public function getName(): string + { + return 'index'; + } + + public function getKey(): string + { + return $this->key; + } + + public function setKey(string $key): self + { + $this->key = $key; + return $this; + } + + public function getType(): string + { + return $this->type; + } + + public function setType(string $type): self + { + $this->type = $type; + return $this; + } + + public function getAttributes(): array + { + return $this->attributes; + } + + /** + * @param list $attributes + * @return self + */ + public function setAttributes(array $attributes): self + { + $this->attributes = $attributes; + return $this; + } + + public function getOrders(): array + { + return $this->orders; + } + + public function setOrders(array $orders): self + { + $this->orders = $orders; + return $this; + } + + public function asArray(): array + { + return [ + 'key' => $this->key, + 'type' => $this->type, + 'attributes' => $this->attributes, + 'orders' => $this->orders, + ]; + } +} \ No newline at end of file diff --git a/src/Transfer/Resources/User.php b/src/Transfer/Resources/User.php index c2c8a50..1f4906e 100644 --- a/src/Transfer/Resources/User.php +++ b/src/Transfer/Resources/User.php @@ -30,7 +30,7 @@ public function __construct( /** * Get Name * - * @returns string + * @return string */ public function getName(): string { @@ -40,7 +40,7 @@ public function getName(): string /** * Get ID * - * @returns string + * @return string */ public function getId(): string { @@ -51,7 +51,7 @@ public function getId(): string * Set ID * * @param string $id - * @returns self + * @return self */ public function setId(string $id): self { @@ -62,7 +62,7 @@ public function setId(string $id): self /** * Get Email * - * @returns string + * @return string */ public function getEmail(): string { @@ -73,7 +73,7 @@ public function getEmail(): string * Set Email * * @param string $email - * @returns self + * @return self */ public function setEmail(string $email): self { @@ -84,7 +84,7 @@ public function setEmail(string $email): self /** * Get Username * - * @returns string + * @return string */ public function getUsername(): string { @@ -95,7 +95,7 @@ public function getUsername(): string * Set Username * * @param string $username - * @returns self + * @return self */ public function setUsername(string $username): self { @@ -106,7 +106,7 @@ public function setUsername(string $username): self /** * Get Password Hash * - * @returns Hash + * @return Hash */ public function getPasswordHash(): Hash { @@ -117,7 +117,7 @@ public function getPasswordHash(): Hash * Set Password Hash * * @param Hash $passwordHash - * @returns self + * @return self */ public function setPasswordHash(Hash $passwordHash): self { @@ -128,7 +128,7 @@ public function setPasswordHash(Hash $passwordHash): self /** * Get Phone * - * @returns string + * @return string */ public function getPhone(): string { @@ -139,7 +139,7 @@ public function getPhone(): string * Set Phone * * @param string $phone - * @returns self + * @return self */ public function setPhone(string $phone): self { @@ -150,7 +150,7 @@ public function setPhone(string $phone): self /** * Get Type * - * @returns array + * @return array */ public function getTypes(): array { @@ -161,7 +161,7 @@ public function getTypes(): array * Set Types * * @param string $types - * @returns self + * @return self */ public function setTypes(string $types): self { @@ -172,7 +172,7 @@ public function setTypes(string $types): self /** * Get OAuth Provider * - * @returns string + * @return string */ public function getOAuthProvider(): string { @@ -183,7 +183,7 @@ public function getOAuthProvider(): string * Set OAuth Provider * * @param string $oauthProvider - * @returns self + * @return self */ public function setOAuthProvider(string $oauthProvider): self { @@ -194,7 +194,7 @@ public function setOAuthProvider(string $oauthProvider): self /** * Get Email Verified * - * @returns bool + * @return bool */ public function getEmailVerified(): bool { @@ -205,7 +205,7 @@ public function getEmailVerified(): bool * Set Email Verified * * @param bool $verified - * @returns self + * @return self */ public function setEmailVerified(bool $verified): self { @@ -216,7 +216,7 @@ public function setEmailVerified(bool $verified): self /** * Get Email Verified * - * @returns bool + * @return bool */ public function getPhoneVerified(): bool { @@ -227,7 +227,7 @@ public function getPhoneVerified(): bool * Set Phone Verified * * @param bool $verified - * @returns self + * @return self */ public function setPhoneVerified(bool $verified): self { @@ -238,7 +238,7 @@ public function setPhoneVerified(bool $verified): self /** * Get Disabled * - * @returns bool + * @return bool */ public function getDisabled(): bool { @@ -249,7 +249,7 @@ public function getDisabled(): bool * Set Disabled * * @param bool $disabled - * @returns self + * @return self */ public function setDisabled(bool $disabled): self { @@ -260,7 +260,7 @@ public function setDisabled(bool $disabled): self /** * Get Preferences * - * @returns array + * @return array */ public function getPreferences(): array { @@ -271,7 +271,7 @@ public function getPreferences(): array * Set Preferences * * @param array $preferences - * @returns self + * @return self */ public function setPreferences(array $preferences): self { @@ -282,7 +282,7 @@ public function setPreferences(array $preferences): self /** * As Array * - * @returns array + * @return array */ public function asArray(): array { diff --git a/src/Transfer/Source.php b/src/Transfer/Source.php index 846763f..95cc6a3 100644 --- a/src/Transfer/Source.php +++ b/src/Transfer/Source.php @@ -48,7 +48,7 @@ abstract class Source * * @param string $resource * - * @returns array + * @return array */ public function &getCounter(string $resource): array { if ($this->counters[$resource]) { @@ -121,6 +121,13 @@ public function run(array $resources, callable $callback): void { }); break; } + case Transfer::RESOURCE_DATABASES: { + $this->exportDatabases(100, function (array $databases) use ($callback) { + $this->resourceCache = array_merge($this->resourceCache, $databases); + $callback(Transfer::RESOURCE_DATABASES, $databases); + }); + break; + } } } } @@ -258,10 +265,23 @@ protected function flatten(array $data, string $prefix = ''): array * @param int $batchSize * @param callable $callback Callback function to be called after each batch, $callback(user[] $batch); * - * @returns User[] + * @return User[] */ public function exportUsers(int $batchSize, callable $callback): void { throw new Exception('Unimplemented, Please check if your source adapter supports this method.'); } + + /** + * Export Databases + * + * @param int $batchSize Max 100 + * @param callable $callback Callback function to be called after each database, $callback(database[] $batch); + * + * @return void + */ + public function exportDatabases(int $batchSize, callable $callback): void + { + throw new Exception('Unimplemented, Please check if your source adapter supports this method.'); + } } diff --git a/src/Transfer/Sources/Appwrite.php b/src/Transfer/Sources/Appwrite.php index 978f434..0644fe5 100644 --- a/src/Transfer/Sources/Appwrite.php +++ b/src/Transfer/Sources/Appwrite.php @@ -5,12 +5,25 @@ use Utopia\Transfer\Source; use Appwrite\Client; use Appwrite\Query; +use Utopia\Transfer\Resources\Attribute; use Utopia\Transfer\Resources\Project; use Utopia\Transfer\Resources\User; use Utopia\Transfer\Transfer; use Utopia\Transfer\Log; use Utopia\Transfer\Resource; +use Utopia\Transfer\Resources\Attributes\BoolAttribute; +use Utopia\Transfer\Resources\Attributes\DateTimeAttribute; +use Utopia\Transfer\Resources\Attributes\EmailAttribute; +use Utopia\Transfer\Resources\Attributes\EnumAttribute; +use Utopia\Transfer\Resources\Attributes\FloatAttribute; +use Utopia\Transfer\Resources\Attributes\IntAttribute; +use Utopia\Transfer\Resources\Attributes\IPAttribute; +use Utopia\Transfer\Resources\Attributes\StringAttribute; +use Utopia\Transfer\Resources\Attributes\URLAttribute; +use Utopia\Transfer\Resources\Collection; +use Utopia\Transfer\Resources\Database; use Utopia\Transfer\Resources\Hash; +use Utopia\Transfer\Resources\Index; class Appwrite extends Source { @@ -22,16 +35,15 @@ class Appwrite extends Source /** * Constructor * - * @param string $endpoint * @param string $project + * @param string $endpoint * @param string $key * - * @returns self + * @return self */ - function __construct(string $endpoint, string $project, string $key) + function __construct(string $project, string $endpoint, string $key) { - $this->appwriteClient = new Client(); - $this->appwriteClient + $this->appwriteClient = (new Client()) ->setEndpoint($endpoint) ->setProject($project) ->setKey($key); @@ -40,7 +52,7 @@ function __construct(string $endpoint, string $project, string $key) /** * Get Name * - * @returns string + * @return string */ public function getName(): string { @@ -50,12 +62,13 @@ public function getName(): string /** * Get Supported Resources * - * @returns array + * @return array */ public function getSupportedResources(): array { return [ Transfer::RESOURCE_USERS, + Transfer::RESOURCE_DATABASES ]; } @@ -64,7 +77,7 @@ public function getSupportedResources(): array * * @param array $resources * - * @returns bool + * @return bool */ public function check(array $resources = []): bool { @@ -74,10 +87,10 @@ public function check(array $resources = []): bool /** * Export Users * - * @param int $batchSize Max 500 + * @param int $batchSize Max 100 * @param callable $callback Callback function to be called after each batch, $callback(user[] $batch); * - * @returns void + * @return void */ public function exportUsers(int $batchSize, callable $callback): void { @@ -125,12 +138,113 @@ public function exportUsers(int $batchSize, callable $callback): void } } + function convertAttribute(array $value): Attribute + { + switch ($value['type']) { + case 'string': + { + if (!isset($value['format'])) + return new StringAttribute($value['key'], $value['required'], $value['array'], $value['default'], $value['size'] ?? 0); + + switch ($value['format']) { + case 'email': + return new EmailAttribute($value['key'], $value['required'], $value['array'], $value['default']); + case 'enum': + return new EnumAttribute($value['key'], $value['elements'], $value['required'], $value['array'], $value['default']); + case 'url': + return new URLAttribute($value['key'], $value['required'], $value['array'], $value['default']); + case 'ip': + return new IPAttribute($value['key'], $value['required'], $value['array'], $value['default']); + case 'datetime': + return new DateTimeAttribute($value['key'], $value['required'], $value['array'], $value['default']); + default: + return new StringAttribute($value['key'], $value['required'], $value['array'], $value['default'], $value['size'] ?? 0); + } + } + case 'boolean': + return new BoolAttribute($value['key'], $value['required'], $value['array'], $value['default']); + case 'integer': + return new IntAttribute($value['key'], $value['required'], $value['array'], $value['default'], $value['min'] ?? 0, $value['max'] ?? 0); + case 'double': + return new FloatAttribute($value['key'], $value['required'], $value['array'], $value['default'], $value['min'] ?? 0, $value['max'] ?? 0); + } + + throw new \Exception('Unknown attribute type: ' . $value['type']); + } + + /** + * Export Databases + * + * @param int $batchSize Max 100 + * @param callable $callback Callback function to be called after each database, $callback(database[] $batch); + * + * @return void + */ + public function exportDatabases(int $batchSize, callable $callback): void + { + $databaseClient = new \Appwrite\Services\Databases($this->appwriteClient); + + $lastDocument = null; + + while (true) { + $queries = [ + Query::limit($batchSize) + ]; + + $databases = []; + + if ($lastDocument) { + $queries[] = Query::cursorAfter($lastDocument); + } + + $response = $databaseClient->list($queries); + + foreach ($response['databases'] as $database) { + $newDatabase = new Database($database['name'], $database['$id']); + + $collections = $databaseClient->listCollections($database['$id']); + + $generalCollections = []; + foreach ($collections['collections'] as $collection) { + $newCollection = new Collection($collection['name'], $collection['$id']); + + $attributes = []; + $indexes = []; + + foreach ($collection['attributes'] as $attribute) { + $attributes[] = $this->convertAttribute($attribute); + } + + foreach($collection['indexes'] as $index) { + $indexes[] = new Index($index['key'], $index['type'], $index['attributes'], $index['orders']); + } + + $newCollection->setAttributes($attributes); + $newCollection->setIndexes($indexes); + + $generalCollections[] = $newCollection; + } + + $newDatabase->setCollections($generalCollections); + $databases[] = $newDatabase; + + $lastDocument = $database['$id']; + } + + $callback($databases); + + if (count($response['databases']) < $batchSize) { + break; + } + } + } + /** * Calculate Types * * @param array $user * - * @returns array + * @return array */ protected function calculateTypes(array $user): array { diff --git a/src/Transfer/Sources/Firebase.php b/src/Transfer/Sources/Firebase.php index dc0ff9e..ee44677 100644 --- a/src/Transfer/Sources/Firebase.php +++ b/src/Transfer/Sources/Firebase.php @@ -2,15 +2,28 @@ namespace Utopia\Transfer\Sources; +use Attribute; use Utopia\Transfer\Source; use Google\Client; +use Google\Service\Firestore\BatchGetDocumentsRequest; +use Google\Service\Firestore\ListCollectionIdsRequest; use Utopia\Transfer\Resources\Project; use Utopia\Transfer\Resources\User; use Utopia\Transfer\Transfer; use Utopia\Transfer\Log; use Utopia\Transfer\Resource; +use Utopia\Transfer\Resources\Attribute as ResourcesAttribute; +use Utopia\Transfer\Resources\Attributes\BoolAttribute; +use Utopia\Transfer\Resources\Attributes\DateTimeAttribute; +use Utopia\Transfer\Resources\Attributes\FloatAttribute; +use Utopia\Transfer\Resources\Attributes\IntAttribute; +use Utopia\Transfer\Resources\Attributes\StringAttribute; +use Utopia\Transfer\Resources\Collection; +use Utopia\Transfer\Resources\Database; use Utopia\Transfer\Resources\Hash; +use function PHPSTORM_META\type; + class Firebase extends Source { const TYPE_OAUTH = 'oauth'; @@ -66,7 +79,8 @@ function getName(): string function getSupportedResources(): array { return [ - Transfer::RESOURCE_USERS + Transfer::RESOURCE_USERS, + Transfer::RESOURCE_DATABASES ]; } @@ -111,7 +125,7 @@ function setProject(Project|string $project): void /** * Get Project * - * @returns Project|null + * @return Project|null */ function getProject(): Project|null { @@ -124,7 +138,7 @@ function getProject(): Project|null * @param int $batchSize Max 500 * @param callable $callback Callback function to be called after each batch, $callback(user[] $batch); * - * @returns void + * @return void */ public function exportUsers(int $batchSize, callable $callback): void { @@ -199,7 +213,175 @@ public function exportUsers(int $batchSize, callable $callback): void } } - function calculateTypes(array $providerData): array { + /** + * Calculate Array Type + * + * @param string $key + * @param \Google\Service\Firestore\ArrayValue $data + * + * @return ResourcesAttribute + */ + function calculateArrayType(string $key, \Google\Service\Firestore\ArrayValue $data): ResourcesAttribute + { + $isSameType = true; + $previousType = null; + + foreach ($data["values"] as $field) { + if (!$previousType) { + $previousType = $this->calculateType($key, $field); + } else if ($previousType->getName() != ($this->calculateType($key, $field))->getName()) { + $isSameType = false; + break; + } + } + + if ($isSameType) { + $previousType->setArray(true); + return $previousType; + } else { + return new StringAttribute($key, false, true, null, 1000000); + } + } + + /** + * Calculate Type + * + * @param string $key + * @param \Google\Service\Firestore\Value $field + * + * @return ResourcesAttribute + */ + function calculateType(string $key, \Google\Service\Firestore\Value $field): ResourcesAttribute + { + if (isset($field["booleanValue"])) + return new BoolAttribute($key, false, false, null); + else if (isset($field["bytesValue"])) + return new StringAttribute($key, false, false, null, 1000000); + else if (isset($field["doubleValue"])) + return new FloatAttribute($key, false, false, null); + else if (isset($field["integerValue"])) + return new IntAttribute($key, false, false, null); + else if (isset($field["mapValue"])) + return new StringAttribute($key, false, false, null, 1000000); + else if (isset($field["nullValue"])) + return new StringAttribute($key, false, false, null, 1000000); + else if (isset($field["referenceValue"])) + return new StringAttribute($key, false, false, null, 1000000); + else if (isset($field["stringValue"])) + return new StringAttribute($key, false, false, null, 1000000); + else if (isset($field["timestampValue"])) + return new DateTimeAttribute($key, false, false, null); + else if (isset($field["geoPointValue"])) + return new StringAttribute($key, false, false, null, 1000000); + else if (isset($field["arrayValue"])) { + return $this->calculateArrayType($key, $field["arrayValue"]); + } else { + $this->logs[Log::WARNING][] = new Log('Failed to determine data type for: ' . $key . ' Falling back to string.', \time()); + return new StringAttribute($key, false, false, null, 1000000); + } + } + + /** + * Predict Schema + * + * @param int $batchSize Max 500 + * @param $collection Collection + * @param &Collection[] $newCollections + * + * @return list + **/ + function predictSchema(int $batchSize, Collection $collection, array &$newCollections) + { + $attributes = []; + + $firestore = new \Google\Service\Firestore($this->googleClient); + + $documents = $firestore->projects_databases_documents->listDocuments('projects/' . $this->project->getId() . '/databases/(default)/documents/' . $collection->getId(), '', [ + 'pageSize' => $batchSize + ]); + + foreach ($documents as $document) { + foreach ($document["fields"] as $key => $value) { + $attributes[] = $this->calculateType($key, $value); + } + + $requestOptions = new ListCollectionIdsRequest(); + $requestOptions->setPageSize(500); + + // Handle subcollections + $subcollections = $firestore->projects_databases_documents->listCollectionIds($document["name"], $requestOptions,[])["collectionIds"]; + + if ($subcollections == null) { + continue; + } + + $subcollections = array_map(function ($subcollection) use ($document, $collection) { + $name = str_replace("projects/".$this->getProject()->getId()."/databases/(default)/documents/", "", $document["name"]); + return $name . '/' . $subcollection; + }, $subcollections); + + $newCollections = array_merge($newCollections, $this->handleCollections($subcollections)); + } + + return $attributes; + } + + /** + * Handle Collections + * + * @param string[] $collectionIDs + * + * @return Collection[] + */ + function handleCollections(array $collectionIDs): array + { + $collections = []; + + foreach ($collectionIDs as $collectionID) { + $collection = new Collection($collectionID, $collectionID); + + $collection->setAttributes($this->predictSchema(500, $collection, $collections)); + + $collections[] = $collection; + } + + return $collections; + } + + /** + * Export Databases + * + * @param int $batchSize Max 500 + * @param callable $callback Callback function to be called after each batch, $callback(database[] $batch); + * + * @return void + */ + public function exportDatabases(int $batchSize, callable $callback): void + { + if (!$this->project || !$this->project->getId()) { + $this->logs[Log::FATAL][] = new Log('Project not set'); + throw new \Exception('Project not set'); + } + + if ($batchSize > 500) { + $this->logs[Log::FATAL][] = new Log('Batch size cannot be greater than 500'); + throw new \Exception('Batch size cannot be greater than 500'); + } + + $firestore = new \Google\Service\Firestore($this->googleClient); + + // Let's grab the root collections. (google's params technically doesn't allow this, however they do it in their own console) + $request = $firestore->projects_databases_documents->listCollectionIds('projects/' . $this->project->getId() . '/databases/(default)/documents', new ListCollectionIdsRequest()); + + $database = new Database('Default', 'Default'); + + $database->setCollections($this->handleCollections($request['collectionIds'])); + + $callback([$database]); + } + + function calculateTypes(array $providerData): array + { if (count($providerData) === 0) { return [User::TYPE_ANONYMOUS]; } @@ -236,8 +418,7 @@ function check(array $resources = []): bool } foreach ($resources as $resource) { - switch ($resource) - { + switch ($resource) { case Transfer::RESOURCE_USERS: $firebase = new \Google\Service\FirebaseManagement($this->googleClient); diff --git a/src/Transfer/Sources/NHost.php b/src/Transfer/Sources/NHost.php index 40f98a1..6d571a1 100644 --- a/src/Transfer/Sources/NHost.php +++ b/src/Transfer/Sources/NHost.php @@ -6,7 +6,15 @@ use Utopia\Transfer\Resources\User; use Utopia\Transfer\Transfer; use Utopia\Transfer\Log; +use Utopia\Transfer\Resources\Attribute; +use Utopia\Transfer\Resources\Database; use Utopia\Transfer\Resources\Hash; +use Utopia\Transfer\Resources\Attributes\BoolAttribute; +use Utopia\Transfer\Resources\Attributes\DateTimeAttribute; +use Utopia\Transfer\Resources\Attributes\FloatAttribute; +use Utopia\Transfer\Resources\Attributes\IntAttribute; +use Utopia\Transfer\Resources\Attributes\StringAttribute; +use Utopia\Transfer\Resources\Collection; class NHost extends Source { @@ -28,7 +36,8 @@ function getName(): string function getSupportedResources(): array { return [ - Transfer::RESOURCE_USERS + Transfer::RESOURCE_USERS, + Transfer::RESOURCE_DATABASES ]; } @@ -38,7 +47,7 @@ function getSupportedResources(): array * @param int $batchSize Max 500 * @param callable $callback Callback function to be called after each batch, $callback(user[] $batch); * - * @returns User[] + * @return User[] */ public function exportUsers(int $batchSize, callable $callback): void { @@ -78,22 +87,160 @@ public function exportUsers(int $batchSize, callable $callback): void } } + /** + * Convert Collection + * + * @param string $tableName + * @return Collection + */ + private function convertCollection(string $tableName): Collection + { + $statement = $this->pdo->prepare('SELECT * FROM information_schema."columns" where "table_name" = :tableName'); + $statement->bindValue(':tableName', $tableName, \PDO::PARAM_STR); + $statement->execute(); + $databaseCollection = $statement->fetchAll(\PDO::FETCH_ASSOC); + + $convertedCollection = new Collection($tableName, $tableName); + + $attributes = []; + + foreach ($databaseCollection as $column) { + $attributes[] = $this->convertAttribute($column); + } + $convertedCollection->setAttributes($attributes); + + return $convertedCollection; + } + + /** + * Convert Attribute + * + * @param array $column + * @return Attribute + */ + private function convertAttribute(array $column): Attribute + { + $isArray = $column['data_type'] === 'ARRAY'; + + switch ($isArray ? str_replace('_', '', $column['udt_name']) : $column['data_type']) { + // Numbers + case 'boolean': + case 'bool': + return new BoolAttribute($column['column_name'], $column['is_nullable'] === 'NO', $isArray, $column['column_default']); + case 'smallint': + case 'int2': + return new IntAttribute($column['column_name'], $column['is_nullable'] === 'NO', $isArray, $column['column_default'], -32768, 32767); + case 'integer': + case 'int4': + return new IntAttribute($column['column_name'], $column['is_nullable'] === 'NO', $isArray, $column['column_default'], -2147483648, 2147483647); + case 'bigint': + case 'int8': + case 'numeric': + return new IntAttribute($column['column_name'], $column['is_nullable'] === 'NO', $isArray, $column['column_default']); + case 'decimal': + case 'real': + case 'double precision': + case 'float4': + case 'float8': + case 'money': + return new FloatAttribute($column['column_name'], $column['is_nullable'] === 'NO', $isArray, $column['column_default']); + // Time (Conversion happens with documents) + case 'timestamp with time zone': + case 'date': + case 'time with time zone': + case 'timestamp without time zone': + case 'timestamptz': + case 'timestamp': + case 'time': + case 'timetz': + case 'interval': + return new DateTimeAttribute($column['column_name'], $column['is_nullable'] === 'NO', $isArray, ($column['column_default'] === 'now()') ? 'now()' : ($column['column_default'] ?? null)); + break; + // Strings and Objects + case 'uuid': + case 'character varying': + case 'text': + case 'character': + case 'json': + case 'jsonb': + case 'varchar': + case 'bytea': + return new StringAttribute( + $column['column_name'], + $column['is_nullable'] === 'NO', + $isArray, + $column['column_default'], + $column['character_maximum_length'] ?? $column['character_octet_length'] ?? 10485760 + ); + break; + default: { + $this->logs[Log::WARNING][] = new Log('Unknown data type: ' . $column['data_type'] . ' for column: ' . $column['column_name'] . ' Falling back to string.', \time()); + return new StringAttribute( + $column['column_name'], + $column['is_nullable'] === 'NO', + $isArray, + $column['column_default'], + $column['character_maximum_length'] ?? $column['character_octet_length'] ?? 10485760 + ); + break; + } + } + } + + /** + * Export Databases + * + * @param int $batchSize Max 100 + * @param callable $callback Callback function to be called after each database, $callback(database[] $batch); + * + * @return void + */ + public function exportDatabases(int $batchSize, callable $callback): void + { + $total = $this->pdo->query('SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = \'public\'')->fetchColumn(); + + $offset = 0; + + // We'll only transfer the public database for now, since it's the only one that exists by default. + //TODO: Handle edge cases where there are user created databases. + + $transferDatabase = new Database('public', 'public'); + + while ($offset < $total) { + $statement = $this->pdo->prepare('SELECT table_name FROM information_schema.tables WHERE table_schema = \'public\' order by table_name LIMIT :limit OFFSET :offset'); + $statement->bindValue(':limit', $batchSize, \PDO::PARAM_INT); + $statement->bindValue(':offset', $offset, \PDO::PARAM_INT); + $statement->execute(); + + $tables = $statement->fetchAll(\PDO::FETCH_ASSOC); + + $offset += $batchSize; + + $transferCollections = []; + + foreach ($tables as $table) { + $transferCollections[] = $this->convertCollection($table['table_name']); + } + + $transferDatabase->setCollections($transferCollections); + } + + $callback([$transferDatabase]); + } + private function calculateUserTypes(array $user): array { - if (empty($user['password_hash']) && empty($user['phone_number'])) - { + if (empty($user['password_hash']) && empty($user['phone_number'])) { return [User::TYPE_ANONYMOUS]; } $types = []; - if (!empty($user['password_hash'])) - { + if (!empty($user['password_hash'])) { $types[] = User::TYPE_EMAIL; } - if (!empty($user['phone_number'])) - { + if (!empty($user['phone_number'])) { $types[] = User::TYPE_PHONE; } @@ -106,8 +253,7 @@ function check(array $resources = []): bool $this->logs[Log::FATAL] = new Log('Failed to connect to database. Error: ' . $this->pdo->errorInfo()[2]); } - foreach ($resources as $resource) - { + foreach ($resources as $resource) { switch ($resource) { case Transfer::RESOURCE_USERS: $statement = $this->pdo->prepare('SELECT COUNT(*) FROM auth.users'); @@ -122,4 +268,4 @@ function check(array $resources = []): bool return true; } -} \ No newline at end of file +} diff --git a/src/Transfer/Sources/Supabase.php b/src/Transfer/Sources/Supabase.php index 710f865..e4f569a 100644 --- a/src/Transfer/Sources/Supabase.php +++ b/src/Transfer/Sources/Supabase.php @@ -2,11 +2,21 @@ namespace Utopia\Transfer\Sources; +use Exception; use Utopia\Transfer\Source; use Utopia\Transfer\Resources\User; use Utopia\Transfer\Transfer; use Utopia\Transfer\Log; use Utopia\Transfer\Resources\Hash; +use Utopia\Transfer\Resources\Attributes\BoolAttribute; +use Utopia\Transfer\Resources\Attributes\DateTimeAttribute; +use Utopia\Transfer\Resources\Attributes\FloatAttribute; +use Utopia\Transfer\Resources\Attributes\IntAttribute; +use Utopia\Transfer\Resources\Attributes\StringAttribute; +use Utopia\Transfer\Resources\Collection; +use Utopia\Transfer\Resources\Attribute; +use Utopia\Transfer\Resources\Database; +use Utopia\Transfer\Resources\Index; class Supabase extends Source { @@ -28,7 +38,8 @@ function getName(): string function getSupportedResources(): array { return [ - Transfer::RESOURCE_USERS + Transfer::RESOURCE_USERS, + Transfer::RESOURCE_DATABASES ]; } @@ -38,7 +49,7 @@ function getSupportedResources(): array * @param int $batchSize Max 500 * @param callable $callback Callback function to be called after each batch, $callback(user[] $batch); * - * @returns User[] + * @return User[] */ public function exportUsers(int $batchSize, callable $callback): void { @@ -78,6 +89,136 @@ public function exportUsers(int $batchSize, callable $callback): void } } + /** + * Convert Collection + * + * @param string $tableName + * @return Collection + */ + private function convertCollection(string $tableName): Collection + { + $statement = $this->pdo->prepare('SELECT * FROM information_schema."columns" where "table_name" = :tableName'); + $statement->bindValue(':tableName', $tableName, \PDO::PARAM_STR); + $statement->execute(); + $databaseCollection = $statement->fetchAll(\PDO::FETCH_ASSOC); + + $convertedCollection = new Collection($tableName, $tableName); + + $attributes = []; + + foreach ($databaseCollection as $column) { + $attributes[] = $this->convertAttribute($column); + } + $convertedCollection->setAttributes($attributes); + + return $convertedCollection; + } + + /** + * Convert Attribute + * + * @param array $column + * @return Attribute + */ + private function convertAttribute(array $column): Attribute + { + $isArray = $column['data_type'] === 'ARRAY'; + + switch ($isArray ? str_replace('_', '', $column['udt_name']) : $column['data_type']) { + // Numbers + case 'boolean': + case 'bool': + return new BoolAttribute($column['column_name'], $column['is_nullable'] === 'NO', $isArray, $column['column_default']); + case 'smallint': + case 'int2': + return new IntAttribute($column['column_name'], $column['is_nullable'] === 'NO', $isArray, $column['column_default'], -32768, 32767); + case 'integer': + case 'int4': + return new IntAttribute($column['column_name'], $column['is_nullable'] === 'NO', $isArray, $column['column_default'], -2147483648, 2147483647); + case 'bigint': + case 'int8': + case 'numeric': + return new IntAttribute($column['column_name'], $column['is_nullable'] === 'NO', $isArray, $column['column_default']); + case 'decimal': + case 'real': + case 'double precision': + case 'float4': + case 'float8': + return new FloatAttribute($column['column_name'], $column['is_nullable'] === 'NO', $isArray, $column['column_default']); + // Time (Conversion happens with documents) + case 'timestamp with time zone': + case 'date': + case 'time with time zone': + case 'timestamp without time zone': + case 'timestamptz': + case 'timestamp': + case 'time': + case 'timetz': + return new DateTimeAttribute($column['column_name'], $column['is_nullable'] === 'NO', $isArray, ($column['column_default'] === 'now()') ? 'now()' : ($column['column_default'] ?? null)); + break; + // Strings and Objects + case 'uuid': + case 'character varying': + case 'text': + case 'character': + case 'json': + case 'jsonb': + case 'varchar': + return new StringAttribute($column['column_name'], $column['is_nullable'] === 'NO', $isArray, + $column['column_default'], + $column['character_maximum_length'] ?? $column['character_octet_length'] ?? 10485760); + break; + default: { + $this->logs[Log::WARNING][] = new Log('Unknown data type: ' . $column['data_type'] . ' for column: ' . $column['column_name'] . ' Falling back to string.', \time()); + return new StringAttribute($column['column_name'], $column['is_nullable'] === 'NO', $isArray, + $column['column_default'], + $column['character_maximum_length'] ?? $column['character_octet_length'] ?? 10485760); + break; + } + } + } + + /** + * Export Databases + * + * @param int $batchSize Max 100 + * @param callable $callback Callback function to be called after each database, $callback(database[] $batch); + * + * @return void + */ + public function exportDatabases(int $batchSize, callable $callback): void + { + $total = $this->pdo->query('SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = \'public\'')->fetchColumn(); + + $offset = 0; + + // We'll only transfer the public database for now, since it's the only one that exists by default. + //TODO: Handle edge cases where there are user created databases. + + $transferDatabase = new Database('public', 'public'); + + while ($offset < $total) { + $statement = $this->pdo->prepare('SELECT table_name FROM information_schema.tables WHERE table_schema = \'public\' order by table_name LIMIT :limit OFFSET :offset'); + $statement->bindValue(':limit', $batchSize, \PDO::PARAM_INT); + $statement->bindValue(':offset', $offset, \PDO::PARAM_INT); + $statement->execute(); + + $tables = $statement->fetchAll(\PDO::FETCH_ASSOC); + + $offset += $batchSize; + + $transferCollections = []; + + foreach ($tables as $table) { + $transferCollections[] = $this->convertCollection($table['table_name']); + } + + $transferDatabase->setCollections($transferCollections); + } + + $callback([$transferDatabase]); + } + private function calculateAuthTypes(array $user): array { if (empty($user['encrypted_password']) && empty($user['phone'])) diff --git a/src/Transfer/Transfer.php b/src/Transfer/Transfer.php index 278a8f0..d90e057 100644 --- a/src/Transfer/Transfer.php +++ b/src/Transfer/Transfer.php @@ -11,7 +11,7 @@ class Transfer const RESOURCE_FILES = 'files'; const RESOURCE_FUNCTIONS = 'functions'; const RESOURCE_DATABASES = 'databases'; - const RESOURCE_COLLECTIONS = 'collections'; + const RESOURCE_DOCUMENTS = 'documents'; /** * @param Source $source @@ -72,7 +72,7 @@ function __construct(Source $source, Destination $destination) 'failed' => 0, 'skipped' => 0, ], - Transfer::RESOURCE_COLLECTIONS => [ + Transfer::RESOURCE_DOCUMENTS => [ 'total' => 0, 'current' => 0, 'failed' => 0, @@ -86,7 +86,7 @@ function __construct(Source $source, Destination $destination) * @var array */ protected array $resources = [ - self::RESOURCE_COLLECTIONS => [], + self::RESOURCE_DOCUMENTS => [], self::RESOURCE_DATABASES => [], self::RESOURCE_FILES => [], self::RESOURCE_FUNCTIONS => [], @@ -159,7 +159,7 @@ public function getLogs($level = ''): array /** * Get Resource Cache * - * @returns array + * @return array */ public function getResourceCache(): array { diff --git a/tests/Transfer/Sources/AppwriteTest.php b/tests/Transfer/Sources/AppwriteTest.php index 81174fd..05a7b33 100644 --- a/tests/Transfer/Sources/AppwriteTest.php +++ b/tests/Transfer/Sources/AppwriteTest.php @@ -49,7 +49,6 @@ public function testGetUsers(): void $result = array_merge($result, $users); }); - foreach ($result as $user) { /** @var User $user */ $this->assertIsObject($user); @@ -60,4 +59,13 @@ public function testGetUsers(): void $this->assertIsArray($result); $this->assertNotEmpty($result); } + + public function testGetDatabases(): void + { + $result = []; + + $this->appwrite->exportDatabases(100, function (array $users) use (&$result) { + $result = array_merge($result, $users); + }); + } } \ No newline at end of file diff --git a/tests/tmp/databaseDMP.json b/tests/tmp/databaseDMP.json new file mode 100644 index 0000000..23f4aed --- /dev/null +++ b/tests/tmp/databaseDMP.json @@ -0,0 +1,102 @@ +{ + "databases": [ + { + "name": "Default", + "id": "Default", + "collections": [ + { + "name": "test\/test2\/test3", + "id": "test\/test2\/test3", + "columns": [ + { + "key": "hello", + "required": false, + "array": false, + "type": "stringAttribute", + "size": 1000000 + } + ], + "indexes": [] + }, + { + "name": "test", + "id": "test", + "columns": [ + { + "key": "null", + "required": false, + "array": false, + "type": "stringAttribute", + "size": 1000000 + }, + { + "key": "string", + "required": false, + "array": false, + "type": "stringAttribute", + "size": 1000000 + }, + { + "key": "geopoint", + "required": false, + "array": false, + "type": "stringAttribute", + "size": 1000000 + }, + { + "key": "timestamp", + "required": false, + "array": false, + "type": "dateTimeAttribute" + }, + { + "key": "number", + "required": false, + "array": false, + "type": "intAttribute", + "min": 0, + "max": 0, + "default": null + }, + { + "key": "boolean", + "required": false, + "array": false, + "type": "boolAttribute", + "default": null + }, + { + "key": "array2", + "required": false, + "array": true, + "type": "stringAttribute", + "size": 1000000 + }, + { + "key": "map", + "required": false, + "array": false, + "type": "stringAttribute", + "size": 1000000 + }, + { + "key": "reference", + "required": false, + "array": false, + "type": "stringAttribute", + "size": 1000000 + }, + { + "key": "test", + "required": false, + "array": false, + "type": "stringAttribute", + "size": 1000000 + } + ], + "indexes": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/tests/tmp/playground.php b/tests/tmp/playground.php new file mode 100644 index 0000000..37b0b7b --- /dev/null +++ b/tests/tmp/playground.php @@ -0,0 +1,79 @@ +load(); + +/** Initialise All Source Adapters */ +$sourceAppwrite = new Appwrite( + $_ENV['SOURCE_APPWRITE_TEST_PROJECT'], + $_ENV['SOURCE_APPWRITE_TEST_ENDPOINT'], + $_ENV['SOURCE_APPWRITE_TEST_KEY'] +); + +$sourceFirebase = new Firebase( + json_decode($_ENV["FIREBASE_TEST_ACCOUNT"], true), + Firebase::AUTH_SERVICEACCOUNT +); + +$sourceNHost = new NHost( + $_ENV["NHOST_TEST_HOST"] ?? '', + $_ENV["NHOST_TEST_DATABASE"] ?? '', + $_ENV["NHOST_TEST_USERNAME"] ?? '', + $_ENV["NHOST_TEST_PASSWORD"] ?? '', +); + +$sourceSupabase = new Supabase( + $_ENV["SUPABASE_TEST_HOST"] ?? '', + $_ENV["SUPABASE_TEST_DATABASE"] ?? '', + $_ENV["SUPABASE_TEST_USERNAME"] ?? '', + $_ENV["SUPABASE_TEST_PASSWORD"] ?? '', +); + +/** Initialise All Destination Adapters */ +$destinationAppwrite = new AppwriteDestination( + $_ENV['DESTINATION_APPWRITE_TEST_PROJECT'], + $_ENV['DESTINATION_APPWRITE_TEST_ENDPOINT'], + $_ENV['DESTINATION_APPWRITE_TEST_KEY'] +); + +$destinationLocal = new Local(__DIR__ . '/databaseDMP.json'); + +/** Initialise Transfer Class */ + +$sourceFirebase->setProject($sourceFirebase->getProjects()[0]); + +$transfer = new Transfer( + $sourceFirebase, + $destinationLocal +); + +/** Run Transfer */ +$transfer->run([ + Transfer::RESOURCE_DATABASES +], function () {}); + +if (!empty($transfer->getLogs(Log::ERROR))) { + echo "\e[41m\e[97mFAILED\e[0m\n"; + + var_dump($transfer->getLogs(Log::ERROR)); +} + +var_dump($transfer->getLogs(Log::WARNING)); \ No newline at end of file From 20615de05e1892630a5e3f1e0db1fd5d56d66d16 Mon Sep 17 00:00:00 2001 From: Bradley Schofield Date: Thu, 16 Feb 2023 13:29:54 +0000 Subject: [PATCH 08/70] Add Subcollection Support for Firebase --- src/Transfer/Destinations/Appwrite.php | 82 +++++++++++++++---- tests/tmp/databaseDMP.json | 106 ++++++++++++++++++++++--- tests/tmp/playground.php | 6 +- 3 files changed, 163 insertions(+), 31 deletions(-) diff --git a/src/Transfer/Destinations/Appwrite.php b/src/Transfer/Destinations/Appwrite.php index f77340b..1ad7d11 100644 --- a/src/Transfer/Destinations/Appwrite.php +++ b/src/Transfer/Destinations/Appwrite.php @@ -26,10 +26,11 @@ use Utopia\Transfer\Resources\Attributes\URLAttribute; use Utopia\Transfer\Resources\Index; -class Appwrite extends Destination { +class Appwrite extends Destination +{ protected Client $client; - public function __construct(protected string $project, string $endpoint, private string $key) + public function __construct(protected string $project, string $endpoint, private string $key) { $this->client = (new Client()) ->setEndpoint($endpoint) @@ -42,7 +43,8 @@ public function __construct(protected string $project, string $endpoint, private * * @return string */ - public function getName(): string { + public function getName(): string + { return 'Appwrite'; } @@ -51,7 +53,8 @@ public function getName(): string { * * @return array */ - public function getSupportedResources(): array { + public function getSupportedResources(): array + { return [ Transfer::RESOURCE_USERS, Transfer::RESOURCE_DATABASES, @@ -64,7 +67,7 @@ public function getSupportedResources(): array { public function check(array $resources = []): bool { $auth = new Users($this->client); - + try { $auth->list(); } catch (\Exception $e) { @@ -86,7 +89,7 @@ public function importPasswordUser(User $user): array|null } switch ($hash->getAlgorithm()) { - case Hash::SCRYPT_MODIFIED: + case Hash::SCRYPT_MODIFIED: $result = $auth->createScryptModifiedUser( $user->getId(), $user->getEmail(), @@ -209,7 +212,7 @@ public function importUsers(array $users, callable $callback): void public function createAttribute(Attribute $attribute, Collection $collection, Database $database): void { $databaseService = new DatabasesService($this->client); - + try { switch ($attribute->getName()) { case Attribute::TYPE_STRING: @@ -310,14 +313,47 @@ public function importDatabases(array $databases, callable $callback): void /** @var Database $database */ try { $databaseService->create($database->getId(), $database->getDBName()); - + + $createdCollections = []; + foreach ($database->getCollections() as $collection) { /** @var Collection $collection */ - var_dump($collection->getCollectionName()); - var_dump(preg_replace('/[^a-zA-Z0-9_ -]/s','_', $collection->getCollectionName())); - $databaseService->createCollection($database->getId(), $collection->getId(), preg_replace('/[^a-zA-Z0-9_ -]/s','_', $collection->getCollectionName())); - - foreach ($collection->getAttributes() as $attribute) { + $createdAttributes = []; + + // Get filename and parent directory + $path = \explode('/', $collection->getCollectionName()); + + $collectionName = $path[count($path) - 1]; + + if (isset($path[count($path) - 2])) { + $collectionName = $path[count($path) - 2]."/".$collectionName; + } + + // Handle special chars + $collectionName = \str_replace([' ', '(', ')', '[', ']', '{', '}', '<', '>', ':', ';', ',', '.', '?', '\\', '|', '=', '+', '*', '&', '^', '%', '$', '#', '@', '!', '~', '`', '"', "'"], '_', $collectionName); + + // Check name length + if (\strlen($collectionName) > 120) { + $collectionName = \substr($collectionName, 0, 120); + } + + $newCollection = $databaseService->createCollection($database->getId(), "unique()", $collectionName); + $collection->setId($newCollection['$id']); + + // Remove duplicate attributes + $filteredAttributes = \array_filter($collection->getAttributes(), function ($attribute) use (&$createdAttributes) { + if (\in_array($attribute->getKey(), $createdAttributes)) { + return false; + } + + $createdAttributes[] = $attribute->getKey(); + + return true; + }); + + $filteredAttributes[] = new StringAttribute($collection->getId(), false, false, null, 1000000); + + foreach ($filteredAttributes as $attribute) { /** @var Attribute $attribute */ $this->createAttribute($attribute, $collection, $database); } @@ -326,18 +362,32 @@ public function importDatabases(array $databases, callable $callback): void $timeout = 0; while (!$this->validateAttributesCreation($collection->getAttributes(), $collection, $database)) { - if ($timeout > 10) { + if ($timeout > 60) { throw new AppwriteException('Timeout while waiting for attributes to be created'); } $timeout++; \sleep(1); } - + foreach ($collection->getIndexes() as $index) { /** @var Index $index */ $databaseService->createIndex($database->getId(), $collection->getId(), $index->getKey(), $index->getType(), $index->getAttributes(), $index->getOrders()); } + + $createdCollections[] = $collection; + } + + $refCollectionID = $databaseService->createCollection($database->getId(), 'refs', 'References')['$id']; + $databaseService->createStringAttribute($database->getId(), $refCollectionID, 'original_name', 1000000, true); + + sleep(2); + + foreach ($createdCollections as $collection) { + /** @var Collection $collection */ + $result = $databaseService->createDocument($database->getId(), $refCollectionID, $collection->getId(), [ + 'original_name' => $collection->getCollectionName() + ]); } $this->logs[Log::SUCCESS][] = new Log('Database imported successfully', \time(), $database); @@ -359,4 +409,4 @@ public function importDatabases(array $databases, callable $callback): void ) ); } -} \ No newline at end of file +} diff --git a/tests/tmp/databaseDMP.json b/tests/tmp/databaseDMP.json index 23f4aed..3334833 100644 --- a/tests/tmp/databaseDMP.json +++ b/tests/tmp/databaseDMP.json @@ -4,6 +4,20 @@ "name": "Default", "id": "Default", "collections": [ + { + "name": "test\/test2\/test2", + "id": "test\/test2\/test2", + "columns": [ + { + "key": "test", + "required": false, + "array": false, + "type": "stringAttribute", + "size": 1000000 + } + ], + "indexes": [] + }, { "name": "test\/test2\/test3", "id": "test\/test2\/test3", @@ -18,12 +32,40 @@ ], "indexes": [] }, + { + "name": "test\/test2\/test4", + "id": "test\/test2\/test4", + "columns": [ + { + "key": "asd", + "required": false, + "array": false, + "type": "stringAttribute", + "size": 1000000 + } + ], + "indexes": [] + }, { "name": "test", "id": "test", "columns": [ { - "key": "null", + "key": "array2", + "required": false, + "array": true, + "type": "stringAttribute", + "size": 1000000 + }, + { + "key": "boolean", + "required": false, + "array": false, + "type": "boolAttribute", + "default": null + }, + { + "key": "map", "required": false, "array": false, "type": "stringAttribute", @@ -43,6 +85,20 @@ "type": "stringAttribute", "size": 1000000 }, + { + "key": "null", + "required": false, + "array": false, + "type": "stringAttribute", + "size": 1000000 + }, + { + "key": "reference", + "required": false, + "array": false, + "type": "stringAttribute", + "size": 1000000 + }, { "key": "timestamp", "required": false, @@ -59,35 +115,63 @@ "default": null }, { - "key": "boolean", + "key": "test", "required": false, "array": false, - "type": "boolAttribute", - "default": null - }, + "type": "stringAttribute", + "size": 1000000 + } + ], + "indexes": [] + }, + { + "name": "users\/C2n8vRP0ldL1haX46zM5\/preferences", + "id": "users\/C2n8vRP0ldL1haX46zM5\/preferences", + "columns": [ { - "key": "array2", + "key": "test", "required": false, - "array": true, + "array": false, "type": "stringAttribute", "size": 1000000 - }, + } + ], + "indexes": [] + }, + { + "name": "users\/qN9XWIunUqKcnuwupjsl\/preferences", + "id": "users\/qN9XWIunUqKcnuwupjsl\/preferences", + "columns": [ { - "key": "map", + "key": "tet", "required": false, "array": false, "type": "stringAttribute", "size": 1000000 }, { - "key": "reference", + "key": "hjasbdjhsabd", + "required": false, + "array": false, + "type": "boolAttribute", + "default": null + } + ], + "indexes": [] + }, + { + "name": "users", + "id": "users", + "columns": [ + { + "key": "username", "required": false, "array": false, "type": "stringAttribute", "size": 1000000 }, { - "key": "test", + "key": "username", "required": false, "array": false, "type": "stringAttribute", diff --git a/tests/tmp/playground.php b/tests/tmp/playground.php index 37b0b7b..b7f6965 100644 --- a/tests/tmp/playground.php +++ b/tests/tmp/playground.php @@ -62,7 +62,7 @@ $transfer = new Transfer( $sourceFirebase, - $destinationLocal + $destinationAppwrite ); /** Run Transfer */ @@ -74,6 +74,4 @@ echo "\e[41m\e[97mFAILED\e[0m\n"; var_dump($transfer->getLogs(Log::ERROR)); -} - -var_dump($transfer->getLogs(Log::WARNING)); \ No newline at end of file +} \ No newline at end of file From 0f6e82b93e13ca2d0d75a7bcc1a47e236400da41 Mon Sep 17 00:00:00 2001 From: Bradley Schofield Date: Thu, 16 Feb 2023 13:37:33 +0000 Subject: [PATCH 09/70] Update Psalm --- composer.json | 2 +- src/Transfer/Source.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index c95186d..292bd55 100644 --- a/composer.json +++ b/composer.json @@ -28,7 +28,7 @@ "require-dev": { "phpunit/phpunit": "^9.3", "squizlabs/php_codesniffer": "^3.6", - "vimeo/psalm": "^5.4", + "vimeo/psalm": "^5.6", "vlucas/phpdotenv": "^5.5" } } diff --git a/src/Transfer/Source.php b/src/Transfer/Source.php index 95cc6a3..1fbfff4 100644 --- a/src/Transfer/Source.php +++ b/src/Transfer/Source.php @@ -265,7 +265,7 @@ protected function flatten(array $data, string $prefix = ''): array * @param int $batchSize * @param callable $callback Callback function to be called after each batch, $callback(user[] $batch); * - * @return User[] + * @return void */ public function exportUsers(int $batchSize, callable $callback): void { From 1c0792cac137ee04d19262aa4c38d847a73743fa Mon Sep 17 00:00:00 2001 From: Bradley Schofield Date: Thu, 16 Feb 2023 14:07:52 +0000 Subject: [PATCH 10/70] Update composer.json --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 292bd55..d96c8df 100644 --- a/composer.json +++ b/composer.json @@ -21,7 +21,7 @@ }, "require": { "php": ">=8.0", - "utopia-php/cli": "^0.14.0", + "utopia-php/cli": "^0.13.0", "google/apiclient": "^2.12.1", "appwrite/appwrite": "^7.2" }, From 4c9bddb3949b7a2ed18c6460beb85121e1c98399 Mon Sep 17 00:00:00 2001 From: Bradley Schofield Date: Thu, 16 Feb 2023 14:46:17 +0000 Subject: [PATCH 11/70] Add getCurrent resource and populate Progress class --- src/Transfer/Progress.php | 184 ++++++++++++++++++++++++++++++++++++++ src/Transfer/Transfer.php | 22 ++++- 2 files changed, 205 insertions(+), 1 deletion(-) diff --git a/src/Transfer/Progress.php b/src/Transfer/Progress.php index a02c097..8c1cbbd 100644 --- a/src/Transfer/Progress.php +++ b/src/Transfer/Progress.php @@ -12,4 +12,188 @@ function __construct( private int $failed = 0, private int $skipped = 0 ){} + + /** + * Get Resource Type + * + * @return string + */ + public function getResourceType(): string + { + return $this->resourceType; + } + + /** + * Set Resource Type + * + * @param string $resourceType + * @return self + */ + + public function setResourceType(string $resourceType): self + { + $this->resourceType = $resourceType; + return $this; + } + + /** + * Get Timestamp + * + * @return int + */ + public function getTimestamp(): int + { + return $this->timestamp; + } + + /** + * Set Timestamp + * + * @param int $timestamp + * @return self + */ + public function setTimestamp(int $timestamp): self + { + $this->timestamp = $timestamp; + return $this; + } + + /** + * Get Total + * + * @return int + */ + public function getTotal(): int + { + return $this->total; + } + + /** + * Set Total + * + * @param int $total + * @return self + */ + public function setTotal(int $total): self + { + $this->total = $total; + return $this; + } + + /** + * Get Current + * + * @return int + */ + public function getCurrent(): int + { + return $this->current; + } + + /** + * Set Current + * + * @param int $current + * @return self + */ + public function setCurrent(int $current): self + { + $this->current = $current; + return $this; + } + + /** + * Get Failed + * + * @return int + */ + public function getFailed(): int + { + return $this->failed; + } + + /** + * Set Failed + * + * @param int $failed + * @return self + */ + public function setFailed(int $failed): self + { + $this->failed = $failed; + return $this; + } + + /** + * Get Skipped + * + * @return int + */ + public function getSkipped(): int + { + return $this->skipped; + } + + /** + * Set Skipped + * + * @param int $skipped + * @return self + */ + public function setSkipped(int $skipped): self + { + $this->skipped = $skipped; + return $this; + } + + /** + * Get Progress + * + * @return float + */ + public function getProgress(): float + { + return ($this->current / $this->total) * 100; + } + + /** + * Get Remaining + * + * @return int + */ + public function getRemaining(): int + { + return $this->total - $this->current; + } + + /** + * Get ETA + * + * @return int + */ + public function getETA(): int + { + return 0; + } + + /** + * As Array + * + * @return array + */ + public function asArray(): array + { + return [ + 'resourceType' => $this->resourceType, + 'timestamp' => $this->timestamp, + 'total' => $this->total, + 'current' => $this->current, + 'failed' => $this->failed, + 'skipped' => $this->skipped, + 'progress' => $this->getProgress(), + 'remaining' => $this->getRemaining(), + 'eta' => $this->getETA(), + ]; + } + } \ No newline at end of file diff --git a/src/Transfer/Transfer.php b/src/Transfer/Transfer.php index d90e057..7bb5c9e 100644 --- a/src/Transfer/Transfer.php +++ b/src/Transfer/Transfer.php @@ -42,6 +42,11 @@ function __construct(Source $source, Destination $destination) */ protected Destination $destination; + /** + * @var string + */ + protected string $currentResource; + /** * Counters * @@ -127,7 +132,11 @@ function __construct(Source $source, Destination $destination) */ public function run(array $resources, callable $callback): void { - $this->destination->run($resources, $callback, $this->source); + $this->destination->run($resources, function (Progress $progress) use ($callback) { + $this->currentResource = $progress->getResourceType(); + + $callback($progress); + }, $this->source); } /** @@ -165,4 +174,15 @@ public function getResourceCache(): array { return $this->resources; } + + /** + * Get Current Resource + * + * @return string + **/ + + public function getCurrentResource(): string + { + return $this->currentResource; + } } From d707dc211365dd0252cf0ec9085b2abb6700a242 Mon Sep 17 00:00:00 2001 From: Bradley Schofield Date: Fri, 17 Feb 2023 13:26:27 +0000 Subject: [PATCH 12/70] Stop using 8.2 features --- composer.json | 1 - src/Transfer/Destinations/Appwrite.php | 10 +- src/Transfer/Destinations/Local.php | 8 +- src/Transfer/Log.php | 10 +- src/Transfer/Progress.php | 33 +++- src/Transfer/Resources/Attribute.php | 8 +- .../Resources/Attributes/BoolAttribute.php | 9 +- .../Attributes/DateTimeAttribute.php | 6 +- .../Resources/Attributes/EmailAttribute.php | 6 +- .../Resources/Attributes/EnumAttribute.php | 8 +- .../Resources/Attributes/FloatAttribute.php | 10 +- .../Resources/Attributes/IPAttribute.php | 6 +- .../Resources/Attributes/IntAttribute.php | 10 +- .../Resources/Attributes/StringAttribute.php | 8 +- .../Resources/Attributes/URLAttribute.php | 6 +- src/Transfer/Resources/Collection.php | 7 +- src/Transfer/Resources/Database.php | 8 +- src/Transfer/Resources/Hash.php | 20 +- src/Transfer/Resources/Index.php | 11 +- src/Transfer/Resources/Project.php | 8 +- src/Transfer/Resources/User.php | 48 +++-- src/Transfer/Sources/Firebase.php | 5 - src/Transfer/Sources/NHost.php | 49 ++++- src/Transfer/Sources/Supabase.php | 177 +----------------- tests/tmp/databaseDMP.json | 46 ++--- tests/tmp/playground.php | 2 +- 26 files changed, 266 insertions(+), 254 deletions(-) diff --git a/composer.json b/composer.json index d96c8df..55d5433 100644 --- a/composer.json +++ b/composer.json @@ -22,7 +22,6 @@ "require": { "php": ">=8.0", "utopia-php/cli": "^0.13.0", - "google/apiclient": "^2.12.1", "appwrite/appwrite": "^7.2" }, "require-dev": { diff --git a/src/Transfer/Destinations/Appwrite.php b/src/Transfer/Destinations/Appwrite.php index 1ad7d11..18fbaa3 100644 --- a/src/Transfer/Destinations/Appwrite.php +++ b/src/Transfer/Destinations/Appwrite.php @@ -30,8 +30,16 @@ class Appwrite extends Destination { protected Client $client; - public function __construct(protected string $project, string $endpoint, private string $key) + protected string $project; + protected string $endpoint; + protected string $key; + + public function __construct(string $project, string $endpoint, string $key) { + $this->project = $project; + $this->endpoint = $endpoint; + $this->key = $key; + $this->client = (new Client()) ->setEndpoint($endpoint) ->setProject($project) diff --git a/src/Transfer/Destinations/Local.php b/src/Transfer/Destinations/Local.php index a85128b..5bb2bb8 100644 --- a/src/Transfer/Destinations/Local.php +++ b/src/Transfer/Destinations/Local.php @@ -21,8 +21,12 @@ class Local extends Destination { private array $data = []; - public function __construct(protected string $path) - {} + protected string $path; + + public function __construct(string $path) + { + $this->path = $path; + } /** * Get Name diff --git a/src/Transfer/Log.php b/src/Transfer/Log.php index 8133fae..8f48a90 100644 --- a/src/Transfer/Log.php +++ b/src/Transfer/Log.php @@ -10,9 +10,15 @@ class Log { const SUCCESS = 'success'; const DEBUG = 'debug'; - public function __construct(private string $message = '', private int $timestamp = 0, protected Resource|null $resource = null) + private string $message = ''; + private int $timestamp = 0; + protected ?Resource $resource = null; + + public function __construct(string $message = '', int $timestamp = 0, ?Resource $resource = null) { - $timestamp = \time(); + $this->message = $message; + $this->timestamp = $timestamp ?? \time(); + $this->resource = $resource; } /** * Get Message diff --git a/src/Transfer/Progress.php b/src/Transfer/Progress.php index 8c1cbbd..a6dd5d7 100644 --- a/src/Transfer/Progress.php +++ b/src/Transfer/Progress.php @@ -4,15 +4,29 @@ class Progress { + private string $resourceType; + private int $timestamp; + private int $total = 0; + private int $current = 0; + private int $failed = 0; + private int $skipped = 0; + function __construct( - private string $resourceType, - private int $timestamp, - private int $total = 0, - private int $current = 0, - private int $failed = 0, - private int $skipped = 0 - ){} - + string $resourceType = '', + int $timestamp = 0, + int $total = 0, + int $current = 0, + int $failed = 0, + int $skipped = 0 + ) { + $this->resourceType = $resourceType; + $this->timestamp = $timestamp ?? \time(); + $this->total = $total; + $this->current = $current; + $this->failed = $failed; + $this->skipped = $skipped; + } + /** * Get Resource Type * @@ -195,5 +209,4 @@ public function asArray(): array 'eta' => $this->getETA(), ]; } - -} \ No newline at end of file +} diff --git a/src/Transfer/Resources/Attribute.php b/src/Transfer/Resources/Attribute.php index 25cd18e..bad8afc 100644 --- a/src/Transfer/Resources/Attribute.php +++ b/src/Transfer/Resources/Attribute.php @@ -15,6 +15,9 @@ class Attribute extends Resource { const TYPE_IP = 'IPAttribute'; const TYPE_URL = 'URLAttribute'; + protected string $key; + protected bool $required; + protected bool $array; /** * @param string $key @@ -22,8 +25,11 @@ class Attribute extends Resource { * @param bool $array * @param int $size */ - function __construct(protected string $key, protected bool $required, protected bool $array) + function __construct(string $key, bool $required = false, bool $array = false) { + $this->key = $key; + $this->required = $required; + $this->array = $array; } function getName(): string diff --git a/src/Transfer/Resources/Attributes/BoolAttribute.php b/src/Transfer/Resources/Attributes/BoolAttribute.php index e3e76ef..b2c6e87 100644 --- a/src/Transfer/Resources/Attributes/BoolAttribute.php +++ b/src/Transfer/Resources/Attributes/BoolAttribute.php @@ -5,14 +5,21 @@ use Utopia\Transfer\Resources\Attribute; class BoolAttribute extends Attribute { + protected string $key; + protected bool $required; + protected bool $array; + protected ?bool $default; + /** * @param string $key * @param bool $required * @param bool $array * @param ?bool $default */ - function __construct(protected string $key, protected bool $required, protected bool $array, protected ?bool $default) + function __construct(string $key, bool $required = false, bool $array = false, ?bool $default = null) { + parent::__construct($key, $required, $array); + $this->default = $default; } function getName(): string diff --git a/src/Transfer/Resources/Attributes/DateTimeAttribute.php b/src/Transfer/Resources/Attributes/DateTimeAttribute.php index 94ee7c9..cc3a06e 100644 --- a/src/Transfer/Resources/Attributes/DateTimeAttribute.php +++ b/src/Transfer/Resources/Attributes/DateTimeAttribute.php @@ -5,14 +5,18 @@ use Utopia\Transfer\Resources\Attribute; class DateTimeAttribute extends Attribute { + protected ?string $default; + /** * @param string $key * @param bool $required * @param bool $array * @param ?string $default */ - function __construct(protected string $key, protected bool $required, protected bool $array, protected ?string $default) + function __construct(string $key, bool $required = false, bool $array = false, ?string $default = null) { + parent::__construct($key, $required, $array); + $this->default = $default; } function getDefault(): ?string diff --git a/src/Transfer/Resources/Attributes/EmailAttribute.php b/src/Transfer/Resources/Attributes/EmailAttribute.php index aa714e2..7f9df3d 100644 --- a/src/Transfer/Resources/Attributes/EmailAttribute.php +++ b/src/Transfer/Resources/Attributes/EmailAttribute.php @@ -5,14 +5,18 @@ use Utopia\Transfer\Resources\Attribute; class EmailAttribute extends Attribute { + protected ?string $default; + /** * @param string $key * @param bool $required * @param bool $array * @param ?string $default */ - function __construct(protected string $key, protected bool $required, protected bool $array, protected ?string $default) + function __construct(string $key, bool $required = false, bool $array = false, ?string $default = null) { + parent::__construct($key, $required, $array); + $this->default = $default; } function getDefault(): ?string diff --git a/src/Transfer/Resources/Attributes/EnumAttribute.php b/src/Transfer/Resources/Attributes/EnumAttribute.php index acb8c1c..e5c9fa8 100644 --- a/src/Transfer/Resources/Attributes/EnumAttribute.php +++ b/src/Transfer/Resources/Attributes/EnumAttribute.php @@ -5,6 +5,9 @@ use Utopia\Transfer\Resources\Attribute; class EnumAttribute extends Attribute { + protected ?string $default; + protected array $elements; + /** * @param string $key * @param string[] $elements @@ -12,8 +15,11 @@ class EnumAttribute extends Attribute { * @param bool $array * @param ?string $default */ - function __construct(protected string $key, protected array $elements, protected bool $required, protected bool $array, protected ?string $default) + function __construct(string $key, array $elements, bool $required, bool $array, ?string $default) { + parent::__construct($key, $required, $array); + $this->default = $default; + $this->elements = $elements; } function getName(): string diff --git a/src/Transfer/Resources/Attributes/FloatAttribute.php b/src/Transfer/Resources/Attributes/FloatAttribute.php index 7dec074..ff46eb6 100644 --- a/src/Transfer/Resources/Attributes/FloatAttribute.php +++ b/src/Transfer/Resources/Attributes/FloatAttribute.php @@ -5,6 +5,10 @@ use Utopia\Transfer\Resources\Attribute; class FloatAttribute extends Attribute { + protected ?float $default; + protected float $min; + protected float $max; + /** * @param string $key * @param bool $required @@ -13,8 +17,12 @@ class FloatAttribute extends Attribute { * @param float $min * @param float $max */ - function __construct(protected string $key, protected bool $required, protected bool $array, protected ?float $default, protected float $min = 0, protected float $max = 0) + function __construct(string $key, bool $required = false, bool $array = false, ?float $default = null, float $min = 0, float $max = 0) { + parent::__construct($key, $required, $array); + $this->default = $default; + $this->min = $min; + $this->max = $max; } function getName(): string diff --git a/src/Transfer/Resources/Attributes/IPAttribute.php b/src/Transfer/Resources/Attributes/IPAttribute.php index 19ae736..ccca663 100644 --- a/src/Transfer/Resources/Attributes/IPAttribute.php +++ b/src/Transfer/Resources/Attributes/IPAttribute.php @@ -5,14 +5,18 @@ use Utopia\Transfer\Resources\Attribute; class IPAttribute extends Attribute { + protected ?string $default; + /** * @param string $key * @param bool $required * @param bool $array * @param string $default */ - function __construct(protected string $key, protected bool $required, protected bool $array, protected ?string $default) + function __construct(string $key, bool $required = false, bool $array = false, ?string $default = null) { + parent::__construct($key, $required, $array); + $this->default = $default; } function getDefault(): ?string diff --git a/src/Transfer/Resources/Attributes/IntAttribute.php b/src/Transfer/Resources/Attributes/IntAttribute.php index 10c9379..f92ad79 100644 --- a/src/Transfer/Resources/Attributes/IntAttribute.php +++ b/src/Transfer/Resources/Attributes/IntAttribute.php @@ -5,6 +5,10 @@ use Utopia\Transfer\Resources\Attribute; class IntAttribute extends Attribute { + protected ?int $default; + protected int $min; + protected int $max; + /** * @param string $key * @param bool $required @@ -13,8 +17,12 @@ class IntAttribute extends Attribute { * @param int $min * @param int $max */ - function __construct(protected string $key, protected bool $required, protected bool $array, protected ?int $default, protected int $min = 0, protected int $max = 0) + function __construct(string $key, bool $required = false, bool $array = false, ?int $default = null, int $min = 0, int $max = 0) { + parent::__construct($key, $required, $array); + $this->default = $default; + $this->min = $min; + $this->max = $max; } function getName(): string diff --git a/src/Transfer/Resources/Attributes/StringAttribute.php b/src/Transfer/Resources/Attributes/StringAttribute.php index b96b328..ee079ce 100644 --- a/src/Transfer/Resources/Attributes/StringAttribute.php +++ b/src/Transfer/Resources/Attributes/StringAttribute.php @@ -5,6 +5,9 @@ use Utopia\Transfer\Resources\Attribute; class StringAttribute extends Attribute { + protected ?string $default; + protected int $size = 256; + /** * @param string $key * @param bool $required @@ -12,8 +15,11 @@ class StringAttribute extends Attribute { * @param ?string $default * @param int $size */ - function __construct(protected string $key, protected bool $required, protected bool $array, protected ?string $default, protected int $size) + function __construct(string $key, bool $required = false, bool $array = false, ?string $default = null, int $size = 256) { + parent::__construct($key, $required, $array); + $this->default = $default; + $this->size = $size; } function getName(): string diff --git a/src/Transfer/Resources/Attributes/URLAttribute.php b/src/Transfer/Resources/Attributes/URLAttribute.php index c3b2c76..a88b77f 100644 --- a/src/Transfer/Resources/Attributes/URLAttribute.php +++ b/src/Transfer/Resources/Attributes/URLAttribute.php @@ -5,14 +5,18 @@ use Utopia\Transfer\Resources\Attribute; class URLAttribute extends Attribute { + protected ?string $default; + /** * @param string $key * @param bool $required * @param bool $array * @param ?string $default */ - function __construct(protected string $key, protected bool $required, protected bool $array, protected ?string $default) + function __construct(string $key, bool $required = false, bool $array = false, ?string $default = null) { + parent::__construct($key, $required, $array); + $this->default = $default; } function getDefault(): ?string diff --git a/src/Transfer/Resources/Collection.php b/src/Transfer/Resources/Collection.php index 6b11e3f..607d23d 100644 --- a/src/Transfer/Resources/Collection.php +++ b/src/Transfer/Resources/Collection.php @@ -17,8 +17,13 @@ class Collection extends Resource */ private array $indexes = []; - public function __construct(protected string $name, protected string $id) + protected string $name; + protected string $id; + + public function __construct(string $name = '', string $id = '') { + $this->name = $name; + $this->id = $id; } public function getName(): string diff --git a/src/Transfer/Resources/Database.php b/src/Transfer/Resources/Database.php index 9989c03..4bd3fae 100644 --- a/src/Transfer/Resources/Database.php +++ b/src/Transfer/Resources/Database.php @@ -19,9 +19,13 @@ class Database extends Resource */ private array $collections = []; - public function __construct(protected string $name, protected string $id) + protected string $name; + protected string $id; + + public function __construct(string $name = '', string $id = '') { - + $this->name = $name; + $this->id = $id; } public function getName(): string diff --git a/src/Transfer/Resources/Hash.php b/src/Transfer/Resources/Hash.php index 4c39223..b909cdb 100644 --- a/src/Transfer/Resources/Hash.php +++ b/src/Transfer/Resources/Hash.php @@ -17,9 +17,27 @@ class Hash extends Resource { public const PHPASS = 'PHPass'; public const SCRYPT = 'Scrypt'; + private string $hash; + private string $salt = ''; + private string $algorithm = self::SHA256; + private string $separator = ''; + private string $signingKey = ''; + private int $passwordCpu = 0; + private int $passwordMemory = 0; + private int $passwordParallel = 0; + private int $passwordLength = 0; - public function __construct(private string $hash, private string $salt = '', private string $algorithm = self::SHA256, private string $separator = '', private string $signingKey = '', private int $passwordCpu = 0, private int $passwordMemory = 0, private int $passwordParallel = 0, private int $passwordLength = 0) + public function __construct(string $hash, string $salt = '', string $algorithm = self::SHA256, string $separator = '', string $signingKey = '', int $passwordCpu = 0, int $passwordMemory = 0, int $passwordParallel = 0, int $passwordLength = 0) { + $this->hash = $hash; + $this->salt = $salt; + $this->algorithm = $algorithm; + $this->separator = $separator; + $this->signingKey = $signingKey; + $this->passwordCpu = $passwordCpu; + $this->passwordMemory = $passwordMemory; + $this->passwordParallel = $passwordParallel; + $this->passwordLength = $passwordLength; } public function getName(): string diff --git a/src/Transfer/Resources/Index.php b/src/Transfer/Resources/Index.php index 7d31379..24ecf1d 100644 --- a/src/Transfer/Resources/Index.php +++ b/src/Transfer/Resources/Index.php @@ -6,6 +6,11 @@ class Index extends Resource { + protected string $key; + protected string $type; + protected array $attributes; + protected array $orders; + /** * @param string $key * @param string $type @@ -13,8 +18,12 @@ class Index extends Resource { * @param array $orders */ - public function __construct(protected string $key, protected string $type, protected array $attributes, protected array $orders) + public function __construct(string $key, string $type = '', array $attributes = [], array $orders = []) { + $this->key = $key; + $this->type = $type; + $this->attributes = $attributes; + $this->orders = $orders; } public function getName(): string diff --git a/src/Transfer/Resources/Project.php b/src/Transfer/Resources/Project.php index 12a2cfb..d18571d 100644 --- a/src/Transfer/Resources/Project.php +++ b/src/Transfer/Resources/Project.php @@ -6,9 +6,13 @@ class Project extends Resource { - public function __construct(protected string $name, protected string $id) + protected string $name; + protected string $id; + + public function __construct(string $name = '', string $id = '') { - + $this->name = $name; + $this->id = $id; } public function getName(): string diff --git a/src/Transfer/Resources/User.php b/src/Transfer/Resources/User.php index 1f4906e..ee7ffbb 100644 --- a/src/Transfer/Resources/User.php +++ b/src/Transfer/Resources/User.php @@ -13,19 +13,43 @@ class User extends Resource const TYPE_MAGIC = 'magic'; const TYPE_OAUTH = 'oauth'; + private string $id = ''; + private string $email = ''; + private string $username = ''; + private Hash $passwordHash = new Hash(''); + private string $phone = ''; + private array $types = [Self::TYPE_ANONYMOUS]; + private string $oauthProvider = ''; + private bool $emailVerified = false; + private bool $phoneVerified = false; + private bool $disabled = false; + private array $preferences = []; + public function __construct( - protected string $id = '', - protected string $email = '', - protected string $username = '', - protected Hash $passwordHash = new Hash(''), - protected string $phone = '', - protected array $types = [Self::TYPE_ANONYMOUS], - protected string $oauthProvider = '', - protected bool $emailVerified = false, - protected bool $phoneVerified = false, - protected bool $disabled = false, - protected array $preferences = [] - ){} + string $id = '', + string $email = '', + string $username = '', + Hash $passwordHash = new Hash(''), + string $phone = '', + array $types = [Self::TYPE_ANONYMOUS], + string $oauthProvider = '', + bool $emailVerified = false, + bool $phoneVerified = false, + bool $disabled = false, + array $preferences = [] + ){ + $this->id = $id; + $this->email = $email; + $this->username = $username; + $this->passwordHash = $passwordHash; + $this->phone = $phone; + $this->types = $types; + $this->oauthProvider = $oauthProvider; + $this->emailVerified = $emailVerified; + $this->phoneVerified = $phoneVerified; + $this->disabled = $disabled; + $this->preferences = $preferences; + } /** * Get Name diff --git a/src/Transfer/Sources/Firebase.php b/src/Transfer/Sources/Firebase.php index ee44677..2a799aa 100644 --- a/src/Transfer/Sources/Firebase.php +++ b/src/Transfer/Sources/Firebase.php @@ -2,16 +2,13 @@ namespace Utopia\Transfer\Sources; -use Attribute; use Utopia\Transfer\Source; use Google\Client; -use Google\Service\Firestore\BatchGetDocumentsRequest; use Google\Service\Firestore\ListCollectionIdsRequest; use Utopia\Transfer\Resources\Project; use Utopia\Transfer\Resources\User; use Utopia\Transfer\Transfer; use Utopia\Transfer\Log; -use Utopia\Transfer\Resource; use Utopia\Transfer\Resources\Attribute as ResourcesAttribute; use Utopia\Transfer\Resources\Attributes\BoolAttribute; use Utopia\Transfer\Resources\Attributes\DateTimeAttribute; @@ -22,8 +19,6 @@ use Utopia\Transfer\Resources\Database; use Utopia\Transfer\Resources\Hash; -use function PHPSTORM_META\type; - class Firebase extends Source { const TYPE_OAUTH = 'oauth'; diff --git a/src/Transfer/Sources/NHost.php b/src/Transfer/Sources/NHost.php index 6d571a1..2a1dd13 100644 --- a/src/Transfer/Sources/NHost.php +++ b/src/Transfer/Sources/NHost.php @@ -23,9 +23,50 @@ class NHost extends Source */ public $pdo; - function __construct(private string $host, private string $databaseName, private string $username, private string $password, private string $port = '5432') + /** + * @var string + */ + public string $host; + + /** + * @var string + */ + public string $databaseName; + + /** + * @var string + */ + public string $username; + + /** + * @var string + */ + public string $password; + + /** + * @var string + */ + public string $port; + + /** + * Constructor + * + * @param string $host + * @param string $databaseName + * @param string $username + * @param string $password + * @param string $port + * + * @return self + */ + function __construct(string $host, string $databaseName, string $username, string $password, string $port = '5432') { - $this->pdo = new \PDO("pgsql:host={$this->host};port={$this->port};dbname={$this->databaseName}", $this->username, $this->password); + $this->host = $host; + $this->databaseName = $databaseName; + $this->username = $username; + $this->password = $password; + $this->port = $port; + $this->pdo = new \PDO("pgsql:host=".$this->host.";port=".$this->port.";dbname=".$this->databaseName, $this->username, $this->password); } function getName(): string @@ -93,7 +134,7 @@ public function exportUsers(int $batchSize, callable $callback): void * @param string $tableName * @return Collection */ - private function convertCollection(string $tableName): Collection + public function convertCollection(string $tableName): Collection { $statement = $this->pdo->prepare('SELECT * FROM information_schema."columns" where "table_name" = :tableName'); $statement->bindValue(':tableName', $tableName, \PDO::PARAM_STR); @@ -118,7 +159,7 @@ private function convertCollection(string $tableName): Collection * @param array $column * @return Attribute */ - private function convertAttribute(array $column): Attribute + public function convertAttribute(array $column): Attribute { $isArray = $column['data_type'] === 'ARRAY'; diff --git a/src/Transfer/Sources/Supabase.php b/src/Transfer/Sources/Supabase.php index e4f569a..88266df 100644 --- a/src/Transfer/Sources/Supabase.php +++ b/src/Transfer/Sources/Supabase.php @@ -2,34 +2,12 @@ namespace Utopia\Transfer\Sources; -use Exception; -use Utopia\Transfer\Source; use Utopia\Transfer\Resources\User; use Utopia\Transfer\Transfer; -use Utopia\Transfer\Log; use Utopia\Transfer\Resources\Hash; -use Utopia\Transfer\Resources\Attributes\BoolAttribute; -use Utopia\Transfer\Resources\Attributes\DateTimeAttribute; -use Utopia\Transfer\Resources\Attributes\FloatAttribute; -use Utopia\Transfer\Resources\Attributes\IntAttribute; -use Utopia\Transfer\Resources\Attributes\StringAttribute; -use Utopia\Transfer\Resources\Collection; -use Utopia\Transfer\Resources\Attribute; -use Utopia\Transfer\Resources\Database; -use Utopia\Transfer\Resources\Index; -class Supabase extends Source +class Supabase extends NHost { - /** - * @var \PDO - */ - public $pdo; - - function __construct(private string $host, private string $databaseName, private string $username, private string $password, private string $port = '5432') - { - $this->pdo = new \PDO("pgsql:host={$this->host};port={$this->port};dbname={$this->databaseName}", $this->username, $this->password); - } - function getName(): string { return 'Supabase'; @@ -89,136 +67,6 @@ public function exportUsers(int $batchSize, callable $callback): void } } - /** - * Convert Collection - * - * @param string $tableName - * @return Collection - */ - private function convertCollection(string $tableName): Collection - { - $statement = $this->pdo->prepare('SELECT * FROM information_schema."columns" where "table_name" = :tableName'); - $statement->bindValue(':tableName', $tableName, \PDO::PARAM_STR); - $statement->execute(); - $databaseCollection = $statement->fetchAll(\PDO::FETCH_ASSOC); - - $convertedCollection = new Collection($tableName, $tableName); - - $attributes = []; - - foreach ($databaseCollection as $column) { - $attributes[] = $this->convertAttribute($column); - } - $convertedCollection->setAttributes($attributes); - - return $convertedCollection; - } - - /** - * Convert Attribute - * - * @param array $column - * @return Attribute - */ - private function convertAttribute(array $column): Attribute - { - $isArray = $column['data_type'] === 'ARRAY'; - - switch ($isArray ? str_replace('_', '', $column['udt_name']) : $column['data_type']) { - // Numbers - case 'boolean': - case 'bool': - return new BoolAttribute($column['column_name'], $column['is_nullable'] === 'NO', $isArray, $column['column_default']); - case 'smallint': - case 'int2': - return new IntAttribute($column['column_name'], $column['is_nullable'] === 'NO', $isArray, $column['column_default'], -32768, 32767); - case 'integer': - case 'int4': - return new IntAttribute($column['column_name'], $column['is_nullable'] === 'NO', $isArray, $column['column_default'], -2147483648, 2147483647); - case 'bigint': - case 'int8': - case 'numeric': - return new IntAttribute($column['column_name'], $column['is_nullable'] === 'NO', $isArray, $column['column_default']); - case 'decimal': - case 'real': - case 'double precision': - case 'float4': - case 'float8': - return new FloatAttribute($column['column_name'], $column['is_nullable'] === 'NO', $isArray, $column['column_default']); - // Time (Conversion happens with documents) - case 'timestamp with time zone': - case 'date': - case 'time with time zone': - case 'timestamp without time zone': - case 'timestamptz': - case 'timestamp': - case 'time': - case 'timetz': - return new DateTimeAttribute($column['column_name'], $column['is_nullable'] === 'NO', $isArray, ($column['column_default'] === 'now()') ? 'now()' : ($column['column_default'] ?? null)); - break; - // Strings and Objects - case 'uuid': - case 'character varying': - case 'text': - case 'character': - case 'json': - case 'jsonb': - case 'varchar': - return new StringAttribute($column['column_name'], $column['is_nullable'] === 'NO', $isArray, - $column['column_default'], - $column['character_maximum_length'] ?? $column['character_octet_length'] ?? 10485760); - break; - default: { - $this->logs[Log::WARNING][] = new Log('Unknown data type: ' . $column['data_type'] . ' for column: ' . $column['column_name'] . ' Falling back to string.', \time()); - return new StringAttribute($column['column_name'], $column['is_nullable'] === 'NO', $isArray, - $column['column_default'], - $column['character_maximum_length'] ?? $column['character_octet_length'] ?? 10485760); - break; - } - } - } - - /** - * Export Databases - * - * @param int $batchSize Max 100 - * @param callable $callback Callback function to be called after each database, $callback(database[] $batch); - * - * @return void - */ - public function exportDatabases(int $batchSize, callable $callback): void - { - $total = $this->pdo->query('SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = \'public\'')->fetchColumn(); - - $offset = 0; - - // We'll only transfer the public database for now, since it's the only one that exists by default. - //TODO: Handle edge cases where there are user created databases. - - $transferDatabase = new Database('public', 'public'); - - while ($offset < $total) { - $statement = $this->pdo->prepare('SELECT table_name FROM information_schema.tables WHERE table_schema = \'public\' order by table_name LIMIT :limit OFFSET :offset'); - $statement->bindValue(':limit', $batchSize, \PDO::PARAM_INT); - $statement->bindValue(':offset', $offset, \PDO::PARAM_INT); - $statement->execute(); - - $tables = $statement->fetchAll(\PDO::FETCH_ASSOC); - - $offset += $batchSize; - - $transferCollections = []; - - foreach ($tables as $table) { - $transferCollections[] = $this->convertCollection($table['table_name']); - } - - $transferDatabase->setCollections($transferCollections); - } - - $callback([$transferDatabase]); - } - private function calculateAuthTypes(array $user): array { if (empty($user['encrypted_password']) && empty($user['phone'])) @@ -240,27 +88,4 @@ private function calculateAuthTypes(array $user): array return $types; } - - function check(array $resources = []): bool - { - if ($this->pdo->errorCode() !== '00000') { - $this->logs[Log::FATAL] = new Log('Failed to connect to database. Error: ' . $this->pdo->errorInfo()[2]); - } - - foreach ($resources as $resource) - { - switch ($resource) { - case Transfer::RESOURCE_USERS: - $statement = $this->pdo->prepare('SELECT COUNT(*) FROM auth.users'); - $statement->execute(); - - if ($statement->errorCode() !== '00000') { - $this->logs[Log::FATAL] = new Log('Failed to access users table. Error: ' . $statement->errorInfo()[2]); - } - break; - } - } - - return true; - } } \ No newline at end of file diff --git a/tests/tmp/databaseDMP.json b/tests/tmp/databaseDMP.json index 3334833..aadc5d9 100644 --- a/tests/tmp/databaseDMP.json +++ b/tests/tmp/databaseDMP.json @@ -51,9 +51,16 @@ "id": "test", "columns": [ { - "key": "array2", + "key": "string", "required": false, - "array": true, + "array": false, + "type": "stringAttribute", + "size": 1000000 + }, + { + "key": "null", + "required": false, + "array": false, "type": "stringAttribute", "size": 1000000 }, @@ -65,54 +72,47 @@ "default": null }, { - "key": "map", + "key": "reference", "required": false, "array": false, "type": "stringAttribute", "size": 1000000 }, { - "key": "string", + "key": "timestamp", "required": false, "array": false, - "type": "stringAttribute", - "size": 1000000 + "type": "dateTimeAttribute" }, { - "key": "geopoint", + "key": "map", "required": false, "array": false, "type": "stringAttribute", "size": 1000000 }, { - "key": "null", + "key": "number", "required": false, "array": false, - "type": "stringAttribute", - "size": 1000000 + "type": "intAttribute", + "min": 0, + "max": 0, + "default": null }, { - "key": "reference", + "key": "geopoint", "required": false, "array": false, "type": "stringAttribute", "size": 1000000 }, { - "key": "timestamp", - "required": false, - "array": false, - "type": "dateTimeAttribute" - }, - { - "key": "number", + "key": "array2", "required": false, - "array": false, - "type": "intAttribute", - "min": 0, - "max": 0, - "default": null + "array": true, + "type": "stringAttribute", + "size": 1000000 }, { "key": "test", diff --git a/tests/tmp/playground.php b/tests/tmp/playground.php index b7f6965..71fdbb9 100644 --- a/tests/tmp/playground.php +++ b/tests/tmp/playground.php @@ -62,7 +62,7 @@ $transfer = new Transfer( $sourceFirebase, - $destinationAppwrite + $destinationLocal ); /** Run Transfer */ From a33d9e16e561885e884d1ed21982e41942b33fee Mon Sep 17 00:00:00 2001 From: Bradley Schofield Date: Fri, 17 Feb 2023 13:39:23 +0000 Subject: [PATCH 13/70] Fix User.php --- src/Transfer/Resources/User.php | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/Transfer/Resources/User.php b/src/Transfer/Resources/User.php index ee7ffbb..aa5d8d5 100644 --- a/src/Transfer/Resources/User.php +++ b/src/Transfer/Resources/User.php @@ -13,23 +13,23 @@ class User extends Resource const TYPE_MAGIC = 'magic'; const TYPE_OAUTH = 'oauth'; - private string $id = ''; - private string $email = ''; - private string $username = ''; - private Hash $passwordHash = new Hash(''); - private string $phone = ''; - private array $types = [Self::TYPE_ANONYMOUS]; - private string $oauthProvider = ''; - private bool $emailVerified = false; - private bool $phoneVerified = false; - private bool $disabled = false; - private array $preferences = []; + protected string $id = ''; + protected string $email = ''; + protected string $username = ''; + protected ?Hash $passwordHash = null; + protected string $phone = ''; + protected array $types = [Self::TYPE_ANONYMOUS]; + protected string $oauthProvider = ''; + protected bool $emailVerified = false; + protected bool $phoneVerified = false; + protected bool $disabled = false; + protected array $preferences = []; public function __construct( string $id = '', string $email = '', string $username = '', - Hash $passwordHash = new Hash(''), + ?Hash $passwordHash = null, string $phone = '', array $types = [Self::TYPE_ANONYMOUS], string $oauthProvider = '', From fb05b47c76553a0de41a5ea3be97e78b0b4c425c Mon Sep 17 00:00:00 2001 From: Bradley Schofield Date: Thu, 23 Feb 2023 23:55:27 +0000 Subject: [PATCH 14/70] Improve Checks function, start work on rewriting tests --- src/Transfer/Destination.php | 7 +- src/Transfer/Destinations/Appwrite.php | 56 +++++- src/Transfer/Destinations/Local.php | 15 +- src/Transfer/Source.php | 7 +- src/Transfer/Sources/Appwrite.php | 65 +++++- src/Transfer/Sources/Firebase.php | 25 ++- src/Transfer/Sources/NHost.php | 22 ++- tests/Transfer/Destinations/AppwriteTest.php | 4 +- tests/Transfer/ProviderTest.phpignore | 69 +++++++ ...ppwriteTest.php => AppwriteSourceTest.php} | 16 +- tests/tmp/databaseDMP.json | 187 +----------------- tests/tmp/playground.php | 4 +- 12 files changed, 252 insertions(+), 225 deletions(-) create mode 100644 tests/Transfer/ProviderTest.phpignore rename tests/Transfer/Sources/{AppwriteTest.php => AppwriteSourceTest.php} (77%) diff --git a/src/Transfer/Destination.php b/src/Transfer/Destination.php index 6ba802e..ee25b8a 100644 --- a/src/Transfer/Destination.php +++ b/src/Transfer/Destination.php @@ -163,12 +163,13 @@ public function run(array $resources, callable $callback, Source $source): void * This is highly recommended to be called before any other method after initialization. * * If no resources are provided, the method should check all resources. + * Returns an array of working resources. * - * @array $resources + * @string[] $resources * - * @return bool + * @return string[] */ - abstract public function check(array $resources = []): bool; + abstract public function check(array $resources = []): array; /** * Call diff --git a/src/Transfer/Destinations/Appwrite.php b/src/Transfer/Destinations/Appwrite.php index 18fbaa3..a82667a 100644 --- a/src/Transfer/Destinations/Appwrite.php +++ b/src/Transfer/Destinations/Appwrite.php @@ -10,6 +10,7 @@ use Utopia\Transfer\Resources\Hash; use Utopia\Transfer\Log; use Utopia\Transfer\Progress; +use Utopia\Transfer\Resource; use Utopia\Transfer\Resources\Attribute; use Utopia\Transfer\Resources\User; use Utopia\Transfer\Transfer; @@ -72,18 +73,57 @@ public function getSupportedResources(): array ]; } - public function check(array $resources = []): bool + public function check(array $resources = []): array { - $auth = new Users($this->client); + $completedResources = []; - try { - $auth->list(); - } catch (\Exception $e) { - $this->logs[Log::ERROR] = new Log($e->getMessage()); - return false; + if (empty($resources)) { + $resources = $this->getSupportedResources(); } - return true; + foreach ($resources as $resource) { + switch ($resource) { + case Transfer::RESOURCE_DATABASES: + $databases = new DatabasesService($this->client); + try { + $databases->list(); + } catch (\Exception $e) { + $this->logs[Log::ERROR] = new Log($e->getMessage()); + return false; + } + break; + case Transfer::RESOURCE_USERS: + $auth = new Users($this->client); + try { + $auth->list(); + } catch (\Exception $e) { + $this->logs[Log::ERROR] = new Log($e->getMessage()); + return false; + } + break; + case Transfer::RESOURCE_DOCUMENTS: + $databases = new DatabasesService($this->client); + try { + $database = $databases->list()[0]; + $collection = $databases->listCollections($database['$id'])[0]; + $documents = $databases->listDocuments($database, $collection['$id']); + $document = $database->getDocument($documents[0]['$id']); + + if (empty($document)) { + $this->logs[Log::ERROR] = new Log('Failed to get document'); + return false; + } + } catch (\Exception $e) { + $this->logs[Log::ERROR] = new Log($e->getMessage()); + return false; + } + break; + } + + $completedResources[] = $resource; + } + + return $completedResources; } public function importPasswordUser(User $user): array|null diff --git a/src/Transfer/Destinations/Local.php b/src/Transfer/Destinations/Local.php index 5bb2bb8..060fb4a 100644 --- a/src/Transfer/Destinations/Local.php +++ b/src/Transfer/Destinations/Local.php @@ -56,11 +56,20 @@ public function getSupportedResources(): array { * Check if destination is valid * * @param array $resources - * @return bool + * @return array */ - public function check(array $resources = []): bool + public function check(array $resources = []): array { - return true; + if (empty($resources)) { + $resources = $this->getSupportedResources(); + } + + // Check we can write to the file + if (!\is_writable($this->path)) { + throw new \Exception('Unable to write to file: ' . $this->path); + } + + return $resources; } public function syncFile(): void diff --git a/src/Transfer/Source.php b/src/Transfer/Source.php index 1fbfff4..5af0b04 100644 --- a/src/Transfer/Source.php +++ b/src/Transfer/Source.php @@ -138,12 +138,13 @@ public function run(array $resources, callable $callback): void { * This is highly recommended to be called before any other method after initialization. * * If no resources are provided, the method should check all resources. + * Returns an array of working resources. * - * @array $resources + * @string[] $resources * - * @return bool + * @return string[] */ - abstract public function check(array $resources = []): bool; + abstract public function check(array $resources = []): array; /** * Call diff --git a/src/Transfer/Sources/Appwrite.php b/src/Transfer/Sources/Appwrite.php index 0644fe5..b10a6f8 100644 --- a/src/Transfer/Sources/Appwrite.php +++ b/src/Transfer/Sources/Appwrite.php @@ -5,6 +5,8 @@ use Utopia\Transfer\Source; use Appwrite\Client; use Appwrite\Query; +use Appwrite\Services\Databases; +use Appwrite\Services\Users; use Utopia\Transfer\Resources\Attribute; use Utopia\Transfer\Resources\Project; use Utopia\Transfer\Resources\User; @@ -30,7 +32,7 @@ class Appwrite extends Source /** * @var Client|null */ - protected $appwriteClient = null; + protected $client = null; /** * Constructor @@ -43,7 +45,7 @@ class Appwrite extends Source */ function __construct(string $project, string $endpoint, string $key) { - $this->appwriteClient = (new Client()) + $this->client = (new Client()) ->setEndpoint($endpoint) ->setProject($project) ->setKey($key); @@ -77,11 +79,59 @@ public function getSupportedResources(): array * * @param array $resources * - * @return bool + * @return array */ - public function check(array $resources = []): bool + public function check(array $resources = []): array { - return true; + $completedResources = []; + + if (empty($resources)) { + $resources = $this->getSupportedResources(); + } + + foreach ($resources as $resource) { + switch ($resource) { + case Transfer::RESOURCE_DATABASES: + $databases = new Databases($this->client); + try { + $databases->list(); + } catch (\Exception $e) { + $this->logs[Log::ERROR] = new Log($e->getMessage()); + return false; + } + break; + case Transfer::RESOURCE_USERS: + $auth = new Users($this->client); + try { + $auth->list(); + } catch (\Exception $e) { + $this->logs[Log::ERROR] = new Log($e->getMessage()); + return false; + } + break; + case Transfer::RESOURCE_DOCUMENTS: + $databases = new Databases($this->client); + try { + $database = $databases->list()[0]; + $collection = $databases->listCollections($database['$id'])[0]; + $documents = $databases->listDocuments($database, $collection['$id']); + $document = $database->getDocument($documents[0]['$id']); + + if (empty($document)) { + $this->logs[Log::ERROR] = new Log('Failed to get document'); + return false; + } + } catch (\Exception $e) { + $this->logs[Log::ERROR] = new Log($e->getMessage()); + return false; + } + break; + } + + $completedResources[] = $resource; + } + + return $completedResources; } /** @@ -94,7 +144,7 @@ public function check(array $resources = []): bool */ public function exportUsers(int $batchSize, callable $callback): void { - $usersClient = new \Appwrite\Services\Users($this->appwriteClient); + $usersClient = new Users($this->client); $lastDocument = null; @@ -109,7 +159,6 @@ public function exportUsers(int $batchSize, callable $callback): void $queries[] = Query::cursorAfter($lastDocument); } - $response = $usersClient->list($queries); foreach ($response['users'] as $user) { @@ -182,7 +231,7 @@ function convertAttribute(array $value): Attribute */ public function exportDatabases(int $batchSize, callable $callback): void { - $databaseClient = new \Appwrite\Services\Databases($this->appwriteClient); + $databaseClient = new Databases($this->client); $lastDocument = null; diff --git a/src/Transfer/Sources/Firebase.php b/src/Transfer/Sources/Firebase.php index 2a799aa..4247d40 100644 --- a/src/Transfer/Sources/Firebase.php +++ b/src/Transfer/Sources/Firebase.php @@ -400,8 +400,14 @@ function calculateTypes(array $providerData): array return $types; } - function check(array $resources = []): bool + function check(array $resources = []): array { + $completedResources = []; + + if (empty($resources)) { + $resources = $this->getSupportedResources(); + } + if (!$this->googleClient) { $this->logs[Log::FATAL][] = new Log('Google Client not initialized'); return false; @@ -438,10 +444,25 @@ function check(array $resources = []): bool return false; } + $completedResources[] = Transfer::RESOURCE_USERS; + break; + case Transfer::RESOURCE_DATABASES: + $firestore = new \Google\Service\Firestore($this->googleClient); + + $request = $firestore->projects_databases_documents->listDocuments('projects/' . $this->project->getId() . '/databases/(default)/documents', '', [ + 'pageSize' => 1 + ]); + + if (!$request['documents']) { + $this->logs[Log::FATAL][] = new Log('Unable to fetch documents'); + return false; + } + + $completedResources[] = Transfer::RESOURCE_DATABASES; break; } } - return true; + return $completedResources; } } diff --git a/src/Transfer/Sources/NHost.php b/src/Transfer/Sources/NHost.php index 2a1dd13..63ce1dc 100644 --- a/src/Transfer/Sources/NHost.php +++ b/src/Transfer/Sources/NHost.php @@ -288,12 +288,18 @@ private function calculateUserTypes(array $user): array return $types; } - function check(array $resources = []): bool + function check(array $resources = []): array { + if (empty($resources)) { + $resources = $this->getSupportedResources(); + } + if ($this->pdo->errorCode() !== '00000') { $this->logs[Log::FATAL] = new Log('Failed to connect to database. Error: ' . $this->pdo->errorInfo()[2]); } + $completedResources = []; + foreach ($resources as $resource) { switch ($resource) { case Transfer::RESOURCE_USERS: @@ -303,10 +309,22 @@ function check(array $resources = []): bool if ($statement->errorCode() !== '00000') { $this->logs[Log::FATAL] = new Log('Failed to access users table. Error: ' . $statement->errorInfo()[2]); } + + $completedResources[] = Transfer::RESOURCE_USERS; + break; + case Transfer::RESOURCE_DATABASES: + $statement = $this->pdo->prepare('SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = \'public\''); + $statement->execute(); + + if ($statement->errorCode() !== '00000') { + $this->logs[Log::FATAL] = new Log('Failed to access tables table. Error: ' . $statement->errorInfo()[2]); + } + + $completedResources[] = Transfer::RESOURCE_DATABASES; break; } } - return true; + return $completedResources; } } diff --git a/tests/Transfer/Destinations/AppwriteTest.php b/tests/Transfer/Destinations/AppwriteTest.php index 7f31ff9..5cfc237 100644 --- a/tests/Transfer/Destinations/AppwriteTest.php +++ b/tests/Transfer/Destinations/AppwriteTest.php @@ -37,13 +37,13 @@ public function setUp(): void { $this->appwrite = new Appwrite( getenv("DESTINATION_APPWRITE_TEST_PROJECT"), - getenv("APPWRITE_TEST_ENDPOINT"), + getenv("DESTINATION_APPWRITE_TEST_ENDPOINT"), getenv("DESTINATION_APPWRITE_TEST_KEY") ); $this->client = new Client(); $this->client - ->setEndpoint(getenv("APPWRITE_TEST_ENDPOINT")) + ->setEndpoint(getenv("DESTINATION_APPWRITE_TEST_ENDPOINT")) ->setProject(getenv("DESTINATION_APPWRITE_TEST_PROJECT")) ->setKey(getenv("DESTINATION_APPWRITE_TEST_KEY")); } diff --git a/tests/Transfer/ProviderTest.phpignore b/tests/Transfer/ProviderTest.phpignore new file mode 100644 index 0000000..438fea7 --- /dev/null +++ b/tests/Transfer/ProviderTest.phpignore @@ -0,0 +1,69 @@ +assertIsObject($this->provider); + $this->assertNotEmpty($this->provider->getSupportedResources()); + } + + public function testExportUsers() + { + $result = []; + + $this->provider->exportUsers(100, function (array $users) use (&$result) { + $result = array_merge($result, $users); + }); + + foreach ($result as $user) { + /** @var User $user */ + $this->assertIsObject($user); + } + + $this->assertIsArray($result); + $this->assertNotEmpty($result); + } + + public function testExportDatabases() + { + $result = []; + + $this->provider->exportDatabases(100, function (array $databases) use (&$result) { + $result = array_merge($result, $databases); + }); + + foreach ($result as $database) { + /** @var Database $database */ + $this->assertIsObject($database); + $this->assertNotEmpty($database->getCollections()); + } + + $this->assertIsArray($result); + $this->assertNotEmpty($result); + } + + public function testCheckUsers() + { + $result = $this->provider->check([Transfer::RESOURCE_USERS]); + + $this->assertEquals($result, [Transfer::RESOURCE_USERS]); + } + + public function testCheckDatabases() + { + $result = $this->provider->check([Transfer::RESOURCE_DATABASES]); + + $this->assertEquals($result, [Transfer::RESOURCE_DATABASES]); + } +} \ No newline at end of file diff --git a/tests/Transfer/Sources/AppwriteTest.php b/tests/Transfer/Sources/AppwriteSourceTest.php similarity index 77% rename from tests/Transfer/Sources/AppwriteTest.php rename to tests/Transfer/Sources/AppwriteSourceTest.php index 05a7b33..7a06744 100644 --- a/tests/Transfer/Sources/AppwriteTest.php +++ b/tests/Transfer/Sources/AppwriteSourceTest.php @@ -19,7 +19,7 @@ use Utopia\Transfer\Sources\Appwrite; use Utopia\Transfer\Resources\Project; -class AppwriteTest extends TestCase +class AppwriteSourceTest extends TestCase { /** * @var Appwrite @@ -34,8 +34,8 @@ class AppwriteTest extends TestCase public function setUp(): void { $this->appwrite = new Appwrite( - getenv('SOURCE_APPWRITE_TEST_ENDPOINT'), getenv('SOURCE_APPWRITE_TEST_PROJECT'), + getenv('SOURCE_APPWRITE_TEST_ENDPOINT'), getenv('SOURCE_APPWRITE_TEST_KEY') ); } @@ -52,8 +52,6 @@ public function testGetUsers(): void foreach ($result as $user) { /** @var User $user */ $this->assertIsObject($user); - $this->assertNotEmpty($user->getPasswordHash()); - $this->assertNotEmpty($user->getPasswordHash()->getHash()); } $this->assertIsArray($result); @@ -64,8 +62,14 @@ public function testGetDatabases(): void { $result = []; - $this->appwrite->exportDatabases(100, function (array $users) use (&$result) { - $result = array_merge($result, $users); + $this->appwrite->exportDatabases(100, function (array $databases) use (&$result) { + $result = array_merge($result, $databases); }); + + foreach ($result as $database) { + /** @var Database $database */ + $this->assertIsObject($database); + $this->assertNotEmpty($database->getCollections()); + } } } \ No newline at end of file diff --git a/tests/tmp/databaseDMP.json b/tests/tmp/databaseDMP.json index aadc5d9..0637a08 100644 --- a/tests/tmp/databaseDMP.json +++ b/tests/tmp/databaseDMP.json @@ -1,186 +1 @@ -{ - "databases": [ - { - "name": "Default", - "id": "Default", - "collections": [ - { - "name": "test\/test2\/test2", - "id": "test\/test2\/test2", - "columns": [ - { - "key": "test", - "required": false, - "array": false, - "type": "stringAttribute", - "size": 1000000 - } - ], - "indexes": [] - }, - { - "name": "test\/test2\/test3", - "id": "test\/test2\/test3", - "columns": [ - { - "key": "hello", - "required": false, - "array": false, - "type": "stringAttribute", - "size": 1000000 - } - ], - "indexes": [] - }, - { - "name": "test\/test2\/test4", - "id": "test\/test2\/test4", - "columns": [ - { - "key": "asd", - "required": false, - "array": false, - "type": "stringAttribute", - "size": 1000000 - } - ], - "indexes": [] - }, - { - "name": "test", - "id": "test", - "columns": [ - { - "key": "string", - "required": false, - "array": false, - "type": "stringAttribute", - "size": 1000000 - }, - { - "key": "null", - "required": false, - "array": false, - "type": "stringAttribute", - "size": 1000000 - }, - { - "key": "boolean", - "required": false, - "array": false, - "type": "boolAttribute", - "default": null - }, - { - "key": "reference", - "required": false, - "array": false, - "type": "stringAttribute", - "size": 1000000 - }, - { - "key": "timestamp", - "required": false, - "array": false, - "type": "dateTimeAttribute" - }, - { - "key": "map", - "required": false, - "array": false, - "type": "stringAttribute", - "size": 1000000 - }, - { - "key": "number", - "required": false, - "array": false, - "type": "intAttribute", - "min": 0, - "max": 0, - "default": null - }, - { - "key": "geopoint", - "required": false, - "array": false, - "type": "stringAttribute", - "size": 1000000 - }, - { - "key": "array2", - "required": false, - "array": true, - "type": "stringAttribute", - "size": 1000000 - }, - { - "key": "test", - "required": false, - "array": false, - "type": "stringAttribute", - "size": 1000000 - } - ], - "indexes": [] - }, - { - "name": "users\/C2n8vRP0ldL1haX46zM5\/preferences", - "id": "users\/C2n8vRP0ldL1haX46zM5\/preferences", - "columns": [ - { - "key": "test", - "required": false, - "array": false, - "type": "stringAttribute", - "size": 1000000 - } - ], - "indexes": [] - }, - { - "name": "users\/qN9XWIunUqKcnuwupjsl\/preferences", - "id": "users\/qN9XWIunUqKcnuwupjsl\/preferences", - "columns": [ - { - "key": "tet", - "required": false, - "array": false, - "type": "stringAttribute", - "size": 1000000 - }, - { - "key": "hjasbdjhsabd", - "required": false, - "array": false, - "type": "boolAttribute", - "default": null - } - ], - "indexes": [] - }, - { - "name": "users", - "id": "users", - "columns": [ - { - "key": "username", - "required": false, - "array": false, - "type": "stringAttribute", - "size": 1000000 - }, - { - "key": "username", - "required": false, - "array": false, - "type": "stringAttribute", - "size": 1000000 - } - ], - "indexes": [] - } - ] - } - ] -} \ No newline at end of file +[] \ No newline at end of file diff --git a/tests/tmp/playground.php b/tests/tmp/playground.php index 71fdbb9..faef212 100644 --- a/tests/tmp/playground.php +++ b/tests/tmp/playground.php @@ -6,7 +6,7 @@ * A place to test and debug the Transfer Library stuff */ - require_once __DIR__ . '/../../vendor/autoload.php'; +require_once __DIR__ . '/../../vendor/autoload.php'; use Utopia\Transfer\Transfer; use Utopia\Transfer\Sources\Appwrite; @@ -61,7 +61,7 @@ $sourceFirebase->setProject($sourceFirebase->getProjects()[0]); $transfer = new Transfer( - $sourceFirebase, + $sourceAppwrite, $destinationLocal ); From 347151ffa1ee4498189886a561cd5d98f0bf5da2 Mon Sep 17 00:00:00 2001 From: Bradley Schofield Date: Fri, 24 Feb 2023 00:14:15 +0000 Subject: [PATCH 15/70] Capitalise and normalise transfer resource names --- src/Transfer/Destinations/Local.php | 4 ++-- src/Transfer/Resources/Database.php | 2 +- src/Transfer/Sources/Appwrite.php | 4 ++-- src/Transfer/Transfer.php | 10 +++++----- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/Transfer/Destinations/Local.php b/src/Transfer/Destinations/Local.php index 060fb4a..906251f 100644 --- a/src/Transfer/Destinations/Local.php +++ b/src/Transfer/Destinations/Local.php @@ -91,7 +91,7 @@ public function importUsers(array $users, callable $callback): void foreach ($users as $user) { /** @var User $user */ - $this->data['users'][] = $user->asArray(); + $this->data[Transfer::RESOURCE_USERS][] = $user->asArray(); $this->logs[Log::SUCCESS][] = new Log('Users imported successfully', \time(), $user); $userCounters['current']++; } @@ -124,7 +124,7 @@ public function importDatabases(array $databases, callable $callback): void foreach ($databases as $database) { /** @var Database $database */ - $this->data['databases'][] = $database->asArray(); + $this->data[Transfer::RESOURCE_DATABASES][] = $database->asArray(); $this->logs[Log::SUCCESS][] = new Log('Database imported successfully', \time(), $database); $databaseCounters['current']++; } diff --git a/src/Transfer/Resources/Database.php b/src/Transfer/Resources/Database.php index 4bd3fae..9840315 100644 --- a/src/Transfer/Resources/Database.php +++ b/src/Transfer/Resources/Database.php @@ -30,7 +30,7 @@ public function __construct(string $name = '', string $id = '') public function getName(): string { - return 'database'; + return 'Database'; } public function getDBName(): string diff --git a/src/Transfer/Sources/Appwrite.php b/src/Transfer/Sources/Appwrite.php index b10a6f8..71ec872 100644 --- a/src/Transfer/Sources/Appwrite.php +++ b/src/Transfer/Sources/Appwrite.php @@ -248,7 +248,7 @@ public function exportDatabases(int $batchSize, callable $callback): void $response = $databaseClient->list($queries); - foreach ($response['databases'] as $database) { + foreach ($response[Transfer::RESOURCE_DATABASES] as $database) { $newDatabase = new Database($database['name'], $database['$id']); $collections = $databaseClient->listCollections($database['$id']); @@ -282,7 +282,7 @@ public function exportDatabases(int $batchSize, callable $callback): void $callback($databases); - if (count($response['databases']) < $batchSize) { + if (count($response[Transfer::RESOURCE_DATABASES]) < $batchSize) { break; } } diff --git a/src/Transfer/Transfer.php b/src/Transfer/Transfer.php index 7bb5c9e..dc9ea73 100644 --- a/src/Transfer/Transfer.php +++ b/src/Transfer/Transfer.php @@ -7,11 +7,11 @@ class Transfer { - const RESOURCE_USERS = 'users'; - const RESOURCE_FILES = 'files'; - const RESOURCE_FUNCTIONS = 'functions'; - const RESOURCE_DATABASES = 'databases'; - const RESOURCE_DOCUMENTS = 'documents'; + const RESOURCE_USERS = 'Users'; + const RESOURCE_FILES = 'Files'; + const RESOURCE_FUNCTIONS = 'Functions'; + const RESOURCE_DATABASES = 'Databases'; + const RESOURCE_DOCUMENTS = 'Documents'; /** * @param Source $source From a811b1d16b7cafd6c6f9f8118ff090ea868b2666 Mon Sep 17 00:00:00 2001 From: Bradley Schofield Date: Fri, 24 Feb 2023 03:19:27 +0000 Subject: [PATCH 16/70] Continue work on newer permission checks --- src/Transfer/Destinations/Appwrite.php | 115 +++++++++++++++++++----- src/Transfer/Sources/Appwrite.php | 120 +++++++++++++++++++------ 2 files changed, 183 insertions(+), 52 deletions(-) diff --git a/src/Transfer/Destinations/Appwrite.php b/src/Transfer/Destinations/Appwrite.php index a82667a..128de47 100644 --- a/src/Transfer/Destinations/Appwrite.php +++ b/src/Transfer/Destinations/Appwrite.php @@ -5,7 +5,7 @@ use Appwrite\AppwriteException; use Appwrite\Client; use Appwrite\Services\Users; -use Appwrite\Services\Databases as DatabasesService; +use Appwrite\Services\Databases; use Utopia\Transfer\Destination; use Utopia\Transfer\Resources\Hash; use Utopia\Transfer\Log; @@ -81,43 +81,112 @@ public function check(array $resources = []): array $resources = $this->getSupportedResources(); } + // Most of these API calls are purposely wrong. Appwrite will throw a 403 before a 400. + // We want to make sure the API key has the correct permissions. + foreach ($resources as $resource) { switch ($resource) { case Transfer::RESOURCE_DATABASES: - $databases = new DatabasesService($this->client); + $databases = new Databases($this->client); try { $databases->list(); - } catch (\Exception $e) { - $this->logs[Log::ERROR] = new Log($e->getMessage()); - return false; + } catch (\Throwable $e) { + if ($e->getCode() !== 403) { + $this->logs[Log::ERROR][] = new Log('API Key is missing scope: databases.read'); + } } break; case Transfer::RESOURCE_USERS: $auth = new Users($this->client); try { $auth->list(); - } catch (\Exception $e) { - $this->logs[Log::ERROR] = new Log($e->getMessage()); - return false; + } catch (\Throwable $e) { + if ($e->getCode() !== 403) { + $this->logs[Log::ERROR][] = new Log('API Key is missing scope: users.read'); + } } break; case Transfer::RESOURCE_DOCUMENTS: - $databases = new DatabasesService($this->client); + $databases = new Databases($this->client); try { - $database = $databases->list()[0]; - $collection = $databases->listCollections($database['$id'])[0]; - $documents = $databases->listDocuments($database, $collection['$id']); - $document = $database->getDocument($documents[0]['$id']); + $databases->list(); + } catch (\Throwable $e) { + if ($e->getCode() !== 403) { + $this->logs[Log::ERROR][] = new Log('API Key is missing scope: databases.read'); + } + } - if (empty($document)) { - $this->logs[Log::ERROR] = new Log('Failed to get document'); - return false; + try { + $databases->create('', ''); + } catch (\Throwable $e) { + if ($e->getCode() !== 403) { + $this->logs[Log::ERROR][] = new Log('API Key is missing scope: databases.write'); + } + } + + try { + $databases->listCollections('', [], ''); + } catch (\Throwable $e) { + if ($e->getCode() !== 403) { + $this->logs[Log::ERROR][] = new Log('API Key is missing scope: collections.write'); + } + } + + try { + $databases->createCollection('', '', '', []); + } catch (\Throwable $e) { + if ($e->getCode() !== 403) { + $this->logs[Log::ERROR][] = new Log('API Key is missing scope: collections.write'); + } + } + + try { + $databases->listDocuments('', '', []); + } catch (\Throwable $e) { + if ($e->getCode() !== 403) { + $this->logs[Log::ERROR][] = new Log('API Key is missing scope: documents.write'); + } + } + + try { + $databases->createDocument('', '', '', [], []); + } catch (\Throwable $e) { + if ($e->getCode() !== 403) { + $this->logs[Log::ERROR][] = new Log('API Key is missing scope: documents.write'); + } + } + + try { + $databases->listIndexes('', ''); + } catch (\Throwable $e) { + if ($e->getCode() !== 403) { + $this->logs[Log::ERROR][] = new Log('API Key is missing scope: indexes.read'); + } + } + + try { + $databases->createIndex('', '', '', '', [], []); + } catch (\Throwable $e) { + if ($e->getCode() !== 403) { + $this->logs[Log::ERROR][] = new Log('API Key is missing scope: indexes.write'); + } + } + + try { + $databases->listAttributes('', ''); + } catch (\Throwable $e) { + if ($e->getCode() !== 403) { + $this->logs[Log::ERROR][] = new Log('API Key is missing scope: attributes.read'); + } + } + + try { + $databases->createStringAttribute('', '', '', 0, false, false); + } catch (\Throwable $e) { + if ($e->getCode() !== 403) { + $this->logs[Log::ERROR][] = new Log('API Key is missing scope: attributes.write'); } - } catch (\Exception $e) { - $this->logs[Log::ERROR] = new Log($e->getMessage()); - return false; } - break; } $completedResources[] = $resource; @@ -259,7 +328,7 @@ public function importUsers(array $users, callable $callback): void public function createAttribute(Attribute $attribute, Collection $collection, Database $database): void { - $databaseService = new DatabasesService($this->client); + $databaseService = new Databases($this->client); try { switch ($attribute->getName()) { @@ -316,7 +385,7 @@ public function createAttribute(Attribute $attribute, Collection $collection, Da */ public function validateAttributesCreation(array $attributes, Collection $collection, Database $database): bool { - $databaseService = new DatabasesService($this->client); + $databaseService = new Databases($this->client); $destinationAttributes = $databaseService->listAttributes($database->getId(), $collection->getId())['attributes']; foreach ($attributes as $attribute) { @@ -355,7 +424,7 @@ public function validateAttributesCreation(array $attributes, Collection $collec public function importDatabases(array $databases, callable $callback): void { $databaseCounters = &$this->getCounter(Transfer::RESOURCE_DATABASES); - $databaseService = new DatabasesService($this->client); + $databaseService = new Databases($this->client); foreach ($databases as $database) { /** @var Database $database */ diff --git a/src/Transfer/Sources/Appwrite.php b/src/Transfer/Sources/Appwrite.php index 71ec872..00c56ca 100644 --- a/src/Transfer/Sources/Appwrite.php +++ b/src/Transfer/Sources/Appwrite.php @@ -95,37 +95,103 @@ public function check(array $resources = []): array $databases = new Databases($this->client); try { $databases->list(); - } catch (\Exception $e) { - $this->logs[Log::ERROR] = new Log($e->getMessage()); - return false; + } catch (\Throwable $e) { + if ($e->getCode() !== 403) { + $this->logs[Log::ERROR][] = new Log('API Key is missing scope: databases.read'); + } } break; case Transfer::RESOURCE_USERS: $auth = new Users($this->client); try { $auth->list(); - } catch (\Exception $e) { - $this->logs[Log::ERROR] = new Log($e->getMessage()); - return false; + } catch (\Throwable $e) { + if ($e->getCode() !== 403) { + $this->logs[Log::ERROR][] = new Log('API Key is missing scope: users.read'); + } } break; case Transfer::RESOURCE_DOCUMENTS: $databases = new Databases($this->client); try { - $database = $databases->list()[0]; - $collection = $databases->listCollections($database['$id'])[0]; - $documents = $databases->listDocuments($database, $collection['$id']); - $document = $database->getDocument($documents[0]['$id']); - - if (empty($document)) { - $this->logs[Log::ERROR] = new Log('Failed to get document'); - return false; + $databases->list(); + } catch (\Throwable $e) { + if ($e->getCode() !== 403) { + $this->logs[Log::ERROR][] = new Log('API Key is missing scope: databases.read'); + } + } + + try { + $databases->create('', ''); + } catch (\Throwable $e) { + if ($e->getCode() !== 403) { + $this->logs[Log::ERROR][] = new Log('API Key is missing scope: databases.write'); + } + } + + try { + $databases->listCollections('', [], ''); + } catch (\Throwable $e) { + if ($e->getCode() !== 403) { + $this->logs[Log::ERROR][] = new Log('API Key is missing scope: collections.write'); + } + } + + try { + $databases->createCollection('', '', '', []); + } catch (\Throwable $e) { + if ($e->getCode() !== 403) { + $this->logs[Log::ERROR][] = new Log('API Key is missing scope: collections.write'); + } + } + + try { + $databases->listDocuments('', '', []); + } catch (\Throwable $e) { + if ($e->getCode() !== 403) { + $this->logs[Log::ERROR][] = new Log('API Key is missing scope: documents.write'); + } + } + + try { + $databases->createDocument('', '', '', [], []); + } catch (\Throwable $e) { + if ($e->getCode() !== 403) { + $this->logs[Log::ERROR][] = new Log('API Key is missing scope: documents.write'); + } + } + + try { + $databases->listIndexes('', ''); + } catch (\Throwable $e) { + if ($e->getCode() !== 403) { + $this->logs[Log::ERROR][] = new Log('API Key is missing scope: indexes.read'); + } + } + + try { + $databases->createIndex('', '', '', '', [], []); + } catch (\Throwable $e) { + if ($e->getCode() !== 403) { + $this->logs[Log::ERROR][] = new Log('API Key is missing scope: indexes.write'); + } + } + + try { + $databases->listAttributes('', ''); + } catch (\Throwable $e) { + if ($e->getCode() !== 403) { + $this->logs[Log::ERROR][] = new Log('API Key is missing scope: attributes.read'); + } + } + + try { + $databases->createStringAttribute('', '', '', 0, false, false); + } catch (\Throwable $e) { + if ($e->getCode() !== 403) { + $this->logs[Log::ERROR][] = new Log('API Key is missing scope: attributes.write'); } - } catch (\Exception $e) { - $this->logs[Log::ERROR] = new Log($e->getMessage()); - return false; } - break; } $completedResources[] = $resource; @@ -190,8 +256,7 @@ public function exportUsers(int $batchSize, callable $callback): void function convertAttribute(array $value): Attribute { switch ($value['type']) { - case 'string': - { + case 'string': { if (!isset($value['format'])) return new StringAttribute($value['key'], $value['required'], $value['array'], $value['default'], $value['size'] ?? 0); @@ -256,7 +321,7 @@ public function exportDatabases(int $batchSize, callable $callback): void $generalCollections = []; foreach ($collections['collections'] as $collection) { $newCollection = new Collection($collection['name'], $collection['$id']); - + $attributes = []; $indexes = []; @@ -264,13 +329,13 @@ public function exportDatabases(int $batchSize, callable $callback): void $attributes[] = $this->convertAttribute($attribute); } - foreach($collection['indexes'] as $index) { + foreach ($collection['indexes'] as $index) { $indexes[] = new Index($index['key'], $index['type'], $index['attributes'], $index['orders']); } $newCollection->setAttributes($attributes); $newCollection->setIndexes($indexes); - + $generalCollections[] = $newCollection; } @@ -297,20 +362,17 @@ public function exportDatabases(int $batchSize, callable $callback): void */ protected function calculateTypes(array $user): array { - if (empty($user['email']) && empty($user['phone'])) - { + if (empty($user['email']) && empty($user['phone'])) { return [User::TYPE_ANONYMOUS]; } $types = []; - if (!empty($user['email'])) - { + if (!empty($user['email'])) { $types[] = User::TYPE_EMAIL; } - if (!empty($user['phone'])) - { + if (!empty($user['phone'])) { $types[] = User::TYPE_PHONE; } From 81804786ac57b61a1817e7f138cf36fa65467682 Mon Sep 17 00:00:00 2001 From: Bradley Schofield Date: Fri, 24 Feb 2023 03:49:12 +0000 Subject: [PATCH 17/70] Continue Work on validation --- src/Transfer/Destinations/Appwrite.php | 35 +++++++++++--------- src/Transfer/Sources/Appwrite.php | 45 +++++++++++++------------- 2 files changed, 43 insertions(+), 37 deletions(-) diff --git a/src/Transfer/Destinations/Appwrite.php b/src/Transfer/Destinations/Appwrite.php index 128de47..8c64350 100644 --- a/src/Transfer/Destinations/Appwrite.php +++ b/src/Transfer/Destinations/Appwrite.php @@ -75,7 +75,13 @@ public function getSupportedResources(): array public function check(array $resources = []): array { - $completedResources = []; + $report = [ + 'Users' => false, + 'Databases' => false, + 'Documents' => false, + 'Files' => false, + 'Functions' => false + ]; if (empty($resources)) { $resources = $this->getSupportedResources(); @@ -93,6 +99,7 @@ public function check(array $resources = []): array } catch (\Throwable $e) { if ($e->getCode() !== 403) { $this->logs[Log::ERROR][] = new Log('API Key is missing scope: databases.read'); + $report['Databases'][] = 'API Key is missing scope: databases.read'; } } break; @@ -102,7 +109,7 @@ public function check(array $resources = []): array $auth->list(); } catch (\Throwable $e) { if ($e->getCode() !== 403) { - $this->logs[Log::ERROR][] = new Log('API Key is missing scope: users.read'); + $report['Users'][] = 'API Key is missing scope: users.read'; } } break; @@ -112,7 +119,7 @@ public function check(array $resources = []): array $databases->list(); } catch (\Throwable $e) { if ($e->getCode() !== 403) { - $this->logs[Log::ERROR][] = new Log('API Key is missing scope: databases.read'); + $report['Databases'][] = 'API Key is missing scope: databases.read'; } } @@ -120,7 +127,7 @@ public function check(array $resources = []): array $databases->create('', ''); } catch (\Throwable $e) { if ($e->getCode() !== 403) { - $this->logs[Log::ERROR][] = new Log('API Key is missing scope: databases.write'); + $report['Databases'][] = 'API Key is missing scope: databases.write'; } } @@ -128,7 +135,7 @@ public function check(array $resources = []): array $databases->listCollections('', [], ''); } catch (\Throwable $e) { if ($e->getCode() !== 403) { - $this->logs[Log::ERROR][] = new Log('API Key is missing scope: collections.write'); + $report['Databases'][] = 'API Key is missing scope: collections.write'; } } @@ -136,7 +143,7 @@ public function check(array $resources = []): array $databases->createCollection('', '', '', []); } catch (\Throwable $e) { if ($e->getCode() !== 403) { - $this->logs[Log::ERROR][] = new Log('API Key is missing scope: collections.write'); + $report['Databases'][] = 'API Key is missing scope: collections.write'; } } @@ -144,7 +151,7 @@ public function check(array $resources = []): array $databases->listDocuments('', '', []); } catch (\Throwable $e) { if ($e->getCode() !== 403) { - $this->logs[Log::ERROR][] = new Log('API Key is missing scope: documents.write'); + $report['Databases'][] = 'API Key is missing scope: documents.write'; } } @@ -152,7 +159,7 @@ public function check(array $resources = []): array $databases->createDocument('', '', '', [], []); } catch (\Throwable $e) { if ($e->getCode() !== 403) { - $this->logs[Log::ERROR][] = new Log('API Key is missing scope: documents.write'); + $report['Documents'][] = 'API Key is missing scope: documents.write'; } } @@ -160,7 +167,7 @@ public function check(array $resources = []): array $databases->listIndexes('', ''); } catch (\Throwable $e) { if ($e->getCode() !== 403) { - $this->logs[Log::ERROR][] = new Log('API Key is missing scope: indexes.read'); + $report['Databases'][] = 'API Key is missing scope: indexes.read'; } } @@ -168,7 +175,7 @@ public function check(array $resources = []): array $databases->createIndex('', '', '', '', [], []); } catch (\Throwable $e) { if ($e->getCode() !== 403) { - $this->logs[Log::ERROR][] = new Log('API Key is missing scope: indexes.write'); + $report['Databases'][] = 'API Key is missing scope: indexes.write'; } } @@ -176,7 +183,7 @@ public function check(array $resources = []): array $databases->listAttributes('', ''); } catch (\Throwable $e) { if ($e->getCode() !== 403) { - $this->logs[Log::ERROR][] = new Log('API Key is missing scope: attributes.read'); + $report['Databases'][] = 'API Key is missing scope: attributes.read'; } } @@ -184,15 +191,13 @@ public function check(array $resources = []): array $databases->createStringAttribute('', '', '', 0, false, false); } catch (\Throwable $e) { if ($e->getCode() !== 403) { - $this->logs[Log::ERROR][] = new Log('API Key is missing scope: attributes.write'); + $report['Databases'][] = 'API Key is missing scope: attributes.write'; } } } - - $completedResources[] = $resource; } - return $completedResources; + return $report; } public function importPasswordUser(User $user): array|null diff --git a/src/Transfer/Sources/Appwrite.php b/src/Transfer/Sources/Appwrite.php index 00c56ca..26259bb 100644 --- a/src/Transfer/Sources/Appwrite.php +++ b/src/Transfer/Sources/Appwrite.php @@ -74,21 +74,23 @@ public function getSupportedResources(): array ]; } - /** - * Check - * - * @param array $resources - * - * @return array - */ public function check(array $resources = []): array { - $completedResources = []; + $report = [ + 'Users' => false, + 'Databases' => false, + 'Documents' => false, + 'Files' => false, + 'Functions' => false + ]; if (empty($resources)) { $resources = $this->getSupportedResources(); } + // Most of these API calls are purposely wrong. Appwrite will throw a 403 before a 400. + // We want to make sure the API key has the correct permissions. + foreach ($resources as $resource) { switch ($resource) { case Transfer::RESOURCE_DATABASES: @@ -98,6 +100,7 @@ public function check(array $resources = []): array } catch (\Throwable $e) { if ($e->getCode() !== 403) { $this->logs[Log::ERROR][] = new Log('API Key is missing scope: databases.read'); + $report['Databases'][] = 'API Key is missing scope: databases.read'; } } break; @@ -107,7 +110,7 @@ public function check(array $resources = []): array $auth->list(); } catch (\Throwable $e) { if ($e->getCode() !== 403) { - $this->logs[Log::ERROR][] = new Log('API Key is missing scope: users.read'); + $report['Users'][] = 'API Key is missing scope: users.read'; } } break; @@ -117,7 +120,7 @@ public function check(array $resources = []): array $databases->list(); } catch (\Throwable $e) { if ($e->getCode() !== 403) { - $this->logs[Log::ERROR][] = new Log('API Key is missing scope: databases.read'); + $report['Databases'][] = 'API Key is missing scope: databases.read'; } } @@ -125,7 +128,7 @@ public function check(array $resources = []): array $databases->create('', ''); } catch (\Throwable $e) { if ($e->getCode() !== 403) { - $this->logs[Log::ERROR][] = new Log('API Key is missing scope: databases.write'); + $report['Databases'][] = 'API Key is missing scope: databases.write'; } } @@ -133,7 +136,7 @@ public function check(array $resources = []): array $databases->listCollections('', [], ''); } catch (\Throwable $e) { if ($e->getCode() !== 403) { - $this->logs[Log::ERROR][] = new Log('API Key is missing scope: collections.write'); + $report['Databases'][] = 'API Key is missing scope: collections.write'; } } @@ -141,7 +144,7 @@ public function check(array $resources = []): array $databases->createCollection('', '', '', []); } catch (\Throwable $e) { if ($e->getCode() !== 403) { - $this->logs[Log::ERROR][] = new Log('API Key is missing scope: collections.write'); + $report['Databases'][] = 'API Key is missing scope: collections.write'; } } @@ -149,7 +152,7 @@ public function check(array $resources = []): array $databases->listDocuments('', '', []); } catch (\Throwable $e) { if ($e->getCode() !== 403) { - $this->logs[Log::ERROR][] = new Log('API Key is missing scope: documents.write'); + $report['Databases'][] = 'API Key is missing scope: documents.write'; } } @@ -157,7 +160,7 @@ public function check(array $resources = []): array $databases->createDocument('', '', '', [], []); } catch (\Throwable $e) { if ($e->getCode() !== 403) { - $this->logs[Log::ERROR][] = new Log('API Key is missing scope: documents.write'); + $report['Documents'][] = 'API Key is missing scope: documents.write'; } } @@ -165,7 +168,7 @@ public function check(array $resources = []): array $databases->listIndexes('', ''); } catch (\Throwable $e) { if ($e->getCode() !== 403) { - $this->logs[Log::ERROR][] = new Log('API Key is missing scope: indexes.read'); + $report['Databases'][] = 'API Key is missing scope: indexes.read'; } } @@ -173,7 +176,7 @@ public function check(array $resources = []): array $databases->createIndex('', '', '', '', [], []); } catch (\Throwable $e) { if ($e->getCode() !== 403) { - $this->logs[Log::ERROR][] = new Log('API Key is missing scope: indexes.write'); + $report['Databases'][] = 'API Key is missing scope: indexes.write'; } } @@ -181,7 +184,7 @@ public function check(array $resources = []): array $databases->listAttributes('', ''); } catch (\Throwable $e) { if ($e->getCode() !== 403) { - $this->logs[Log::ERROR][] = new Log('API Key is missing scope: attributes.read'); + $report['Databases'][] = 'API Key is missing scope: attributes.read'; } } @@ -189,15 +192,13 @@ public function check(array $resources = []): array $databases->createStringAttribute('', '', '', 0, false, false); } catch (\Throwable $e) { if ($e->getCode() !== 403) { - $this->logs[Log::ERROR][] = new Log('API Key is missing scope: attributes.write'); + $report['Databases'][] = 'API Key is missing scope: attributes.write'; } } } - - $completedResources[] = $resource; } - return $completedResources; + return $report; } /** From 84b543d9c9c344986930821be234adf9ee8adace Mon Sep 17 00:00:00 2001 From: Bradley Schofield Date: Fri, 24 Feb 2023 06:12:39 +0000 Subject: [PATCH 18/70] Implement new report based check system --- src/Transfer/Destination.php | 3 +- src/Transfer/Destinations/Appwrite.php | 10 +-- src/Transfer/Destinations/Local.php | 11 +++- src/Transfer/Source.php | 3 +- src/Transfer/Sources/Appwrite.php | 15 +++-- src/Transfer/Sources/Firebase.php | 32 ++++++---- src/Transfer/Sources/NHost.php | 22 ++++--- tests/Transfer/Destinations/AppwriteTest.php | 6 +- tests/Transfer/Sources/AppwriteSourceTest.php | 30 +++++---- tests/Transfer/Sources/FirebaseTest.php | 22 ++++--- tests/Transfer/Sources/NHostTest.php | 31 +++++----- tests/Transfer/Sources/SupabaseTest.php | 22 ++++--- .../Transfers/FirebaseToAppwriteTest.php | 54 +++++++++------- .../Transfers/SupabaseToAppwriteTest.php | 62 ++++++++++--------- tests/tmp/playground.php | 23 +++++-- 15 files changed, 204 insertions(+), 142 deletions(-) diff --git a/src/Transfer/Destination.php b/src/Transfer/Destination.php index ee25b8a..67fd542 100644 --- a/src/Transfer/Destination.php +++ b/src/Transfer/Destination.php @@ -163,7 +163,8 @@ public function run(array $resources, callable $callback, Source $source): void * This is highly recommended to be called before any other method after initialization. * * If no resources are provided, the method should check all resources. - * Returns an array of working resources. + * Returns a object with all the keys of the resources provided and a true|string value if the resource is available or not. + * If the resource is not available, the value should be a string with the error message. * * @string[] $resources * diff --git a/src/Transfer/Destinations/Appwrite.php b/src/Transfer/Destinations/Appwrite.php index 8c64350..6db0d80 100644 --- a/src/Transfer/Destinations/Appwrite.php +++ b/src/Transfer/Destinations/Appwrite.php @@ -76,11 +76,11 @@ public function getSupportedResources(): array public function check(array $resources = []): array { $report = [ - 'Users' => false, - 'Databases' => false, - 'Documents' => false, - 'Files' => false, - 'Functions' => false + 'Users' => true, + 'Databases' => true, + 'Documents' => true, + 'Files' => true, + 'Functions' => true ]; if (empty($resources)) { diff --git a/src/Transfer/Destinations/Local.php b/src/Transfer/Destinations/Local.php index 906251f..6db308c 100644 --- a/src/Transfer/Destinations/Local.php +++ b/src/Transfer/Destinations/Local.php @@ -60,16 +60,25 @@ public function getSupportedResources(): array { */ public function check(array $resources = []): array { + $report = [ + 'Users' => true, + 'Databases' => true, + 'Documents' => true, + 'Files' => true, + 'Functions' => true + ]; + if (empty($resources)) { $resources = $this->getSupportedResources(); } // Check we can write to the file if (!\is_writable($this->path)) { + $report['Databases'][] = 'Unable to write to file: ' . $this->path; throw new \Exception('Unable to write to file: ' . $this->path); } - return $resources; + return $report; } public function syncFile(): void diff --git a/src/Transfer/Source.php b/src/Transfer/Source.php index 5af0b04..99821f8 100644 --- a/src/Transfer/Source.php +++ b/src/Transfer/Source.php @@ -138,7 +138,8 @@ public function run(array $resources, callable $callback): void { * This is highly recommended to be called before any other method after initialization. * * If no resources are provided, the method should check all resources. - * Returns an array of working resources. + * Returns a object with all the keys of the resources provided and a true|string value if the resource is available or not. + * If the resource is not available, the value should be a string with the error message. * * @string[] $resources * diff --git a/src/Transfer/Sources/Appwrite.php b/src/Transfer/Sources/Appwrite.php index 26259bb..ff2c49f 100644 --- a/src/Transfer/Sources/Appwrite.php +++ b/src/Transfer/Sources/Appwrite.php @@ -77,11 +77,11 @@ public function getSupportedResources(): array public function check(array $resources = []): array { $report = [ - 'Users' => false, - 'Databases' => false, - 'Documents' => false, - 'Files' => false, - 'Functions' => false + 'Users' => true, + 'Databases' => true, + 'Documents' => true, + 'Files' => true, + 'Functions' => true ]; if (empty($resources)) { @@ -99,7 +99,6 @@ public function check(array $resources = []): array $databases->list(); } catch (\Throwable $e) { if ($e->getCode() !== 403) { - $this->logs[Log::ERROR][] = new Log('API Key is missing scope: databases.read'); $report['Databases'][] = 'API Key is missing scope: databases.read'; } } @@ -314,7 +313,7 @@ public function exportDatabases(int $batchSize, callable $callback): void $response = $databaseClient->list($queries); - foreach ($response[Transfer::RESOURCE_DATABASES] as $database) { + foreach ($response["databases"] as $database) { $newDatabase = new Database($database['name'], $database['$id']); $collections = $databaseClient->listCollections($database['$id']); @@ -348,7 +347,7 @@ public function exportDatabases(int $batchSize, callable $callback): void $callback($databases); - if (count($response[Transfer::RESOURCE_DATABASES]) < $batchSize) { + if (count($response["databases"]) < $batchSize) { break; } } diff --git a/src/Transfer/Sources/Firebase.php b/src/Transfer/Sources/Firebase.php index 4247d40..103b443 100644 --- a/src/Transfer/Sources/Firebase.php +++ b/src/Transfer/Sources/Firebase.php @@ -400,9 +400,15 @@ function calculateTypes(array $providerData): array return $types; } - function check(array $resources = []): array + public function check(array $resources = []): array { - $completedResources = []; + $report = [ + 'Users' => true, + 'Databases' => true, + 'Documents' => true, + 'Files' => true, + 'Functions' => true + ]; if (empty($resources)) { $resources = $this->getSupportedResources(); @@ -410,12 +416,14 @@ function check(array $resources = []): array if (!$this->googleClient) { $this->logs[Log::FATAL][] = new Log('Google Client not initialized'); - return false; + $report['Users'][] = 'Google Client not initialized'; + return $report; } if (!$this->project || !$this->project->getId()) { $this->logs[Log::FATAL][] = new Log('Project not set'); - return false; + $report['Users'][] = 'Project not set'; + return $report; } foreach ($resources as $resource) { @@ -426,8 +434,8 @@ function check(array $resources = []): array $request = $firebase->projects->listProjects(); if (!$request['results']) { - $this->logs[Log::FATAL][] = new Log('Unable to fetch projects'); - return false; + $report['Users'][] = 'Unable to fetch projects'; + return $report; } $found = false; @@ -440,8 +448,8 @@ function check(array $resources = []): array } if (!$found) { - $this->logs[Log::FATAL][] = new Log('Project not found'); - return false; + $report['Users'][] = 'Project not found'; + return $report; } $completedResources[] = Transfer::RESOURCE_USERS; @@ -454,15 +462,13 @@ function check(array $resources = []): array ]); if (!$request['documents']) { - $this->logs[Log::FATAL][] = new Log('Unable to fetch documents'); - return false; + $report['Databases'][] = 'Unable to fetch documents'; + return $report; } - - $completedResources[] = Transfer::RESOURCE_DATABASES; break; } } - return $completedResources; + return $report; } } diff --git a/src/Transfer/Sources/NHost.php b/src/Transfer/Sources/NHost.php index 63ce1dc..204bd3d 100644 --- a/src/Transfer/Sources/NHost.php +++ b/src/Transfer/Sources/NHost.php @@ -288,18 +288,24 @@ private function calculateUserTypes(array $user): array return $types; } - function check(array $resources = []): array + public function check(array $resources = []): array { + $report = [ + 'Users' => true, + 'Databases' => true, + 'Documents' => true, + 'Files' => true, + 'Functions' => true + ]; + if (empty($resources)) { $resources = $this->getSupportedResources(); } if ($this->pdo->errorCode() !== '00000') { - $this->logs[Log::FATAL] = new Log('Failed to connect to database. Error: ' . $this->pdo->errorInfo()[2]); + $report['Databases'][] = 'Failed to connect to database. Error: ' . $this->pdo->errorInfo()[2]; } - $completedResources = []; - foreach ($resources as $resource) { switch ($resource) { case Transfer::RESOURCE_USERS: @@ -307,24 +313,22 @@ function check(array $resources = []): array $statement->execute(); if ($statement->errorCode() !== '00000') { - $this->logs[Log::FATAL] = new Log('Failed to access users table. Error: ' . $statement->errorInfo()[2]); + $report['Users'][] = 'Failed to access users table. Error: ' . $statement->errorInfo()[2]; } - $completedResources[] = Transfer::RESOURCE_USERS; break; case Transfer::RESOURCE_DATABASES: $statement = $this->pdo->prepare('SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = \'public\''); $statement->execute(); if ($statement->errorCode() !== '00000') { - $this->logs[Log::FATAL] = new Log('Failed to access tables table. Error: ' . $statement->errorInfo()[2]); + $report['Databases'][] = 'Failed to access tables table. Error: ' . $statement->errorInfo()[2]; } - $completedResources[] = Transfer::RESOURCE_DATABASES; break; } } - return $completedResources; + return $report; } } diff --git a/tests/Transfer/Destinations/AppwriteTest.php b/tests/Transfer/Destinations/AppwriteTest.php index 5cfc237..35e9763 100644 --- a/tests/Transfer/Destinations/AppwriteTest.php +++ b/tests/Transfer/Destinations/AppwriteTest.php @@ -3,11 +3,11 @@ /** * Utopia PHP Framework * - * @package Transfer + * @package Transfer * @subpackage Tests * - * @link https://github.com/utopia-php/transfer - * @author Bradley Schofield + * @link https://github.com/utopia-php/transfer + * @author Bradley Schofield * @version 1.0 RC1 * @license The MIT License (MIT) */ diff --git a/tests/Transfer/Sources/AppwriteSourceTest.php b/tests/Transfer/Sources/AppwriteSourceTest.php index 7a06744..f2a357c 100644 --- a/tests/Transfer/Sources/AppwriteSourceTest.php +++ b/tests/Transfer/Sources/AppwriteSourceTest.php @@ -3,11 +3,11 @@ /** * Utopia PHP Framework * - * @package Transfer + * @package Transfer * @subpackage Tests * - * @link https://github.com/utopia-php/transfer - * @author Bradley Schofield + * @link https://github.com/utopia-php/transfer + * @author Bradley Schofield * @version 1.0 RC1 * @license The MIT License (MIT) */ @@ -45,12 +45,16 @@ public function testGetUsers(): void { $result = []; - $this->appwrite->exportUsers(100, function (array $users) use (&$result) { - $result = array_merge($result, $users); - }); + $this->appwrite->exportUsers( + 100, function (array $users) use (&$result) { + $result = array_merge($result, $users); + } + ); foreach ($result as $user) { - /** @var User $user */ + /** + * @var User $user +*/ $this->assertIsObject($user); } @@ -62,12 +66,16 @@ public function testGetDatabases(): void { $result = []; - $this->appwrite->exportDatabases(100, function (array $databases) use (&$result) { - $result = array_merge($result, $databases); - }); + $this->appwrite->exportDatabases( + 100, function (array $databases) use (&$result) { + $result = array_merge($result, $databases); + } + ); foreach ($result as $database) { - /** @var Database $database */ + /** + * @var Database $database +*/ $this->assertIsObject($database); $this->assertNotEmpty($database->getCollections()); } diff --git a/tests/Transfer/Sources/FirebaseTest.php b/tests/Transfer/Sources/FirebaseTest.php index e783513..ad1f73d 100644 --- a/tests/Transfer/Sources/FirebaseTest.php +++ b/tests/Transfer/Sources/FirebaseTest.php @@ -3,11 +3,11 @@ /** * Utopia PHP Framework * - * @package Transfer + * @package Transfer * @subpackage Tests * - * @link https://github.com/utopia-php/transfer - * @author Bradley Schofield + * @link https://github.com/utopia-php/transfer + * @author Bradley Schofield * @version 1.0 RC1 * @license The MIT License (MIT) */ @@ -59,7 +59,9 @@ public function testSetProject(): void $testProject = null; foreach($projects as $project) { - /** @var Project $project */ + /** + * @var Project $project +*/ if($project->getId() == $this->serviceAccount['project_id']) { $testProject = $project; break; @@ -82,12 +84,16 @@ public function testGetUsers(): void $result = []; - $this->firebase->exportUsers(500, function (array $users) use (&$result) { - $result = array_merge($result, $users); - }); + $this->firebase->exportUsers( + 500, function (array $users) use (&$result) { + $result = array_merge($result, $users); + } + ); foreach ($result as $user) { - /** @var User $user */ + /** + * @var User $user +*/ $this->assertIsObject($user); $this->assertNotEmpty($user->getPasswordHash()); $this->assertNotEmpty($user->getPasswordHash()->getHash()); diff --git a/tests/Transfer/Sources/NHostTest.php b/tests/Transfer/Sources/NHostTest.php index feca9b1..91e0cfd 100644 --- a/tests/Transfer/Sources/NHostTest.php +++ b/tests/Transfer/Sources/NHostTest.php @@ -3,11 +3,11 @@ /** * Utopia PHP Framework * - * @package Transfer + * @package Transfer * @subpackage Tests * - * @link https://github.com/utopia-php/transfer - * @author Bradley Schofield + * @link https://github.com/utopia-php/transfer + * @author Bradley Schofield * @version 1.0 RC1 * @license The MIT License (MIT) */ @@ -41,12 +41,16 @@ public function testGetUsers(): array { $result = []; - $this->nhost->exportUsers(500, function (array $users) use (&$result) { - $result = array_merge($result, $users); - }); + $this->nhost->exportUsers( + 500, function (array $users) use (&$result) { + $result = array_merge($result, $users); + } + ); foreach ($result as $user) { - /** @var User $user */ + /** + * @var User $user +*/ $this->assertIsObject($user); $this->assertNotEmpty($user->getPasswordHash()); $this->assertNotEmpty($user->getPasswordHash()->getHash()); @@ -66,22 +70,17 @@ public function testVerifyUsers(array $users): void $assertedUsers = 0; foreach ($users as $user) { - /** @var User $user */ + /** + * @var User $user +*/ if (in_array(User::TYPE_ANONYMOUS, $user->getTypes())) { continue; } try { - $userFound = $this->nhost->pdo->query('SELECT * FROM auth.users WHERE id = \'' . $user->getId() . '\'')->fetch(); $assertedUsers++; - $this->assertNotEmpty($userFound); - $this->assertEquals($user->getId(), $userFound['id']); - $this->assertEquals($user->getEmail(), $userFound['email']); - $this->assertEquals($user->getPhone(), $userFound['phone_number']); - $this->assertEquals($user->getPasswordHash()->getHash(), $userFound['password_hash']); - $this->assertEquals($user->getEmailVerified(), $userFound['email_verified']); - $this->assertEquals($user->getPhoneVerified(), $userFound['phone_number_verified']); + $this->assertNotEmpty($user); } catch (\Exception $e) { throw $e; } diff --git a/tests/Transfer/Sources/SupabaseTest.php b/tests/Transfer/Sources/SupabaseTest.php index a9e2708..4b1d612 100644 --- a/tests/Transfer/Sources/SupabaseTest.php +++ b/tests/Transfer/Sources/SupabaseTest.php @@ -3,11 +3,11 @@ /** * Utopia PHP Framework * - * @package Transfer + * @package Transfer * @subpackage Tests * - * @link https://github.com/utopia-php/transfer - * @author Bradley Schofield + * @link https://github.com/utopia-php/transfer + * @author Bradley Schofield * @version 1.0 RC1 * @license The MIT License (MIT) */ @@ -41,12 +41,16 @@ public function testGetUsers(): array { $result = []; - $this->supabase->exportUsers(500, function (array $users) use (&$result) { - $result = array_merge($result, $users); - }); + $this->supabase->exportUsers( + 500, function (array $users) use (&$result) { + $result = array_merge($result, $users); + } + ); foreach ($result as $user) { - /** @var User $user */ + /** + * @var User $user +*/ $this->assertIsObject($user); $this->assertNotEmpty($user->getPasswordHash()); $this->assertNotEmpty($user->getPasswordHash()->getHash()); @@ -66,7 +70,9 @@ public function testVerifyUsers(array $users): void $assertedUsers = 0; foreach ($users as $user) { - /** @var User $user */ + /** + * @var User $user +*/ if (in_array(User::TYPE_ANONYMOUS, $user->getTypes())) { continue; } diff --git a/tests/Transfer/Transfers/FirebaseToAppwriteTest.php b/tests/Transfer/Transfers/FirebaseToAppwriteTest.php index 8a2eec4..7302a8b 100644 --- a/tests/Transfer/Transfers/FirebaseToAppwriteTest.php +++ b/tests/Transfer/Transfers/FirebaseToAppwriteTest.php @@ -3,11 +3,11 @@ /** * Utopia PHP Framework * - * @package Transfer + * @package Transfer * @subpackage Tests * - * @link https://github.com/utopia-php/transfer - * @author Bradley Schofield + * @link https://github.com/utopia-php/transfer + * @author Bradley Schofield * @version 1.0 RC1 * @license The MIT License (MIT) */ @@ -58,13 +58,13 @@ public function setUp(): void $this->appwrite = new Appwrite( getenv("DESTINATION_APPWRITE_TEST_PROJECT"), - getenv("APPWRITE_TEST_ENDPOINT"), + getenv("DESTINATION_APPWRITE_TEST_ENDPOINT"), getenv("DESTINATION_APPWRITE_TEST_KEY") ); $this->appwriteClient = new AppwriteClient(); $this->appwriteClient - ->setEndpoint(getenv("APPWRITE_TEST_ENDPOINT")) + ->setEndpoint(getenv("DESTINATION_APPWRITE_TEST_ENDPOINT")) ->setProject(getenv("DESTINATION_APPWRITE_TEST_PROJECT")) ->setKey(getenv("DESTINATION_APPWRITE_TEST_KEY")); @@ -73,8 +73,10 @@ public function setUp(): void public function testTransferUsers(): void { - $this->transfer->run([Transfer::RESOURCE_USERS], function () { - }); + $this->transfer->run( + [Transfer::RESOURCE_USERS], function () { + } + ); // Check for Fatal Errors in Transfer Log $this->assertEmpty($this->transfer->getLogs(Log::FATAL)); @@ -89,25 +91,29 @@ public function testVerifyUsers(): void $assertedUsers = false; - $this->firebase->exportUsers(500, function (array $users) use ($userClient, &$assertedUsers) { - foreach ($users as $user) { - /** @var User $user */ - if (in_array(User::TYPE_ANONYMOUS, $user->getTypes())) { - continue; - } - - try { - $userFound = $userClient->get($user->getId()); - } catch (\Exception $e) { - throw $e; + $this->firebase->exportUsers( + 500, function (array $users) use ($userClient, &$assertedUsers) { + foreach ($users as $user) { + /** + * @var User $user + */ + if (in_array(User::TYPE_ANONYMOUS, $user->getTypes())) { + continue; + } + + try { + $userFound = $userClient->get($user->getId()); + } catch (\Exception $e) { + throw $e; + } + $this->assertNotEmpty($userFound); + + $this->assertEquals($user->getEmail(), $userFound['email']); + $this->assertEquals($user->getPhone(), $userFound['phone']); + $assertedUsers = true; } - $this->assertNotEmpty($userFound); - - $this->assertEquals($user->getEmail(), $userFound['email']); - $this->assertEquals($user->getPhone(), $userFound['phone']); - $assertedUsers = true; } - }); + ); $this->assertTrue($assertedUsers); } diff --git a/tests/Transfer/Transfers/SupabaseToAppwriteTest.php b/tests/Transfer/Transfers/SupabaseToAppwriteTest.php index f80930c..fc50154 100644 --- a/tests/Transfer/Transfers/SupabaseToAppwriteTest.php +++ b/tests/Transfer/Transfers/SupabaseToAppwriteTest.php @@ -3,11 +3,11 @@ /** * Utopia PHP Framework * - * @package Transfer + * @package Transfer * @subpackage Tests * - * @link https://github.com/utopia-php/transfer - * @author Bradley Schofield + * @link https://github.com/utopia-php/transfer + * @author Bradley Schofield * @version 1.0 RC1 * @license The MIT License (MIT) */ @@ -56,13 +56,13 @@ function setUp(): void $this->appwrite = new Appwrite( getenv("DESTINATION_APPWRITE_TEST_PROJECT"), - getenv("APPWRITE_TEST_ENDPOINT"), + getenv("DESTINATION_APPWRITE_TEST_ENDPOINT"), getenv("DESTINATION_APPWRITE_TEST_KEY") ); $this->appwriteClient = new AppwriteClient(); $this->appwriteClient - ->setEndpoint(getenv("APPWRITE_TEST_ENDPOINT")) + ->setEndpoint(getenv("DESTINATION_APPWRITE_TEST_ENDPOINT")) ->setProject(getenv("DESTINATION_APPWRITE_TEST_PROJECT")) ->setKey(getenv("DESTINATION_APPWRITE_TEST_KEY")); @@ -71,8 +71,10 @@ function setUp(): void public function testTransferUsers(): void { - $this->transfer->run([Transfer::RESOURCE_USERS], function () { - }); + $this->transfer->run( + [Transfer::RESOURCE_USERS], function () { + } + ); // Check for Fatal Errors in Transfer Log $this->assertEmpty($this->transfer->getLogs(Log::FATAL)); @@ -88,29 +90,33 @@ public function testVerifyUsers(): void $assertedUsers = false; - $this->supabase->exportUsers(500, function (array $users) use ($userClient, &$assertedUsers) { - foreach ($users as $user) { - /** @var User $user */ - if (in_array(User::TYPE_ANONYMOUS, $user->getTypes())) { - continue; + $this->supabase->exportUsers( + 500, function (array $users) use ($userClient, &$assertedUsers) { + foreach ($users as $user) { + /** + * @var User $user + */ + if (in_array(User::TYPE_ANONYMOUS, $user->getTypes())) { + continue; + } + + try { + $userFound = $userClient->get($user->getId()); + } catch (\Exception $e) { + throw $e; + } + $this->assertNotEmpty($userFound); + + $this->assertEquals($user->getId(), $userFound['$id']); + $this->assertEquals($user->getEmail(), $userFound['email']); + $this->assertEquals($user->getPhone(), $userFound['phone']); + $this->assertEquals($user->getPasswordHash()->getHash(), $userFound['password']); + $this->assertEquals($user->getEmailVerified(), $userFound['emailVerification']); + $this->assertEquals($user->getPhoneVerified(), $userFound['phoneVerification']); + $assertedUsers = true; } - - try { - $userFound = $userClient->get($user->getId()); - } catch (\Exception $e) { - throw $e; - } - $this->assertNotEmpty($userFound); - - $this->assertEquals($user->getId(), $userFound['$id']); - $this->assertEquals($user->getEmail(), $userFound['email']); - $this->assertEquals($user->getPhone(), $userFound['phone']); - $this->assertEquals($user->getPasswordHash()->getHash(), $userFound['password']); - $this->assertEquals($user->getEmailVerified(), $userFound['emailVerification']); - $this->assertEquals($user->getPhoneVerified(), $userFound['phoneVerification']); - $assertedUsers = true; } - }); + ); $this->assertTrue($assertedUsers); } diff --git a/tests/tmp/playground.php b/tests/tmp/playground.php index faef212..ca2bbac 100644 --- a/tests/tmp/playground.php +++ b/tests/tmp/playground.php @@ -21,7 +21,9 @@ $dotenv = Dotenv::createImmutable(__DIR__); $dotenv->load(); -/** Initialise All Source Adapters */ +/** + * Initialise All Source Adapters +*/ $sourceAppwrite = new Appwrite( $_ENV['SOURCE_APPWRITE_TEST_PROJECT'], $_ENV['SOURCE_APPWRITE_TEST_ENDPOINT'], @@ -47,7 +49,9 @@ $_ENV["SUPABASE_TEST_PASSWORD"] ?? '', ); -/** Initialise All Destination Adapters */ +/** + * Initialise All Destination Adapters +*/ $destinationAppwrite = new AppwriteDestination( $_ENV['DESTINATION_APPWRITE_TEST_PROJECT'], $_ENV['DESTINATION_APPWRITE_TEST_ENDPOINT'], @@ -56,7 +60,9 @@ $destinationLocal = new Local(__DIR__ . '/databaseDMP.json'); -/** Initialise Transfer Class */ +/** + * Initialise Transfer Class +*/ $sourceFirebase->setProject($sourceFirebase->getProjects()[0]); @@ -65,10 +71,15 @@ $destinationLocal ); -/** Run Transfer */ -$transfer->run([ +/** + * Run Transfer +*/ +$transfer->run( + [ Transfer::RESOURCE_DATABASES -], function () {}); + ], function () { + } +); if (!empty($transfer->getLogs(Log::ERROR))) { echo "\e[41m\e[97mFAILED\e[0m\n"; From 6260c98e97bbd0283b9d4243dba5736d34f0fe46 Mon Sep 17 00:00:00 2001 From: Bradley Schofield Date: Fri, 24 Feb 2023 06:28:38 +0000 Subject: [PATCH 19/70] Rollback Update --- src/Transfer/Destinations/Appwrite.php | 10 +++++----- src/Transfer/Destinations/Local.php | 10 +++++----- src/Transfer/Sources/Appwrite.php | 10 +++++----- src/Transfer/Sources/Firebase.php | 10 +++++----- src/Transfer/Sources/NHost.php | 10 +++++----- 5 files changed, 25 insertions(+), 25 deletions(-) diff --git a/src/Transfer/Destinations/Appwrite.php b/src/Transfer/Destinations/Appwrite.php index 6db0d80..d9fbd97 100644 --- a/src/Transfer/Destinations/Appwrite.php +++ b/src/Transfer/Destinations/Appwrite.php @@ -76,11 +76,11 @@ public function getSupportedResources(): array public function check(array $resources = []): array { $report = [ - 'Users' => true, - 'Databases' => true, - 'Documents' => true, - 'Files' => true, - 'Functions' => true + 'Users' => [], + 'Databases' => [], + 'Documents' => [], + 'Files' => [], + 'Functions' => [] ]; if (empty($resources)) { diff --git a/src/Transfer/Destinations/Local.php b/src/Transfer/Destinations/Local.php index 6db308c..27058de 100644 --- a/src/Transfer/Destinations/Local.php +++ b/src/Transfer/Destinations/Local.php @@ -61,11 +61,11 @@ public function getSupportedResources(): array { public function check(array $resources = []): array { $report = [ - 'Users' => true, - 'Databases' => true, - 'Documents' => true, - 'Files' => true, - 'Functions' => true + 'Users' => [], + 'Databases' => [], + 'Documents' => [], + 'Files' => [], + 'Functions' => [] ]; if (empty($resources)) { diff --git a/src/Transfer/Sources/Appwrite.php b/src/Transfer/Sources/Appwrite.php index ff2c49f..7ac7f2f 100644 --- a/src/Transfer/Sources/Appwrite.php +++ b/src/Transfer/Sources/Appwrite.php @@ -77,11 +77,11 @@ public function getSupportedResources(): array public function check(array $resources = []): array { $report = [ - 'Users' => true, - 'Databases' => true, - 'Documents' => true, - 'Files' => true, - 'Functions' => true + 'Users' => [], + 'Databases' => [], + 'Documents' => [], + 'Files' => [], + 'Functions' => [] ]; if (empty($resources)) { diff --git a/src/Transfer/Sources/Firebase.php b/src/Transfer/Sources/Firebase.php index 103b443..01d16cf 100644 --- a/src/Transfer/Sources/Firebase.php +++ b/src/Transfer/Sources/Firebase.php @@ -403,11 +403,11 @@ function calculateTypes(array $providerData): array public function check(array $resources = []): array { $report = [ - 'Users' => true, - 'Databases' => true, - 'Documents' => true, - 'Files' => true, - 'Functions' => true + 'Users' => [], + 'Databases' => [], + 'Documents' => [], + 'Files' => [], + 'Functions' => [] ]; if (empty($resources)) { diff --git a/src/Transfer/Sources/NHost.php b/src/Transfer/Sources/NHost.php index 204bd3d..4c7ba5f 100644 --- a/src/Transfer/Sources/NHost.php +++ b/src/Transfer/Sources/NHost.php @@ -291,11 +291,11 @@ private function calculateUserTypes(array $user): array public function check(array $resources = []): array { $report = [ - 'Users' => true, - 'Databases' => true, - 'Documents' => true, - 'Files' => true, - 'Functions' => true + 'Users' => [], + 'Databases' => [], + 'Documents' => [], + 'Files' => [], + 'Functions' => [] ]; if (empty($resources)) { From fae74e218c5deaa8ec13a7e85921791f8974948f Mon Sep 17 00:00:00 2001 From: Bradley Schofield Date: Fri, 24 Feb 2023 16:24:24 +0000 Subject: [PATCH 20/70] Use correct status codes --- src/Transfer/Destinations/Appwrite.php | 24 ++++++++++++------------ src/Transfer/Sources/Appwrite.php | 24 ++++++++++++------------ 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/src/Transfer/Destinations/Appwrite.php b/src/Transfer/Destinations/Appwrite.php index d9fbd97..668b0f6 100644 --- a/src/Transfer/Destinations/Appwrite.php +++ b/src/Transfer/Destinations/Appwrite.php @@ -97,7 +97,7 @@ public function check(array $resources = []): array try { $databases->list(); } catch (\Throwable $e) { - if ($e->getCode() !== 403) { + if ($e->getCode() == 401) { $this->logs[Log::ERROR][] = new Log('API Key is missing scope: databases.read'); $report['Databases'][] = 'API Key is missing scope: databases.read'; } @@ -108,7 +108,7 @@ public function check(array $resources = []): array try { $auth->list(); } catch (\Throwable $e) { - if ($e->getCode() !== 403) { + if ($e->getCode() == 401) { $report['Users'][] = 'API Key is missing scope: users.read'; } } @@ -118,7 +118,7 @@ public function check(array $resources = []): array try { $databases->list(); } catch (\Throwable $e) { - if ($e->getCode() !== 403) { + if ($e->getCode() == 401) { $report['Databases'][] = 'API Key is missing scope: databases.read'; } } @@ -126,7 +126,7 @@ public function check(array $resources = []): array try { $databases->create('', ''); } catch (\Throwable $e) { - if ($e->getCode() !== 403) { + if ($e->getCode() == 401) { $report['Databases'][] = 'API Key is missing scope: databases.write'; } } @@ -134,7 +134,7 @@ public function check(array $resources = []): array try { $databases->listCollections('', [], ''); } catch (\Throwable $e) { - if ($e->getCode() !== 403) { + if ($e->getCode() == 401) { $report['Databases'][] = 'API Key is missing scope: collections.write'; } } @@ -142,7 +142,7 @@ public function check(array $resources = []): array try { $databases->createCollection('', '', '', []); } catch (\Throwable $e) { - if ($e->getCode() !== 403) { + if ($e->getCode() == 401) { $report['Databases'][] = 'API Key is missing scope: collections.write'; } } @@ -150,7 +150,7 @@ public function check(array $resources = []): array try { $databases->listDocuments('', '', []); } catch (\Throwable $e) { - if ($e->getCode() !== 403) { + if ($e->getCode() == 401) { $report['Databases'][] = 'API Key is missing scope: documents.write'; } } @@ -158,7 +158,7 @@ public function check(array $resources = []): array try { $databases->createDocument('', '', '', [], []); } catch (\Throwable $e) { - if ($e->getCode() !== 403) { + if ($e->getCode() == 401) { $report['Documents'][] = 'API Key is missing scope: documents.write'; } } @@ -166,7 +166,7 @@ public function check(array $resources = []): array try { $databases->listIndexes('', ''); } catch (\Throwable $e) { - if ($e->getCode() !== 403) { + if ($e->getCode() == 401) { $report['Databases'][] = 'API Key is missing scope: indexes.read'; } } @@ -174,7 +174,7 @@ public function check(array $resources = []): array try { $databases->createIndex('', '', '', '', [], []); } catch (\Throwable $e) { - if ($e->getCode() !== 403) { + if ($e->getCode() == 401) { $report['Databases'][] = 'API Key is missing scope: indexes.write'; } } @@ -182,7 +182,7 @@ public function check(array $resources = []): array try { $databases->listAttributes('', ''); } catch (\Throwable $e) { - if ($e->getCode() !== 403) { + if ($e->getCode() == 401) { $report['Databases'][] = 'API Key is missing scope: attributes.read'; } } @@ -190,7 +190,7 @@ public function check(array $resources = []): array try { $databases->createStringAttribute('', '', '', 0, false, false); } catch (\Throwable $e) { - if ($e->getCode() !== 403) { + if ($e->getCode() == 401) { $report['Databases'][] = 'API Key is missing scope: attributes.write'; } } diff --git a/src/Transfer/Sources/Appwrite.php b/src/Transfer/Sources/Appwrite.php index 7ac7f2f..25b1227 100644 --- a/src/Transfer/Sources/Appwrite.php +++ b/src/Transfer/Sources/Appwrite.php @@ -98,7 +98,7 @@ public function check(array $resources = []): array try { $databases->list(); } catch (\Throwable $e) { - if ($e->getCode() !== 403) { + if ($e->getCode() == 401) { $report['Databases'][] = 'API Key is missing scope: databases.read'; } } @@ -108,7 +108,7 @@ public function check(array $resources = []): array try { $auth->list(); } catch (\Throwable $e) { - if ($e->getCode() !== 403) { + if ($e->getCode() == 401) { $report['Users'][] = 'API Key is missing scope: users.read'; } } @@ -118,7 +118,7 @@ public function check(array $resources = []): array try { $databases->list(); } catch (\Throwable $e) { - if ($e->getCode() !== 403) { + if ($e->getCode() == 401) { $report['Databases'][] = 'API Key is missing scope: databases.read'; } } @@ -126,7 +126,7 @@ public function check(array $resources = []): array try { $databases->create('', ''); } catch (\Throwable $e) { - if ($e->getCode() !== 403) { + if ($e->getCode() == 401) { $report['Databases'][] = 'API Key is missing scope: databases.write'; } } @@ -134,7 +134,7 @@ public function check(array $resources = []): array try { $databases->listCollections('', [], ''); } catch (\Throwable $e) { - if ($e->getCode() !== 403) { + if ($e->getCode() == 401) { $report['Databases'][] = 'API Key is missing scope: collections.write'; } } @@ -142,7 +142,7 @@ public function check(array $resources = []): array try { $databases->createCollection('', '', '', []); } catch (\Throwable $e) { - if ($e->getCode() !== 403) { + if ($e->getCode() == 401) { $report['Databases'][] = 'API Key is missing scope: collections.write'; } } @@ -150,7 +150,7 @@ public function check(array $resources = []): array try { $databases->listDocuments('', '', []); } catch (\Throwable $e) { - if ($e->getCode() !== 403) { + if ($e->getCode() == 401) { $report['Databases'][] = 'API Key is missing scope: documents.write'; } } @@ -158,7 +158,7 @@ public function check(array $resources = []): array try { $databases->createDocument('', '', '', [], []); } catch (\Throwable $e) { - if ($e->getCode() !== 403) { + if ($e->getCode() == 401) { $report['Documents'][] = 'API Key is missing scope: documents.write'; } } @@ -166,7 +166,7 @@ public function check(array $resources = []): array try { $databases->listIndexes('', ''); } catch (\Throwable $e) { - if ($e->getCode() !== 403) { + if ($e->getCode() == 401) { $report['Databases'][] = 'API Key is missing scope: indexes.read'; } } @@ -174,7 +174,7 @@ public function check(array $resources = []): array try { $databases->createIndex('', '', '', '', [], []); } catch (\Throwable $e) { - if ($e->getCode() !== 403) { + if ($e->getCode() == 401) { $report['Databases'][] = 'API Key is missing scope: indexes.write'; } } @@ -182,7 +182,7 @@ public function check(array $resources = []): array try { $databases->listAttributes('', ''); } catch (\Throwable $e) { - if ($e->getCode() !== 403) { + if ($e->getCode() == 401) { $report['Databases'][] = 'API Key is missing scope: attributes.read'; } } @@ -190,7 +190,7 @@ public function check(array $resources = []): array try { $databases->createStringAttribute('', '', '', 0, false, false); } catch (\Throwable $e) { - if ($e->getCode() !== 403) { + if ($e->getCode() == 401) { $report['Databases'][] = 'API Key is missing scope: attributes.write'; } } From 3949a5b7cfd47430ab30b8a12ebd812a032fff48 Mon Sep 17 00:00:00 2001 From: Bradley Schofield Date: Wed, 22 Mar 2023 13:30:47 +0900 Subject: [PATCH 21/70] Various Fixes and Other things found while implementing API + Fixed a few null safety errors + Fixed a bug where a 0 division could occur on adapters that did not support totals when requesting progress --- src/Transfer/Destination.php | 6 +++--- src/Transfer/Progress.php | 4 ++++ src/Transfer/Source.php | 6 +++--- src/Transfer/Sources/NHost.php | 4 ++-- 4 files changed, 12 insertions(+), 8 deletions(-) diff --git a/src/Transfer/Destination.php b/src/Transfer/Destination.php index 67fd542..d62d588 100644 --- a/src/Transfer/Destination.php +++ b/src/Transfer/Destination.php @@ -95,12 +95,12 @@ public function registerLogs(array &$logs): void { /** * Get Resource Counters * - * @param string $resource + * @param string $resource = null * * @return array */ - public function &getCounter(string $resource): array { - if ($this->counters[$resource]) { + public function &getCounter(?string $resource = null): array { + if ($resource && $this->counters[$resource]) { return $this->counters[$resource]; } else { $this->counters[$resource] = [ diff --git a/src/Transfer/Progress.php b/src/Transfer/Progress.php index a6dd5d7..231515e 100644 --- a/src/Transfer/Progress.php +++ b/src/Transfer/Progress.php @@ -167,6 +167,10 @@ public function setSkipped(int $skipped): self */ public function getProgress(): float { + if ($this->total === 0) { + return 0; + } + return ($this->current / $this->total) * 100; } diff --git a/src/Transfer/Source.php b/src/Transfer/Source.php index 99821f8..eff2a7d 100644 --- a/src/Transfer/Source.php +++ b/src/Transfer/Source.php @@ -46,12 +46,12 @@ abstract class Source /** * Get Resource Counters * - * @param string $resource + * @param string $resource = null * * @return array */ - public function &getCounter(string $resource): array { - if ($this->counters[$resource]) { + public function &getCounter(string $resource = null): array { + if ($resource && $this->counters[$resource]) { return $this->counters[$resource]; } else { $this->counters[$resource] = [ diff --git a/src/Transfer/Sources/NHost.php b/src/Transfer/Sources/NHost.php index 4c7ba5f..8dc414a 100644 --- a/src/Transfer/Sources/NHost.php +++ b/src/Transfer/Sources/NHost.php @@ -302,8 +302,8 @@ public function check(array $resources = []): array $resources = $this->getSupportedResources(); } - if ($this->pdo->errorCode() !== '00000') { - $report['Databases'][] = 'Failed to connect to database. Error: ' . $this->pdo->errorInfo()[2]; + if (!empty($this->pdo->errorCode())) { + $report['Databases'][] = 'Failed to connect to database. PDO Code: '. $this->pdo->errorCode() . (empty($this->pdo->errorInfo()[2]) ? '' : ' Error: ' . $this->pdo->errorInfo()[2]); } foreach ($resources as $resource) { From 3173861ec14146e3548e24bdb27c0859c0da5351 Mon Sep 17 00:00:00 2001 From: Bradley Schofield Date: Mon, 27 Mar 2023 13:31:42 +0900 Subject: [PATCH 22/70] First iteration --- src/Transfer/Resources/Index.php | 4 +++ src/Transfer/Sources/NHost.php | 52 +++++++++++++++++++++++++++++++- tests/tmp/playground.php | 14 ++++----- 3 files changed, 62 insertions(+), 8 deletions(-) diff --git a/src/Transfer/Resources/Index.php b/src/Transfer/Resources/Index.php index 24ecf1d..bd439bf 100644 --- a/src/Transfer/Resources/Index.php +++ b/src/Transfer/Resources/Index.php @@ -11,6 +11,10 @@ class Index extends Resource { protected array $attributes; protected array $orders; + const TYPE_UNIQUE = 'unique'; + const TYPE_FULLTEXT = 'fulltext'; + const TYPE_KEY = 'key'; + /** * @param string $key * @param string $type diff --git a/src/Transfer/Sources/NHost.php b/src/Transfer/Sources/NHost.php index 8dc414a..9b37ff0 100644 --- a/src/Transfer/Sources/NHost.php +++ b/src/Transfer/Sources/NHost.php @@ -15,6 +15,7 @@ use Utopia\Transfer\Resources\Attributes\IntAttribute; use Utopia\Transfer\Resources\Attributes\StringAttribute; use Utopia\Transfer\Resources\Collection; +use Utopia\Transfer\Resources\Index; class NHost extends Source { @@ -150,6 +151,19 @@ public function convertCollection(string $tableName): Collection } $convertedCollection->setAttributes($attributes); + // Handle Indexes + + $indexStatement = $this->pdo->prepare('SELECT indexname, indexdef FROM pg_indexes WHERE tablename = :tableName'); + $indexStatement->bindValue(':tableName', $tableName, \PDO::PARAM_STR); + $indexStatement->execute(); + + $databaseIndexes = $indexStatement->fetchAll(\PDO::FETCH_ASSOC); + $indexes = []; + + foreach ($databaseIndexes as $index) { + $indexes[] = $this->convertIndex($index); + } + return $convertedCollection; } @@ -228,6 +242,42 @@ public function convertAttribute(array $column): Attribute } } + /** + * Convert Index + * + * @param string $table + * @return Index|false + */ + public function convertIndex(array $index): Index|false + { + $pattern = "/CREATE (?UNIQUE)? INDEX (?\w+) ON (?\w+\.\w+) USING (?\w+) \((?\w+)\)/"; + + if (\preg_match($pattern, $index['indexdef'], $matches)) { + // We only support BTree indexes + if ($matches['method'] !== 'btree') { + $this->logs[Log::ERROR][] = new Log('Skipping index due to unsupported type: ' . $matches['method'] . ' for index: ' . $matches['name'] . '. Transfers only support BTree.', \time()); + + return false; + } + + $type = ""; + + if ($matches['unique'] === 'UNIQUE') { + $type = Index::TYPE_UNIQUE; + } else { + $type = Index::TYPE_FULLTEXT; + } + + $targets = explode(" ", $matches['columns']); + + return new Index($matches['name'], $type, $targets, ["ASC"]); + } else { + $this->logs[Log::ERROR][] = new Log('Skipping index due to unsupported format: ' . $index['indexdef'] . ' for index: ' . $index['indexname'] . '. Transfers only support BTree.', \time()); + + return false; + } + } + /** * Export Databases * @@ -243,7 +293,7 @@ public function exportDatabases(int $batchSize, callable $callback): void $offset = 0; // We'll only transfer the public database for now, since it's the only one that exists by default. - //TODO: Handle edge cases where there are user created databases. + //TODO: Handle edge cases where there are user created databases and data. $transferDatabase = new Database('public', 'public'); diff --git a/tests/tmp/playground.php b/tests/tmp/playground.php index ca2bbac..6cc96d6 100644 --- a/tests/tmp/playground.php +++ b/tests/tmp/playground.php @@ -35,12 +35,12 @@ Firebase::AUTH_SERVICEACCOUNT ); -$sourceNHost = new NHost( - $_ENV["NHOST_TEST_HOST"] ?? '', - $_ENV["NHOST_TEST_DATABASE"] ?? '', - $_ENV["NHOST_TEST_USERNAME"] ?? '', - $_ENV["NHOST_TEST_PASSWORD"] ?? '', -); +// $sourceNHost = new NHost( +// $_ENV["NHOST_TEST_HOST"] ?? '', +// $_ENV["NHOST_TEST_DATABASE"] ?? '', +// $_ENV["NHOST_TEST_USERNAME"] ?? '', +// $_ENV["NHOST_TEST_PASSWORD"] ?? '', +// ); $sourceSupabase = new Supabase( $_ENV["SUPABASE_TEST_HOST"] ?? '', @@ -67,7 +67,7 @@ $sourceFirebase->setProject($sourceFirebase->getProjects()[0]); $transfer = new Transfer( - $sourceAppwrite, + $sourceSupabase, $destinationLocal ); From c042ac601ad4aeeb74e078fc42368f5dbd7bf392 Mon Sep 17 00:00:00 2001 From: Bradley Schofield Date: Mon, 27 Mar 2023 14:27:02 +0900 Subject: [PATCH 23/70] Improve algorithm for converting Indexes from Jake's feedback --- src/Transfer/Resource.php | 4 ++++ src/Transfer/Resources/Index.php | 3 ++- src/Transfer/Sources/NHost.php | 30 ++++++++++++++++++++++++------ 3 files changed, 30 insertions(+), 7 deletions(-) diff --git a/src/Transfer/Resource.php b/src/Transfer/Resource.php index 769dec1..eb3a838 100644 --- a/src/Transfer/Resource.php +++ b/src/Transfer/Resource.php @@ -4,6 +4,10 @@ abstract class Resource { + /** + * ID of the resource, if any. + * If "unique()" then handle it using a random ID of the destination platform. + */ protected string $id = ''; /** diff --git a/src/Transfer/Resources/Index.php b/src/Transfer/Resources/Index.php index bd439bf..b7f94e9 100644 --- a/src/Transfer/Resources/Index.php +++ b/src/Transfer/Resources/Index.php @@ -22,8 +22,9 @@ class Index extends Resource { * @param array $orders */ - public function __construct(string $key, string $type = '', array $attributes = [], array $orders = []) + public function __construct(string $id, string $key, string $type = '', array $attributes = [], array $orders = []) { + $this->id = $id; $this->key = $key; $this->type = $type; $this->attributes = $attributes; diff --git a/src/Transfer/Sources/NHost.php b/src/Transfer/Sources/NHost.php index 9b37ff0..68d47de 100644 --- a/src/Transfer/Sources/NHost.php +++ b/src/Transfer/Sources/NHost.php @@ -161,7 +161,9 @@ public function convertCollection(string $tableName): Collection $indexes = []; foreach ($databaseIndexes as $index) { - $indexes[] = $this->convertIndex($index); + $result = $this->convertIndex($index); + + $indexes[] = $result; } return $convertedCollection; @@ -250,7 +252,7 @@ public function convertAttribute(array $column): Attribute */ public function convertIndex(array $index): Index|false { - $pattern = "/CREATE (?UNIQUE)? INDEX (?\w+) ON (?
\w+\.\w+) USING (?\w+) \((?\w+)\)/"; + $pattern = "/CREATE (?\w+)? INDEX (?\w+) ON (?
\w+\.\w+) USING (?\w+) \((?\w+)\)/"; if (\preg_match($pattern, $index['indexdef'], $matches)) { // We only support BTree indexes @@ -262,15 +264,31 @@ public function convertIndex(array $index): Index|false $type = ""; - if ($matches['unique'] === 'UNIQUE') { + if ($matches['type'] === 'UNIQUE') { $type = Index::TYPE_UNIQUE; - } else { + } else if ($matches['type'] === 'FULLTEXT') { $type = Index::TYPE_FULLTEXT; + } else { + $type = Index::TYPE_KEY; } - $targets = explode(" ", $matches['columns']); + $attributes = []; + $order = []; + + $targets = explode(",", $matches['columns']); + + foreach ($targets as $target) { + if (\strpos($target, ' ') !== false) { + $target = \explode(' ', $target); + $attributes[] = $target[0]; + $order[] = $target[1]; + } else { + $attributes[] = $target; + $order[] = "ASC"; + } + } - return new Index($matches['name'], $type, $targets, ["ASC"]); + return new Index($matches['name'], $matches['name'], $type, $attributes, $order); } else { $this->logs[Log::ERROR][] = new Log('Skipping index due to unsupported format: ' . $index['indexdef'] . ' for index: ' . $index['indexname'] . '. Transfers only support BTree.', \time()); From ab75702c5e5468789857420a0a9e7e4c5b3ac457 Mon Sep 17 00:00:00 2001 From: Bradley Schofield Date: Mon, 27 Mar 2023 22:26:06 +0900 Subject: [PATCH 24/70] Finish Implementing Indexes for both NHost and Supabase --- src/Transfer/Destinations/Appwrite.php | 39 +++++++++++++++----------- src/Transfer/Resources/Database.php | 18 +++++++++++- src/Transfer/Sources/Firebase.php | 2 +- src/Transfer/Sources/NHost.php | 4 +-- tests/tmp/playground.php | 2 +- 5 files changed, 43 insertions(+), 22 deletions(-) diff --git a/src/Transfer/Destinations/Appwrite.php b/src/Transfer/Destinations/Appwrite.php index 668b0f6..38a80ef 100644 --- a/src/Transfer/Destinations/Appwrite.php +++ b/src/Transfer/Destinations/Appwrite.php @@ -10,7 +10,6 @@ use Utopia\Transfer\Resources\Hash; use Utopia\Transfer\Log; use Utopia\Transfer\Progress; -use Utopia\Transfer\Resource; use Utopia\Transfer\Resources\Attribute; use Utopia\Transfer\Resources\User; use Utopia\Transfer\Transfer; @@ -442,13 +441,16 @@ public function importDatabases(array $databases, callable $callback): void /** @var Collection $collection */ $createdAttributes = []; - // Get filename and parent directory - $path = \explode('/', $collection->getCollectionName()); + if ($database->getType() == Database::DB_NON_RELATIONAL) { + $path = \explode('/', $collection->getCollectionName()); - $collectionName = $path[count($path) - 1]; + $collectionName = $path[count($path) - 1]; - if (isset($path[count($path) - 2])) { - $collectionName = $path[count($path) - 2]."/".$collectionName; + if (isset($path[count($path) - 2])) { + $collectionName = $path[count($path) - 2] . "/" . $collectionName; + } + } else { + $collectionName = $collection->getCollectionName(); } // Handle special chars @@ -462,7 +464,7 @@ public function importDatabases(array $databases, callable $callback): void $newCollection = $databaseService->createCollection($database->getId(), "unique()", $collectionName); $collection->setId($newCollection['$id']); - // Remove duplicate attributes + // Remove duplicate attributes, TODO: Merge them together. $filteredAttributes = \array_filter($collection->getAttributes(), function ($attribute) use (&$createdAttributes) { if (\in_array($attribute->getKey(), $createdAttributes)) { return false; @@ -500,16 +502,19 @@ public function importDatabases(array $databases, callable $callback): void $createdCollections[] = $collection; } - $refCollectionID = $databaseService->createCollection($database->getId(), 'refs', 'References')['$id']; - $databaseService->createStringAttribute($database->getId(), $refCollectionID, 'original_name', 1000000, true); - - sleep(2); - - foreach ($createdCollections as $collection) { - /** @var Collection $collection */ - $result = $databaseService->createDocument($database->getId(), $refCollectionID, $collection->getId(), [ - 'original_name' => $collection->getCollectionName() - ]); + // TODO: Rewrite to use new Appwrite relations + if ($database->getType() == Database::DB_NON_RELATIONAL) { + $refCollectionID = $databaseService->createCollection($database->getId(), 'refs', 'References')['$id']; + $databaseService->createStringAttribute($database->getId(), $refCollectionID, 'original_name', 1000000, true); + + sleep(2); + + foreach ($createdCollections as $collection) { + /** @var Collection $collection */ + $result = $databaseService->createDocument($database->getId(), $refCollectionID, $collection->getId(), [ + 'original_name' => $collection->getCollectionName() + ]); + } } $this->logs[Log::SUCCESS][] = new Log('Database imported successfully', \time(), $database); diff --git a/src/Transfer/Resources/Database.php b/src/Transfer/Resources/Database.php index 9840315..01b1275 100644 --- a/src/Transfer/Resources/Database.php +++ b/src/Transfer/Resources/Database.php @@ -14,6 +14,9 @@ class Database extends Resource { + const DB_RELATIONAL = 'relational'; + const DB_NON_RELATIONAL = 'non-relational'; + /** * @var list $collections */ @@ -21,11 +24,13 @@ class Database extends Resource protected string $name; protected string $id; + protected string $type; - public function __construct(string $name = '', string $id = '') + public function __construct(string $name = '', string $id = '', string $type = self::DB_RELATIONAL) { $this->name = $name; $this->id = $id; + $this->type = $type; } public function getName(): string @@ -49,6 +54,17 @@ public function setId(string $id): self return $this; } + public function getType(): string + { + return $this->type; + } + + public function setType(string $type): self + { + $this->type = $type; + return $this; + } + public function getCollections(): array { return $this->collections; diff --git a/src/Transfer/Sources/Firebase.php b/src/Transfer/Sources/Firebase.php index 01d16cf..d6f21f6 100644 --- a/src/Transfer/Sources/Firebase.php +++ b/src/Transfer/Sources/Firebase.php @@ -368,7 +368,7 @@ public function exportDatabases(int $batchSize, callable $callback): void // Let's grab the root collections. (google's params technically doesn't allow this, however they do it in their own console) $request = $firestore->projects_databases_documents->listCollectionIds('projects/' . $this->project->getId() . '/databases/(default)/documents', new ListCollectionIdsRequest()); - $database = new Database('Default', 'Default'); + $database = new Database('Default', 'Default', Database::DB_NON_RELATIONAL); $database->setCollections($this->handleCollections($request['collectionIds'])); diff --git a/src/Transfer/Sources/NHost.php b/src/Transfer/Sources/NHost.php index 68d47de..f1bcd5e 100644 --- a/src/Transfer/Sources/NHost.php +++ b/src/Transfer/Sources/NHost.php @@ -159,12 +159,12 @@ public function convertCollection(string $tableName): Collection $databaseIndexes = $indexStatement->fetchAll(\PDO::FETCH_ASSOC); $indexes = []; - foreach ($databaseIndexes as $index) { $result = $this->convertIndex($index); $indexes[] = $result; } + $convertedCollection->setIndexes($indexes); return $convertedCollection; } @@ -211,7 +211,7 @@ public function convertAttribute(array $column): Attribute case 'time': case 'timetz': case 'interval': - return new DateTimeAttribute($column['column_name'], $column['is_nullable'] === 'NO', $isArray, ($column['column_default'] === 'now()') ? 'now()' : ($column['column_default'] ?? null)); + return new DateTimeAttribute($column['column_name'], $column['is_nullable'] === 'NO', $isArray, null); break; // Strings and Objects case 'uuid': diff --git a/tests/tmp/playground.php b/tests/tmp/playground.php index 6cc96d6..7b0f34d 100644 --- a/tests/tmp/playground.php +++ b/tests/tmp/playground.php @@ -68,7 +68,7 @@ $transfer = new Transfer( $sourceSupabase, - $destinationLocal + $destinationAppwrite ); /** From 568ba73cb50152be127a0b4f5a930dcced8c903c Mon Sep 17 00:00:00 2001 From: Bradley Schofield Date: Wed, 29 Mar 2023 14:59:49 +0900 Subject: [PATCH 25/70] Finish Implementing Document support in both Nhost and Supabase --- src/Transfer/Destination.php | 81 ++++++++++++----- src/Transfer/Destinations/Appwrite.php | 46 +++++++++- src/Transfer/Destinations/Local.php | 33 +++++++ src/Transfer/Resource.php | 1 - .../Resources/Attributes/FloatAttribute.php | 14 +-- .../Resources/Attributes/IntAttribute.php | 18 ++-- src/Transfer/Resources/Database.php | 3 + src/Transfer/Resources/Document.php | 86 +++++++++++++++++++ src/Transfer/Source.php | 72 +++++++++++++++- src/Transfer/Sources/NHost.php | 67 ++++++++++++++- src/Transfer/Sources/Supabase.php | 8 -- tests/tmp/playground.php | 3 +- 12 files changed, 375 insertions(+), 57 deletions(-) create mode 100644 src/Transfer/Resources/Document.php diff --git a/src/Transfer/Destination.php b/src/Transfer/Destination.php index d62d588..dc802bd 100644 --- a/src/Transfer/Destination.php +++ b/src/Transfer/Destination.php @@ -27,7 +27,13 @@ abstract class Destination * * @var array $resourceCache */ - protected $resourceCache = []; + protected $resourceCache = [ + Transfer::RESOURCE_DATABASES => [], + Transfer::RESOURCE_DOCUMENTS => [], + Transfer::RESOURCE_FILES => [], + Transfer::RESOURCE_FUNCTIONS => [], + Transfer::RESOURCE_USERS => [] + ]; /** * @var string @@ -67,7 +73,8 @@ abstract public function getSupportedResources(): array; * * @return Source */ - public function getSource(): Source { + public function getSource(): Source + { return $this->source; } @@ -78,7 +85,8 @@ public function getSource(): Source { * * @return self */ - public function setSource(Source $source): self { + public function setSource(Source $source): self + { $this->source = $source; return $this; } @@ -88,7 +96,8 @@ public function setSource(Source $source): self { * * @param array &$logs */ - public function registerLogs(array &$logs): void { + public function registerLogs(array &$logs): void + { $this->logs = &$logs; } @@ -99,7 +108,8 @@ public function registerLogs(array &$logs): void { * * @return array */ - public function &getCounter(?string $resource = null): array { + public function &getCounter(?string $resource = null): array + { if ($resource && $this->counters[$resource]) { return $this->counters[$resource]; } else { @@ -122,7 +132,8 @@ public function &getCounter(?string $resource = null): array { * * @return void */ - public function registerTransferHooks(array &$cache, array &$counters): void { + public function registerTransferHooks(array &$cache, array &$counters): void + { $this->resourceCache = &$cache; $this->counters = &$counters; } @@ -133,28 +144,30 @@ public function registerTransferHooks(array &$cache, array &$counters): void { * @param array $resources * @param callable $callback */ - public function run(array $resources, callable $callback, Source $source): void { + public function run(array $resources, callable $callback, Source $source): void + { $this->source = $source; - - foreach ($resources as $resource) { - if (!in_array($resource, $this->getSupportedResources())) { - $this->logs[Log::FATAL] = new Log("Cannot Transfer unsupported resource: '".$resource."'"); - throw new \Exception("Cannot Transfer unsupported resource: '".$resource."'"); - } - $source->run($resources, function (string $resourceType, array $resource) use ($callback) { - switch ($resourceType) { - case Transfer::RESOURCE_USERS: { + $source->run($resources, function (string $resourceType, array $resource) use ($callback) { + switch ($resourceType) { + case Transfer::RESOURCE_USERS: { $this->importUsers($resource, $callback); break; } - case Transfer::RESOURCE_DATABASES: { + case Transfer::RESOURCE_DATABASES: { $this->importDatabases($resource, $callback); break; } - } - }); - } + case Transfer::RESOURCE_DOCUMENTS: { + $this->importDocuments($resource, $callback); + break; + } + case Transfer::RESOURCE_FILES: { + $this->importFiles($resource, $callback); + break; + } + } + }); } /** @@ -292,7 +305,8 @@ protected function flatten(array $data, string $prefix = ''): array * @param array $users * @param callable $callback (Progress $progress) */ - protected function importUsers(array $users, callable $callback): void { + protected function importUsers(array $users, callable $callback): void + { throw new \Exception("Not Implemented"); } @@ -302,7 +316,30 @@ protected function importUsers(array $users, callable $callback): void { * @param array $databases * @param callable $callback (Progress $progress) */ - protected function importDatabases(array $databases, callable $callback): void { + protected function importDatabases(array $databases, callable $callback): void + { + throw new \Exception("Not Implemented"); + } + + /** + * Import Documents + * + * @param array $documents + * @param callable $callback (Progress $progress) + */ + protected function importDocuments(array $documents, callable $callback): void + { + throw new \Exception("Not Implemented"); + } + + /** + * Import Files + * + * @param array $files + * @param callable $callback (Progress $progress) + */ + protected function importFiles(array $files, callable $callback): void + { throw new \Exception("Not Implemented"); } } diff --git a/src/Transfer/Destinations/Appwrite.php b/src/Transfer/Destinations/Appwrite.php index 38a80ef..f4ce9a1 100644 --- a/src/Transfer/Destinations/Appwrite.php +++ b/src/Transfer/Destinations/Appwrite.php @@ -15,6 +15,7 @@ use Utopia\Transfer\Transfer; use Utopia\Transfer\Resources\Database; use Utopia\Transfer\Resources\Collection; +use Utopia\Transfer\Resources\Document; use Utopia\Transfer\Resources\Attributes\BoolAttribute; use Utopia\Transfer\Resources\Attributes\DateTimeAttribute; use Utopia\Transfer\Resources\Attributes\EmailAttribute; @@ -34,6 +35,8 @@ class Appwrite extends Destination protected string $endpoint; protected string $key; + private array $conversionTable = []; + public function __construct(string $project, string $endpoint, string $key) { $this->project = $project; @@ -342,11 +345,11 @@ public function createAttribute(Attribute $attribute, Collection $collection, Da break; case Attribute::TYPE_INTEGER: /** @var IntAttribute $attribute */ - $databaseService->createIntegerAttribute($database->getId(), $collection->getId(), $attribute->getKey(), $attribute->getRequired(), $attribute->getMin(), $attribute->getMax(), $attribute->getDefault(), $attribute->getArray()); + $databaseService->createIntegerAttribute($database->getId(), $collection->getId(), $attribute->getKey(), $attribute->getRequired(), $attribute->getMin(), $attribute->getMax() ?? null, $attribute->getDefault(), $attribute->getArray()); break; case Attribute::TYPE_FLOAT: /** @var FloatAttribute $attribute */ - $databaseService->createFloatAttribute($database->getId(), $collection->getId(), $attribute->getKey(), $attribute->getRequired(), $attribute->getDefault(), $attribute->getArray()); + $databaseService->createFloatAttribute($database->getId(), $collection->getId(), $attribute->getKey(), $attribute->getRequired(), null, null, $attribute->getDefault(), $attribute->getArray()); break; case Attribute::TYPE_BOOLEAN: /** @var BoolAttribute $attribute */ @@ -461,7 +464,7 @@ public function importDatabases(array $databases, callable $callback): void $collectionName = \substr($collectionName, 0, 120); } - $newCollection = $databaseService->createCollection($database->getId(), "unique()", $collectionName); + $newCollection = $databaseService->createCollection($database->getId(), $collection->getId(), $collectionName); $collection->setId($newCollection['$id']); // Remove duplicate attributes, TODO: Merge them together. @@ -536,4 +539,41 @@ public function importDatabases(array $databases, callable $callback): void ) ); } + + + /** + * Import Documents + * + * @param array $documents + * @param callable $callback (Progress $progress) + */ + protected function importDocuments(array $documents, callable $callback): void { + $documentCounters = &$this->getCounter(Transfer::RESOURCE_DOCUMENTS); + $databaseService = new Databases($this->client); + + foreach ($documents as $document) { + /** @var Document $document */ + + try { + $databaseService->createDocument($document->getDatabase(), $document->getCollection()->getId(), 'unique()', $document->getData()); + + $this->logs[Log::SUCCESS][] = new Log('Document imported successfully', \time(), $document); + $documentCounters['current']++; + } catch (AppwriteException $e) { + $this->logs[Log::ERROR][] = new Log($e->getMessage(), \time(), $document); + $documentCounters['failed']++; + } + } + + $callback( + new Progress( + Transfer::RESOURCE_DOCUMENTS, + time(), + $documentCounters['total'], + $documentCounters['current'], + $documentCounters['failed'], + $documentCounters['skipped'] + ) + ); + } } diff --git a/src/Transfer/Destinations/Local.php b/src/Transfer/Destinations/Local.php index 27058de..436c763 100644 --- a/src/Transfer/Destinations/Local.php +++ b/src/Transfer/Destinations/Local.php @@ -151,4 +151,37 @@ public function importDatabases(array $databases, callable $callback): void $this->syncFile(); } + + /** + * Import Documents + * + * @param array $documents + * @param callable $callback + * + * @return void + */ + public function importDocuments(array $documents, callable $callback): void + { + $documentCounters = &$this->getCounter(Transfer::RESOURCE_DOCUMENTS); + + foreach ($documents as $document) { + /** @var Database $document */ + $this->data[Transfer::RESOURCE_DOCUMENTS][] = $document->asArray(); + $this->logs[Log::SUCCESS][] = new Log('Document imported successfully', \time(), $document); + $documentCounters['current']++; + } + + $callback( + new Progress( + Transfer::RESOURCE_DOCUMENTS, + time(), + $documentCounters['total'], + $documentCounters['current'], + $documentCounters['failed'], + $documentCounters['skipped'] + ) + ); + + $this->syncFile(); + } } \ No newline at end of file diff --git a/src/Transfer/Resource.php b/src/Transfer/Resource.php index eb3a838..f1c1f5a 100644 --- a/src/Transfer/Resource.php +++ b/src/Transfer/Resource.php @@ -6,7 +6,6 @@ abstract class Resource { /** * ID of the resource, if any. - * If "unique()" then handle it using a random ID of the destination platform. */ protected string $id = ''; diff --git a/src/Transfer/Resources/Attributes/FloatAttribute.php b/src/Transfer/Resources/Attributes/FloatAttribute.php index ff46eb6..8bbc247 100644 --- a/src/Transfer/Resources/Attributes/FloatAttribute.php +++ b/src/Transfer/Resources/Attributes/FloatAttribute.php @@ -6,18 +6,18 @@ class FloatAttribute extends Attribute { protected ?float $default; - protected float $min; - protected float $max; + protected ?float $min; + protected ?float $max; /** * @param string $key * @param bool $required * @param bool $array * @param ?float $default - * @param float $min - * @param float $max + * @param ?float $min + * @param ?float $max */ - function __construct(string $key, bool $required = false, bool $array = false, ?float $default = null, float $min = 0, float $max = 0) + function __construct(string $key, bool $required = false, bool $array = false, ?float $default = null, float $min = null, float $max = null) { parent::__construct($key, $required, $array); $this->default = $default; @@ -30,12 +30,12 @@ function getName(): string return 'floatAttribute'; } - function getMin(): float + function getMin(): float|null { return $this->min; } - function getMax(): float + function getMax(): float|null { return $this->max; } diff --git a/src/Transfer/Resources/Attributes/IntAttribute.php b/src/Transfer/Resources/Attributes/IntAttribute.php index f92ad79..a360c7a 100644 --- a/src/Transfer/Resources/Attributes/IntAttribute.php +++ b/src/Transfer/Resources/Attributes/IntAttribute.php @@ -6,18 +6,18 @@ class IntAttribute extends Attribute { protected ?int $default; - protected int $min; - protected int $max; + protected ?int $min; + protected ?int $max; /** * @param string $key * @param bool $required * @param bool $array * @param ?int $default - * @param int $min - * @param int $max + * @param ?int $min + * @param ?int $max */ - function __construct(string $key, bool $required = false, bool $array = false, ?int $default = null, int $min = 0, int $max = 0) + function __construct(string $key, bool $required = false, bool $array = false, ?int $default = null, int $min = null, int $max = null) { parent::__construct($key, $required, $array); $this->default = $default; @@ -30,23 +30,23 @@ function getName(): string return 'intAttribute'; } - function getMin(): int + function getMin(): int|null { return $this->min; } - function getMax(): int + function getMax(): int|null { return $this->max; } - function setMin(int $min): self + function setMin(int|null $min): self { $this->min = $min; return $this; } - function setMax(int $max): self + function setMax(int|null $max): self { $this->max = $max; return $this; diff --git a/src/Transfer/Resources/Database.php b/src/Transfer/Resources/Database.php index 01b1275..a364cab 100644 --- a/src/Transfer/Resources/Database.php +++ b/src/Transfer/Resources/Database.php @@ -65,6 +65,9 @@ public function setType(string $type): self return $this; } + /** + * @return list + */ public function getCollections(): array { return $this->collections; diff --git a/src/Transfer/Resources/Document.php b/src/Transfer/Resources/Document.php new file mode 100644 index 0000000..57b530b --- /dev/null +++ b/src/Transfer/Resources/Document.php @@ -0,0 +1,86 @@ +id = $id; + $this->database = $database; + $this->collection = $collection; + $this->data = $data; + } + + public function getName(): string + { + return 'document'; + } + + public function getId(): string + { + return $this->id; + } + + public function setId(string $id): self + { + $this->id = $id; + return $this; + } + + public function getDatabase(): string + { + return $this->database; + } + + public function setDatabase(string $database): self + { + $this->database = $database; + return $this; + } + + public function getCollection(): Collection + { + return $this->collection; + } + + public function setCollection(Collection $collection): self + { + $this->collection = $collection; + return $this; + } + + public function getData(): array + { + return $this->data; + } + + /** + * Set Data + * + * @param array $data + * + * @return self + */ + public function setData(array $data): self + { + $this->data = $data; + return $this; + } + + public function asArray(): array + { + return [ + 'id' => $this->id, + 'database' => $this->database, + 'collection' => $this->collection, + 'attributes' => $this->data, + ]; + } +} \ No newline at end of file diff --git a/src/Transfer/Source.php b/src/Transfer/Source.php index eff2a7d..c38e74c 100644 --- a/src/Transfer/Source.php +++ b/src/Transfer/Source.php @@ -27,7 +27,13 @@ abstract class Source * * @var array $resourceCache */ - protected $resourceCache = []; + protected $resourceCache = [ + Transfer::RESOURCE_DATABASES => [], + Transfer::RESOURCE_DOCUMENTS => [], + Transfer::RESOURCE_FILES => [], + Transfer::RESOURCE_FUNCTIONS => [], + Transfer::RESOURCE_USERS => [], + ]; /** * Counters @@ -116,18 +122,39 @@ public function run(array $resources, callable $callback): void { switch ($resource) { case Transfer::RESOURCE_USERS: { $this->exportUsers(100, function (array $users) use ($callback) { - $this->resourceCache = array_merge($this->resourceCache, $users); + $this->resourceCache[Transfer::RESOURCE_USERS] = array_merge($this->resourceCache[Transfer::RESOURCE_USERS], $users); $callback(Transfer::RESOURCE_USERS, $users); }); break; } case Transfer::RESOURCE_DATABASES: { $this->exportDatabases(100, function (array $databases) use ($callback) { - $this->resourceCache = array_merge($this->resourceCache, $databases); + $this->resourceCache[Transfer::RESOURCE_DATABASES] = array_merge($this->resourceCache[Transfer::RESOURCE_DATABASES], $databases); $callback(Transfer::RESOURCE_DATABASES, $databases); }); break; } + case Transfer::RESOURCE_DOCUMENTS: { + $this->exportDocuments(100, function (array $documents) use ($callback) { + $this->resourceCache[Transfer::RESOURCE_DOCUMENTS] = array_merge($this->resourceCache[Transfer::RESOURCE_DOCUMENTS], $documents); + $callback(Transfer::RESOURCE_DOCUMENTS, $documents); + }); + break; + } + case Transfer::RESOURCE_FILES: { + $this->exportFiles(5, function (array $files) use ($callback) { + $this->resourceCache[Transfer::RESOURCE_FILES] = array_merge($this->resourceCache[Transfer::RESOURCE_FILES], $files); + $callback(Transfer::RESOURCE_FILES, $files); + }); + break; + } + case Transfer::RESOURCE_FUNCTIONS: { + $this->exportFunctions(100, function (array $functions) use ($callback) { + $this->resourceCache[Transfer::RESOURCE_FUNCTIONS] = array_merge($this->resourceCache, $functions); + $callback(Transfer::RESOURCE_FUNCTIONS, $functions); + }); + break; + } } } } @@ -286,4 +313,43 @@ public function exportDatabases(int $batchSize, callable $callback): void { throw new Exception('Unimplemented, Please check if your source adapter supports this method.'); } + + /** + * Export Documents + * + * @param int $batchSize Max 100 + * @param callable $callback Callback function to be called after each document, $callback(document[] $batch); + * + * @return void + */ + public function exportDocuments(int $batchSize, callable $callback): void + { + throw new Exception('Unimplemented, Please check if your source adapter supports this method.'); + } + + /** + * Export Files + * + * @param int $batchSize Max 100 + * @param callable $callback Callback function to be called after each file, $callback(file[] $batch); + * + * @return void + */ + public function exportFiles(int $batchSize, callable $callback): void + { + throw new Exception('Unimplemented, Please check if your source adapter supports this method.'); + } + + /** + * Export Functions + * + * @param int $batchSize Max 100 + * @param callable $callback Callback function to be called after each function, $callback(function[] $batch); + * + * @return void + */ + public function exportFunctions(int $batchSize, callable $callback): void + { + throw new Exception('Unimplemented, Please check if your source adapter supports this method.'); + } } diff --git a/src/Transfer/Sources/NHost.php b/src/Transfer/Sources/NHost.php index f1bcd5e..014c190 100644 --- a/src/Transfer/Sources/NHost.php +++ b/src/Transfer/Sources/NHost.php @@ -15,6 +15,7 @@ use Utopia\Transfer\Resources\Attributes\IntAttribute; use Utopia\Transfer\Resources\Attributes\StringAttribute; use Utopia\Transfer\Resources\Collection; +use Utopia\Transfer\Resources\Document; use Utopia\Transfer\Resources\Index; class NHost extends Source @@ -67,7 +68,7 @@ function __construct(string $host, string $databaseName, string $username, strin $this->username = $username; $this->password = $password; $this->port = $port; - $this->pdo = new \PDO("pgsql:host=".$this->host.";port=".$this->port.";dbname=".$this->databaseName, $this->username, $this->password); + $this->pdo = new \PDO("pgsql:host=" . $this->host . ";port=" . $this->port . ";dbname=" . $this->databaseName, $this->username, $this->password); } function getName(): string @@ -79,7 +80,8 @@ function getSupportedResources(): array { return [ Transfer::RESOURCE_USERS, - Transfer::RESOURCE_DATABASES + Transfer::RESOURCE_DATABASES, + Transfer::RESOURCE_DOCUMENTS, ]; } @@ -337,6 +339,61 @@ public function exportDatabases(int $batchSize, callable $callback): void $callback([$transferDatabase]); } + /** + * Export Documents + * + * @param int $batchSize Max 100 + * @param callable $callback Callback function to be called after each batch, $callback(document[] $batch); + * + * @return void + */ + public function exportDocuments(int $batchSize, callable $callback): void + { + $databases = $this->resourceCache[Transfer::RESOURCE_DATABASES]; + + foreach ($databases as $database) { + /** @var Database $database */ + $collections = $database->getCollections(); + + foreach ($collections as $collection) { + $total = $this->pdo->query('SELECT COUNT(*) FROM ' . $collection->getCollectionName())->fetchColumn(); + + $offset = 0; + + while ($offset < $total) { + $statement = $this->pdo->prepare('SELECT row_to_json(t) FROM (SELECT * FROM ' . $collection->getCollectionName() . ' LIMIT :limit OFFSET :offset) t;'); + $statement->bindValue(':limit', $batchSize, \PDO::PARAM_INT); + $statement->bindValue(':offset', $offset, \PDO::PARAM_INT); + $statement->execute(); + + $documents = $statement->fetchAll(\PDO::FETCH_ASSOC); + + $offset += $batchSize; + + $transferDocuments = []; + + foreach ($documents as $document) { + $data = json_decode($document['row_to_json'], true); + + $processedData = []; + foreach ($collection->getAttributes() as $attribute) { + /* @var Attribute $attribute */ + if (!$attribute->getArray() && \is_array($data[$attribute->getKey()])) { + $processedData[$attribute->getKey()] = json_encode($data[$attribute->getKey()]); + } else { + $processedData[$attribute->getKey()] = $data[$attribute->getKey()]; + } + } + + $transferDocuments[] = new Document('unique()', 'public', $collection, $processedData); + } + + $callback($transferDocuments); + } + } + } + } + private function calculateUserTypes(array $user): array { if (empty($user['password_hash']) && empty($user['phone_number'])) { @@ -371,7 +428,7 @@ public function check(array $resources = []): array } if (!empty($this->pdo->errorCode())) { - $report['Databases'][] = 'Failed to connect to database. PDO Code: '. $this->pdo->errorCode() . (empty($this->pdo->errorInfo()[2]) ? '' : ' Error: ' . $this->pdo->errorInfo()[2]); + $report['Databases'][] = 'Failed to connect to database. PDO Code: ' . $this->pdo->errorCode() . (empty($this->pdo->errorInfo()[2]) ? '' : ' Error: ' . $this->pdo->errorInfo()[2]); } foreach ($resources as $resource) { @@ -394,6 +451,10 @@ public function check(array $resources = []): array } break; + case Transfer::RESOURCE_DOCUMENTS: + if (!in_array(Transfer::RESOURCE_DATABASES, $resources)) { + $report['Documents'][] = 'Documents resource requires Databases resource to be enabled.'; + } } } diff --git a/src/Transfer/Sources/Supabase.php b/src/Transfer/Sources/Supabase.php index 88266df..60de709 100644 --- a/src/Transfer/Sources/Supabase.php +++ b/src/Transfer/Sources/Supabase.php @@ -13,14 +13,6 @@ function getName(): string return 'Supabase'; } - function getSupportedResources(): array - { - return [ - Transfer::RESOURCE_USERS, - Transfer::RESOURCE_DATABASES - ]; - } - /** * Export Users * diff --git a/tests/tmp/playground.php b/tests/tmp/playground.php index 7b0f34d..d294906 100644 --- a/tests/tmp/playground.php +++ b/tests/tmp/playground.php @@ -76,7 +76,8 @@ */ $transfer->run( [ - Transfer::RESOURCE_DATABASES + Transfer::RESOURCE_DATABASES, + Transfer::RESOURCE_DOCUMENTS, ], function () { } ); From c17bb839be2bbc3b3f88bcb7005b9baad3b75c6d Mon Sep 17 00:00:00 2001 From: Bradley Schofield Date: Mon, 3 Apr 2023 14:21:06 +0900 Subject: [PATCH 26/70] Implement Full Appwrite support + Add permissions for Appwrite + Cleanup various pieces of code --- README.md | 19 ++++++++ src/Transfer/Destinations/Appwrite.php | 38 +++++++-------- src/Transfer/Resources/Collection.php | 45 ++++++++++++++++- src/Transfer/Resources/Document.php | 29 +++++++++-- src/Transfer/Sources/Appwrite.php | 67 ++++++++++++++++++++++++-- src/Transfer/Sources/NHost.php | 17 +++++-- tests/tmp/playground.php | 16 +++--- 7 files changed, 192 insertions(+), 39 deletions(-) diff --git a/README.md b/README.md index 644c2f5..20e3251 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,25 @@ require_once __DIR__ . '/../../vendor/autoload.php'; //TODO: Finish this ``` +## Supported Resources Chart + +Sources: +| | Users | Databases | Documents | Files | Functions | +|----------|-------|-----------|-----------|-------|-----------| +| Appwrite | ✅ | ✅ | ✅ | | | +| Supabase | ✅ | ✅ | ✅ | | | +| NHost | ✅ | ✅ | ✅ | | | +| Firebase | ✅ | ✅ | | | | + +Destinations: +| | Users | Databases | Documents | Files | Functions | +|----------|-------|-----------|-----------|-------|-----------| +| Appwrite | ✅ | ✅ | ✅ | | | +| Local | ✅ | ✅ | ✅ | ✅ | ✅ | + +> **Warning** +> The Local destination should be used for testing purposes only. It is not recommended to use this destination in production or as a backup. The local destination is there to confirm that a source is working correctly and to test the transfer process with needing a target destination instance. + ## System Requirements Utopia Transfer requires PHP 8.0 or later. We recommend using the latest PHP version whenever possible. diff --git a/src/Transfer/Destinations/Appwrite.php b/src/Transfer/Destinations/Appwrite.php index f4ce9a1..c2edf0a 100644 --- a/src/Transfer/Destinations/Appwrite.php +++ b/src/Transfer/Destinations/Appwrite.php @@ -35,8 +35,6 @@ class Appwrite extends Destination protected string $endpoint; protected string $key; - private array $conversionTable = []; - public function __construct(string $project, string $endpoint, string $key) { $this->project = $project; @@ -444,27 +442,27 @@ public function importDatabases(array $databases, callable $callback): void /** @var Collection $collection */ $createdAttributes = []; - if ($database->getType() == Database::DB_NON_RELATIONAL) { - $path = \explode('/', $collection->getCollectionName()); + // if ($database->getType() == Database::DB_NON_RELATIONAL) { + // $path = \explode('/', $collection->getCollectionName()); - $collectionName = $path[count($path) - 1]; + // $collectionName = $path[count($path) - 1]; - if (isset($path[count($path) - 2])) { - $collectionName = $path[count($path) - 2] . "/" . $collectionName; - } - } else { - $collectionName = $collection->getCollectionName(); - } + // if (isset($path[count($path) - 2])) { + // $collectionName = $path[count($path) - 2] . "/" . $collectionName; + // } + // } else { + // $collectionName = $collection->getCollectionName(); + // } - // Handle special chars - $collectionName = \str_replace([' ', '(', ')', '[', ']', '{', '}', '<', '>', ':', ';', ',', '.', '?', '\\', '|', '=', '+', '*', '&', '^', '%', '$', '#', '@', '!', '~', '`', '"', "'"], '_', $collectionName); + // // Handle special chars + // $collectionName = \str_replace([' ', '(', ')', '[', ']', '{', '}', '<', '>', ':', ';', ',', '.', '?', '\\', '|', '=', '+', '*', '&', '^', '%', '$', '#', '@', '!', '~', '`', '"', "'"], '_', $collectionName); - // Check name length - if (\strlen($collectionName) > 120) { - $collectionName = \substr($collectionName, 0, 120); - } + // // Check name length + // if (\strlen($collectionName) > 120) { + // $collectionName = \substr($collectionName, 0, 120); + // } - $newCollection = $databaseService->createCollection($database->getId(), $collection->getId(), $collectionName); + $newCollection = $databaseService->createCollection($database->getId(), $collection->getId(), $collection->getCollectionName(), $collection->getPermissions(), $collection->getDocumentSecurity()); $collection->setId($newCollection['$id']); // Remove duplicate attributes, TODO: Merge them together. @@ -478,8 +476,6 @@ public function importDatabases(array $databases, callable $callback): void return true; }); - $filteredAttributes[] = new StringAttribute($collection->getId(), false, false, null, 1000000); - foreach ($filteredAttributes as $attribute) { /** @var Attribute $attribute */ $this->createAttribute($attribute, $collection, $database); @@ -555,7 +551,7 @@ protected function importDocuments(array $documents, callable $callback): void { /** @var Document $document */ try { - $databaseService->createDocument($document->getDatabase(), $document->getCollection()->getId(), 'unique()', $document->getData()); + $databaseService->createDocument($document->getDatabase()->getId(), $document->getCollection()->getId(), $document->getId() ?? 'unique()', $document->getData(), $document->getPermissions()); $this->logs[Log::SUCCESS][] = new Log('Document imported successfully', \time(), $document); $documentCounters['current']++; diff --git a/src/Transfer/Resources/Collection.php b/src/Transfer/Resources/Collection.php index 607d23d..0421e2b 100644 --- a/src/Transfer/Resources/Collection.php +++ b/src/Transfer/Resources/Collection.php @@ -17,13 +17,32 @@ class Collection extends Resource */ private array $indexes = []; + /** + * @var array $permissions + */ + protected array $permissions = []; + + /** + * @var bool $documentSecurity + */ + protected bool $documentSecurity = false; + + /** + * @var string $name + */ protected string $name; + + /** + * @var string $id + */ protected string $id; - public function __construct(string $name = '', string $id = '') + public function __construct(string $name = '', string $id = '', bool $documentSecurity = false, array $permissions = []) { $this->name = $name; $this->id = $id; + $this->documentSecurity = $documentSecurity; + $this->permissions = $permissions; } public function getName(): string @@ -53,6 +72,28 @@ public function setId(string $id): self return $this; } + public function getDocumentSecurity(): bool + { + return $this->documentSecurity; + } + + public function setDocumentSecurity(bool $documentSecurity): self + { + $this->documentSecurity = $documentSecurity; + return $this; + } + + public function getPermissions(): array + { + return $this->permissions; + } + + public function setPermissions(array $permissions): self + { + $this->permissions = $permissions; + return $this; + } + public function getAttributes(): array { return $this->columns; @@ -94,6 +135,8 @@ public function asArray(): array 'indexes' => array_map(function ($index) { return $index->asArray(); }, $this->indexes), + 'permissions' => $this->permissions, + 'documentSecurity' => $this->documentSecurity, ]; } } \ No newline at end of file diff --git a/src/Transfer/Resources/Document.php b/src/Transfer/Resources/Document.php index 57b530b..97e39c8 100644 --- a/src/Transfer/Resources/Document.php +++ b/src/Transfer/Resources/Document.php @@ -6,16 +6,18 @@ class Document extends Resource { protected string $id; - protected string $database; + protected Database $database; protected Collection $collection; protected array $data; + protected array $permissions; - public function __construct(string $id, string $database, Collection $collection, array $data = []) + public function __construct(string $id, Database $database, Collection $collection, array $data = [], array $permissions = []) { $this->id = $id; $this->database = $database; $this->collection = $collection; $this->data = $data; + $this->permissions = $permissions; } public function getName(): string @@ -34,12 +36,12 @@ public function setId(string $id): self return $this; } - public function getDatabase(): string + public function getDatabase(): Database { return $this->database; } - public function setDatabase(string $database): self + public function setDatabase(Database $database): self { $this->database = $database; return $this; @@ -74,6 +76,24 @@ public function setData(array $data): self return $this; } + public function getPermissions(): array + { + return $this->permissions; + } + + /** + * Set Permissions + * + * @param array $permissions + * + * @return self + */ + public function setPermissions(array $permissions): self + { + $this->permissions = $permissions; + return $this; + } + public function asArray(): array { return [ @@ -81,6 +101,7 @@ public function asArray(): array 'database' => $this->database, 'collection' => $this->collection, 'attributes' => $this->data, + 'permissions' => $this->permissions, ]; } } \ No newline at end of file diff --git a/src/Transfer/Sources/Appwrite.php b/src/Transfer/Sources/Appwrite.php index 25b1227..9527095 100644 --- a/src/Transfer/Sources/Appwrite.php +++ b/src/Transfer/Sources/Appwrite.php @@ -24,6 +24,7 @@ use Utopia\Transfer\Resources\Attributes\URLAttribute; use Utopia\Transfer\Resources\Collection; use Utopia\Transfer\Resources\Database; +use Utopia\Transfer\Resources\Document; use Utopia\Transfer\Resources\Hash; use Utopia\Transfer\Resources\Index; @@ -70,7 +71,8 @@ public function getSupportedResources(): array { return [ Transfer::RESOURCE_USERS, - Transfer::RESOURCE_DATABASES + Transfer::RESOURCE_DATABASES, + Transfer::RESOURCE_DOCUMENTS, ]; } @@ -320,7 +322,7 @@ public function exportDatabases(int $batchSize, callable $callback): void $generalCollections = []; foreach ($collections['collections'] as $collection) { - $newCollection = new Collection($collection['name'], $collection['$id']); + $newCollection = new Collection($collection['name'], $collection['$id'], $collection['documentSecurity'], $collection['$permissions']); $attributes = []; $indexes = []; @@ -330,7 +332,7 @@ public function exportDatabases(int $batchSize, callable $callback): void } foreach ($collection['indexes'] as $index) { - $indexes[] = new Index($index['key'], $index['type'], $index['attributes'], $index['orders']); + $indexes[] = new Index('unique()', $index['key'], $index['type'], $index['attributes'], $index['orders']); } $newCollection->setAttributes($attributes); @@ -353,6 +355,65 @@ public function exportDatabases(int $batchSize, callable $callback): void } } + /** + * Export Documents + * + * @param int $batchSize Max 100 + * @param callable $callback Callback function to be called after each batch, $callback(document[] $batch); + * + * @return void + */ + public function exportDocuments(int $batchSize, callable $callback): void + { + $databaseClient = new Databases($this->client); + + $databases = $this->resourceCache[Transfer::RESOURCE_DATABASES]; + + foreach ($databases as $database) { + /** @var Database $database */ + $collections = $database->getCollections(); + + foreach ($collections as $collection) { + /** @var Collection $collection */ + $lastDocument = null; + + while (true) { + $queries = [ + Query::limit($batchSize) + ]; + + $documents = []; + + if ($lastDocument) { + $queries[] = Query::cursorAfter($lastDocument); + } + + $response = $databaseClient->listDocuments($database->getId(), $collection->getId(), $queries); + + foreach ($response["documents"] as $document) { + $id = $document['$id']; + $permissions = $document['$permissions']; + unset($document['$id']); + unset($document['$permissions']); + unset($document['$collectionId']); + unset($document['$updatedAt']); + unset($document['$createdAt']); + unset($document['$databaseId']); + + $documents[] = new Document($id, $database, $collection, $document, $permissions); + $lastDocument = $id; + } + + $callback($documents); + + if (count($response["documents"]) < $batchSize) { + break; + } + } + } + } + } + /** * Calculate Types * diff --git a/src/Transfer/Sources/NHost.php b/src/Transfer/Sources/NHost.php index 014c190..996b7f2 100644 --- a/src/Transfer/Sources/NHost.php +++ b/src/Transfer/Sources/NHost.php @@ -68,7 +68,12 @@ function __construct(string $host, string $databaseName, string $username, strin $this->username = $username; $this->password = $password; $this->port = $port; - $this->pdo = new \PDO("pgsql:host=" . $this->host . ";port=" . $this->port . ";dbname=" . $this->databaseName, $this->username, $this->password); + + try { + $this->pdo = new \PDO("pgsql:host=" . $this->host . ";port=" . $this->port . ";dbname=" . $this->databaseName, $this->username, $this->password); + } catch (\PDOException $e) { + $this->logs[Log::ERROR] = new Log('Failed to connect to database: ' . $e->getMessage(), \time()); + } } function getName(): string @@ -264,7 +269,7 @@ public function convertIndex(array $index): Index|false return false; } - $type = ""; + $type = ''; if ($matches['type'] === 'UNIQUE') { $type = Index::TYPE_UNIQUE; @@ -385,7 +390,7 @@ public function exportDocuments(int $batchSize, callable $callback): void } } - $transferDocuments[] = new Document('unique()', 'public', $collection, $processedData); + $transferDocuments[] = new Document('unique()', $database, $collection, $processedData); } $callback($transferDocuments); @@ -427,6 +432,12 @@ public function check(array $resources = []): array $resources = $this->getSupportedResources(); } + try { + $this->pdo = new \PDO("pgsql:host=" . $this->host . ";port=" . $this->port . ";dbname=" . $this->databaseName, $this->username, $this->password); + } catch (\PDOException $e) { + $report['Databases'][] = 'Failed to connect to database. PDO Code: ' . $e->getCode() . ' Error: ' . $e->getMessage(); + } + if (!empty($this->pdo->errorCode())) { $report['Databases'][] = 'Failed to connect to database. PDO Code: ' . $this->pdo->errorCode() . (empty($this->pdo->errorInfo()[2]) ? '' : ' Error: ' . $this->pdo->errorInfo()[2]); } diff --git a/tests/tmp/playground.php b/tests/tmp/playground.php index d294906..b219673 100644 --- a/tests/tmp/playground.php +++ b/tests/tmp/playground.php @@ -35,12 +35,12 @@ Firebase::AUTH_SERVICEACCOUNT ); -// $sourceNHost = new NHost( -// $_ENV["NHOST_TEST_HOST"] ?? '', -// $_ENV["NHOST_TEST_DATABASE"] ?? '', -// $_ENV["NHOST_TEST_USERNAME"] ?? '', -// $_ENV["NHOST_TEST_PASSWORD"] ?? '', -// ); +$sourceNHost = new NHost( + $_ENV["NHOST_TEST_HOST"] ?? '', + $_ENV["NHOST_TEST_DATABASE"] ?? '', + $_ENV["NHOST_TEST_USERNAME"] ?? '', + $_ENV["NHOST_TEST_PASSWORD"] ?? '', +); $sourceSupabase = new Supabase( $_ENV["SUPABASE_TEST_HOST"] ?? '', @@ -85,5 +85,7 @@ if (!empty($transfer->getLogs(Log::ERROR))) { echo "\e[41m\e[97mFAILED\e[0m\n"; - var_dump($transfer->getLogs(Log::ERROR)); + foreach ($transfer->getLogs(Log::ERROR) as $log) { + echo "\e[31m{$log->getMessage()}\e[0m"; + } } \ No newline at end of file From d32b002bd263359b8295860fd0934d39368f0c2f Mon Sep 17 00:00:00 2001 From: Bradley Schofield Date: Mon, 3 Apr 2023 16:49:47 +0900 Subject: [PATCH 27/70] Move playground out of tmp --- tests/tmp/playground.php => playground.php | 0 tests/tmp/databaseDMP.json | 1 - 2 files changed, 1 deletion(-) rename tests/tmp/playground.php => playground.php (100%) delete mode 100644 tests/tmp/databaseDMP.json diff --git a/tests/tmp/playground.php b/playground.php similarity index 100% rename from tests/tmp/playground.php rename to playground.php diff --git a/tests/tmp/databaseDMP.json b/tests/tmp/databaseDMP.json deleted file mode 100644 index 0637a08..0000000 --- a/tests/tmp/databaseDMP.json +++ /dev/null @@ -1 +0,0 @@ -[] \ No newline at end of file From 020599bd57f7fb2bb991f8bf960b00866eded6f6 Mon Sep 17 00:00:00 2001 From: Bradley Schofield Date: Mon, 3 Apr 2023 17:14:15 +0900 Subject: [PATCH 28/70] Add and run formatter --- README.md | 2 + composer.json | 4 +- phpcs.xml | 15 + src/Transfer/Destination.php | 72 ++--- src/Transfer/Destinations/Appwrite.php | 21 +- src/Transfer/Destinations/Local.php | 33 +- src/Transfer/Log.php | 29 +- src/Transfer/Progress.php | 34 +- src/Transfer/Resource.php | 10 +- src/Transfer/Resources/Attribute.php | 45 +-- .../Resources/Attributes/BoolAttribute.php | 15 +- .../Attributes/DateTimeAttribute.php | 13 +- .../Resources/Attributes/EmailAttribute.php | 13 +- .../Resources/Attributes/EnumAttribute.php | 19 +- .../Resources/Attributes/FloatAttribute.php | 23 +- .../Resources/Attributes/IPAttribute.php | 13 +- .../Resources/Attributes/IntAttribute.php | 23 +- .../Resources/Attributes/StringAttribute.php | 19 +- .../Resources/Attributes/URLAttribute.php | 13 +- src/Transfer/Resources/Collection.php | 2 +- src/Transfer/Resources/Database.php | 6 +- src/Transfer/Resources/Document.php | 13 +- src/Transfer/Resources/Hash.php | 43 +-- src/Transfer/Resources/Index.php | 12 +- src/Transfer/Resources/Project.php | 2 +- src/Transfer/Resources/User.php | 68 ++-- src/Transfer/Source.php | 79 +++-- src/Transfer/Sources/Appwrite.php | 292 ++++++++++++------ src/Transfer/Sources/Firebase.php | 94 +++--- src/Transfer/Sources/NHost.php | 53 ++-- src/Transfer/Sources/Supabase.php | 19 +- src/Transfer/Transfer.php | 30 +- tests/Transfer/Sources/AppwriteSourceTest.php | 14 +- tests/Transfer/Sources/FirebaseTest.php | 15 +- tests/Transfer/Sources/NHostTest.php | 11 +- tests/Transfer/Sources/SupabaseTest.php | 11 +- .../Transfers/FirebaseToAppwriteTest.php | 8 +- .../Transfers/SupabaseToAppwriteTest.php | 10 +- 38 files changed, 664 insertions(+), 534 deletions(-) create mode 100644 phpcs.xml diff --git a/README.md b/README.md index 20e3251..c5fd82f 100644 --- a/README.md +++ b/README.md @@ -43,6 +43,8 @@ Destinations: > **Warning** > The Local destination should be used for testing purposes only. It is not recommended to use this destination in production or as a backup. The local destination is there to confirm that a source is working correctly and to test the transfer process with needing a target destination instance. + + ## System Requirements Utopia Transfer requires PHP 8.0 or later. We recommend using the latest PHP version whenever possible. diff --git a/composer.json b/composer.json index 55d5433..59c5e6f 100644 --- a/composer.json +++ b/composer.json @@ -26,8 +26,8 @@ }, "require-dev": { "phpunit/phpunit": "^9.3", - "squizlabs/php_codesniffer": "^3.6", "vimeo/psalm": "^5.6", - "vlucas/phpdotenv": "^5.5" + "vlucas/phpdotenv": "^5.5", + "squizlabs/php_codesniffer": "3.*" } } diff --git a/phpcs.xml b/phpcs.xml new file mode 100644 index 0000000..89c508c --- /dev/null +++ b/phpcs.xml @@ -0,0 +1,15 @@ + + + + ./src + ./tests + + + + * + + + + * + + \ No newline at end of file diff --git a/src/Transfer/Destination.php b/src/Transfer/Destination.php index dc802bd..9ca45de 100644 --- a/src/Transfer/Destination.php +++ b/src/Transfer/Destination.php @@ -17,14 +17,14 @@ abstract class Destination /** * Logs - * + * * @var array $logs */ protected $logs = []; /** * Resource Cache - * + * * @var array $resourceCache */ protected $resourceCache = [ @@ -42,35 +42,35 @@ abstract class Destination /** * Counters - * + * * @var array $counter */ protected $counters = []; /** * Source - * + * * @var Source $source */ protected Source $source; /** * Gets the name of the adapter. - * + * * @return string */ abstract public function getName(): string; /** * Get Supported Resources - * + * * @return array */ abstract public function getSupportedResources(): array; /** * Get Source - * + * * @return Source */ public function getSource(): Source @@ -80,9 +80,9 @@ public function getSource(): Source /** * Set Soruce - * + * * @param Source $source - * + * * @return self */ public function setSource(Source $source): self @@ -93,7 +93,7 @@ public function setSource(Source $source): self /** * Register Logs Array - * + * * @param array &$logs */ public function registerLogs(array &$logs): void @@ -103,9 +103,9 @@ public function registerLogs(array &$logs): void /** * Get Resource Counters - * + * * @param string $resource = null - * + * * @return array */ public function &getCounter(?string $resource = null): array @@ -126,10 +126,10 @@ public function &getCounter(?string $resource = null): array /** * Register Transfer Hooks - * + * * @param array &$cache * @param array &$counters - * + * * @return void */ public function registerTransferHooks(array &$cache, array &$counters): void @@ -140,7 +140,7 @@ public function registerTransferHooks(array &$cache, array &$counters): void /** * Transfer Resources to Destination from Source callback - * + * * @param array $resources * @param callable $callback */ @@ -150,22 +150,18 @@ public function run(array $resources, callable $callback, Source $source): void $source->run($resources, function (string $resourceType, array $resource) use ($callback) { switch ($resourceType) { - case Transfer::RESOURCE_USERS: { - $this->importUsers($resource, $callback); - break; - } - case Transfer::RESOURCE_DATABASES: { - $this->importDatabases($resource, $callback); - break; - } - case Transfer::RESOURCE_DOCUMENTS: { - $this->importDocuments($resource, $callback); - break; - } - case Transfer::RESOURCE_FILES: { - $this->importFiles($resource, $callback); - break; - } + case Transfer::RESOURCE_USERS: + $this->importUsers($resource, $callback); + break; + case Transfer::RESOURCE_DATABASES: + $this->importDatabases($resource, $callback); + break; + case Transfer::RESOURCE_DOCUMENTS: + $this->importDocuments($resource, $callback); + break; + case Transfer::RESOURCE_FILES: + $this->importFiles($resource, $callback); + break; } }); } @@ -174,13 +170,13 @@ public function run(array $resources, callable $callback, Source $source): void * Check Requirements * Performs a suite of API Checks, Resource Checks, etc... to ensure the adapter is ready to be used. * This is highly recommended to be called before any other method after initialization. - * + * * If no resources are provided, the method should check all resources. * Returns a object with all the keys of the resources provided and a true|string value if the resource is available or not. * If the resource is not available, the value should be a string with the error message. - * + * * @string[] $resources - * + * * @return string[] */ abstract public function check(array $resources = []): array; @@ -301,7 +297,7 @@ protected function flatten(array $data, string $prefix = ''): array /** * Import Users - * + * * @param array $users * @param callable $callback (Progress $progress) */ @@ -312,7 +308,7 @@ protected function importUsers(array $users, callable $callback): void /** * Import Database - * + * * @param array $databases * @param callable $callback (Progress $progress) */ @@ -323,7 +319,7 @@ protected function importDatabases(array $databases, callable $callback): void /** * Import Documents - * + * * @param array $documents * @param callable $callback (Progress $progress) */ @@ -334,7 +330,7 @@ protected function importDocuments(array $documents, callable $callback): void /** * Import Files - * + * * @param array $files * @param callable $callback (Progress $progress) */ diff --git a/src/Transfer/Destinations/Appwrite.php b/src/Transfer/Destinations/Appwrite.php index c2edf0a..e65a344 100644 --- a/src/Transfer/Destinations/Appwrite.php +++ b/src/Transfer/Destinations/Appwrite.php @@ -49,7 +49,7 @@ public function __construct(string $project, string $endpoint, string $key) /** * Get Name - * + * * @return string */ public function getName(): string @@ -59,7 +59,7 @@ public function getName(): string /** * Get Supported Resources - * + * * @return array */ public function getSupportedResources(): array @@ -381,11 +381,11 @@ public function createAttribute(Attribute $attribute, Collection $collection, Da /** * Validate Attributes Creation - * + * * @param Attribute[] $attributes * @param Collection $collection * @param Database $database - * + * * @return bool */ public function validateAttributesCreation(array $attributes, Collection $collection, Database $database): bool @@ -420,10 +420,10 @@ public function validateAttributesCreation(array $attributes, Collection $collec /** * Import Databases - * + * * @param array $databases * @param callable $callback - * + * * @return void */ public function importDatabases(array $databases, callable $callback): void @@ -505,9 +505,9 @@ public function importDatabases(array $databases, callable $callback): void if ($database->getType() == Database::DB_NON_RELATIONAL) { $refCollectionID = $databaseService->createCollection($database->getId(), 'refs', 'References')['$id']; $databaseService->createStringAttribute($database->getId(), $refCollectionID, 'original_name', 1000000, true); - + sleep(2); - + foreach ($createdCollections as $collection) { /** @var Collection $collection */ $result = $databaseService->createDocument($database->getId(), $refCollectionID, $collection->getId(), [ @@ -539,11 +539,12 @@ public function importDatabases(array $databases, callable $callback): void /** * Import Documents - * + * * @param array $documents * @param callable $callback (Progress $progress) */ - protected function importDocuments(array $documents, callable $callback): void { + protected function importDocuments(array $documents, callable $callback): void + { $documentCounters = &$this->getCounter(Transfer::RESOURCE_DOCUMENTS); $databaseService = new Databases($this->client); diff --git a/src/Transfer/Destinations/Local.php b/src/Transfer/Destinations/Local.php index 436c763..1c8dc40 100644 --- a/src/Transfer/Destinations/Local.php +++ b/src/Transfer/Destinations/Local.php @@ -14,35 +14,38 @@ /** * Local - * + * * Used to export data to a local file system or for testing purposes. * Exports all data to a single JSON File. */ -class Local extends Destination { +class Local extends Destination +{ private array $data = []; protected string $path; - public function __construct(string $path) + public function __construct(string $path) { $this->path = $path; } /** * Get Name - * + * * @return string */ - public function getName(): string { + public function getName(): string + { return 'Local'; } /** * Get Supported Resources - * + * * @return array */ - public function getSupportedResources(): array { + public function getSupportedResources(): array + { return [ Transfer::RESOURCE_USERS, Transfer::RESOURCE_DATABASES, @@ -54,7 +57,7 @@ public function getSupportedResources(): array { /** * Check if destination is valid - * + * * @param array $resources * @return array */ @@ -88,10 +91,10 @@ public function syncFile(): void /** * Import Users - * + * * @param array $users * @param callable $callback - * + * * @return void */ public function importUsers(array $users, callable $callback): void @@ -121,10 +124,10 @@ public function importUsers(array $users, callable $callback): void /** * Import Databases - * + * * @param array $databases * @param callable $callback - * + * * @return void */ public function importDatabases(array $databases, callable $callback): void @@ -154,10 +157,10 @@ public function importDatabases(array $databases, callable $callback): void /** * Import Documents - * + * * @param array $documents * @param callable $callback - * + * * @return void */ public function importDocuments(array $documents, callable $callback): void @@ -184,4 +187,4 @@ public function importDocuments(array $documents, callable $callback): void $this->syncFile(); } -} \ No newline at end of file +} diff --git a/src/Transfer/Log.php b/src/Transfer/Log.php index 8f48a90..d0cab36 100644 --- a/src/Transfer/Log.php +++ b/src/Transfer/Log.php @@ -2,13 +2,14 @@ namespace Utopia\Transfer; -class Log { - const INFO = 'info'; - const WARNING = 'warning'; - const ERROR = 'error'; - const FATAL = 'fatal'; - const SUCCESS = 'success'; - const DEBUG = 'debug'; +class Log +{ + public const INFO = 'info'; + public const WARNING = 'warning'; + public const ERROR = 'error'; + public const FATAL = 'fatal'; + public const SUCCESS = 'success'; + public const DEBUG = 'debug'; private string $message = ''; private int $timestamp = 0; @@ -22,7 +23,7 @@ public function __construct(string $message = '', int $timestamp = 0, ?Resource } /** * Get Message - * + * * @return string */ public function getMessage(): string @@ -32,7 +33,7 @@ public function getMessage(): string /** * Set Message - * + * * @param string $message * @return self */ @@ -44,7 +45,7 @@ public function setMessage(string $message): self /** * Get Timestamp - * + * * @return int */ public function getTimestamp(): int @@ -54,7 +55,7 @@ public function getTimestamp(): int /** * Set Timestamp - * + * * @param int $timestamp * @return self */ @@ -66,7 +67,7 @@ public function setTimestamp(int $timestamp): self /** * Get Resource - * + * * @return Resource|null */ public function getResource(): ?Resource @@ -76,7 +77,7 @@ public function getResource(): ?Resource /** * As Array - * + * * @return array */ public function asArray(): array @@ -87,4 +88,4 @@ public function asArray(): array 'resource' => $this->resource ? $this->resource->asArray() : null, ]; } -} \ No newline at end of file +} diff --git a/src/Transfer/Progress.php b/src/Transfer/Progress.php index 231515e..79e4f4a 100644 --- a/src/Transfer/Progress.php +++ b/src/Transfer/Progress.php @@ -11,7 +11,7 @@ class Progress private int $failed = 0; private int $skipped = 0; - function __construct( + public function __construct( string $resourceType = '', int $timestamp = 0, int $total = 0, @@ -29,7 +29,7 @@ function __construct( /** * Get Resource Type - * + * * @return string */ public function getResourceType(): string @@ -39,7 +39,7 @@ public function getResourceType(): string /** * Set Resource Type - * + * * @param string $resourceType * @return self */ @@ -52,7 +52,7 @@ public function setResourceType(string $resourceType): self /** * Get Timestamp - * + * * @return int */ public function getTimestamp(): int @@ -62,7 +62,7 @@ public function getTimestamp(): int /** * Set Timestamp - * + * * @param int $timestamp * @return self */ @@ -74,7 +74,7 @@ public function setTimestamp(int $timestamp): self /** * Get Total - * + * * @return int */ public function getTotal(): int @@ -84,7 +84,7 @@ public function getTotal(): int /** * Set Total - * + * * @param int $total * @return self */ @@ -96,7 +96,7 @@ public function setTotal(int $total): self /** * Get Current - * + * * @return int */ public function getCurrent(): int @@ -106,7 +106,7 @@ public function getCurrent(): int /** * Set Current - * + * * @param int $current * @return self */ @@ -118,7 +118,7 @@ public function setCurrent(int $current): self /** * Get Failed - * + * * @return int */ public function getFailed(): int @@ -128,7 +128,7 @@ public function getFailed(): int /** * Set Failed - * + * * @param int $failed * @return self */ @@ -140,7 +140,7 @@ public function setFailed(int $failed): self /** * Get Skipped - * + * * @return int */ public function getSkipped(): int @@ -150,7 +150,7 @@ public function getSkipped(): int /** * Set Skipped - * + * * @param int $skipped * @return self */ @@ -162,7 +162,7 @@ public function setSkipped(int $skipped): self /** * Get Progress - * + * * @return float */ public function getProgress(): float @@ -176,7 +176,7 @@ public function getProgress(): float /** * Get Remaining - * + * * @return int */ public function getRemaining(): int @@ -186,7 +186,7 @@ public function getRemaining(): int /** * Get ETA - * + * * @return int */ public function getETA(): int @@ -196,7 +196,7 @@ public function getETA(): int /** * As Array - * + * * @return array */ public function asArray(): array diff --git a/src/Transfer/Resource.php b/src/Transfer/Resource.php index f1c1f5a..617dfa2 100644 --- a/src/Transfer/Resource.php +++ b/src/Transfer/Resource.php @@ -11,14 +11,14 @@ abstract class Resource /** * Gets the name of the adapter. - * + * * @return string */ abstract public function getName(): string; /** * Get ID - * + * * @return string */ public function getId(): string @@ -28,7 +28,7 @@ public function getId(): string /** * Set ID - * + * * @param string $id * @return self */ @@ -40,8 +40,8 @@ public function setId(string $id): self /** * As Array - * + * * @return array */ abstract public function asArray(): array; -} \ No newline at end of file +} diff --git a/src/Transfer/Resources/Attribute.php b/src/Transfer/Resources/Attribute.php index bad8afc..c837f81 100644 --- a/src/Transfer/Resources/Attribute.php +++ b/src/Transfer/Resources/Attribute.php @@ -4,16 +4,17 @@ use Utopia\Transfer\Resource; -class Attribute extends Resource { - const TYPE_STRING = 'stringAttribute'; - const TYPE_INTEGER = 'intAttribute'; - const TYPE_FLOAT = 'floatAttribute'; - const TYPE_BOOLEAN = 'boolAttribute'; - const TYPE_DATETIME = 'dateTimeAttribute'; - const TYPE_EMAIL = 'emailAttribute'; - const TYPE_ENUM = 'enumAttribute'; - const TYPE_IP = 'IPAttribute'; - const TYPE_URL = 'URLAttribute'; +class Attribute extends Resource +{ + public const TYPE_STRING = 'stringAttribute'; + public const TYPE_INTEGER = 'intAttribute'; + public const TYPE_FLOAT = 'floatAttribute'; + public const TYPE_BOOLEAN = 'boolAttribute'; + public const TYPE_DATETIME = 'dateTimeAttribute'; + public const TYPE_EMAIL = 'emailAttribute'; + public const TYPE_ENUM = 'enumAttribute'; + public const TYPE_IP = 'IPAttribute'; + public const TYPE_URL = 'URLAttribute'; protected string $key; protected bool $required; @@ -25,52 +26,52 @@ class Attribute extends Resource { * @param bool $array * @param int $size */ - function __construct(string $key, bool $required = false, bool $array = false) + public function __construct(string $key, bool $required = false, bool $array = false) { $this->key = $key; $this->required = $required; $this->array = $array; } - - function getName(): string + + public function getName(): string { return 'attribute'; } - function getKey(): string + public function getKey(): string { return $this->key; } - - function setKey(string $key): self + + public function setKey(string $key): self { $this->key = $key; return $this; } - function getRequired(): bool + public function getRequired(): bool { return $this->required; } - function setRequired(bool $required): self + public function setRequired(bool $required): self { $this->required = $required; return $this; } - function getArray(): bool + public function getArray(): bool { return $this->array; } - function setArray(bool $array): self + public function setArray(bool $array): self { $this->array = $array; return $this; } - function asArray(): array + public function asArray(): array { return [ 'key' => $this->key, @@ -79,4 +80,4 @@ function asArray(): array 'type' => $this->getName(), ]; } -} \ No newline at end of file +} diff --git a/src/Transfer/Resources/Attributes/BoolAttribute.php b/src/Transfer/Resources/Attributes/BoolAttribute.php index b2c6e87..5d42f40 100644 --- a/src/Transfer/Resources/Attributes/BoolAttribute.php +++ b/src/Transfer/Resources/Attributes/BoolAttribute.php @@ -4,7 +4,8 @@ use Utopia\Transfer\Resources\Attribute; -class BoolAttribute extends Attribute { +class BoolAttribute extends Attribute +{ protected string $key; protected bool $required; protected bool $array; @@ -16,31 +17,31 @@ class BoolAttribute extends Attribute { * @param bool $array * @param ?bool $default */ - function __construct(string $key, bool $required = false, bool $array = false, ?bool $default = null) + public function __construct(string $key, bool $required = false, bool $array = false, ?bool $default = null) { parent::__construct($key, $required, $array); $this->default = $default; } - function getName(): string + public function getName(): string { return 'boolAttribute'; } - function getDefault(): ?bool + public function getDefault(): ?bool { return $this->default; } - function setDefault(bool $default): void + public function setDefault(bool $default): void { $this->default = $default; } - function asArray(): array + public function asArray(): array { return array_merge(parent::asArray(), [ 'default' => $this->default, ]); } -} \ No newline at end of file +} diff --git a/src/Transfer/Resources/Attributes/DateTimeAttribute.php b/src/Transfer/Resources/Attributes/DateTimeAttribute.php index cc3a06e..5a00685 100644 --- a/src/Transfer/Resources/Attributes/DateTimeAttribute.php +++ b/src/Transfer/Resources/Attributes/DateTimeAttribute.php @@ -4,7 +4,8 @@ use Utopia\Transfer\Resources\Attribute; -class DateTimeAttribute extends Attribute { +class DateTimeAttribute extends Attribute +{ protected ?string $default; /** @@ -13,24 +14,24 @@ class DateTimeAttribute extends Attribute { * @param bool $array * @param ?string $default */ - function __construct(string $key, bool $required = false, bool $array = false, ?string $default = null) + public function __construct(string $key, bool $required = false, bool $array = false, ?string $default = null) { parent::__construct($key, $required, $array); $this->default = $default; } - function getDefault(): ?string + public function getDefault(): ?string { return $this->default; } - function setDefault(string $default): void + public function setDefault(string $default): void { $this->default = $default; } - function getName(): string + public function getName(): string { return 'dateTimeAttribute'; } -} \ No newline at end of file +} diff --git a/src/Transfer/Resources/Attributes/EmailAttribute.php b/src/Transfer/Resources/Attributes/EmailAttribute.php index 7f9df3d..969f1a1 100644 --- a/src/Transfer/Resources/Attributes/EmailAttribute.php +++ b/src/Transfer/Resources/Attributes/EmailAttribute.php @@ -4,7 +4,8 @@ use Utopia\Transfer\Resources\Attribute; -class EmailAttribute extends Attribute { +class EmailAttribute extends Attribute +{ protected ?string $default; /** @@ -13,24 +14,24 @@ class EmailAttribute extends Attribute { * @param bool $array * @param ?string $default */ - function __construct(string $key, bool $required = false, bool $array = false, ?string $default = null) + public function __construct(string $key, bool $required = false, bool $array = false, ?string $default = null) { parent::__construct($key, $required, $array); $this->default = $default; } - function getDefault(): ?string + public function getDefault(): ?string { return $this->default; } - function setDefault(string $default): void + public function setDefault(string $default): void { $this->default = $default; } - function getName(): string + public function getName(): string { return 'emailAttribute'; } -} \ No newline at end of file +} diff --git a/src/Transfer/Resources/Attributes/EnumAttribute.php b/src/Transfer/Resources/Attributes/EnumAttribute.php index e5c9fa8..0207322 100644 --- a/src/Transfer/Resources/Attributes/EnumAttribute.php +++ b/src/Transfer/Resources/Attributes/EnumAttribute.php @@ -4,7 +4,8 @@ use Utopia\Transfer\Resources\Attribute; -class EnumAttribute extends Attribute { +class EnumAttribute extends Attribute +{ protected ?string $default; protected array $elements; @@ -15,43 +16,43 @@ class EnumAttribute extends Attribute { * @param bool $array * @param ?string $default */ - function __construct(string $key, array $elements, bool $required, bool $array, ?string $default) + public function __construct(string $key, array $elements, bool $required, bool $array, ?string $default) { parent::__construct($key, $required, $array); $this->default = $default; $this->elements = $elements; } - function getName(): string + public function getName(): string { return 'enumAttribute'; } - function getElements(): array + public function getElements(): array { return $this->elements; } - function setElements(array $elements): self + public function setElements(array $elements): self { $this->elements = $elements; return $this; } - function getDefault(): ?string + public function getDefault(): ?string { return $this->default; } - function setDefault(string $default): void + public function setDefault(string $default): void { $this->default = $default; } - function asArray(): array + public function asArray(): array { return array_merge(parent::asArray(), [ 'elements' => $this->elements, ]); } -} \ No newline at end of file +} diff --git a/src/Transfer/Resources/Attributes/FloatAttribute.php b/src/Transfer/Resources/Attributes/FloatAttribute.php index 8bbc247..7bd9e3e 100644 --- a/src/Transfer/Resources/Attributes/FloatAttribute.php +++ b/src/Transfer/Resources/Attributes/FloatAttribute.php @@ -4,7 +4,8 @@ use Utopia\Transfer\Resources\Attribute; -class FloatAttribute extends Attribute { +class FloatAttribute extends Attribute +{ protected ?float $default; protected ?float $min; protected ?float $max; @@ -17,7 +18,7 @@ class FloatAttribute extends Attribute { * @param ?float $min * @param ?float $max */ - function __construct(string $key, bool $required = false, bool $array = false, ?float $default = null, float $min = null, float $max = null) + public function __construct(string $key, bool $required = false, bool $array = false, ?float $default = null, float $min = null, float $max = null) { parent::__construct($key, $required, $array); $this->default = $default; @@ -25,45 +26,45 @@ function __construct(string $key, bool $required = false, bool $array = false, ? $this->max = $max; } - function getName(): string + public function getName(): string { return 'floatAttribute'; } - function getMin(): float|null + public function getMin(): float|null { return $this->min; } - function getMax(): float|null + public function getMax(): float|null { return $this->max; } - function setMin(float $min): self + public function setMin(float $min): self { $this->min = $min; return $this; } - function setMax(float $max): self + public function setMax(float $max): self { $this->max = $max; return $this; } - function getDefault(): ?float + public function getDefault(): ?float { return $this->default; } - function setDefault(float $default): self + public function setDefault(float $default): self { $this->default = $default; return $this; } - function asArray(): array + public function asArray(): array { return array_merge(parent::asArray(), [ 'min' => $this->getMin(), @@ -71,4 +72,4 @@ function asArray(): array 'default' => $this->getDefault(), ]); } -} \ No newline at end of file +} diff --git a/src/Transfer/Resources/Attributes/IPAttribute.php b/src/Transfer/Resources/Attributes/IPAttribute.php index ccca663..4f73a54 100644 --- a/src/Transfer/Resources/Attributes/IPAttribute.php +++ b/src/Transfer/Resources/Attributes/IPAttribute.php @@ -4,7 +4,8 @@ use Utopia\Transfer\Resources\Attribute; -class IPAttribute extends Attribute { +class IPAttribute extends Attribute +{ protected ?string $default; /** @@ -13,24 +14,24 @@ class IPAttribute extends Attribute { * @param bool $array * @param string $default */ - function __construct(string $key, bool $required = false, bool $array = false, ?string $default = null) + public function __construct(string $key, bool $required = false, bool $array = false, ?string $default = null) { parent::__construct($key, $required, $array); $this->default = $default; } - function getDefault(): ?string + public function getDefault(): ?string { return $this->default; } - function setDefault(string $default): void + public function setDefault(string $default): void { $this->default = $default; } - function getName(): string + public function getName(): string { return 'IPAttribute'; } -} \ No newline at end of file +} diff --git a/src/Transfer/Resources/Attributes/IntAttribute.php b/src/Transfer/Resources/Attributes/IntAttribute.php index a360c7a..a1c1d8f 100644 --- a/src/Transfer/Resources/Attributes/IntAttribute.php +++ b/src/Transfer/Resources/Attributes/IntAttribute.php @@ -4,7 +4,8 @@ use Utopia\Transfer\Resources\Attribute; -class IntAttribute extends Attribute { +class IntAttribute extends Attribute +{ protected ?int $default; protected ?int $min; protected ?int $max; @@ -17,7 +18,7 @@ class IntAttribute extends Attribute { * @param ?int $min * @param ?int $max */ - function __construct(string $key, bool $required = false, bool $array = false, ?int $default = null, int $min = null, int $max = null) + public function __construct(string $key, bool $required = false, bool $array = false, ?int $default = null, int $min = null, int $max = null) { parent::__construct($key, $required, $array); $this->default = $default; @@ -25,45 +26,45 @@ function __construct(string $key, bool $required = false, bool $array = false, ? $this->max = $max; } - function getName(): string + public function getName(): string { return 'intAttribute'; } - function getMin(): int|null + public function getMin(): int|null { return $this->min; } - function getMax(): int|null + public function getMax(): int|null { return $this->max; } - function setMin(int|null $min): self + public function setMin(int|null $min): self { $this->min = $min; return $this; } - function setMax(int|null $max): self + public function setMax(int|null $max): self { $this->max = $max; return $this; } - function getDefault(): ?int + public function getDefault(): ?int { return $this->default; } - function setDefault(int $default): self + public function setDefault(int $default): self { $this->default = $default; return $this; } - function asArray(): array + public function asArray(): array { return array_merge(parent::asArray(), [ 'min' => $this->getMin(), @@ -71,4 +72,4 @@ function asArray(): array 'default' => $this->getDefault(), ]); } -} \ No newline at end of file +} diff --git a/src/Transfer/Resources/Attributes/StringAttribute.php b/src/Transfer/Resources/Attributes/StringAttribute.php index ee079ce..f7fff7b 100644 --- a/src/Transfer/Resources/Attributes/StringAttribute.php +++ b/src/Transfer/Resources/Attributes/StringAttribute.php @@ -4,7 +4,8 @@ use Utopia\Transfer\Resources\Attribute; -class StringAttribute extends Attribute { +class StringAttribute extends Attribute +{ protected ?string $default; protected int $size = 256; @@ -15,43 +16,43 @@ class StringAttribute extends Attribute { * @param ?string $default * @param int $size */ - function __construct(string $key, bool $required = false, bool $array = false, ?string $default = null, int $size = 256) + public function __construct(string $key, bool $required = false, bool $array = false, ?string $default = null, int $size = 256) { parent::__construct($key, $required, $array); $this->default = $default; $this->size = $size; } - function getName(): string + public function getName(): string { return 'stringAttribute'; } - function getSize(): int + public function getSize(): int { return $this->size; } - function setSize(int $size): self + public function setSize(int $size): self { $this->size = $size; return $this; } - function getDefault(): ?string + public function getDefault(): ?string { return $this->default; } - function setDefault(string $default): void + public function setDefault(string $default): void { $this->default = $default; } - function asArray(): array + public function asArray(): array { return array_merge(parent::asArray(), [ 'size' => $this->size, ]); } -} \ No newline at end of file +} diff --git a/src/Transfer/Resources/Attributes/URLAttribute.php b/src/Transfer/Resources/Attributes/URLAttribute.php index a88b77f..b427161 100644 --- a/src/Transfer/Resources/Attributes/URLAttribute.php +++ b/src/Transfer/Resources/Attributes/URLAttribute.php @@ -4,7 +4,8 @@ use Utopia\Transfer\Resources\Attribute; -class URLAttribute extends Attribute { +class URLAttribute extends Attribute +{ protected ?string $default; /** @@ -13,24 +14,24 @@ class URLAttribute extends Attribute { * @param bool $array * @param ?string $default */ - function __construct(string $key, bool $required = false, bool $array = false, ?string $default = null) + public function __construct(string $key, bool $required = false, bool $array = false, ?string $default = null) { parent::__construct($key, $required, $array); $this->default = $default; } - function getDefault(): ?string + public function getDefault(): ?string { return $this->default; } - function setDefault(string $default): void + public function setDefault(string $default): void { $this->default = $default; } - function getName(): string + public function getName(): string { return 'URLAttribute'; } -} \ No newline at end of file +} diff --git a/src/Transfer/Resources/Collection.php b/src/Transfer/Resources/Collection.php index 0421e2b..a34d4b6 100644 --- a/src/Transfer/Resources/Collection.php +++ b/src/Transfer/Resources/Collection.php @@ -139,4 +139,4 @@ public function asArray(): array 'documentSecurity' => $this->documentSecurity, ]; } -} \ No newline at end of file +} diff --git a/src/Transfer/Resources/Database.php b/src/Transfer/Resources/Database.php index a364cab..38cb23f 100644 --- a/src/Transfer/Resources/Database.php +++ b/src/Transfer/Resources/Database.php @@ -14,8 +14,8 @@ class Database extends Resource { - const DB_RELATIONAL = 'relational'; - const DB_NON_RELATIONAL = 'non-relational'; + public const DB_RELATIONAL = 'relational'; + public const DB_NON_RELATIONAL = 'non-relational'; /** * @var list $collections @@ -94,4 +94,4 @@ public function asArray(): array }, $this->collections) ]; } -} \ No newline at end of file +} diff --git a/src/Transfer/Resources/Document.php b/src/Transfer/Resources/Document.php index 97e39c8..d9da133 100644 --- a/src/Transfer/Resources/Document.php +++ b/src/Transfer/Resources/Document.php @@ -4,7 +4,8 @@ use Utopia\Transfer\Resource; -class Document extends Resource { +class Document extends Resource +{ protected string $id; protected Database $database; protected Collection $collection; @@ -65,9 +66,9 @@ public function getData(): array /** * Set Data - * + * * @param array $data - * + * * @return self */ public function setData(array $data): self @@ -83,9 +84,9 @@ public function getPermissions(): array /** * Set Permissions - * + * * @param array $permissions - * + * * @return self */ public function setPermissions(array $permissions): self @@ -104,4 +105,4 @@ public function asArray(): array 'permissions' => $this->permissions, ]; } -} \ No newline at end of file +} diff --git a/src/Transfer/Resources/Hash.php b/src/Transfer/Resources/Hash.php index b909cdb..5aa8b9f 100644 --- a/src/Transfer/Resources/Hash.php +++ b/src/Transfer/Resources/Hash.php @@ -8,7 +8,8 @@ * Helper class for hashing. */ -class Hash extends Resource { +class Hash extends Resource +{ public const SCRYPT_MODIFIED = 'ScryptModified'; public const BCRYPT = 'Bcrypt'; public const MD5 = 'MD5'; @@ -47,7 +48,7 @@ public function getName(): string /** * Get Hash - * + * * @return string */ public function getHash(): string @@ -57,7 +58,7 @@ public function getHash(): string /** * Set Hash - * + * * @param string $hash * @return self */ @@ -69,7 +70,7 @@ public function setHash(string $hash): self /** * Get Salt - * + * * @return string */ public function getSalt(): string @@ -79,7 +80,7 @@ public function getSalt(): string /** * Set Salt - * + * * @param string $salt * @return self */ @@ -91,7 +92,7 @@ public function setSalt(string $salt): self /** * Get Algorithm - * + * * @return string */ public function getAlgorithm(): string @@ -101,7 +102,7 @@ public function getAlgorithm(): string /** * Set Algorithm - * + * * @param string $algorithm * @return self */ @@ -113,7 +114,7 @@ public function setAlgorithm(string $algorithm): self /** * Get Separator - * + * * @return string */ public function getSeparator(): string @@ -123,7 +124,7 @@ public function getSeparator(): string /** * Set Separator - * + * * @param string $separator * @return self */ @@ -135,7 +136,7 @@ public function setSeparator(string $separator): self /** * Get Signing Key - * + * * @return string */ public function getSigningKey(): string @@ -145,7 +146,7 @@ public function getSigningKey(): string /** * Set Signing Key - * + * * @param string $signingKey * @return self */ @@ -157,7 +158,7 @@ public function setSigningKey(string $signingKey): self /** * Get Password CPU - * + * * @return int */ public function getPasswordCpu(): int @@ -167,7 +168,7 @@ public function getPasswordCpu(): int /** * Set Password CPU - * + * * @param int $passwordCpu * @return self */ @@ -179,7 +180,7 @@ public function setPasswordCpu(int $passwordCpu): self /** * Get Password Memory - * + * * @return int */ public function getPasswordMemory(): int @@ -189,7 +190,7 @@ public function getPasswordMemory(): int /** * Set Password Memory - * + * * @param int $passwordMemory * @return self */ @@ -201,7 +202,7 @@ public function setPasswordMemory(int $passwordMemory): self /** * Get Password Parallel - * + * * @return int */ public function getPasswordParallel(): int @@ -211,7 +212,7 @@ public function getPasswordParallel(): int /** * Set Password Parallel - * + * * @param int $passwordParallel * @return self */ @@ -223,7 +224,7 @@ public function setPasswordParallel(int $passwordParallel): self /** * Get Password Length - * + * * @return int */ public function getPasswordLength(): int @@ -233,7 +234,7 @@ public function getPasswordLength(): int /** * Set Password Length - * + * * @param int $passwordLength * @return self */ @@ -245,7 +246,7 @@ public function setPasswordLength(int $passwordLength): self /** * As Array - * + * * @return array */ public function asArray(): array @@ -262,4 +263,4 @@ public function asArray(): array 'passwordLength' => $this->passwordLength, ]; } -} \ No newline at end of file +} diff --git a/src/Transfer/Resources/Index.php b/src/Transfer/Resources/Index.php index b7f94e9..5933335 100644 --- a/src/Transfer/Resources/Index.php +++ b/src/Transfer/Resources/Index.php @@ -4,16 +4,16 @@ use Utopia\Transfer\Resource; -class Index extends Resource { - +class Index extends Resource +{ protected string $key; protected string $type; protected array $attributes; protected array $orders; - const TYPE_UNIQUE = 'unique'; - const TYPE_FULLTEXT = 'fulltext'; - const TYPE_KEY = 'key'; + public const TYPE_UNIQUE = 'unique'; + public const TYPE_FULLTEXT = 'fulltext'; + public const TYPE_KEY = 'key'; /** * @param string $key @@ -93,4 +93,4 @@ public function asArray(): array 'orders' => $this->orders, ]; } -} \ No newline at end of file +} diff --git a/src/Transfer/Resources/Project.php b/src/Transfer/Resources/Project.php index d18571d..9bab657 100644 --- a/src/Transfer/Resources/Project.php +++ b/src/Transfer/Resources/Project.php @@ -38,4 +38,4 @@ public function asArray(): array 'id' => $this->id, ]; } -} \ No newline at end of file +} diff --git a/src/Transfer/Resources/User.php b/src/Transfer/Resources/User.php index aa5d8d5..a190622 100644 --- a/src/Transfer/Resources/User.php +++ b/src/Transfer/Resources/User.php @@ -7,18 +7,18 @@ class User extends Resource { - const TYPE_EMAIL = 'email'; - const TYPE_PHONE = 'phone'; - const TYPE_ANONYMOUS = 'anonymous'; - const TYPE_MAGIC = 'magic'; - const TYPE_OAUTH = 'oauth'; + public const TYPE_EMAIL = 'email'; + public const TYPE_PHONE = 'phone'; + public const TYPE_ANONYMOUS = 'anonymous'; + public const TYPE_MAGIC = 'magic'; + public const TYPE_OAUTH = 'oauth'; protected string $id = ''; protected string $email = ''; protected string $username = ''; protected ?Hash $passwordHash = null; protected string $phone = ''; - protected array $types = [Self::TYPE_ANONYMOUS]; + protected array $types = [self::TYPE_ANONYMOUS]; protected string $oauthProvider = ''; protected bool $emailVerified = false; protected bool $phoneVerified = false; @@ -31,13 +31,13 @@ public function __construct( string $username = '', ?Hash $passwordHash = null, string $phone = '', - array $types = [Self::TYPE_ANONYMOUS], + array $types = [self::TYPE_ANONYMOUS], string $oauthProvider = '', bool $emailVerified = false, bool $phoneVerified = false, bool $disabled = false, array $preferences = [] - ){ + ) { $this->id = $id; $this->email = $email; $this->username = $username; @@ -53,7 +53,7 @@ public function __construct( /** * Get Name - * + * * @return string */ public function getName(): string @@ -61,9 +61,9 @@ public function getName(): string return 'user'; } - /** + /** * Get ID - * + * * @return string */ public function getId(): string @@ -73,7 +73,7 @@ public function getId(): string /** * Set ID - * + * * @param string $id * @return self */ @@ -85,7 +85,7 @@ public function setId(string $id): self /** * Get Email - * + * * @return string */ public function getEmail(): string @@ -95,7 +95,7 @@ public function getEmail(): string /** * Set Email - * + * * @param string $email * @return self */ @@ -107,7 +107,7 @@ public function setEmail(string $email): self /** * Get Username - * + * * @return string */ public function getUsername(): string @@ -117,7 +117,7 @@ public function getUsername(): string /** * Set Username - * + * * @param string $username * @return self */ @@ -129,7 +129,7 @@ public function setUsername(string $username): self /** * Get Password Hash - * + * * @return Hash */ public function getPasswordHash(): Hash @@ -139,7 +139,7 @@ public function getPasswordHash(): Hash /** * Set Password Hash - * + * * @param Hash $passwordHash * @return self */ @@ -151,7 +151,7 @@ public function setPasswordHash(Hash $passwordHash): self /** * Get Phone - * + * * @return string */ public function getPhone(): string @@ -161,7 +161,7 @@ public function getPhone(): string /** * Set Phone - * + * * @param string $phone * @return self */ @@ -173,7 +173,7 @@ public function setPhone(string $phone): self /** * Get Type - * + * * @return array */ public function getTypes(): array @@ -183,7 +183,7 @@ public function getTypes(): array /** * Set Types - * + * * @param string $types * @return self */ @@ -195,7 +195,7 @@ public function setTypes(string $types): self /** * Get OAuth Provider - * + * * @return string */ public function getOAuthProvider(): string @@ -205,7 +205,7 @@ public function getOAuthProvider(): string /** * Set OAuth Provider - * + * * @param string $oauthProvider * @return self */ @@ -217,7 +217,7 @@ public function setOAuthProvider(string $oauthProvider): self /** * Get Email Verified - * + * * @return bool */ public function getEmailVerified(): bool @@ -227,7 +227,7 @@ public function getEmailVerified(): bool /** * Set Email Verified - * + * * @param bool $verified * @return self */ @@ -239,7 +239,7 @@ public function setEmailVerified(bool $verified): self /** * Get Email Verified - * + * * @return bool */ public function getPhoneVerified(): bool @@ -249,7 +249,7 @@ public function getPhoneVerified(): bool /** * Set Phone Verified - * + * * @param bool $verified * @return self */ @@ -261,7 +261,7 @@ public function setPhoneVerified(bool $verified): self /** * Get Disabled - * + * * @return bool */ public function getDisabled(): bool @@ -271,7 +271,7 @@ public function getDisabled(): bool /** * Set Disabled - * + * * @param bool $disabled * @return self */ @@ -283,7 +283,7 @@ public function setDisabled(bool $disabled): self /** * Get Preferences - * + * * @return array */ public function getPreferences(): array @@ -293,7 +293,7 @@ public function getPreferences(): array /** * Set Preferences - * + * * @param array $preferences * @return self */ @@ -305,7 +305,7 @@ public function setPreferences(array $preferences): self /** * As Array - * + * * @return array */ public function asArray(): array @@ -322,4 +322,4 @@ public function asArray(): array 'phoneVerified' => $this->phoneVerified, ]; } -} \ No newline at end of file +} diff --git a/src/Transfer/Source.php b/src/Transfer/Source.php index c38e74c..074b228 100644 --- a/src/Transfer/Source.php +++ b/src/Transfer/Source.php @@ -17,14 +17,14 @@ abstract class Source /** * Logs - * + * * @var array $logs */ protected $logs = []; /** * Resource Cache - * + * * @var array $resourceCache */ protected $resourceCache = [ @@ -37,26 +37,27 @@ abstract class Source /** * Counters - * + * * @var array $counters */ protected $counters = []; /** * Endpoint - * + * * @var string $endpoint */ protected $endpoint = ''; /** * Get Resource Counters - * + * * @param string $resource = null - * + * * @return array */ - public function &getCounter(string $resource = null): array { + public function &getCounter(string $resource = null): array + { if ($resource && $this->counters[$resource]) { return $this->counters[$resource]; } else { @@ -73,88 +74,86 @@ public function &getCounter(string $resource = null): array { /** * Gets the name of the adapter. - * + * * @return string */ abstract public function getName(): string; /** * Get Supported Resources - * + * * @return array */ abstract public function getSupportedResources(): array; /** * Register Logs Array - * + * * @param array &$logs */ - public function registerLogs(array &$logs): void { + public function registerLogs(array &$logs): void + { $this->logs = &$logs; } /** * Register Transfer Hooks - * + * * @param array &$cache * @param array &$counters - * + * * @return void */ - public function registerTransferHooks(array &$cache, array &$counters): void { + public function registerTransferHooks(array &$cache, array &$counters): void + { $this->resourceCache = &$cache; $this->counters = &$counters; } /** * Transfer Resources into destination - * + * * @param array $resources * @param callable $callback */ - public function run(array $resources, callable $callback): void { + public function run(array $resources, callable $callback): void + { foreach ($resources as $resource) { if (!in_array($resource, $this->getSupportedResources())) { - throw new \Exception("Cannot Transfer unsupported resource: '".$resource."'"); + throw new \Exception("Cannot Transfer unsupported resource: '" . $resource . "'"); } switch ($resource) { - case Transfer::RESOURCE_USERS: { + case Transfer::RESOURCE_USERS: $this->exportUsers(100, function (array $users) use ($callback) { $this->resourceCache[Transfer::RESOURCE_USERS] = array_merge($this->resourceCache[Transfer::RESOURCE_USERS], $users); $callback(Transfer::RESOURCE_USERS, $users); }); break; - } - case Transfer::RESOURCE_DATABASES: { + case Transfer::RESOURCE_DATABASES: $this->exportDatabases(100, function (array $databases) use ($callback) { $this->resourceCache[Transfer::RESOURCE_DATABASES] = array_merge($this->resourceCache[Transfer::RESOURCE_DATABASES], $databases); $callback(Transfer::RESOURCE_DATABASES, $databases); }); break; - } - case Transfer::RESOURCE_DOCUMENTS: { + case Transfer::RESOURCE_DOCUMENTS: $this->exportDocuments(100, function (array $documents) use ($callback) { $this->resourceCache[Transfer::RESOURCE_DOCUMENTS] = array_merge($this->resourceCache[Transfer::RESOURCE_DOCUMENTS], $documents); $callback(Transfer::RESOURCE_DOCUMENTS, $documents); }); break; - } - case Transfer::RESOURCE_FILES: { + case Transfer::RESOURCE_FILES: $this->exportFiles(5, function (array $files) use ($callback) { $this->resourceCache[Transfer::RESOURCE_FILES] = array_merge($this->resourceCache[Transfer::RESOURCE_FILES], $files); $callback(Transfer::RESOURCE_FILES, $files); }); break; - } - case Transfer::RESOURCE_FUNCTIONS: { + case Transfer::RESOURCE_FUNCTIONS: $this->exportFunctions(100, function (array $functions) use ($callback) { $this->resourceCache[Transfer::RESOURCE_FUNCTIONS] = array_merge($this->resourceCache, $functions); $callback(Transfer::RESOURCE_FUNCTIONS, $functions); }); break; - } } } } @@ -163,13 +162,13 @@ public function run(array $resources, callable $callback): void { * Check Requirements * Performs a suite of API Checks, Resource Checks, etc... to ensure the adapter is ready to be used. * This is highly recommended to be called before any other method after initialization. - * + * * If no resources are provided, the method should check all resources. * Returns a object with all the keys of the resources provided and a true|string value if the resource is available or not. * If the resource is not available, the value should be a string with the error message. - * + * * @string[] $resources - * + * * @return string[] */ abstract public function check(array $resources = []): array; @@ -290,10 +289,10 @@ protected function flatten(array $data, string $prefix = ''): array /** * Export Users - * + * * @param int $batchSize * @param callable $callback Callback function to be called after each batch, $callback(user[] $batch); - * + * * @return void */ public function exportUsers(int $batchSize, callable $callback): void @@ -303,10 +302,10 @@ public function exportUsers(int $batchSize, callable $callback): void /** * Export Databases - * + * * @param int $batchSize Max 100 * @param callable $callback Callback function to be called after each database, $callback(database[] $batch); - * + * * @return void */ public function exportDatabases(int $batchSize, callable $callback): void @@ -316,10 +315,10 @@ public function exportDatabases(int $batchSize, callable $callback): void /** * Export Documents - * + * * @param int $batchSize Max 100 * @param callable $callback Callback function to be called after each document, $callback(document[] $batch); - * + * * @return void */ public function exportDocuments(int $batchSize, callable $callback): void @@ -329,10 +328,10 @@ public function exportDocuments(int $batchSize, callable $callback): void /** * Export Files - * + * * @param int $batchSize Max 100 * @param callable $callback Callback function to be called after each file, $callback(file[] $batch); - * + * * @return void */ public function exportFiles(int $batchSize, callable $callback): void @@ -342,10 +341,10 @@ public function exportFiles(int $batchSize, callable $callback): void /** * Export Functions - * + * * @param int $batchSize Max 100 * @param callable $callback Callback function to be called after each function, $callback(function[] $batch); - * + * * @return void */ public function exportFunctions(int $batchSize, callable $callback): void diff --git a/src/Transfer/Sources/Appwrite.php b/src/Transfer/Sources/Appwrite.php index 9527095..1739784 100644 --- a/src/Transfer/Sources/Appwrite.php +++ b/src/Transfer/Sources/Appwrite.php @@ -37,14 +37,14 @@ class Appwrite extends Source /** * Constructor - * + * * @param string $project * @param string $endpoint * @param string $key - * + * * @return self */ - function __construct(string $project, string $endpoint, string $key) + public function __construct(string $project, string $endpoint, string $key) { $this->client = (new Client()) ->setEndpoint($endpoint) @@ -54,17 +54,17 @@ function __construct(string $project, string $endpoint, string $key) /** * Get Name - * + * * @return string */ public function getName(): string { - return 'Appwrite'; + return "Appwrite"; } /** * Get Supported Resources - * + * * @return array */ public function getSupportedResources(): array @@ -79,11 +79,11 @@ public function getSupportedResources(): array public function check(array $resources = []): array { $report = [ - 'Users' => [], - 'Databases' => [], - 'Documents' => [], - 'Files' => [], - 'Functions' => [] + "Users" => [], + "Databases" => [], + "Documents" => [], + "Files" => [], + "Functions" => [], ]; if (empty($resources)) { @@ -101,7 +101,8 @@ public function check(array $resources = []): array $databases->list(); } catch (\Throwable $e) { if ($e->getCode() == 401) { - $report['Databases'][] = 'API Key is missing scope: databases.read'; + $report["Databases"][] = + "API Key is missing scope: databases.read"; } } break; @@ -111,7 +112,8 @@ public function check(array $resources = []): array $auth->list(); } catch (\Throwable $e) { if ($e->getCode() == 401) { - $report['Users'][] = 'API Key is missing scope: users.read'; + $report["Users"][] = + "API Key is missing scope: users.read"; } } break; @@ -121,79 +123,96 @@ public function check(array $resources = []): array $databases->list(); } catch (\Throwable $e) { if ($e->getCode() == 401) { - $report['Databases'][] = 'API Key is missing scope: databases.read'; + $report["Databases"][] = + "API Key is missing scope: databases.read"; } } try { - $databases->create('', ''); + $databases->create("", ""); } catch (\Throwable $e) { if ($e->getCode() == 401) { - $report['Databases'][] = 'API Key is missing scope: databases.write'; + $report["Databases"][] = + "API Key is missing scope: databases.write"; } } try { - $databases->listCollections('', [], ''); + $databases->listCollections("", [], ""); } catch (\Throwable $e) { if ($e->getCode() == 401) { - $report['Databases'][] = 'API Key is missing scope: collections.write'; + $report["Databases"][] = + "API Key is missing scope: collections.write"; } } try { - $databases->createCollection('', '', '', []); + $databases->createCollection("", "", "", []); } catch (\Throwable $e) { if ($e->getCode() == 401) { - $report['Databases'][] = 'API Key is missing scope: collections.write'; + $report["Databases"][] = + "API Key is missing scope: collections.write"; } } try { - $databases->listDocuments('', '', []); + $databases->listDocuments("", "", []); } catch (\Throwable $e) { if ($e->getCode() == 401) { - $report['Databases'][] = 'API Key is missing scope: documents.write'; + $report["Databases"][] = + "API Key is missing scope: documents.write"; } } try { - $databases->createDocument('', '', '', [], []); + $databases->createDocument("", "", "", [], []); } catch (\Throwable $e) { if ($e->getCode() == 401) { - $report['Documents'][] = 'API Key is missing scope: documents.write'; + $report["Documents"][] = + "API Key is missing scope: documents.write"; } } try { - $databases->listIndexes('', ''); + $databases->listIndexes("", ""); } catch (\Throwable $e) { if ($e->getCode() == 401) { - $report['Databases'][] = 'API Key is missing scope: indexes.read'; + $report["Databases"][] = + "API Key is missing scope: indexes.read"; } } try { - $databases->createIndex('', '', '', '', [], []); + $databases->createIndex("", "", "", "", [], []); } catch (\Throwable $e) { if ($e->getCode() == 401) { - $report['Databases'][] = 'API Key is missing scope: indexes.write'; + $report["Databases"][] = + "API Key is missing scope: indexes.write"; } } try { - $databases->listAttributes('', ''); + $databases->listAttributes("", ""); } catch (\Throwable $e) { if ($e->getCode() == 401) { - $report['Databases'][] = 'API Key is missing scope: attributes.read'; + $report["Databases"][] = + "API Key is missing scope: attributes.read"; } } try { - $databases->createStringAttribute('', '', '', 0, false, false); + $databases->createStringAttribute( + "", + "", + "", + 0, + false, + false + ); } catch (\Throwable $e) { if ($e->getCode() == 401) { - $report['Databases'][] = 'API Key is missing scope: attributes.write'; + $report["Databases"][] = + "API Key is missing scope: attributes.write"; } } } @@ -204,10 +223,10 @@ public function check(array $resources = []): array /** * Export Users - * + * * @param int $batchSize Max 100 * @param callable $callback Callback function to be called after each batch, $callback(user[] $batch); - * + * * @return void */ public function exportUsers(int $batchSize, callable $callback): void @@ -219,9 +238,7 @@ public function exportUsers(int $batchSize, callable $callback): void while (true) { $users = []; - $queries = [ - Query::limit($batchSize) - ]; + $queries = [Query::limit($batchSize)]; if ($lastDocument) { $queries[] = Query::cursorAfter($lastDocument); @@ -229,19 +246,19 @@ public function exportUsers(int $batchSize, callable $callback): void $response = $usersClient->list($queries); - foreach ($response['users'] as $user) { + foreach ($response["users"] as $user) { $users[] = new User( $user['$id'], - $user['email'], - $user['name'], - new Hash($user['password'], $user['hash']), - $user['phone'], + $user["email"], + $user["name"], + new Hash($user["password"], $user["hash"]), + $user["phone"], $this->calculateTypes($user), - '', - $user['emailVerification'], - $user['phoneVerification'], - !$user['status'], - $user['prefs'] + "", + $user["emailVerification"], + $user["phoneVerification"], + !$user["status"], + $user["prefs"] ); $lastDocument = $user['$id']; @@ -255,45 +272,102 @@ public function exportUsers(int $batchSize, callable $callback): void } } - function convertAttribute(array $value): Attribute + public function convertAttribute(array $value): Attribute { - switch ($value['type']) { - case 'string': { - if (!isset($value['format'])) - return new StringAttribute($value['key'], $value['required'], $value['array'], $value['default'], $value['size'] ?? 0); - - switch ($value['format']) { - case 'email': - return new EmailAttribute($value['key'], $value['required'], $value['array'], $value['default']); - case 'enum': - return new EnumAttribute($value['key'], $value['elements'], $value['required'], $value['array'], $value['default']); - case 'url': - return new URLAttribute($value['key'], $value['required'], $value['array'], $value['default']); - case 'ip': - return new IPAttribute($value['key'], $value['required'], $value['array'], $value['default']); - case 'datetime': - return new DateTimeAttribute($value['key'], $value['required'], $value['array'], $value['default']); - default: - return new StringAttribute($value['key'], $value['required'], $value['array'], $value['default'], $value['size'] ?? 0); - } + switch ($value["type"]) { + case "string": + if (!isset($value["format"])) { + return new StringAttribute( + $value["key"], + $value["required"], + $value["array"], + $value["default"], + $value["size"] ?? 0 + ); + } + + switch ($value["format"]) { + case "email": + return new EmailAttribute( + $value["key"], + $value["required"], + $value["array"], + $value["default"] + ); + case "enum": + return new EnumAttribute( + $value["key"], + $value["elements"], + $value["required"], + $value["array"], + $value["default"] + ); + case "url": + return new URLAttribute( + $value["key"], + $value["required"], + $value["array"], + $value["default"] + ); + case "ip": + return new IPAttribute( + $value["key"], + $value["required"], + $value["array"], + $value["default"] + ); + case "datetime": + return new DateTimeAttribute( + $value["key"], + $value["required"], + $value["array"], + $value["default"] + ); + default: + return new StringAttribute( + $value["key"], + $value["required"], + $value["array"], + $value["default"], + $value["size"] ?? 0 + ); } - case 'boolean': - return new BoolAttribute($value['key'], $value['required'], $value['array'], $value['default']); - case 'integer': - return new IntAttribute($value['key'], $value['required'], $value['array'], $value['default'], $value['min'] ?? 0, $value['max'] ?? 0); - case 'double': - return new FloatAttribute($value['key'], $value['required'], $value['array'], $value['default'], $value['min'] ?? 0, $value['max'] ?? 0); + case "boolean": + return new BoolAttribute( + $value["key"], + $value["required"], + $value["array"], + $value["default"] + ); + case "integer": + return new IntAttribute( + $value["key"], + $value["required"], + $value["array"], + $value["default"], + $value["min"] ?? 0, + $value["max"] ?? 0 + ); + case "double": + return new FloatAttribute( + $value["key"], + $value["required"], + $value["array"], + $value["default"], + $value["min"] ?? 0, + $value["max"] ?? 0 + ); } - throw new \Exception('Unknown attribute type: ' . $value['type']); + throw new \Exception("Unknown attribute type: " . $value["type"]); } /** * Export Databases - * + * * @param int $batchSize Max 100 * @param callable $callback Callback function to be called after each database, $callback(database[] $batch); - * + * * @return void */ public function exportDatabases(int $batchSize, callable $callback): void @@ -303,9 +377,7 @@ public function exportDatabases(int $batchSize, callable $callback): void $lastDocument = null; while (true) { - $queries = [ - Query::limit($batchSize) - ]; + $queries = [Query::limit($batchSize)]; $databases = []; @@ -316,23 +388,39 @@ public function exportDatabases(int $batchSize, callable $callback): void $response = $databaseClient->list($queries); foreach ($response["databases"] as $database) { - $newDatabase = new Database($database['name'], $database['$id']); + $newDatabase = new Database( + $database["name"], + $database['$id'] + ); - $collections = $databaseClient->listCollections($database['$id']); + $collections = $databaseClient->listCollections( + $database['$id'] + ); $generalCollections = []; - foreach ($collections['collections'] as $collection) { - $newCollection = new Collection($collection['name'], $collection['$id'], $collection['documentSecurity'], $collection['$permissions']); + foreach ($collections["collections"] as $collection) { + $newCollection = new Collection( + $collection["name"], + $collection['$id'], + $collection["documentSecurity"], + $collection['$permissions'] + ); $attributes = []; $indexes = []; - foreach ($collection['attributes'] as $attribute) { + foreach ($collection["attributes"] as $attribute) { $attributes[] = $this->convertAttribute($attribute); } - foreach ($collection['indexes'] as $index) { - $indexes[] = new Index('unique()', $index['key'], $index['type'], $index['attributes'], $index['orders']); + foreach ($collection["indexes"] as $index) { + $indexes[] = new Index( + "unique()", + $index["key"], + $index["type"], + $index["attributes"], + $index["orders"] + ); } $newCollection->setAttributes($attributes); @@ -357,10 +445,10 @@ public function exportDatabases(int $batchSize, callable $callback): void /** * Export Documents - * + * * @param int $batchSize Max 100 * @param callable $callback Callback function to be called after each batch, $callback(document[] $batch); - * + * * @return void */ public function exportDocuments(int $batchSize, callable $callback): void @@ -378,9 +466,7 @@ public function exportDocuments(int $batchSize, callable $callback): void $lastDocument = null; while (true) { - $queries = [ - Query::limit($batchSize) - ]; + $queries = [Query::limit($batchSize)]; $documents = []; @@ -388,7 +474,11 @@ public function exportDocuments(int $batchSize, callable $callback): void $queries[] = Query::cursorAfter($lastDocument); } - $response = $databaseClient->listDocuments($database->getId(), $collection->getId(), $queries); + $response = $databaseClient->listDocuments( + $database->getId(), + $collection->getId(), + $queries + ); foreach ($response["documents"] as $document) { $id = $document['$id']; @@ -400,7 +490,13 @@ public function exportDocuments(int $batchSize, callable $callback): void unset($document['$createdAt']); unset($document['$databaseId']); - $documents[] = new Document($id, $database, $collection, $document, $permissions); + $documents[] = new Document( + $id, + $database, + $collection, + $document, + $permissions + ); $lastDocument = $id; } @@ -416,24 +512,24 @@ public function exportDocuments(int $batchSize, callable $callback): void /** * Calculate Types - * + * * @param array $user - * + * * @return array */ protected function calculateTypes(array $user): array { - if (empty($user['email']) && empty($user['phone'])) { + if (empty($user["email"]) && empty($user["phone"])) { return [User::TYPE_ANONYMOUS]; } $types = []; - if (!empty($user['email'])) { + if (!empty($user["email"])) { $types[] = User::TYPE_EMAIL; } - if (!empty($user['phone'])) { + if (!empty($user["phone"])) { $types[] = User::TYPE_PHONE; } diff --git a/src/Transfer/Sources/Firebase.php b/src/Transfer/Sources/Firebase.php index d6f21f6..7bd37a6 100644 --- a/src/Transfer/Sources/Firebase.php +++ b/src/Transfer/Sources/Firebase.php @@ -21,8 +21,8 @@ class Firebase extends Source { - const TYPE_OAUTH = 'oauth'; - const AUTH_SERVICEACCOUNT = 'serviceaccount'; + public const TYPE_OAUTH = 'oauth'; + public const AUTH_SERVICEACCOUNT = 'serviceaccount'; /** * @var array{object: array, type: string} @@ -44,11 +44,11 @@ class Firebase extends Source /** * Constructor - * - * @param array $authObject Service Account Credentials for AUTH_SERVICEACCOUNT + * + * @param array $authObject Service Account Credentials for AUTH_SERVICEACCOUNT * @param string $authType Can be either Firebase::TYPE_OAUTH or Firebase::AUTH_APIKEY */ - function __construct(array $authObject = [], string $authType = self::AUTH_SERVICEACCOUNT) + public function __construct(array $authObject = [], string $authType = self::AUTH_SERVICEACCOUNT) { if (!in_array($authType, [self::TYPE_OAUTH, self::AUTH_SERVICEACCOUNT])) { throw new \Exception('Invalid authentication type'); @@ -58,7 +58,7 @@ function __construct(array $authObject = [], string $authType = self::AUTH_SERVI if ($authType === self::TYPE_OAUTH) { $this->googleClient->setAccessToken($authObject); - } else if ($authType === self::AUTH_SERVICEACCOUNT) { + } elseif ($authType === self::AUTH_SERVICEACCOUNT) { $this->googleClient->setAuthConfig($authObject); } @@ -66,12 +66,12 @@ function __construct(array $authObject = [], string $authType = self::AUTH_SERVI $this->googleClient->addScope('https://www.googleapis.com/auth/cloud-platform'); } - function getName(): string + public function getName(): string { return 'Firebase'; } - function getSupportedResources(): array + public function getSupportedResources(): array { return [ Transfer::RESOURCE_USERS, @@ -79,7 +79,7 @@ function getSupportedResources(): array ]; } - function getProjects(): array + public function getProjects(): array { $projects = []; @@ -105,10 +105,10 @@ function getProjects(): array /** * Set Project - * + * * @param Project|string $project */ - function setProject(Project|string $project): void + public function setProject(Project|string $project): void { if (is_string($project)) { $project = new Project($project, $project); @@ -119,20 +119,20 @@ function setProject(Project|string $project): void /** * Get Project - * + * * @return Project|null */ - function getProject(): Project|null + public function getProject(): Project|null { return $this->project; } /** * Export Users - * + * * @param int $batchSize Max 500 * @param callable $callback Callback function to be called after each batch, $callback(user[] $batch); - * + * * @return void */ public function exportUsers(int $batchSize, callable $callback): void @@ -210,13 +210,13 @@ public function exportUsers(int $batchSize, callable $callback): void /** * Calculate Array Type - * + * * @param string $key * @param \Google\Service\Firestore\ArrayValue $data - * + * * @return ResourcesAttribute */ - function calculateArrayType(string $key, \Google\Service\Firestore\ArrayValue $data): ResourcesAttribute + public function calculateArrayType(string $key, \Google\Service\Firestore\ArrayValue $data): ResourcesAttribute { $isSameType = true; $previousType = null; @@ -224,7 +224,7 @@ function calculateArrayType(string $key, \Google\Service\Firestore\ArrayValue $d foreach ($data["values"] as $field) { if (!$previousType) { $previousType = $this->calculateType($key, $field); - } else if ($previousType->getName() != ($this->calculateType($key, $field))->getName()) { + } elseif ($previousType->getName() != ($this->calculateType($key, $field))->getName()) { $isSameType = false; break; } @@ -240,35 +240,35 @@ function calculateArrayType(string $key, \Google\Service\Firestore\ArrayValue $d /** * Calculate Type - * + * * @param string $key * @param \Google\Service\Firestore\Value $field - * + * * @return ResourcesAttribute */ - function calculateType(string $key, \Google\Service\Firestore\Value $field): ResourcesAttribute + public function calculateType(string $key, \Google\Service\Firestore\Value $field): ResourcesAttribute { - if (isset($field["booleanValue"])) + if (isset($field["booleanValue"])) { return new BoolAttribute($key, false, false, null); - else if (isset($field["bytesValue"])) + } elseif (isset($field["bytesValue"])) { return new StringAttribute($key, false, false, null, 1000000); - else if (isset($field["doubleValue"])) + } elseif (isset($field["doubleValue"])) { return new FloatAttribute($key, false, false, null); - else if (isset($field["integerValue"])) + } elseif (isset($field["integerValue"])) { return new IntAttribute($key, false, false, null); - else if (isset($field["mapValue"])) + } elseif (isset($field["mapValue"])) { return new StringAttribute($key, false, false, null, 1000000); - else if (isset($field["nullValue"])) + } elseif (isset($field["nullValue"])) { return new StringAttribute($key, false, false, null, 1000000); - else if (isset($field["referenceValue"])) + } elseif (isset($field["referenceValue"])) { return new StringAttribute($key, false, false, null, 1000000); - else if (isset($field["stringValue"])) + } elseif (isset($field["stringValue"])) { return new StringAttribute($key, false, false, null, 1000000); - else if (isset($field["timestampValue"])) + } elseif (isset($field["timestampValue"])) { return new DateTimeAttribute($key, false, false, null); - else if (isset($field["geoPointValue"])) - return new StringAttribute($key, false, false, null, 1000000); - else if (isset($field["arrayValue"])) { + } elseif (isset($field["geoPointValue"])) { + return new StringAttribute($key, false, false, null, 1000000); + } elseif (isset($field["arrayValue"])) { return $this->calculateArrayType($key, $field["arrayValue"]); } else { $this->logs[Log::WARNING][] = new Log('Failed to determine data type for: ' . $key . ' Falling back to string.', \time()); @@ -276,16 +276,16 @@ function calculateType(string $key, \Google\Service\Firestore\Value $field): Res } } - /** + /** * Predict Schema - * + * * @param int $batchSize Max 500 * @param $collection Collection * @param &Collection[] $newCollections - * + * * @return list **/ - function predictSchema(int $batchSize, Collection $collection, array &$newCollections) + public function predictSchema(int $batchSize, Collection $collection, array &$newCollections) { $attributes = []; @@ -304,14 +304,14 @@ function predictSchema(int $batchSize, Collection $collection, array &$newCollec $requestOptions->setPageSize(500); // Handle subcollections - $subcollections = $firestore->projects_databases_documents->listCollectionIds($document["name"], $requestOptions,[])["collectionIds"]; + $subcollections = $firestore->projects_databases_documents->listCollectionIds($document["name"], $requestOptions, [])["collectionIds"]; if ($subcollections == null) { continue; } $subcollections = array_map(function ($subcollection) use ($document, $collection) { - $name = str_replace("projects/".$this->getProject()->getId()."/databases/(default)/documents/", "", $document["name"]); + $name = str_replace("projects/" . $this->getProject()->getId() . "/databases/(default)/documents/", "", $document["name"]); return $name . '/' . $subcollection; }, $subcollections); @@ -323,12 +323,12 @@ function predictSchema(int $batchSize, Collection $collection, array &$newCollec /** * Handle Collections - * + * * @param string[] $collectionIDs - * + * * @return Collection[] */ - function handleCollections(array $collectionIDs): array + public function handleCollections(array $collectionIDs): array { $collections = []; @@ -343,12 +343,12 @@ function handleCollections(array $collectionIDs): array return $collections; } - /** + /** * Export Databases - * + * * @param int $batchSize Max 500 * @param callable $callback Callback function to be called after each batch, $callback(database[] $batch); - * + * * @return void */ public function exportDatabases(int $batchSize, callable $callback): void @@ -375,7 +375,7 @@ public function exportDatabases(int $batchSize, callable $callback): void $callback([$database]); } - function calculateTypes(array $providerData): array + public function calculateTypes(array $providerData): array { if (count($providerData) === 0) { return [User::TYPE_ANONYMOUS]; diff --git a/src/Transfer/Sources/NHost.php b/src/Transfer/Sources/NHost.php index 996b7f2..034722a 100644 --- a/src/Transfer/Sources/NHost.php +++ b/src/Transfer/Sources/NHost.php @@ -52,16 +52,16 @@ class NHost extends Source /** * Constructor - * + * * @param string $host * @param string $databaseName * @param string $username * @param string $password * @param string $port - * + * * @return self */ - function __construct(string $host, string $databaseName, string $username, string $password, string $port = '5432') + public function __construct(string $host, string $databaseName, string $username, string $password, string $port = '5432') { $this->host = $host; $this->databaseName = $databaseName; @@ -76,12 +76,12 @@ function __construct(string $host, string $databaseName, string $username, strin } } - function getName(): string + public function getName(): string { return 'NHost'; } - function getSupportedResources(): array + public function getSupportedResources(): array { return [ Transfer::RESOURCE_USERS, @@ -92,11 +92,11 @@ function getSupportedResources(): array /** * Export Users - * + * * @param int $batchSize Max 500 * @param callable $callback Callback function to be called after each batch, $callback(user[] $batch); - * - * @return User[] + * + * @return User[] */ public function exportUsers(int $batchSize, callable $callback): void { @@ -138,7 +138,7 @@ public function exportUsers(int $batchSize, callable $callback): void /** * Convert Collection - * + * * @param string $tableName * @return Collection */ @@ -178,7 +178,7 @@ public function convertCollection(string $tableName): Collection /** * Convert Attribute - * + * * @param array $column * @return Attribute */ @@ -237,23 +237,22 @@ public function convertAttribute(array $column): Attribute $column['character_maximum_length'] ?? $column['character_octet_length'] ?? 10485760 ); break; - default: { - $this->logs[Log::WARNING][] = new Log('Unknown data type: ' . $column['data_type'] . ' for column: ' . $column['column_name'] . ' Falling back to string.', \time()); - return new StringAttribute( - $column['column_name'], - $column['is_nullable'] === 'NO', - $isArray, - $column['column_default'], - $column['character_maximum_length'] ?? $column['character_octet_length'] ?? 10485760 - ); - break; - } + default: + $this->logs[Log::WARNING][] = new Log('Unknown data type: ' . $column['data_type'] . ' for column: ' . $column['column_name'] . ' Falling back to string.', \time()); + return new StringAttribute( + $column['column_name'], + $column['is_nullable'] === 'NO', + $isArray, + $column['column_default'], + $column['character_maximum_length'] ?? $column['character_octet_length'] ?? 10485760 + ); + break; } } /** * Convert Index - * + * * @param string $table * @return Index|false */ @@ -273,7 +272,7 @@ public function convertIndex(array $index): Index|false if ($matches['type'] === 'UNIQUE') { $type = Index::TYPE_UNIQUE; - } else if ($matches['type'] === 'FULLTEXT') { + } elseif ($matches['type'] === 'FULLTEXT') { $type = Index::TYPE_FULLTEXT; } else { $type = Index::TYPE_KEY; @@ -305,10 +304,10 @@ public function convertIndex(array $index): Index|false /** * Export Databases - * + * * @param int $batchSize Max 100 * @param callable $callback Callback function to be called after each database, $callback(database[] $batch); - * + * * @return void */ public function exportDatabases(int $batchSize, callable $callback): void @@ -346,10 +345,10 @@ public function exportDatabases(int $batchSize, callable $callback): void /** * Export Documents - * + * * @param int $batchSize Max 100 * @param callable $callback Callback function to be called after each batch, $callback(document[] $batch); - * + * * @return void */ public function exportDocuments(int $batchSize, callable $callback): void diff --git a/src/Transfer/Sources/Supabase.php b/src/Transfer/Sources/Supabase.php index 60de709..a8350eb 100644 --- a/src/Transfer/Sources/Supabase.php +++ b/src/Transfer/Sources/Supabase.php @@ -8,18 +8,18 @@ class Supabase extends NHost { - function getName(): string + public function getName(): string { return 'Supabase'; } /** * Export Users - * + * * @param int $batchSize Max 500 * @param callable $callback Callback function to be called after each batch, $callback(user[] $batch); - * - * @return User[] + * + * @return User[] */ public function exportUsers(int $batchSize, callable $callback): void { @@ -61,23 +61,20 @@ public function exportUsers(int $batchSize, callable $callback): void private function calculateAuthTypes(array $user): array { - if (empty($user['encrypted_password']) && empty($user['phone'])) - { + if (empty($user['encrypted_password']) && empty($user['phone'])) { return [User::TYPE_ANONYMOUS]; } $types = []; - if (!empty($user['encrypted_password'])) - { + if (!empty($user['encrypted_password'])) { $types[] = User::TYPE_EMAIL; } - if (!empty($user['phone'])) - { + if (!empty($user['phone'])) { $types[] = User::TYPE_PHONE; } return $types; } -} \ No newline at end of file +} diff --git a/src/Transfer/Transfer.php b/src/Transfer/Transfer.php index dc9ea73..b03ab58 100644 --- a/src/Transfer/Transfer.php +++ b/src/Transfer/Transfer.php @@ -7,19 +7,19 @@ class Transfer { - const RESOURCE_USERS = 'Users'; - const RESOURCE_FILES = 'Files'; - const RESOURCE_FUNCTIONS = 'Functions'; - const RESOURCE_DATABASES = 'Databases'; - const RESOURCE_DOCUMENTS = 'Documents'; + public const RESOURCE_USERS = 'Users'; + public const RESOURCE_FILES = 'Files'; + public const RESOURCE_FUNCTIONS = 'Functions'; + public const RESOURCE_DATABASES = 'Databases'; + public const RESOURCE_DOCUMENTS = 'Documents'; /** * @param Source $source * @param Destination $destination - * + * * @return Transfer */ - function __construct(Source $source, Destination $destination) + public function __construct(Source $source, Destination $destination) { $this->source = $source; $this->destination = $destination; @@ -49,7 +49,7 @@ function __construct(Source $source, Destination $destination) /** * Counters - * + * * @var array $counter */ protected $counters = [ @@ -87,7 +87,7 @@ function __construct(Source $source, Destination $destination) /** * A local cache of resources that were transferred. - * + * * @var array */ protected array $resources = [ @@ -126,7 +126,7 @@ function __construct(Source $source, Destination $destination) /** * Transfer Resources between adapters - * + * * @param array $resources * @param callable $callback (Progress $progress) */ @@ -141,11 +141,11 @@ public function run(array $resources, callable $callback): void /** * Get Logs - * + * * If no level is provided then the function returns all logs combined ordered by timestamp. - * + * * @param string $level - * + * * @return array */ public function getLogs($level = ''): array @@ -167,7 +167,7 @@ public function getLogs($level = ''): array /** * Get Resource Cache - * + * * @return array */ public function getResourceCache(): array @@ -177,7 +177,7 @@ public function getResourceCache(): array /** * Get Current Resource - * + * * @return string **/ diff --git a/tests/Transfer/Sources/AppwriteSourceTest.php b/tests/Transfer/Sources/AppwriteSourceTest.php index f2a357c..f5402fb 100644 --- a/tests/Transfer/Sources/AppwriteSourceTest.php +++ b/tests/Transfer/Sources/AppwriteSourceTest.php @@ -30,7 +30,7 @@ class AppwriteSourceTest extends TestCase * @var array */ public $serviceAccount; - + public function setUp(): void { $this->appwrite = new Appwrite( @@ -46,14 +46,15 @@ public function testGetUsers(): void $result = []; $this->appwrite->exportUsers( - 100, function (array $users) use (&$result) { + 100, + public function (array $users) use (&$result) { $result = array_merge($result, $users); } ); foreach ($result as $user) { /** - * @var User $user + * @var User $user */ $this->assertIsObject($user); } @@ -67,17 +68,18 @@ public function testGetDatabases(): void $result = []; $this->appwrite->exportDatabases( - 100, function (array $databases) use (&$result) { + 100, + public function (array $databases) use (&$result) { $result = array_merge($result, $databases); } ); foreach ($result as $database) { /** - * @var Database $database + * @var Database $database */ $this->assertIsObject($database); $this->assertNotEmpty($database->getCollections()); } } -} \ No newline at end of file +} diff --git a/tests/Transfer/Sources/FirebaseTest.php b/tests/Transfer/Sources/FirebaseTest.php index ad1f73d..40251d1 100644 --- a/tests/Transfer/Sources/FirebaseTest.php +++ b/tests/Transfer/Sources/FirebaseTest.php @@ -30,7 +30,7 @@ class FirebaseTest extends TestCase * @var array */ public $serviceAccount; - + public function setUp(): void { $this->serviceAccount = json_decode(getEnv("FIREBASE_TEST_ACCOUNT"), true); @@ -58,11 +58,11 @@ public function testSetProject(): void */ $testProject = null; - foreach($projects as $project) { + foreach ($projects as $project) { /** - * @var Project $project + * @var Project $project */ - if($project->getId() == $this->serviceAccount['project_id']) { + if ($project->getId() == $this->serviceAccount['project_id']) { $testProject = $project; break; } @@ -85,14 +85,15 @@ public function testGetUsers(): void $result = []; $this->firebase->exportUsers( - 500, function (array $users) use (&$result) { + 500, + public function (array $users) use (&$result) { $result = array_merge($result, $users); } ); foreach ($result as $user) { /** - * @var User $user + * @var User $user */ $this->assertIsObject($user); $this->assertNotEmpty($user->getPasswordHash()); @@ -102,4 +103,4 @@ public function testGetUsers(): void $this->assertIsArray($result); $this->assertNotEmpty($result); } -} \ No newline at end of file +} diff --git a/tests/Transfer/Sources/NHostTest.php b/tests/Transfer/Sources/NHostTest.php index 91e0cfd..69f2c5b 100644 --- a/tests/Transfer/Sources/NHostTest.php +++ b/tests/Transfer/Sources/NHostTest.php @@ -42,14 +42,15 @@ public function testGetUsers(): array $result = []; $this->nhost->exportUsers( - 500, function (array $users) use (&$result) { + 500, + public function (array $users) use (&$result) { $result = array_merge($result, $users); } ); foreach ($result as $user) { /** - * @var User $user + * @var User $user */ $this->assertIsObject($user); $this->assertNotEmpty($user->getPasswordHash()); @@ -71,7 +72,7 @@ public function testVerifyUsers(array $users): void foreach ($users as $user) { /** - * @var User $user + * @var User $user */ if (in_array(User::TYPE_ANONYMOUS, $user->getTypes())) { continue; @@ -79,7 +80,7 @@ public function testVerifyUsers(array $users): void try { $assertedUsers++; - + $this->assertNotEmpty($user); } catch (\Exception $e) { throw $e; @@ -88,4 +89,4 @@ public function testVerifyUsers(array $users): void $this->assertGreaterThan(1, $assertedUsers); } -} \ No newline at end of file +} diff --git a/tests/Transfer/Sources/SupabaseTest.php b/tests/Transfer/Sources/SupabaseTest.php index 4b1d612..d945126 100644 --- a/tests/Transfer/Sources/SupabaseTest.php +++ b/tests/Transfer/Sources/SupabaseTest.php @@ -42,14 +42,15 @@ public function testGetUsers(): array $result = []; $this->supabase->exportUsers( - 500, function (array $users) use (&$result) { + 500, + public function (array $users) use (&$result) { $result = array_merge($result, $users); } ); foreach ($result as $user) { /** - * @var User $user + * @var User $user */ $this->assertIsObject($user); $this->assertNotEmpty($user->getPasswordHash()); @@ -71,7 +72,7 @@ public function testVerifyUsers(array $users): void foreach ($users as $user) { /** - * @var User $user + * @var User $user */ if (in_array(User::TYPE_ANONYMOUS, $user->getTypes())) { continue; @@ -80,7 +81,7 @@ public function testVerifyUsers(array $users): void try { $userFound = $this->supabase->pdo->query('SELECT * FROM auth.users WHERE id = \'' . $user->getId() . '\'')->fetch(); $assertedUsers++; - + $this->assertNotEmpty($userFound); $this->assertEquals($user->getId(), $userFound['id']); $this->assertEquals($user->getEmail(), $userFound['email']); @@ -95,4 +96,4 @@ public function testVerifyUsers(array $users): void $this->assertGreaterThan(1, $assertedUsers); } -} \ No newline at end of file +} diff --git a/tests/Transfer/Transfers/FirebaseToAppwriteTest.php b/tests/Transfer/Transfers/FirebaseToAppwriteTest.php index 7302a8b..6ee97e5 100644 --- a/tests/Transfer/Transfers/FirebaseToAppwriteTest.php +++ b/tests/Transfer/Transfers/FirebaseToAppwriteTest.php @@ -74,7 +74,8 @@ public function setUp(): void public function testTransferUsers(): void { $this->transfer->run( - [Transfer::RESOURCE_USERS], function () { + [Transfer::RESOURCE_USERS], + public function () { } ); @@ -92,10 +93,11 @@ public function testVerifyUsers(): void $assertedUsers = false; $this->firebase->exportUsers( - 500, function (array $users) use ($userClient, &$assertedUsers) { + 500, + public function (array $users) use ($userClient, &$assertedUsers) { foreach ($users as $user) { /** - * @var User $user + * @var User $user */ if (in_array(User::TYPE_ANONYMOUS, $user->getTypes())) { continue; diff --git a/tests/Transfer/Transfers/SupabaseToAppwriteTest.php b/tests/Transfer/Transfers/SupabaseToAppwriteTest.php index fc50154..d93a075 100644 --- a/tests/Transfer/Transfers/SupabaseToAppwriteTest.php +++ b/tests/Transfer/Transfers/SupabaseToAppwriteTest.php @@ -45,7 +45,7 @@ class SupabaseToAppwriteTest extends TestCase */ public $transfer; - function setUp(): void + public function setUp(): void { $this->supabase = new Supabase( getEnv("SUPABASE_TEST_HOST"), @@ -72,7 +72,8 @@ function setUp(): void public function testTransferUsers(): void { $this->transfer->run( - [Transfer::RESOURCE_USERS], function () { + [Transfer::RESOURCE_USERS], + public function () { } ); @@ -91,10 +92,11 @@ public function testVerifyUsers(): void $assertedUsers = false; $this->supabase->exportUsers( - 500, function (array $users) use ($userClient, &$assertedUsers) { + 500, + public function (array $users) use ($userClient, &$assertedUsers) { foreach ($users as $user) { /** - * @var User $user + * @var User $user */ if (in_array(User::TYPE_ANONYMOUS, $user->getTypes())) { continue; From 579e02466432d88ba92d70e86553d3e4cc5af867 Mon Sep 17 00:00:00 2001 From: Bradley Schofield Date: Mon, 3 Apr 2023 18:31:45 +0900 Subject: [PATCH 29/70] Fix Playground Imports --- playground.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/playground.php b/playground.php index b219673..67e310c 100644 --- a/playground.php +++ b/playground.php @@ -6,7 +6,7 @@ * A place to test and debug the Transfer Library stuff */ -require_once __DIR__ . '/../../vendor/autoload.php'; +require_once __DIR__ . '/vendor/autoload.php'; use Utopia\Transfer\Transfer; use Utopia\Transfer\Sources\Appwrite; From e8d601abc3206a0c55c44c8494a22418cba920ec Mon Sep 17 00:00:00 2001 From: Bradley Schofield Date: Mon, 17 Apr 2023 18:47:41 +0900 Subject: [PATCH 30/70] Implement File Transfers for Appwrite --- .gitignore | 3 +- .vscode/launch.json | 48 +++++++ composer.json | 3 +- playground.php | 7 +- src/Transfer/Destination.php | 3 +- src/Transfer/Destinations/Appwrite.php | 150 +++++++++++++++++++++- src/Transfer/Destinations/Local.php | 56 ++++++++- src/Transfer/Resources/Bucket.php | 164 ++++++++++++++++++++++++ src/Transfer/Resources/File.php | 116 +++++++++++++++++ src/Transfer/Resources/FileData.php | 56 +++++++++ src/Transfer/Source.php | 9 +- src/Transfer/Sources/Appwrite.php | 166 ++++++++++++++++++++++++- 12 files changed, 765 insertions(+), 16 deletions(-) create mode 100644 .vscode/launch.json create mode 100644 src/Transfer/Resources/Bucket.php create mode 100644 src/Transfer/Resources/File.php create mode 100644 src/Transfer/Resources/FileData.php diff --git a/.gitignore b/.gitignore index 0fdb91f..69da22e 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,5 @@ composer.lock *.cache .env test-service-account.json -.DS_Store \ No newline at end of file +.DS_Store +localBackup/ \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..9dc6b4d --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,48 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "Listen for Xdebug", + "type": "php", + "request": "launch", + "port": 9003 + }, + { + "name": "Launch currently open script", + "type": "php", + "request": "launch", + "program": "${file}", + "cwd": "${fileDirname}", + "port": 0, + "runtimeArgs": [ + "-dxdebug.start_with_request=yes" + ], + "env": { + "XDEBUG_MODE": "debug,develop", + "XDEBUG_CONFIG": "client_port=${port}" + } + }, + { + "name": "Launch Built-in web server", + "type": "php", + "request": "launch", + "runtimeArgs": [ + "-dxdebug.mode=debug", + "-dxdebug.start_with_request=yes", + "-S", + "localhost:0" + ], + "program": "", + "cwd": "${workspaceRoot}", + "port": 9003, + "serverReadyAction": { + "pattern": "Development Server \\(http://localhost:([0-9]+)\\) started", + "uriFormat": "http://localhost:%s", + "action": "openExternally" + } + } + ] +} \ No newline at end of file diff --git a/composer.json b/composer.json index 59c5e6f..4b02503 100644 --- a/composer.json +++ b/composer.json @@ -22,7 +22,8 @@ "require": { "php": ">=8.0", "utopia-php/cli": "^0.13.0", - "appwrite/appwrite": "^7.2" + "appwrite/appwrite": "^8.0", + "google/apiclient": "^2.12.1" }, "require-dev": { "phpunit/phpunit": "^9.3", diff --git a/playground.php b/playground.php index 67e310c..0ae952c 100644 --- a/playground.php +++ b/playground.php @@ -58,7 +58,7 @@ $_ENV['DESTINATION_APPWRITE_TEST_KEY'] ); -$destinationLocal = new Local(__DIR__ . '/databaseDMP.json'); +$destinationLocal = new Local(__DIR__ . '/localBackup/'); /** * Initialise Transfer Class @@ -67,7 +67,7 @@ $sourceFirebase->setProject($sourceFirebase->getProjects()[0]); $transfer = new Transfer( - $sourceSupabase, + $sourceAppwrite, $destinationAppwrite ); @@ -76,8 +76,7 @@ */ $transfer->run( [ - Transfer::RESOURCE_DATABASES, - Transfer::RESOURCE_DOCUMENTS, + Transfer::RESOURCE_DATABASES ], function () { } ); diff --git a/src/Transfer/Destination.php b/src/Transfer/Destination.php index 9ca45de..3c97ff9 100644 --- a/src/Transfer/Destination.php +++ b/src/Transfer/Destination.php @@ -3,6 +3,7 @@ namespace Utopia\Transfer; use Exception; +use Utopia\Transfer\Resources\File; abstract class Destination { @@ -331,7 +332,7 @@ protected function importDocuments(array $documents, callable $callback): void /** * Import Files * - * @param array $files + * @param array $resource file[]|bucket[]|FileData[] * @param callable $callback (Progress $progress) */ protected function importFiles(array $files, callable $callback): void diff --git a/src/Transfer/Destinations/Appwrite.php b/src/Transfer/Destinations/Appwrite.php index e65a344..3753a97 100644 --- a/src/Transfer/Destinations/Appwrite.php +++ b/src/Transfer/Destinations/Appwrite.php @@ -6,6 +6,7 @@ use Appwrite\Client; use Appwrite\Services\Users; use Appwrite\Services\Databases; +use Appwrite\Services\Storage; use Utopia\Transfer\Destination; use Utopia\Transfer\Resources\Hash; use Utopia\Transfer\Log; @@ -25,6 +26,8 @@ use Utopia\Transfer\Resources\Attributes\IPAttribute; use Utopia\Transfer\Resources\Attributes\StringAttribute; use Utopia\Transfer\Resources\Attributes\URLAttribute; +use Utopia\Transfer\Resources\Bucket; +use Utopia\Transfer\Resources\FileData; use Utopia\Transfer\Resources\Index; class Appwrite extends Destination @@ -485,7 +488,7 @@ public function importDatabases(array $databases, callable $callback): void $timeout = 0; while (!$this->validateAttributesCreation($collection->getAttributes(), $collection, $database)) { - if ($timeout > 60) { + if ($timeout > 5) { throw new AppwriteException('Timeout while waiting for attributes to be created'); } @@ -573,4 +576,149 @@ protected function importDocuments(array $documents, callable $callback): void ) ); } + + /** + * Import Bucket + * + * @param Bucket $bucket + * @param callable $callback (Progress $progress) + */ + protected function importBucket(Bucket $bucket, callable $callback): void + { + // $bucketCounters = &$this->getCounter(Transfer::RESOURCE_BUCKETS); + $storageService = new Storage($this->client); + + /** @var Bucket $bucket */ + try { + $newBucket = $storageService->createBucket( + $bucket->getId(), + $bucket->getBucketName(), + $bucket->getPermissions(), + $bucket->getFileSecurity(), + true, // Set to true for now, we'll come back later. + $bucket->getMaxFileSize(), + $bucket->getAllowedFileExtensions(), + $bucket->getCompression(), + $bucket->getEncryption(), + $bucket->getAntiVirus() + ); + $bucket->setId($newBucket['$id']); + + $this->logs[Log::SUCCESS][] = new Log('Bucket imported successfully', \time(), $bucket); + // $bucketCounters['current']++; + } catch (AppwriteException $e) { + $this->logs[Log::ERROR][] = new Log($e->getMessage(), \time(), $bucket); + // $bucketCounters['failed']++; + } + + // $callback( + // new Progress( + // Transfer::RESOURCE_BUCKETS, + // time(), + // $bucketCounters['total'], + // $bucketCounters['current'], + // $bucketCounters['failed'], + // $bucketCounters['skipped'] + // ) + // ); + } + + /** + * Import Files + * + * @param array $resources File[]|Bucket[]|FileData[] + * @param callable $callback (Progress $progress) + */ + protected function importFiles(array $resources, callable $callback): void + { + $fileCounters = &$this->getCounter(Transfer::RESOURCE_FILES); + + foreach ($resources as $resource) { + if ($resource instanceof Bucket) { + $this->importBucket($resource, $callback); + } elseif ($resource instanceof FileData) { + $this->importFileData($resource, $callback); + } + } + + $callback( + new Progress( + Transfer::RESOURCE_FILES, + time(), + $fileCounters['total'], + $fileCounters['current'], + $fileCounters['failed'], + $fileCounters['skipped'] + ) + ); + } + + /** + * Import File Data + * + * @param FileData $filePart + * @param callable $callback (Progress $progress) + */ + protected function importFileData(FileData $filePart, callable $callback): void + { + $fileCounters = &$this->getCounter(Transfer::RESOURCE_FILES); + + try { + $file = $filePart->getFile(); + $bucketId = $file->getBucket()->getId(); + + $response = null; + + if ($file->getSize() <= (5 * 1024 * 1024)) { + $response = $this->client->call( + 'POST', + "/v1/storage/buckets/{$bucketId}/files", + [ + 'content-type' => 'multipart/form-data', + ], + [ + 'bucketId' => $bucketId, + 'fileId' => $file->getId(), + 'file' => new \CurlFile('data://' . $file->getMimeType() . ';base64,' . base64_encode($filePart->getData()), $file->getMimeType(), $file->getFileName()), + 'permissions' => $file->getPermissions(), + ] + ); + + $this->logs[Log::SUCCESS][] = new Log('File imported successfully', \time(), $file); + $fileCounters['current']++; + + return; + } + + $response = $this->client->call( + 'POST', + "/v1/storage/buckets/{$bucketId}/files", + [ + 'content-type' => 'multipart/form-data', + 'content-range' => 'bytes ' . ($filePart->getStart()) . '-' . ($filePart->getEnd() == ($file->getSize() - 1) ? $file->getSize() : $filePart->getEnd()) . '/' . $file->getSize(), + ], + [ + 'bucketId' => $bucketId, + 'fileId' => $file->getId(), + 'file' => new \CurlFile('data://' . $file->getMimeType() . ';base64,' . base64_encode($filePart->getData()), $file->getMimeType(), $file->getFileName()), + 'permissions' => $file->getPermissions(), + ] + ); + + if ($filePart->getEnd() == ($file->getSize() - 1)) { + // Signatures for Encrypted files are invalid, so we skip the check + if ($file->getBucket()->getEncryption() == false || $file->getSize() > (20 * 1024 * 1024)) { + if ($response['signature'] !== $file->getSignature()) { + $this->logs[Log::WARNING][] = new Log('File signature mismatch, Possibly corrupted.', \time(), $file); + } + } + + $this->logs[Log::SUCCESS][] = new Log('File imported successfully', \time(), $file); + $fileCounters['current']++; + } + } catch (AppwriteException $e) { + $this->logs[Log::ERROR][] = new Log($e->getMessage(), \time(), $file); + $fileCounters['failed']++; + } + } } diff --git a/src/Transfer/Destinations/Local.php b/src/Transfer/Destinations/Local.php index 1c8dc40..0fc6bdf 100644 --- a/src/Transfer/Destinations/Local.php +++ b/src/Transfer/Destinations/Local.php @@ -9,7 +9,10 @@ use Utopia\Transfer\Log; use Utopia\Transfer\Progress; use Utopia\Transfer\Resources\Database; +use Utopia\Transfer\Resources\File; use Utopia\Transfer\Resources\User; +use Utopia\Transfer\Resources\Bucket; +use Utopia\Transfer\Resources\FileData; use Utopia\Transfer\Transfer; /** @@ -27,6 +30,11 @@ class Local extends Destination public function __construct(string $path) { $this->path = $path; + + if (!\file_exists($this->path)) { + mkdir($this->path, 0777, true); + mkdir($this->path . '/files', 0777, true); + } } /** @@ -76,7 +84,7 @@ public function check(array $resources = []): array } // Check we can write to the file - if (!\is_writable($this->path)) { + if (!\is_writable($this->path . '/backup.json')) { $report['Databases'][] = 'Unable to write to file: ' . $this->path; throw new \Exception('Unable to write to file: ' . $this->path); } @@ -86,7 +94,7 @@ public function check(array $resources = []): array public function syncFile(): void { - \file_put_contents($this->path, \json_encode($this->data, JSON_PRETTY_PRINT)); + \file_put_contents($this->path . '/backup.json', \json_encode($this->data, JSON_PRETTY_PRINT)); } /** @@ -187,4 +195,48 @@ public function importDocuments(array $documents, callable $callback): void $this->syncFile(); } + + /** + * Import Files + * + * @param array $resource file[]|bucket[] + * @param callable $callback (Progress $progress) + */ + protected function importFiles(array $resources, callable $callback): void + { + $fileCounters = &$this->getCounter(Transfer::RESOURCE_FILES); + //TODO: Improve counters with a custom class, currently files and buckets share the same counter. + + foreach ($resources as $resource) { + if ($resource instanceof File) { + $this->data[Transfer::RESOURCE_FILES][] = $resource->asArray(); + $this->logs[Log::SUCCESS][] = new Log('File imported successfully', \time(), $resource); + + if (\file_exists($this->path . '/files/' . $resource->getFileName())) { + \unlink($this->path . '/files/' . $resource->getFileName()); + } + + $fileCounters['current']++; + } elseif ($resource instanceof Bucket) { + $this->data[Transfer::RESOURCE_FILES][] = $resource->asArray(); + $this->logs[Log::SUCCESS][] = new Log('Bucket imported successfully', \time(), $resource); + $fileCounters['current']++; + } elseif ($resource instanceof FileData) { + file_put_contents($this->path . '/files/' . $resource->getFile()->getFileName(), $resource->getData(), FILE_APPEND); + } + } + + $callback( + new Progress( + Transfer::RESOURCE_FILES, + time(), + $fileCounters['total'], + $fileCounters['current'], + $fileCounters['failed'], + $fileCounters['skipped'] + ) + ); + + $this->syncFile(); + } } diff --git a/src/Transfer/Resources/Bucket.php b/src/Transfer/Resources/Bucket.php new file mode 100644 index 0000000..926ef2e --- /dev/null +++ b/src/Transfer/Resources/Bucket.php @@ -0,0 +1,164 @@ +id = $id; + $this->permissions = $permissions; + $this->fileSecurity = $fileSecurity; + $this->name = $name; + $this->enabled = $enabled; + $this->maxFileSize = $maxFileSize; + $this->allowedFileExtensions = $allowedFileExtensions; + $this->compression = $compression; + $this->encryption = $encryption; + $this->antiVirus = $antiVirus; + } + + public function getName(): string + { + return 'bucket'; + } + + public function getId(): string + { + return $this->id; + } + + public function setId(string $id): self + { + $this->id = $id; + return $this; + } + + public function getPermissions(): array + { + return $this->permissions; + } + + public function setPermissions(array $permissions): self + { + $this->permissions = $permissions; + return $this; + } + + public function getFileSecurity(): bool + { + return $this->fileSecurity; + } + + public function setFileSecurity(bool $fileSecurity): self + { + $this->fileSecurity = $fileSecurity; + return $this; + } + + public function getBucketName(): string + { + return $this->name; + } + + public function setBucketName(string $name): self + { + $this->name = $name; + return $this; + } + + public function getEnabled(): bool + { + return $this->enabled; + } + + public function setEnabled(bool $enabled): self + { + $this->enabled = $enabled; + return $this; + } + + public function getMaxFileSize(): int + { + return $this->maxFileSize; + } + + public function setMaxFileSize(int $maxFileSize): self + { + $this->maxFileSize = $maxFileSize; + return $this; + } + + public function getAllowedFileExtensions(): array + { + return $this->allowedFileExtensions; + } + + public function setAllowedFileExtensions(array $allowedFileExtensions): self + { + $this->allowedFileExtensions = $allowedFileExtensions; + return $this; + } + + public function getCompression(): string + { + return $this->compression; + } + + public function setCompression(string $compression): self + { + $this->compression = $compression; + return $this; + } + + public function getEncryption(): bool + { + return $this->encryption; + } + + public function setEncryption(bool $encryption): self + { + $this->encryption = $encryption; + return $this; + } + + public function getAntiVirus(): bool + { + return $this->antiVirus; + } + + public function setAntiVirus(bool $antiVirus): self + { + $this->antiVirus = $antiVirus; + return $this; + } + + public function asArray(): array + { + return [ + 'id' => $this->id, + 'permissions' => $this->permissions, + 'fileSecurity' => $this->fileSecurity, + 'name' => $this->name, + 'enabled' => $this->enabled, + 'maxFileSize' => $this->maxFileSize, + 'allowedFileExtensions' => $this->allowedFileExtensions, + 'compression' => $this->compression, + 'encryption' => $this->encryption, + 'antiVirus' => $this->antiVirus, + ]; + } +} diff --git a/src/Transfer/Resources/File.php b/src/Transfer/Resources/File.php new file mode 100644 index 0000000..24f5c22 --- /dev/null +++ b/src/Transfer/Resources/File.php @@ -0,0 +1,116 @@ +id = $id; + $this->bucket = $bucket; + $this->name = $name; + $this->signature = $signature; + $this->mimeType = $mimeType; + $this->permissions = $permissions; + $this->size = $size; + } + + public function getName(): string + { + return 'file'; + } + + public function getId(): string + { + return $this->id; + } + + public function setId(string $id): self + { + $this->id = $id; + return $this; + } + + public function getBucket(): Bucket + { + return $this->bucket; + } + + public function setBucket(Bucket $bucket): self + { + $this->bucket = $bucket; + return $this; + } + + public function getFileName(): string + { + return $this->name; + } + + public function setFileName(string $name): self + { + $this->name = $name; + return $this; + } + + public function getSignature(): string + { + return $this->signature; + } + + public function setSignature(string $signature): self + { + $this->signature = $signature; + return $this; + } + + public function getMimeType(): string + { + return $this->mimeType; + } + + public function setMimeType(string $mimeType): self + { + $this->mimeType = $mimeType; + return $this; + } + + public function getPermissions(): array + { + return $this->permissions; + } + + public function getSize(): int + { + return $this->size; + } + + public function setSize(int $size): self + { + $this->size = $size; + return $this; + } + + public function asArray(): array + { + return [ + 'id' => $this->id, + 'bucket' => $this->bucket->asArray(), + 'name' => $this->name, + 'signature' => $this->signature, + 'mimeType' => $this->mimeType, + 'size' => $this->size, + ]; + } +} diff --git a/src/Transfer/Resources/FileData.php b/src/Transfer/Resources/FileData.php new file mode 100644 index 0000000..13d1cdb --- /dev/null +++ b/src/Transfer/Resources/FileData.php @@ -0,0 +1,56 @@ +data = $data; + $this->start = $start; + $this->end = $end; + $this->file = $file; + } + + public function getName(): string + { + return 'FileData'; + } + + public function getData(): string + { + return $this->data; + } + + public function getStart(): int + { + return $this->start; + } + + public function getEnd(): int + { + return $this->end; + } + + public function getFile(): File + { + return $this->file; + } + + public function asArray(): array + { + return [ + 'data' => $this->data, + 'start' => $this->start, + 'end' => $this->end, + 'file' => $this->file->asArray(), + ]; + } +} diff --git a/src/Transfer/Source.php b/src/Transfer/Source.php index 074b228..ecec4ec 100644 --- a/src/Transfer/Source.php +++ b/src/Transfer/Source.php @@ -3,6 +3,7 @@ namespace Utopia\Transfer; use Exception; +use Utopia\Transfer\Resources\FileData; abstract class Source { @@ -144,7 +145,9 @@ public function run(array $resources, callable $callback): void break; case Transfer::RESOURCE_FILES: $this->exportFiles(5, function (array $files) use ($callback) { - $this->resourceCache[Transfer::RESOURCE_FILES] = array_merge($this->resourceCache[Transfer::RESOURCE_FILES], $files); + if (!$files[0] instanceof FileData) { + $this->resourceCache[Transfer::RESOURCE_FILES] = array_merge($this->resourceCache[Transfer::RESOURCE_FILES], $files); + } $callback(Transfer::RESOURCE_FILES, $files); }); break; @@ -329,8 +332,8 @@ public function exportDocuments(int $batchSize, callable $callback): void /** * Export Files * - * @param int $batchSize Max 100 - * @param callable $callback Callback function to be called after each file, $callback(file[] $batch); + * @param int $batchSize Max 5 + * @param callable $callback Callback function to be called after each batch, $callback(File[]|Bucket[] $batch); * * @return void */ diff --git a/src/Transfer/Sources/Appwrite.php b/src/Transfer/Sources/Appwrite.php index 1739784..970d236 100644 --- a/src/Transfer/Sources/Appwrite.php +++ b/src/Transfer/Sources/Appwrite.php @@ -6,13 +6,12 @@ use Appwrite\Client; use Appwrite\Query; use Appwrite\Services\Databases; +use Appwrite\Services\Storage; use Appwrite\Services\Users; +use Utopia\Transfer\Log; use Utopia\Transfer\Resources\Attribute; -use Utopia\Transfer\Resources\Project; use Utopia\Transfer\Resources\User; use Utopia\Transfer\Transfer; -use Utopia\Transfer\Log; -use Utopia\Transfer\Resource; use Utopia\Transfer\Resources\Attributes\BoolAttribute; use Utopia\Transfer\Resources\Attributes\DateTimeAttribute; use Utopia\Transfer\Resources\Attributes\EmailAttribute; @@ -27,6 +26,9 @@ use Utopia\Transfer\Resources\Document; use Utopia\Transfer\Resources\Hash; use Utopia\Transfer\Resources\Index; +use Utopia\Transfer\Resources\Bucket; +use Utopia\Transfer\Resources\File; +use Utopia\Transfer\Resources\FileData; class Appwrite extends Source { @@ -35,6 +37,16 @@ class Appwrite extends Source */ protected $client = null; + /** + * @var string + */ + protected string $project = ''; + + /** + * @var string + */ + protected string $key = ''; + /** * Constructor * @@ -50,6 +62,10 @@ public function __construct(string $project, string $endpoint, string $key) ->setEndpoint($endpoint) ->setProject($project) ->setKey($key); + + $this->endpoint = $endpoint; + $this->project = $project; + $this->key = $key; } /** @@ -73,6 +89,7 @@ public function getSupportedResources(): array Transfer::RESOURCE_USERS, Transfer::RESOURCE_DATABASES, Transfer::RESOURCE_DOCUMENTS, + Transfer::RESOURCE_FILES, ]; } @@ -535,4 +552,147 @@ protected function calculateTypes(array $user): array return $types; } + + /** + * Export Files + * + * @param int $batchSize Max 5 + * @param callable $callback Callback function to be called after each batch, $callback(File[]|Bucket[] $batch); + */ + public function exportFiles(int $batchSize, callable $callback): void + { + $storageClient = new Storage($this->client); + + $buckets = $storageClient->listBuckets(); + + $convertedBuckets = []; + + foreach ($buckets['buckets'] as $bucket) { + $convertedBuckets[] = new Bucket( + $bucket['$id'], + $bucket['$permissions'], + $bucket['fileSecurity'], + $bucket['name'], + $bucket['enabled'], + $bucket['maximumFileSize'], + $bucket['allowedFileExtensions'], + $bucket['compression'], + $bucket['encryption'], + $bucket['antivirus'], + ); + } + + if (empty($convertedBuckets)) { + return; + } + + $callback($convertedBuckets); + + foreach ($convertedBuckets as $bucket) { + $lastDocument = null; + + while (true) { + $queries = [Query::limit($batchSize)]; + + $files = []; + + if ($lastDocument) { + $queries[] = Query::cursorAfter($lastDocument); + } + + $response = $storageClient->listFiles( + $bucket->getId(), + $queries + ); + + foreach ($response["files"] as $file) { + $files[] = new File( + $file['$id'], + $bucket, + $file['name'], + $file['signature'], + $file['mimeType'], + $file['$permissions'], + $file['sizeOriginal'], + ); + $lastDocument = $file['$id']; + } + + foreach ($files as $file) { + $this->streamFile($file, $callback); + } + + if (count($response["files"]) < $batchSize) { + break; + } + } + } + } + + /** + * Stream File + * Streams a file to the destination + * + * @param File $file + * @param callable $callback (array $data) + * + * @return void + */ + protected function streamFile(File $file, callable $callback): void + { + // Set the chunk size (5MB) + $chunkSize = 5 * 1024 * 1024; + $start = 0; + $end = $chunkSize - 1; + + // Get the file size + $fileSize = $file->getSize(); + + // Initialize cURL + $ch = curl_init("{$this->endpoint}/storage/buckets/{$file->getBucket()->getId()}/files/{$file->getId()}/download"); + + // Loop until the entire file is downloaded + while ($start < $fileSize) { + // Set the Range header + $range = "Range: bytes=$start-$end"; + curl_setopt($ch, CURLOPT_HTTPHEADER, [ + $range, + 'X-Appwrite-key: ' . $this->key, + 'X-Appwrite-Project: ' . $this->project, + ]); + + // Set cURL options + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); + + // Download the chunk + $chunkData = curl_exec($ch); + + $status = curl_getinfo($ch, CURLINFO_HTTP_CODE); + + if ($status !== 200 && $status !== 206) { + $this->logs[Log::FATAL][] = new Log('Failed to download file, Error: ' . $chunkData); + throw new \Exception('Failed to download file, Error: ' . $chunkData); + } + + // Send the chunk to the callback function + $callback([new FileData( + $chunkData, + $start, + $end, + $file + )]); + + // Update the range + $start += $chunkSize; + $end += $chunkSize; + + if ($end > $fileSize) { + $end = $fileSize - 1; + } + } + + // Close cURL + curl_close($ch); + } } From 2318e6df49d4ac7d2f76fc03e1a5d6d065a06fa8 Mon Sep 17 00:00:00 2001 From: Bradley Schofield Date: Mon, 17 Apr 2023 20:44:30 +0900 Subject: [PATCH 31/70] Begin implementing Relationships --- .gitignore | 3 +- composer.json | 3 +- playground.php | 7 +- src/Transfer/Destinations/Appwrite.php | 9 +- src/Transfer/Destinations/Local.php | 11 +- src/Transfer/Resources/Attribute.php | 1 + .../Attributes/RelationshipAttribute.php | 102 ++++++++++++++++++ src/Transfer/Sources/Appwrite.php | 13 +++ 8 files changed, 139 insertions(+), 10 deletions(-) create mode 100644 src/Transfer/Resources/Attributes/RelationshipAttribute.php diff --git a/.gitignore b/.gitignore index 0fdb91f..69da22e 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,5 @@ composer.lock *.cache .env test-service-account.json -.DS_Store \ No newline at end of file +.DS_Store +localBackup/ \ No newline at end of file diff --git a/composer.json b/composer.json index 59c5e6f..4b02503 100644 --- a/composer.json +++ b/composer.json @@ -22,7 +22,8 @@ "require": { "php": ">=8.0", "utopia-php/cli": "^0.13.0", - "appwrite/appwrite": "^7.2" + "appwrite/appwrite": "^8.0", + "google/apiclient": "^2.12.1" }, "require-dev": { "phpunit/phpunit": "^9.3", diff --git a/playground.php b/playground.php index 67e310c..0ae952c 100644 --- a/playground.php +++ b/playground.php @@ -58,7 +58,7 @@ $_ENV['DESTINATION_APPWRITE_TEST_KEY'] ); -$destinationLocal = new Local(__DIR__ . '/databaseDMP.json'); +$destinationLocal = new Local(__DIR__ . '/localBackup/'); /** * Initialise Transfer Class @@ -67,7 +67,7 @@ $sourceFirebase->setProject($sourceFirebase->getProjects()[0]); $transfer = new Transfer( - $sourceSupabase, + $sourceAppwrite, $destinationAppwrite ); @@ -76,8 +76,7 @@ */ $transfer->run( [ - Transfer::RESOURCE_DATABASES, - Transfer::RESOURCE_DOCUMENTS, + Transfer::RESOURCE_DATABASES ], function () { } ); diff --git a/src/Transfer/Destinations/Appwrite.php b/src/Transfer/Destinations/Appwrite.php index e65a344..48ab716 100644 --- a/src/Transfer/Destinations/Appwrite.php +++ b/src/Transfer/Destinations/Appwrite.php @@ -25,6 +25,7 @@ use Utopia\Transfer\Resources\Attributes\IPAttribute; use Utopia\Transfer\Resources\Attributes\StringAttribute; use Utopia\Transfer\Resources\Attributes\URLAttribute; +use Utopia\Transfer\Resources\Attributes\RelationshipAttribute; use Utopia\Transfer\Resources\Index; class Appwrite extends Destination @@ -373,6 +374,12 @@ public function createAttribute(Attribute $attribute, Collection $collection, Da /** @var EnumAttribute $attribute */ $databaseService->createEnumAttribute($database->getId(), $collection->getId(), $attribute->getKey(), $attribute->getElements(), $attribute->getRequired(), $attribute->getDefault(), $attribute->getArray()); break; + case Attribute::TYPE_RELATIONSHIP: + /** @var RelationshipAttribute $attribute */ + $databaseService->createRelationshipAttribute($database->getId(), $collection->getId(), $attribute->getRelatedCollection(), $attribute->getRelationType(), $attribute->getTwoWay(), $attribute->getKey(), $attribute->getTwoWayKey(), $attribute->getOnDelete()); + break; + default: + throw new \Exception('Invalid attribute type'); } } catch (\Exception $e) { $this->logs[Log::ERROR][] = new Log($e->getMessage(), \time(), $attribute); @@ -485,7 +492,7 @@ public function importDatabases(array $databases, callable $callback): void $timeout = 0; while (!$this->validateAttributesCreation($collection->getAttributes(), $collection, $database)) { - if ($timeout > 60) { + if ($timeout > 5) { throw new AppwriteException('Timeout while waiting for attributes to be created'); } diff --git a/src/Transfer/Destinations/Local.php b/src/Transfer/Destinations/Local.php index 1c8dc40..353d520 100644 --- a/src/Transfer/Destinations/Local.php +++ b/src/Transfer/Destinations/Local.php @@ -27,6 +27,11 @@ class Local extends Destination public function __construct(string $path) { $this->path = $path; + + if (!\file_exists($this->path)) { + mkdir($this->path, 0777, true); + mkdir($this->path . '/files', 0777, true); + } } /** @@ -76,7 +81,7 @@ public function check(array $resources = []): array } // Check we can write to the file - if (!\is_writable($this->path)) { + if (!\is_writable($this->path . '/backup.json')) { $report['Databases'][] = 'Unable to write to file: ' . $this->path; throw new \Exception('Unable to write to file: ' . $this->path); } @@ -86,7 +91,7 @@ public function check(array $resources = []): array public function syncFile(): void { - \file_put_contents($this->path, \json_encode($this->data, JSON_PRETTY_PRINT)); + \file_put_contents($this->path . '/backup.json', \json_encode($this->data, JSON_PRETTY_PRINT)); } /** @@ -187,4 +192,4 @@ public function importDocuments(array $documents, callable $callback): void $this->syncFile(); } -} +} \ No newline at end of file diff --git a/src/Transfer/Resources/Attribute.php b/src/Transfer/Resources/Attribute.php index c837f81..ed832ab 100644 --- a/src/Transfer/Resources/Attribute.php +++ b/src/Transfer/Resources/Attribute.php @@ -15,6 +15,7 @@ class Attribute extends Resource public const TYPE_ENUM = 'enumAttribute'; public const TYPE_IP = 'IPAttribute'; public const TYPE_URL = 'URLAttribute'; + public const TYPE_RELATIONSHIP = 'relationshipAttribute'; protected string $key; protected bool $required; diff --git a/src/Transfer/Resources/Attributes/RelationshipAttribute.php b/src/Transfer/Resources/Attributes/RelationshipAttribute.php new file mode 100644 index 0000000..dd65147 --- /dev/null +++ b/src/Transfer/Resources/Attributes/RelationshipAttribute.php @@ -0,0 +1,102 @@ +relatedCollection = $relatedCollection; + $this->relationType = $relationType; + $this->twoWay = $twoWay; + $this->twoWayKey = $twoWayKey; + $this->onDelete = $onDelete; + $this->side = $side; + } + + public function getName(): string + { + return 'relationshipAttribute'; + } + + public function getRelatedCollection(): string + { + return $this->relatedCollection; + } + + public function setRelatedCollection(string $relatedCollection): void + { + $this->relatedCollection = $relatedCollection; + } + + public function getRelationType(): string + { + return $this->relationType; + } + + public function setRelationType(string $relationType): void + { + $this->relationType = $relationType; + } + + public function getTwoWay(): bool + { + return $this->twoWay; + } + + public function setTwoWay(bool $twoWay): void + { + $this->twoWay = $twoWay; + } + + public function getTwoWayKey(): string + { + return $this->twoWayKey; + } + + public function setTwoWayKey(string $twoWayKey): void + { + $this->twoWayKey = $twoWayKey; + } + + public function getOnDelete(): string + { + return $this->onDelete; + } + + public function setOnDelete(string $onDelete): void + { + $this->onDelete = $onDelete; + } + + public function getSide(): string + { + return $this->side; + } + + public function setSide(string $side): void + { + $this->side = $side; + } +} diff --git a/src/Transfer/Sources/Appwrite.php b/src/Transfer/Sources/Appwrite.php index 1739784..f2ad74f 100644 --- a/src/Transfer/Sources/Appwrite.php +++ b/src/Transfer/Sources/Appwrite.php @@ -22,6 +22,7 @@ use Utopia\Transfer\Resources\Attributes\IPAttribute; use Utopia\Transfer\Resources\Attributes\StringAttribute; use Utopia\Transfer\Resources\Attributes\URLAttribute; +use Utopia\Transfer\Resources\Attributes\RelationshipAttribute; use Utopia\Transfer\Resources\Collection; use Utopia\Transfer\Resources\Database; use Utopia\Transfer\Resources\Document; @@ -357,6 +358,18 @@ public function convertAttribute(array $value): Attribute $value["min"] ?? 0, $value["max"] ?? 0 ); + case "relationship": + return new RelationshipAttribute( + $value["key"], + $value["required"], + $value["array"], + $value["relatedCollection"], + $value["relationType"], + $value["twoWay"], + $value["twoWayKey"], + $value["onDelete"], + $value["side"] + ); } throw new \Exception("Unknown attribute type: " . $value["type"]); From 2373dc3160d67b788c9486df82f3ff34612510a3 Mon Sep 17 00:00:00 2001 From: Bradley Schofield Date: Wed, 26 Apr 2023 12:09:35 +0900 Subject: [PATCH 32/70] Delete .vscode directory --- .vscode/launch.json | 48 --------------------------------------------- 1 file changed, 48 deletions(-) delete mode 100644 .vscode/launch.json diff --git a/.vscode/launch.json b/.vscode/launch.json deleted file mode 100644 index 9dc6b4d..0000000 --- a/.vscode/launch.json +++ /dev/null @@ -1,48 +0,0 @@ -{ - // Use IntelliSense to learn about possible attributes. - // Hover to view descriptions of existing attributes. - // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 - "version": "0.2.0", - "configurations": [ - { - "name": "Listen for Xdebug", - "type": "php", - "request": "launch", - "port": 9003 - }, - { - "name": "Launch currently open script", - "type": "php", - "request": "launch", - "program": "${file}", - "cwd": "${fileDirname}", - "port": 0, - "runtimeArgs": [ - "-dxdebug.start_with_request=yes" - ], - "env": { - "XDEBUG_MODE": "debug,develop", - "XDEBUG_CONFIG": "client_port=${port}" - } - }, - { - "name": "Launch Built-in web server", - "type": "php", - "request": "launch", - "runtimeArgs": [ - "-dxdebug.mode=debug", - "-dxdebug.start_with_request=yes", - "-S", - "localhost:0" - ], - "program": "", - "cwd": "${workspaceRoot}", - "port": 9003, - "serverReadyAction": { - "pattern": "Development Server \\(http://localhost:([0-9]+)\\) started", - "uriFormat": "http://localhost:%s", - "action": "openExternally" - } - } - ] -} \ No newline at end of file From 1745d1eedafec85a02d0f63726b51fc6ee1a986a Mon Sep 17 00:00:00 2001 From: Bradley Schofield Date: Wed, 26 Apr 2023 14:26:25 +0900 Subject: [PATCH 33/70] Continue Refactor work --- .env.example | 12 +- .gitignore | 3 +- README.md | 26 +- composer.json | 3 +- playground.php | 35 +- src/Transfer/Destination.php | 299 +----- src/Transfer/Destinations/Appwrite.php | 882 ++++++++---------- src/Transfer/Destinations/Local.php | 195 +--- src/Transfer/Log.php | 91 -- src/Transfer/Resource.php | 93 +- src/Transfer/ResourceCache.php | 83 ++ src/Transfer/Resources/{ => Auth}/Hash.php | 10 +- src/Transfer/Resources/Auth/Team.php | 89 ++ .../Resources/Auth/TeamMembership.php | 85 ++ src/Transfer/Resources/{ => Auth}/User.php | 14 +- .../Resources/{ => Database}/Attribute.php | 29 +- .../Attributes/BoolAttribute.php | 12 +- .../Attributes/DateTimeAttribute.php | 12 +- .../Attributes/EmailAttribute.php | 12 +- .../Attributes/EnumAttribute.php | 12 +- .../Attributes/FloatAttribute.php | 12 +- .../{ => Database}/Attributes/IPAttribute.php | 12 +- .../Attributes/IntAttribute.php | 12 +- .../Attributes/RelationshipAttribute.php | 12 +- .../Attributes/StringAttribute.php | 12 +- .../Attributes/URLAttribute.php | 12 +- .../Resources/{ => Database}/Collection.php | 62 +- .../Resources/{ => Database}/Database.php | 10 +- .../Resources/{ => Database}/Document.php | 10 +- .../Resources/{ => Database}/Index.php | 26 +- src/Transfer/Resources/Functions/EnvVar.php | 72 ++ src/Transfer/Resources/Functions/Func.php | 136 +++ src/Transfer/Resources/Project.php | 8 +- .../Resources/{ => Storage}/Bucket.php | 10 +- src/Transfer/Resources/{ => Storage}/File.php | 11 +- .../Resources/{ => Storage}/FileData.php | 10 +- src/Transfer/Source.php | 325 +------ src/Transfer/Sources/Appwrite.php | 420 ++++++--- src/Transfer/Sources/Firebase.php | 58 +- src/Transfer/Sources/FirebaseG2.php | 190 ++++ src/Transfer/Sources/NHost.php | 175 ++-- src/Transfer/Sources/Supabase.php | 151 ++- src/Transfer/Target.php | 196 ++++ src/Transfer/Transfer.php | 114 +-- tests/Transfer/Destinations/AppwriteTest.php | 4 +- tests/Transfer/ProviderTest.phpignore | 12 +- tests/Transfer/Sources/AppwriteSourceTest.php | 2 +- tests/Transfer/Sources/FirebaseTest.php | 2 +- tests/Transfer/Sources/NHostTest.php | 2 +- tests/Transfer/Sources/SupabaseTest.php | 2 +- .../Transfers/FirebaseToAppwriteTest.php | 4 +- .../Transfers/SupabaseToAppwriteTest.php | 4 +- 52 files changed, 2321 insertions(+), 1764 deletions(-) delete mode 100644 src/Transfer/Log.php create mode 100644 src/Transfer/ResourceCache.php rename src/Transfer/Resources/{ => Auth}/Hash.php (96%) create mode 100644 src/Transfer/Resources/Auth/Team.php create mode 100644 src/Transfer/Resources/Auth/TeamMembership.php rename src/Transfer/Resources/{ => Auth}/User.php (95%) rename src/Transfer/Resources/{ => Database}/Attribute.php (70%) rename src/Transfer/Resources/{ => Database}/Attributes/BoolAttribute.php (61%) rename src/Transfer/Resources/{ => Database}/Attributes/DateTimeAttribute.php (53%) rename src/Transfer/Resources/{ => Database}/Attributes/EmailAttribute.php (52%) rename src/Transfer/Resources/{ => Database}/Attributes/EnumAttribute.php (68%) rename src/Transfer/Resources/{ => Database}/Attributes/FloatAttribute.php (72%) rename src/Transfer/Resources/{ => Database}/Attributes/IPAttribute.php (52%) rename src/Transfer/Resources/{ => Database}/Attributes/IntAttribute.php (72%) rename src/Transfer/Resources/{ => Database}/Attributes/RelationshipAttribute.php (78%) rename src/Transfer/Resources/{ => Database}/Attributes/StringAttribute.php (66%) rename src/Transfer/Resources/{ => Database}/Attributes/URLAttribute.php (52%) rename src/Transfer/Resources/{ => Database}/Collection.php (67%) rename src/Transfer/Resources/{ => Database}/Database.php (90%) rename src/Transfer/Resources/{ => Database}/Document.php (91%) rename src/Transfer/Resources/{ => Database}/Index.php (71%) create mode 100644 src/Transfer/Resources/Functions/EnvVar.php create mode 100644 src/Transfer/Resources/Functions/Func.php rename src/Transfer/Resources/{ => Storage}/Bucket.php (95%) rename src/Transfer/Resources/{ => Storage}/File.php (92%) rename src/Transfer/Resources/{ => Storage}/FileData.php (82%) create mode 100644 src/Transfer/Sources/FirebaseG2.php create mode 100644 src/Transfer/Target.php diff --git a/.env.example b/.env.example index 45bc350..6916e59 100644 --- a/.env.example +++ b/.env.example @@ -7,4 +7,14 @@ SOURCE_APPWRITE_TEST_ENDPOINT=http://localhost/v1 SOURCE_APPWRITE_TEST_KEY=xxxxxxxxxxxxxxxxxx FIREBASE_TEST_PROJECT=testProject -FIREBASE_TEST_ACCOUNT='{type: "service_account", ...}' \ No newline at end of file +FIREBASE_TEST_ACCOUNT='{type: "service_account", ...}' + +SUPABASE_TEST_HOST=db.xxxxxxxxx.supabase.co +SUPABASE_TEST_DATABASE=postgres +SUPABASE_TEST_USERNAME=xxxxxxxxxxxxxxxxxx +SUPABASE_TEST_PASSWORD=xxxxxxxxxxxxxxxxxx + +NHOST_TEST_HOST=db.xxxxxxxxx.nhost.run +NHOST_TEST_DATABASE=xxxxxxxxxxxxxxxxxx +NHOST_TEST_USERNAME=xxxxxxxxxxxxxxxxxx +NHOST_TEST_PASSWORD=xxxxxxxxxxxxxxxxxx diff --git a/.gitignore b/.gitignore index 69da22e..ab409cd 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,5 @@ composer.lock .env test-service-account.json .DS_Store -localBackup/ \ No newline at end of file +localBackup/ +.vscode/ \ No newline at end of file diff --git a/README.md b/README.md index c5fd82f..390735a 100644 --- a/README.md +++ b/README.md @@ -19,9 +19,29 @@ Init in your application: ```php run( + [ + Transfer::GROUP_AUTH + ], function ($status) { + echo $status['message'] . PHP_EOL; + } +); ``` ## Supported Resources Chart @@ -29,7 +49,7 @@ require_once __DIR__ . '/../../vendor/autoload.php'; Sources: | | Users | Databases | Documents | Files | Functions | |----------|-------|-----------|-----------|-------|-----------| -| Appwrite | ✅ | ✅ | ✅ | | | +| Appwrite | ✅ | ✅ | ✅ | ✅ | ✅ | | Supabase | ✅ | ✅ | ✅ | | | | NHost | ✅ | ✅ | ✅ | | | | Firebase | ✅ | ✅ | | | | @@ -37,7 +57,7 @@ Sources: Destinations: | | Users | Databases | Documents | Files | Functions | |----------|-------|-----------|-----------|-------|-----------| -| Appwrite | ✅ | ✅ | ✅ | | | +| Appwrite | ✅ | ✅ | ✅ | ✅ | ✅ | | Local | ✅ | ✅ | ✅ | ✅ | ✅ | > **Warning** diff --git a/composer.json b/composer.json index 4b02503..aa87a2c 100644 --- a/composer.json +++ b/composer.json @@ -22,8 +22,7 @@ "require": { "php": ">=8.0", "utopia-php/cli": "^0.13.0", - "appwrite/appwrite": "^8.0", - "google/apiclient": "^2.12.1" + "appwrite/appwrite": "^8.0" }, "require-dev": { "phpunit/phpunit": "^9.3", diff --git a/playground.php b/playground.php index 0ae952c..70773d7 100644 --- a/playground.php +++ b/playground.php @@ -12,11 +12,11 @@ use Utopia\Transfer\Sources\Appwrite; use Utopia\Transfer\Destinations\Appwrite as AppwriteDestination; use Utopia\Transfer\Destinations\Local; -use Utopia\Transfer\Log; use Utopia\Transfer\Sources\Firebase; use Utopia\Transfer\Sources\NHost; use Utopia\Transfer\Sources\Supabase; use Dotenv\Dotenv; +use Utopia\Transfer\Sources\FirebaseG2; $dotenv = Dotenv::createImmutable(__DIR__); $dotenv->load(); @@ -35,14 +35,21 @@ Firebase::AUTH_SERVICEACCOUNT ); -$sourceNHost = new NHost( - $_ENV["NHOST_TEST_HOST"] ?? '', - $_ENV["NHOST_TEST_DATABASE"] ?? '', - $_ENV["NHOST_TEST_USERNAME"] ?? '', - $_ENV["NHOST_TEST_PASSWORD"] ?? '', +$sourceFirebaseG2 = new FirebaseG2( + json_decode($_ENV["FIREBASE_TEST_ACCOUNT"], true), + "amadeus-a3730" ); +// $sourceNHost = new NHost( +// $_ENV["NHOST_TEST_HOST"] ?? '', +// $_ENV["NHOST_TEST_DATABASE"] ?? '', +// $_ENV["NHOST_TEST_USERNAME"] ?? '', +// $_ENV["NHOST_TEST_PASSWORD"] ?? '', +// ); + $sourceSupabase = new Supabase( + $_ENV['SUPABASE_TEST_ENDPOINT'] ?? '', + $_ENV['SUPABASE_TEST_KEY'] ?? '', $_ENV["SUPABASE_TEST_HOST"] ?? '', $_ENV["SUPABASE_TEST_DATABASE"] ?? '', $_ENV["SUPABASE_TEST_USERNAME"] ?? '', @@ -67,8 +74,8 @@ $sourceFirebase->setProject($sourceFirebase->getProjects()[0]); $transfer = new Transfer( - $sourceAppwrite, - $destinationAppwrite + $sourceSupabase, + $destinationLocal ); /** @@ -76,15 +83,7 @@ */ $transfer->run( [ - Transfer::RESOURCE_DATABASES + Transfer::GROUP_STORAGE ], function () { } -); - -if (!empty($transfer->getLogs(Log::ERROR))) { - echo "\e[41m\e[97mFAILED\e[0m\n"; - - foreach ($transfer->getLogs(Log::ERROR) as $log) { - echo "\e[31m{$log->getMessage()}\e[0m"; - } -} \ No newline at end of file +); \ No newline at end of file diff --git a/src/Transfer/Destination.php b/src/Transfer/Destination.php index 3c97ff9..3ed667f 100644 --- a/src/Transfer/Destination.php +++ b/src/Transfer/Destination.php @@ -2,52 +2,8 @@ namespace Utopia\Transfer; -use Exception; -use Utopia\Transfer\Resources\File; - -abstract class Destination +abstract class Destination extends Target { - /** - * Global Headers - * - * @var array - */ - protected $headers = [ - 'Content-Type' => '', - ]; - - /** - * Logs - * - * @var array $logs - */ - protected $logs = []; - - /** - * Resource Cache - * - * @var array $resourceCache - */ - protected $resourceCache = [ - Transfer::RESOURCE_DATABASES => [], - Transfer::RESOURCE_DOCUMENTS => [], - Transfer::RESOURCE_FILES => [], - Transfer::RESOURCE_FUNCTIONS => [], - Transfer::RESOURCE_USERS => [] - ]; - - /** - * @var string - */ - protected string $endpoint = ''; - - /** - * Counters - * - * @var array $counter - */ - protected $counters = []; - /** * Source * @@ -55,20 +11,6 @@ abstract class Destination */ protected Source $source; - /** - * Gets the name of the adapter. - * - * @return string - */ - abstract public function getName(): string; - - /** - * Get Supported Resources - * - * @return array - */ - abstract public function getSupportedResources(): array; - /** * Get Source * @@ -92,251 +34,26 @@ public function setSource(Source $source): self return $this; } - /** - * Register Logs Array - * - * @param array &$logs - */ - public function registerLogs(array &$logs): void - { - $this->logs = &$logs; - } - - /** - * Get Resource Counters - * - * @param string $resource = null - * - * @return array - */ - public function &getCounter(?string $resource = null): array - { - if ($resource && $this->counters[$resource]) { - return $this->counters[$resource]; - } else { - $this->counters[$resource] = [ - 'total' => 0, - 'current' => 0, - 'failed' => 0, - 'skipped' => 0, - ]; - - return $this->counters[$resource]; - } - } - - /** - * Register Transfer Hooks - * - * @param array &$cache - * @param array &$counters - * - * @return void - */ - public function registerTransferHooks(array &$cache, array &$counters): void - { - $this->resourceCache = &$cache; - $this->counters = &$counters; - } - /** * Transfer Resources to Destination from Source callback * * @param array $resources * @param callable $callback */ - public function run(array $resources, callable $callback, Source $source): void + public function run(array $resources, callable $callback): void { - $this->source = $source; - - $source->run($resources, function (string $resourceType, array $resource) use ($callback) { - switch ($resourceType) { - case Transfer::RESOURCE_USERS: - $this->importUsers($resource, $callback); - break; - case Transfer::RESOURCE_DATABASES: - $this->importDatabases($resource, $callback); - break; - case Transfer::RESOURCE_DOCUMENTS: - $this->importDocuments($resource, $callback); - break; - case Transfer::RESOURCE_FILES: - $this->importFiles($resource, $callback); - break; - } + $this->source->run($resources, function (string $group, array $resources) use ($callback) { + $this->importResources($resources, $callback, $group); }); } /** - * Check Requirements - * Performs a suite of API Checks, Resource Checks, etc... to ensure the adapter is ready to be used. - * This is highly recommended to be called before any other method after initialization. - * - * If no resources are provided, the method should check all resources. - * Returns a object with all the keys of the resources provided and a true|string value if the resource is available or not. - * If the resource is not available, the value should be a string with the error message. - * - * @string[] $resources - * - * @return string[] - */ - abstract public function check(array $resources = []): array; - - /** - * Call + * Import Resources * - * Make an API call - * - * @param string $method - * @param string $path - * @param array $params - * @param array $headers - * @return array|string - * @throws \Exception - */ - public function call(string $method, string $path = '', array $headers = array(), array $params = array()): array|string - { - $headers = array_merge($this->headers, $headers); - $ch = curl_init((str_contains($path, 'http') ? $path : $this->endpoint . $path . (($method == 'GET' && !empty($params)) ? '?' . http_build_query($params) : ''))); - $responseHeaders = []; - $responseStatus = -1; - $responseType = ''; - $responseBody = ''; - - switch ($headers['Content-Type']) { - case 'application/json': - $query = json_encode($params); - break; - - case 'multipart/form-data': - $query = $this->flatten($params); - break; - - default: - $query = http_build_query($params); - break; - } - - foreach ($headers as $i => $header) { - $headers[] = $i . ':' . $header; - unset($headers[$i]); - } - - curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); - curl_setopt($ch, CURLOPT_USERAGENT, php_uname('s') . '-' . php_uname('r') . ':php-' . phpversion()); - curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); - curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); - curl_setopt($ch, CURLOPT_HEADERFUNCTION, function ($curl, $header) use (&$responseHeaders) { - $len = strlen($header); - $header = explode(':', strtolower($header), 2); - - if (count($header) < 2) { // ignore invalid headers - return $len; - } - - $responseHeaders[strtolower(trim($header[0]))] = trim($header[1]); - - return $len; - }); - - if ($method != 'GET') { - curl_setopt($ch, CURLOPT_POSTFIELDS, $query); - } - - $responseBody = curl_exec($ch); - - $responseType = $responseHeaders['Content-Type'] ?? ''; - $responseStatus = curl_getinfo($ch, CURLINFO_HTTP_CODE); - - switch (substr($responseType, 0, strpos($responseType, ';'))) { - case 'application/json': - $responseBody = json_decode($responseBody, true); - break; - } - - if (curl_errno($ch)) { - throw new \Exception(curl_error($ch)); - } - - curl_close($ch); - - if ($responseStatus >= 400) { - if (is_array($responseBody)) { - throw new \Exception(json_encode($responseBody)); - } else { - throw new \Exception($responseStatus . ': ' . $responseBody); - } - } - - return $responseBody; - } - - /** - * Flatten params array to PHP multiple format - * - * @param array $data - * @param string $prefix - * @return array - */ - protected function flatten(array $data, string $prefix = ''): array - { - $output = []; - - foreach ($data as $key => $value) { - $finalKey = $prefix ? "{$prefix}[{$key}]" : $key; - - if (is_array($value)) { - $output += $this->flatten($value, $finalKey); // @todo: handle name collision here if needed - } else { - $output[$finalKey] = $value; - } - } - - return $output; - } - - /** - * Import Users - * - * @param array $users - * @param callable $callback (Progress $progress) - */ - protected function importUsers(array $users, callable $callback): void - { - throw new \Exception("Not Implemented"); - } - - /** - * Import Database - * - * @param array $databases - * @param callable $callback (Progress $progress) - */ - protected function importDatabases(array $databases, callable $callback): void - { - throw new \Exception("Not Implemented"); - } - - /** - * Import Documents - * - * @param array $documents + * @param array $resources * @param callable $callback (Progress $progress) - */ - protected function importDocuments(array $documents, callable $callback): void - { - throw new \Exception("Not Implemented"); - } - - /** - * Import Files + * @param string $group * - * @param array $resource file[]|bucket[]|FileData[] - * @param callable $callback (Progress $progress) */ - protected function importFiles(array $files, callable $callback): void - { - throw new \Exception("Not Implemented"); - } + abstract public function importResources(array $resources, callable $callback, string $group): void; } diff --git a/src/Transfer/Destinations/Appwrite.php b/src/Transfer/Destinations/Appwrite.php index a1f863e..d5060bd 100644 --- a/src/Transfer/Destinations/Appwrite.php +++ b/src/Transfer/Destinations/Appwrite.php @@ -2,41 +2,43 @@ namespace Utopia\Transfer\Destinations; -use Appwrite\AppwriteException; use Appwrite\Client; use Appwrite\Services\Users; use Appwrite\Services\Databases; +use Appwrite\Services\Functions; use Appwrite\Services\Storage; +use Appwrite\Services\Teams; use Utopia\Transfer\Destination; -use Utopia\Transfer\Resources\Hash; -use Utopia\Transfer\Log; -use Utopia\Transfer\Progress; -use Utopia\Transfer\Resources\Attribute; -use Utopia\Transfer\Resources\User; use Utopia\Transfer\Transfer; -use Utopia\Transfer\Resources\Database; -use Utopia\Transfer\Resources\Collection; -use Utopia\Transfer\Resources\Document; -use Utopia\Transfer\Resources\Bucket; -use Utopia\Transfer\Resources\FileData; -use Utopia\Transfer\Resources\Index; -use Utopia\Transfer\Resources\Attributes\BoolAttribute; -use Utopia\Transfer\Resources\Attributes\DateTimeAttribute; -use Utopia\Transfer\Resources\Attributes\EmailAttribute; -use Utopia\Transfer\Resources\Attributes\EnumAttribute; -use Utopia\Transfer\Resources\Attributes\FloatAttribute; -use Utopia\Transfer\Resources\Attributes\IntAttribute; -use Utopia\Transfer\Resources\Attributes\IPAttribute; -use Utopia\Transfer\Resources\Attributes\StringAttribute; -use Utopia\Transfer\Resources\Attributes\URLAttribute; -use Utopia\Transfer\Resources\Attributes\RelationshipAttribute; +use Utopia\Transfer\Resources\Auth\User; +use Utopia\Transfer\Resources\Auth\Hash; +use Utopia\Transfer\Resources\Auth\TeamMembership; +use Utopia\Transfer\Resources\Storage\Bucket; +use Utopia\Transfer\Resources\Storage\FileData; +use Utopia\Transfer\Resources\Storage\Index; +use Utopia\Transfer\Resources\Functions\Func; +use Utopia\Transfer\Resources\Functions\EnvVar; +use Utopia\Transfer\Resources\Database\Database; +use Utopia\Transfer\Resources\Database\Collection; +use Utopia\Transfer\Resources\Database\Attribute; +use Utopia\Transfer\Resources\Database\Attributes\BoolAttribute; +use Utopia\Transfer\Resources\Database\Attributes\DateTimeAttribute; +use Utopia\Transfer\Resources\Database\Attributes\EmailAttribute; +use Utopia\Transfer\Resources\Database\Attributes\EnumAttribute; +use Utopia\Transfer\Resources\Database\Attributes\FloatAttribute; +use Utopia\Transfer\Resources\Database\Attributes\IntAttribute; +use Utopia\Transfer\Resources\Database\Attributes\IPAttribute; +use Utopia\Transfer\Resources\Database\Attributes\StringAttribute; +use Utopia\Transfer\Resources\Database\Attributes\URLAttribute; +use Utopia\Transfer\Resources\Database\Attributes\RelationshipAttribute; +use Utopia\Transfer\Resources\Database\Document; +use Utopia\Transfer\Resource; class Appwrite extends Destination { protected Client $client; protected string $project; - protected string $endpoint; protected string $key; public function __construct(string $project, string $endpoint, string $key) @@ -69,11 +71,11 @@ public function getName(): string public function getSupportedResources(): array { return [ - Transfer::RESOURCE_USERS, - Transfer::RESOURCE_DATABASES, - Transfer::RESOURCE_DOCUMENTS, - Transfer::RESOURCE_FILES, - Transfer::RESOURCE_FUNCTIONS + Transfer::GROUP_AUTH, + Transfer::GROUP_DATABASES, + Transfer::GROUP_DOCUMENTS, + Transfer::GROUP_STORAGE, + Transfer::GROUP_FUNCTIONS ]; } @@ -92,22 +94,20 @@ public function check(array $resources = []): array } // Most of these API calls are purposely wrong. Appwrite will throw a 403 before a 400. - // We want to make sure the API key has the correct permissions. - + // We want to make sure the API key has full read and write access to the project. foreach ($resources as $resource) { switch ($resource) { - case Transfer::RESOURCE_DATABASES: + case Transfer::GROUP_DATABASES: $databases = new Databases($this->client); try { $databases->list(); } catch (\Throwable $e) { if ($e->getCode() == 401) { - $this->logs[Log::ERROR][] = new Log('API Key is missing scope: databases.read'); $report['Databases'][] = 'API Key is missing scope: databases.read'; } } break; - case Transfer::RESOURCE_USERS: + case Transfer::GROUP_AUTH: $auth = new Users($this->client); try { $auth->list(); @@ -117,7 +117,7 @@ public function check(array $resources = []): array } } break; - case Transfer::RESOURCE_DOCUMENTS: + case Transfer::GROUP_DOCUMENTS: $databases = new Databases($this->client); try { $databases->list(); @@ -204,528 +204,460 @@ public function check(array $resources = []): array return $report; } - public function importPasswordUser(User $user): array|null + function importResources(array $resources, callable $callback, string $group): void { - $auth = new Users($this->client); - $hash = $user->getPasswordHash(); - $result = null; + foreach ($resources as $resource) { + /** @var Resource $resource */ + switch ($resource->getGroup()) { + case Transfer::GROUP_DATABASES: + $responseResource = $this->importDatabaseResource($resource); + break; + case Transfer::GROUP_DOCUMENTS: + $responseResource = $this->importDocumentResource($resource); + break; + case Transfer::GROUP_STORAGE: + $responseResource = $this->importFileResource($resource); + break; + case Transfer::GROUP_AUTH: + $responseResource = $this->importAuthResource($resource); + break; + case Transfer::GROUP_FUNCTIONS: + $responseResource = $this->importFunctionResource($resource); + break; + } - if (empty($hash->getHash())) { - throw new \Exception('User password hash is empty'); + $this->resourceCache->update($responseResource); } + } - switch ($hash->getAlgorithm()) { - case Hash::SCRYPT_MODIFIED: - $result = $auth->createScryptModifiedUser( - $user->getId(), - $user->getEmail(), - $hash->getHash(), - $hash->getSalt(), - $hash->getSeparator(), - $hash->getSigningKey(), - $user->getUsername() - ); + public function importDatabaseResource(Resource $resource): Resource + { + $databaseService = new Databases($this->client); + + $response = null; + $resource->setStatus(Resource::STATUS_PROCESSING); + + try { + switch ($resource->getName()) { + case Resource::TYPE_DATABASE: + /** @var Database $resource */ + $response = $databaseService->create($resource->getId(), $resource->getDBName()); + break; + case Resource::TYPE_COLLECTION: + /** @var Collection $resource */ + $response = $newCollection = $databaseService->createCollection( + $resource->getDatabase()->getId(), + $resource->getId(), + $resource->getCollectionName(), + $resource->getPermissions(), + $resource->getDocumentSecurity() + ); + $resource->setId($newCollection['$id']); + break; + case Resource::TYPE_INDEX: + /** @var Index $resource */ + $response = $databaseService->createIndex( + $resource->getCollection()->getDatabase()->getId(), + $resource->getCollection()->getId(), + $resource->getKey(), + $resource->getType(), + $resource->getAttributes(), + $resource->getOrders() + ); + break; + case Resource::TYPE_ATTRIBUTE: + /** @var Attribute $resource */ + $this->createAttribute($resource); + break; + case Resource::TYPE_DOCUMENT: + /** @var Document $resource */ + $response = $databaseService->createDocument( + $resource->getDatabase()->getId(), + $resource->getCollection()->getId(), + $resource->getId(), + $resource->getData(), + $resource->getPermissions() + ); + break; + } + + $resource->setStatus(Resource::STATUS_SUCCESS); + } catch (\Exception $e) { + $resource->setStatus(Resource::STATUS_ERROR, $e->getMessage()); + } finally { + return $resource; + } + } + + public function createAttribute(Attribute $attribute): void + { + $databaseService = new Databases($this->client); + + switch ($attribute->getTypeName()) { + case Attribute::TYPE_STRING: + /** @var StringAttribute $attribute */ + $databaseService->createStringAttribute($attribute->getCollection()->getDatabase()->getId(), $attribute->getCollection()->getId(), $attribute->getKey(), $attribute->getSize(), $attribute->getRequired(), $attribute->getDefault(), $attribute->getArray()); break; - case Hash::BCRYPT: - $result = $auth->createBcryptUser( - $user->getId(), - $user->getEmail(), - $hash->getHash(), - $user->getUsername() - ); + case Attribute::TYPE_INTEGER: + /** @var IntAttribute $attribute */ + $databaseService->createIntegerAttribute($attribute->getCollection()->getDatabase()->getId(), $attribute->getCollection()->getId(), $attribute->getKey(), $attribute->getRequired(), $attribute->getMin(), $attribute->getMax() ?? null, $attribute->getDefault(), $attribute->getArray()); break; - case Hash::ARGON2: - $result = $auth->createArgon2User( - $user->getId(), - $user->getEmail(), - $hash->getHash(), - $user->getUsername() - ); + case Attribute::TYPE_FLOAT: + /** @var FloatAttribute $attribute */ + $databaseService->createFloatAttribute($attribute->getCollection()->getDatabase()->getId(), $attribute->getCollection()->getId(), $attribute->getKey(), $attribute->getRequired(), null, null, $attribute->getDefault(), $attribute->getArray()); break; - case Hash::SHA256: - $result = $auth->createShaUser( - $user->getId(), - $user->getEmail(), - $hash->getHash(), - 'sha256', - $user->getUsername() - ); + case Attribute::TYPE_BOOLEAN: + /** @var BoolAttribute $attribute */ + $databaseService->createBooleanAttribute($attribute->getCollection()->getDatabase()->getId(), $attribute->getCollection()->getId(), $attribute->getKey(), $attribute->getRequired(), $attribute->getDefault(), $attribute->getArray()); break; - case Hash::PHPASS: - $result = $auth->createPHPassUser( - $user->getId(), - $user->getEmail(), - $hash->getHash(), - $user->getUsername() - ); + case Attribute::TYPE_DATETIME: + /** @var DateTimeAttribute $attribute */ + $databaseService->createDateTimeAttribute($attribute->getCollection()->getDatabase()->getId(), $attribute->getCollection()->getId(), $attribute->getKey(), $attribute->getRequired(), $attribute->getDefault(), $attribute->getArray()); break; - case Hash::SCRYPT: - $result = $auth->createScryptUser( - $user->getId(), - $user->getEmail(), - $hash->getHash(), - $hash->getSalt(), - $hash->getPasswordCpu(), - $hash->getPasswordMemory(), - $hash->getPasswordParallel(), - $hash->getPasswordLength(), - $user->getUsername() - ); + case Attribute::TYPE_EMAIL: + /** @var EmailAttribute $attribute */ + $databaseService->createEmailAttribute($attribute->getCollection()->getDatabase()->getId(), $attribute->getCollection()->getId(), $attribute->getKey(), $attribute->getRequired(), $attribute->getDefault(), $attribute->getArray()); + break; + case Attribute::TYPE_IP: + /** @var IPAttribute $attribute */ + $databaseService->createIPAttribute($attribute->getCollection()->getDatabase()->getId(), $attribute->getCollection()->getId(), $attribute->getKey(), $attribute->getRequired(), $attribute->getDefault(), $attribute->getArray()); + break; + case Attribute::TYPE_URL: + /** @var URLAttribute $attribute */ + $databaseService->createUrlAttribute($attribute->getCollection()->getDatabase()->getId(), $attribute->getCollection()->getId(), $attribute->getKey(), $attribute->getRequired(), $attribute->getDefault(), $attribute->getArray()); + break; + case Attribute::TYPE_ENUM: + /** @var EnumAttribute $attribute */ + $databaseService->createEnumAttribute($attribute->getCollection()->getDatabase()->getId(), $attribute->getCollection()->getId(), $attribute->getKey(), $attribute->getElements(), $attribute->getRequired(), $attribute->getDefault(), $attribute->getArray()); + break; + case Attribute::TYPE_RELATIONSHIP: + /** @var RelationshipAttribute $attribute */ + $databaseService->createRelationshipAttribute($attribute->getCollection()->getDatabase()->getId(), $attribute->getCollection()->getId(), $attribute->getRelatedCollection(), $attribute->getRelationType(), $attribute->getTwoWay(), $attribute->getKey(), $attribute->getTwoWayKey(), $attribute->getOnDelete()); break; + default: + throw new \Exception('Invalid attribute type'); } - return $result; + // Wait for attribute to be created + $this->awaitAttributeCreation($attribute, 5); } - public function importUsers(array $users, callable $callback): void + /** + * Await Attribute Creation + * + * @param Attribute $attribute + * @param int $timeout + * + * @return bool + */ + public function awaitAttributeCreation(Attribute $attribute, int $timeout): bool { - $userCounters = &$this->getCounter(Transfer::RESOURCE_USERS); - $auth = new Users($this->client); - - foreach ($users as $user) { - /** @var \Utopia\Transfer\Resources\User $user */ - try { - $createdUser = in_array(User::TYPE_EMAIL, $user->getTypes()) ? $this->importPasswordUser($user) : $auth->create($user->getId(), $user->getEmail(), $user->getPhone(), null, $user->getName()); - - if (!$createdUser) { - $this->logs[Log::ERROR][] = new Log('Failed to import user', \time(), $user); - $userCounters = &$this->getCounter(Transfer::RESOURCE_USERS); - $userCounters['failed']++; - } else { - // Add more data to the user - if ($user->getUsername()) { - $auth->updateName($user->getId(), $user->getUsername()); - } + $databaseService = new Databases($this->client); - if ($user->getPhone()) { - $auth->updatePhone($user->getId(), $user->getPhone()); - } + $start = \time(); - if ($user->getEmailVerified()) { - $auth->updateEmailVerification($user->getId(), $user->getEmailVerified()); - } - - if ($user->getPhoneVerified()) { - $auth->updatePhoneVerification($user->getId(), $user->getPhoneVerified()); - } + while (\time() - $start < $timeout) { + $response = $databaseService->getAttribute($attribute->getCollection()->getDatabase()->getId(), $attribute->getCollection()->getId(), $attribute->getKey()); - if ($user->getDisabled()) { - $auth->updateStatus($user->getId(), !$user->getDisabled()); - } - - $this->logs[Log::SUCCESS][] = new Log('User imported successfully', \time(), $user); - $userCounters['current']++; - } - } catch (\Exception $e) { - $this->logs[Log::ERROR][] = new Log($e->getMessage(), \time(), $user); - $counter = &$this->getCounter(Transfer::RESOURCE_USERS); - $counter['failed']++; + if ($response['status'] === 'available') { + return true; } + + \usleep(500000); } - $callback( - new Progress( - Transfer::RESOURCE_USERS, - time(), - $userCounters['total'], - $userCounters['current'], - $userCounters['failed'], - $userCounters['skipped'] - ) - ); + throw new \Exception('Attribute creation timeout'); } - public function createAttribute(Attribute $attribute, Collection $collection, Database $database): void + public function importDocumentResource(Resource $resource): Resource { $databaseService = new Databases($this->client); try { - switch ($attribute->getName()) { - case Attribute::TYPE_STRING: - /** @var StringAttribute $attribute */ - $databaseService->createStringAttribute($database->getId(), $collection->getId(), $attribute->getKey(), $attribute->getSize(), $attribute->getRequired(), $attribute->getDefault(), $attribute->getArray()); - break; - case Attribute::TYPE_INTEGER: - /** @var IntAttribute $attribute */ - $databaseService->createIntegerAttribute($database->getId(), $collection->getId(), $attribute->getKey(), $attribute->getRequired(), $attribute->getMin(), $attribute->getMax() ?? null, $attribute->getDefault(), $attribute->getArray()); - break; - case Attribute::TYPE_FLOAT: - /** @var FloatAttribute $attribute */ - $databaseService->createFloatAttribute($database->getId(), $collection->getId(), $attribute->getKey(), $attribute->getRequired(), null, null, $attribute->getDefault(), $attribute->getArray()); - break; - case Attribute::TYPE_BOOLEAN: - /** @var BoolAttribute $attribute */ - $databaseService->createBooleanAttribute($database->getId(), $collection->getId(), $attribute->getKey(), $attribute->getRequired(), $attribute->getDefault(), $attribute->getArray()); - break; - case Attribute::TYPE_DATETIME: - /** @var DateTimeAttribute $attribute */ - $databaseService->createDateTimeAttribute($database->getId(), $collection->getId(), $attribute->getKey(), $attribute->getRequired(), $attribute->getDefault(), $attribute->getArray()); + switch ($resource->getName()) { + case Resource::TYPE_DOCUMENT: + /** @var Document $resource */ + $databaseService->createDocument( + $resource->getDatabase()->getId(), + $resource->getCollection()->getId(), + $resource->getId(), + $resource->getData(), + $resource->getPermissions() + ); break; - case Attribute::TYPE_EMAIL: - /** @var EmailAttribute $attribute */ - $databaseService->createEmailAttribute($database->getId(), $collection->getId(), $attribute->getKey(), $attribute->getRequired(), $attribute->getDefault(), $attribute->getArray()); - break; - case Attribute::TYPE_IP: - /** @var IPAttribute $attribute */ - $databaseService->createIPAttribute($database->getId(), $collection->getId(), $attribute->getKey(), $attribute->getRequired(), $attribute->getDefault(), $attribute->getArray()); - break; - case Attribute::TYPE_URL: - /** @var URLAttribute $attribute */ - $databaseService->createUrlAttribute($database->getId(), $collection->getId(), $attribute->getKey(), $attribute->getRequired(), $attribute->getDefault(), $attribute->getArray()); - break; - case Attribute::TYPE_ENUM: - /** @var EnumAttribute $attribute */ - $databaseService->createEnumAttribute($database->getId(), $collection->getId(), $attribute->getKey(), $attribute->getElements(), $attribute->getRequired(), $attribute->getDefault(), $attribute->getArray()); - break; - case Attribute::TYPE_RELATIONSHIP: - /** @var RelationshipAttribute $attribute */ - $databaseService->createRelationshipAttribute($database->getId(), $collection->getId(), $attribute->getRelatedCollection(), $attribute->getRelationType(), $attribute->getTwoWay(), $attribute->getKey(), $attribute->getTwoWayKey(), $attribute->getOnDelete()); - break; - default: - throw new \Exception('Invalid attribute type'); } + + $resource->setStatus(Resource::STATUS_SUCCESS); } catch (\Exception $e) { - $this->logs[Log::ERROR][] = new Log($e->getMessage(), \time(), $attribute); + $resource->setStatus(Resource::STATUS_ERROR, $e->getMessage()); + } finally { + return $resource; } } - /** - * Validate Attributes Creation - * - * @param Attribute[] $attributes - * @param Collection $collection - * @param Database $database - * - * @return bool - */ - public function validateAttributesCreation(array $attributes, Collection $collection, Database $database): bool + public function importFileResource(Resource $resource): Resource { - $databaseService = new Databases($this->client); - $destinationAttributes = $databaseService->listAttributes($database->getId(), $collection->getId())['attributes']; + $storageService = new Storage($this->client); - foreach ($attributes as $attribute) { - /** @var Attribute $attribute */ - $foundAttribute = null; + $response = null; - foreach ($destinationAttributes as $destinationAttribute) { - if ($destinationAttribute['key'] === $attribute->getKey()) { - $foundAttribute = $destinationAttribute; + try { + switch ($resource->getName()) { + case Resource::TYPE_FILE: + /** @var File $resource */ + $response = $storageService->createFile( + $resource->getBucket()->getId(), + $resource->getId(), + $resource->getFileName(), + $resource->getPermissions() + ); break; - } + case Resource::TYPE_FILEDATA: + return $this->importFileData($resource); + break; + case Resource::TYPE_BUCKET: + /** @var Bucket $resource */ + $response = $storageService->createBucket( + $resource->getId(), + $resource->getBucketName(), + $resource->getPermissions(), + $resource->getFileSecurity(), + true, // Set to true for now, we'll come back later. + $resource->getMaxFileSize(), + $resource->getAllowedFileExtensions(), + $resource->getCompression(), + $resource->getEncryption(), + $resource->getAntiVirus() + ); + $resource->setId($response['$id']); } - if ($foundAttribute) { - if ($foundAttribute['status'] !== 'available') { - return false; - } else { - continue; - } - } else { - return false; - } + $resource->setStatus(Resource::STATUS_SUCCESS); + } catch (\Exception $e) { + $resource->setStatus(Resource::STATUS_ERROR, $e->getMessage()); + } finally { + return $resource; } - - return true; } /** - * Import Databases - * - * @param array $databases - * @param callable $callback + * Import File Data * - * @return void + * @param FileData $filePart + * @returns FileData */ - public function importDatabases(array $databases, callable $callback): void + public function importFileData(FileData $resource): FileData { - $databaseCounters = &$this->getCounter(Transfer::RESOURCE_DATABASES); - $databaseService = new Databases($this->client); - - foreach ($databases as $database) { - /** @var Database $database */ - try { - $databaseService->create($database->getId(), $database->getDBName()); - - $createdCollections = []; - - foreach ($database->getCollections() as $collection) { - /** @var Collection $collection */ - $createdAttributes = []; - - // if ($database->getType() == Database::DB_NON_RELATIONAL) { - // $path = \explode('/', $collection->getCollectionName()); + $file = $resource->getFile(); + $bucketId = $file->getBucket()->getId(); - // $collectionName = $path[count($path) - 1]; + $response = null; - // if (isset($path[count($path) - 2])) { - // $collectionName = $path[count($path) - 2] . "/" . $collectionName; - // } - // } else { - // $collectionName = $collection->getCollectionName(); - // } - - // // Handle special chars - // $collectionName = \str_replace([' ', '(', ')', '[', ']', '{', '}', '<', '>', ':', ';', ',', '.', '?', '\\', '|', '=', '+', '*', '&', '^', '%', '$', '#', '@', '!', '~', '`', '"', "'"], '_', $collectionName); + if ($file->getSize() <= Transfer::STORAGE_MAX_CHUNK_SIZE) { + $response = $this->client->call( + 'POST', + "/v1/storage/buckets/{$bucketId}/files", + [ + 'content-type' => 'multipart/form-data', + ], + [ + 'bucketId' => $bucketId, + 'fileId' => $file->getId(), + 'file' => new \CurlFile('data://' . $file->getMimeType() . ';base64,' . base64_encode($resource->getData()), $file->getMimeType(), $file->getFileName()), + 'permissions' => $file->getPermissions(), + ] + ); - // // Check name length - // if (\strlen($collectionName) > 120) { - // $collectionName = \substr($collectionName, 0, 120); - // } + $resource->setStatus(Resource::STATUS_SUCCESS); + return $resource; + } - $newCollection = $databaseService->createCollection($database->getId(), $collection->getId(), $collection->getCollectionName(), $collection->getPermissions(), $collection->getDocumentSecurity()); - $collection->setId($newCollection['$id']); + $response = $this->client->call( + 'POST', + "/v1/storage/buckets/{$bucketId}/files", + [ + 'content-type' => 'multipart/form-data', + 'content-range' => 'bytes ' . ($resource->getStart()) . '-' . ($resource->getEnd() == ($file->getSize() - 1) ? $file->getSize() : $resource->getEnd()) . '/' . $file->getSize(), + ], + [ + 'bucketId' => $bucketId, + 'fileId' => $file->getId(), + 'file' => new \CurlFile('data://' . $file->getMimeType() . ';base64,' . base64_encode($resource->getData()), $file->getMimeType(), $file->getFileName()), + 'permissions' => $file->getPermissions(), + ] + ); - // Remove duplicate attributes, TODO: Merge them together. - $filteredAttributes = \array_filter($collection->getAttributes(), function ($attribute) use (&$createdAttributes) { - if (\in_array($attribute->getKey(), $createdAttributes)) { - return false; - } + if ($resource->getEnd() == ($file->getSize() - 1)) { + // Signatures for Encrypted files are invalid, so we skip the check + if ($file->getBucket()->getEncryption() == false || $file->getSize() > (20 * 1024 * 1024)) { + if ($response['signature'] !== $file->getSignature()) { + $resource->setStatus(Resource::STATUS_WARNING, 'File signature mismatch, Possibly corrupted.'); + } + } + } - $createdAttributes[] = $attribute->getKey(); + return $resource; + } - return true; - }); + public function importAuthResource(Resource $resource): Resource + { + $userService = new Users($this->client); + $teamService = new Teams($this->client); - foreach ($filteredAttributes as $attribute) { - /** @var Attribute $attribute */ - $this->createAttribute($attribute, $collection, $database); + try { + switch ($resource->getName()) { + case Resource::TYPE_USER: + /** @var User $resource */ + if (in_array(User::TYPE_EMAIL, $resource->getTypes())) { + $this->importPasswordUser($resource); + } else { + $userService->create($resource->getId(), $resource->getEmail(), $resource->getPhone(), null, $resource->getName()); } - // We need to wait for all the attributes to be created before creating the indexes. - $timeout = 0; - - while (!$this->validateAttributesCreation($collection->getAttributes(), $collection, $database)) { - if ($timeout > 5) { - throw new AppwriteException('Timeout while waiting for attributes to be created'); - } - - $timeout++; - \sleep(1); + if ($resource->getUsername()) { + $userService->updateName($resource->getId(), $resource->getUsername()); } - foreach ($collection->getIndexes() as $index) { - /** @var Index $index */ - $databaseService->createIndex($database->getId(), $collection->getId(), $index->getKey(), $index->getType(), $index->getAttributes(), $index->getOrders()); + if ($resource->getPhone()) { + $userService->updatePhone($resource->getId(), $resource->getPhone()); } - $createdCollections[] = $collection; - } - - // TODO: Rewrite to use new Appwrite relations - if ($database->getType() == Database::DB_NON_RELATIONAL) { - $refCollectionID = $databaseService->createCollection($database->getId(), 'refs', 'References')['$id']; - $databaseService->createStringAttribute($database->getId(), $refCollectionID, 'original_name', 1000000, true); - - sleep(2); - - foreach ($createdCollections as $collection) { - /** @var Collection $collection */ - $result = $databaseService->createDocument($database->getId(), $refCollectionID, $collection->getId(), [ - 'original_name' => $collection->getCollectionName() - ]); + if ($resource->getEmailVerified()) { + $userService->updateEmailVerification($resource->getId(), $resource->getEmailVerified()); } - } - - $this->logs[Log::SUCCESS][] = new Log('Database imported successfully', \time(), $database); - $databaseCounters['current']++; - } catch (AppwriteException $e) { - $this->logs[Log::ERROR][] = new Log($e->getMessage(), \time(), $database); - $databaseCounters['failed']++; - } - } - $callback( - new Progress( - Transfer::RESOURCE_DATABASES, - time(), - $databaseCounters['total'], - $databaseCounters['current'], - $databaseCounters['failed'], - $databaseCounters['skipped'] - ) - ); - } - - - /** - * Import Documents - * - * @param array $documents - * @param callable $callback (Progress $progress) - */ - protected function importDocuments(array $documents, callable $callback): void - { - $documentCounters = &$this->getCounter(Transfer::RESOURCE_DOCUMENTS); - $databaseService = new Databases($this->client); - - foreach ($documents as $document) { - /** @var Document $document */ + if ($resource->getPhoneVerified()) { + $userService->updatePhoneVerification($resource->getId(), $resource->getPhoneVerified()); + } - try { - $databaseService->createDocument($document->getDatabase()->getId(), $document->getCollection()->getId(), $document->getId() ?? 'unique()', $document->getData(), $document->getPermissions()); + if ($resource->getDisabled()) { + $userService->updateStatus($resource->getId(), !$resource->getDisabled()); + } - $this->logs[Log::SUCCESS][] = new Log('Document imported successfully', \time(), $document); - $documentCounters['current']++; - } catch (AppwriteException $e) { - $this->logs[Log::ERROR][] = new Log($e->getMessage(), \time(), $document); - $documentCounters['failed']++; + break; + case Resource::TYPE_TEAM: + /** @var Team $resource */ + $teamService->create($resource->getId(), $resource->getName()); + $teamService->updatePrefs($resource->getId(), $resource->getPrefs()); + break; + case Resource::TYPE_TEAM_MEMBERSHIP: + /** @var TeamMembership $resource */ + //TODO: Discuss in meeting. + // $teamService->createMembership($resource->getTeam()->getId(), $resource->getRoles(), ) + // break; } + } catch (\Exception $e) { + $resource->setStatus(Resource::STATUS_ERROR, $e->getMessage()); + } finally { + return $resource; } - - $callback( - new Progress( - Transfer::RESOURCE_DOCUMENTS, - time(), - $documentCounters['total'], - $documentCounters['current'], - $documentCounters['failed'], - $documentCounters['skipped'] - ) - ); } - /** - * Import Bucket - * - * @param Bucket $bucket - * @param callable $callback (Progress $progress) - */ - protected function importBucket(Bucket $bucket, callable $callback): void + public function importPasswordUser(User $user): array|null { - // $bucketCounters = &$this->getCounter(Transfer::RESOURCE_BUCKETS); - $storageService = new Storage($this->client); - - /** @var Bucket $bucket */ - try { - $newBucket = $storageService->createBucket( - $bucket->getId(), - $bucket->getBucketName(), - $bucket->getPermissions(), - $bucket->getFileSecurity(), - true, // Set to true for now, we'll come back later. - $bucket->getMaxFileSize(), - $bucket->getAllowedFileExtensions(), - $bucket->getCompression(), - $bucket->getEncryption(), - $bucket->getAntiVirus() - ); - $bucket->setId($newBucket['$id']); + $auth = new Users($this->client); + $hash = $user->getPasswordHash(); + $result = null; - $this->logs[Log::SUCCESS][] = new Log('Bucket imported successfully', \time(), $bucket); - // $bucketCounters['current']++; - } catch (AppwriteException $e) { - $this->logs[Log::ERROR][] = new Log($e->getMessage(), \time(), $bucket); - // $bucketCounters['failed']++; + if (empty($hash->getHash())) { + throw new \Exception('User password hash is empty'); } - // $callback( - // new Progress( - // Transfer::RESOURCE_BUCKETS, - // time(), - // $bucketCounters['total'], - // $bucketCounters['current'], - // $bucketCounters['failed'], - // $bucketCounters['skipped'] - // ) - // ); - } - - /** - * Import Files - * - * @param array $resources File[]|Bucket[]|FileData[] - * @param callable $callback (Progress $progress) - */ - protected function importFiles(array $resources, callable $callback): void - { - $fileCounters = &$this->getCounter(Transfer::RESOURCE_FILES); - - foreach ($resources as $resource) { - if ($resource instanceof Bucket) { - $this->importBucket($resource, $callback); - } elseif ($resource instanceof FileData) { - $this->importFileData($resource, $callback); - } + switch ($hash->getAlgorithm()) { + case Hash::SCRYPT_MODIFIED: + $result = $auth->createScryptModifiedUser( + $user->getId(), + $user->getEmail(), + $hash->getHash(), + $hash->getSalt(), + $hash->getSeparator(), + $hash->getSigningKey(), + $user->getUsername() + ); + break; + case Hash::BCRYPT: + $result = $auth->createBcryptUser( + $user->getId(), + $user->getEmail(), + $hash->getHash(), + $user->getUsername() + ); + break; + case Hash::ARGON2: + $result = $auth->createArgon2User( + $user->getId(), + $user->getEmail(), + $hash->getHash(), + $user->getUsername() + ); + break; + case Hash::SHA256: + $result = $auth->createShaUser( + $user->getId(), + $user->getEmail(), + $hash->getHash(), + 'sha256', + $user->getUsername() + ); + break; + case Hash::PHPASS: + $result = $auth->createPHPassUser( + $user->getId(), + $user->getEmail(), + $hash->getHash(), + $user->getUsername() + ); + break; + case Hash::SCRYPT: + $result = $auth->createScryptUser( + $user->getId(), + $user->getEmail(), + $hash->getHash(), + $hash->getSalt(), + $hash->getPasswordCpu(), + $hash->getPasswordMemory(), + $hash->getPasswordParallel(), + $hash->getPasswordLength(), + $user->getUsername() + ); + break; } - $callback( - new Progress( - Transfer::RESOURCE_FILES, - time(), - $fileCounters['total'], - $fileCounters['current'], - $fileCounters['failed'], - $fileCounters['skipped'] - ) - ); + return $result; } - /** - * Import File Data - * - * @param FileData $filePart - * @param callable $callback (Progress $progress) - */ - protected function importFileData(FileData $filePart, callable $callback): void + public function importFunctionResource(Resource $resource): Resource { - $fileCounters = &$this->getCounter(Transfer::RESOURCE_FILES); + $functions = new Functions($this->client); try { - $file = $filePart->getFile(); - $bucketId = $file->getBucket()->getId(); - - $response = null; - - if ($file->getSize() <= (5 * 1024 * 1024)) { - $response = $this->client->call( - 'POST', - "/v1/storage/buckets/{$bucketId}/files", - [ - 'content-type' => 'multipart/form-data', - ], - [ - 'bucketId' => $bucketId, - 'fileId' => $file->getId(), - 'file' => new \CurlFile('data://' . $file->getMimeType() . ';base64,' . base64_encode($filePart->getData()), $file->getMimeType(), $file->getFileName()), - 'permissions' => $file->getPermissions(), - ] - ); - - $this->logs[Log::SUCCESS][] = new Log('File imported successfully', \time(), $file); - $fileCounters['current']++; - - return; + switch ($resource->getName()) { + case Resource::TYPE_FUNCTION: + /** @var Func $resource */ + $functions->create( + $resource->getId(), + $resource->getFunctionName(), + $resource->getRuntime(), + $resource->getExecute(), + $resource->getEvents(), + $resource->getSchedule(), + $resource->getTimeout(), + $resource->getEnabled() + ); + case Resource::TYPE_ENVVAR: + /** @var EnvVar $resource */ + $functions->createVariable( + $resource->getFunc()->getId(), + $resource->getKey(), + $resource->getValue() + ); } - $response = $this->client->call( - 'POST', - "/v1/storage/buckets/{$bucketId}/files", - [ - 'content-type' => 'multipart/form-data', - 'content-range' => 'bytes ' . ($filePart->getStart()) . '-' . ($filePart->getEnd() == ($file->getSize() - 1) ? $file->getSize() : $filePart->getEnd()) . '/' . $file->getSize(), - ], - [ - 'bucketId' => $bucketId, - 'fileId' => $file->getId(), - 'file' => new \CurlFile('data://' . $file->getMimeType() . ';base64,' . base64_encode($filePart->getData()), $file->getMimeType(), $file->getFileName()), - 'permissions' => $file->getPermissions(), - ] - ); - - if ($filePart->getEnd() == ($file->getSize() - 1)) { - // Signatures for Encrypted files are invalid, so we skip the check - if ($file->getBucket()->getEncryption() == false || $file->getSize() > (20 * 1024 * 1024)) { - if ($response['signature'] !== $file->getSignature()) { - $this->logs[Log::WARNING][] = new Log('File signature mismatch, Possibly corrupted.', \time(), $file); - } - } - - $this->logs[Log::SUCCESS][] = new Log('File imported successfully', \time(), $file); - $fileCounters['current']++; - } - } catch (AppwriteException $e) { - $this->logs[Log::ERROR][] = new Log($e->getMessage(), \time(), $file); - $fileCounters['failed']++; + $resource->setStatus(Resource::STATUS_SUCCESS); + } catch (\Exception $e) { + $resource->setStatus(Resource::STATUS_ERROR, $e->getMessage()); + } finally { + return $resource; } } } diff --git a/src/Transfer/Destinations/Local.php b/src/Transfer/Destinations/Local.php index 0fc6bdf..2dd5f5b 100644 --- a/src/Transfer/Destinations/Local.php +++ b/src/Transfer/Destinations/Local.php @@ -2,17 +2,10 @@ namespace Utopia\Transfer\Destinations; -use Appwrite\Client; -use Appwrite\Services\Users; use Utopia\Transfer\Destination; -use Utopia\Transfer\Resources\Hash; -use Utopia\Transfer\Log; -use Utopia\Transfer\Progress; -use Utopia\Transfer\Resources\Database; -use Utopia\Transfer\Resources\File; -use Utopia\Transfer\Resources\User; -use Utopia\Transfer\Resources\Bucket; -use Utopia\Transfer\Resources\FileData; +use Utopia\Transfer\Resources\Storage\File; +use Utopia\Transfer\Resources\Storage\FileData; +use Utopia\Transfer\Resource; use Utopia\Transfer\Transfer; /** @@ -55,11 +48,11 @@ public function getName(): string public function getSupportedResources(): array { return [ - Transfer::RESOURCE_USERS, - Transfer::RESOURCE_DATABASES, - Transfer::RESOURCE_DOCUMENTS, - Transfer::RESOURCE_FILES, - Transfer::RESOURCE_FUNCTIONS + Transfer::GROUP_AUTH, + Transfer::GROUP_DATABASES, + Transfer::GROUP_DOCUMENTS, + Transfer::GROUP_STORAGE, + Transfer::GROUP_FUNCTIONS ]; } @@ -97,146 +90,44 @@ public function syncFile(): void \file_put_contents($this->path . '/backup.json', \json_encode($this->data, JSON_PRETTY_PRINT)); } - /** - * Import Users - * - * @param array $users - * @param callable $callback - * - * @return void - */ - public function importUsers(array $users, callable $callback): void - { - $userCounters = &$this->getCounter(Transfer::RESOURCE_USERS); - - foreach ($users as $user) { - /** @var User $user */ - $this->data[Transfer::RESOURCE_USERS][] = $user->asArray(); - $this->logs[Log::SUCCESS][] = new Log('Users imported successfully', \time(), $user); - $userCounters['current']++; - } - - $callback( - new Progress( - Transfer::RESOURCE_USERS, - time(), - $userCounters['total'], - $userCounters['current'], - $userCounters['failed'], - $userCounters['skipped'] - ) - ); - - $this->syncFile(); - } - - /** - * Import Databases - * - * @param array $databases - * @param callable $callback - * - * @return void - */ - public function importDatabases(array $databases, callable $callback): void - { - $databaseCounters = &$this->getCounter(Transfer::RESOURCE_DATABASES); - - foreach ($databases as $database) { - /** @var Database $database */ - $this->data[Transfer::RESOURCE_DATABASES][] = $database->asArray(); - $this->logs[Log::SUCCESS][] = new Log('Database imported successfully', \time(), $database); - $databaseCounters['current']++; - } - - $callback( - new Progress( - Transfer::RESOURCE_DATABASES, - time(), - $databaseCounters['total'], - $databaseCounters['current'], - $databaseCounters['failed'], - $databaseCounters['skipped'] - ) - ); - - $this->syncFile(); - } - - /** - * Import Documents - * - * @param array $documents - * @param callable $callback - * - * @return void - */ - public function importDocuments(array $documents, callable $callback): void - { - $documentCounters = &$this->getCounter(Transfer::RESOURCE_DOCUMENTS); - - foreach ($documents as $document) { - /** @var Database $document */ - $this->data[Transfer::RESOURCE_DOCUMENTS][] = $document->asArray(); - $this->logs[Log::SUCCESS][] = new Log('Document imported successfully', \time(), $document); - $documentCounters['current']++; - } - - $callback( - new Progress( - Transfer::RESOURCE_DOCUMENTS, - time(), - $documentCounters['total'], - $documentCounters['current'], - $documentCounters['failed'], - $documentCounters['skipped'] - ) - ); - - $this->syncFile(); - } - - /** - * Import Files - * - * @param array $resource file[]|bucket[] - * @param callable $callback (Progress $progress) - */ - protected function importFiles(array $resources, callable $callback): void + public function importResources(array $resources, callable $callback, string $group): void { - $fileCounters = &$this->getCounter(Transfer::RESOURCE_FILES); - //TODO: Improve counters with a custom class, currently files and buckets share the same counter. - foreach ($resources as $resource) { - if ($resource instanceof File) { - $this->data[Transfer::RESOURCE_FILES][] = $resource->asArray(); - $this->logs[Log::SUCCESS][] = new Log('File imported successfully', \time(), $resource); - - if (\file_exists($this->path . '/files/' . $resource->getFileName())) { - \unlink($this->path . '/files/' . $resource->getFileName()); - } - - $fileCounters['current']++; - } elseif ($resource instanceof Bucket) { - $this->data[Transfer::RESOURCE_FILES][] = $resource->asArray(); - $this->logs[Log::SUCCESS][] = new Log('Bucket imported successfully', \time(), $resource); - $fileCounters['current']++; - } elseif ($resource instanceof FileData) { - file_put_contents($this->path . '/files/' . $resource->getFile()->getFileName(), $resource->getData(), FILE_APPEND); + /** @var Resource $resource */ + switch ($resource->getName()) { + case "FileData": { + /** @var FileData $resource */ + + // Handle folders + if (str_contains($resource->getFile()->getFileName(), '/')) { + $folders = explode('/', $resource->getFile()->getFileName()); + $folderPath = $this->path . '/files'; + + foreach ($folders as $folder) { + $folderPath .= '/' . $folder; + + if (!\file_exists($folderPath) && str_contains($folder, '.') === false) { + mkdir($folderPath, 0777, true); + } + } + } + + file_put_contents($this->path . '/files/' . $resource->getFile()->getFileName(), $resource->getData(), FILE_APPEND); + break; + } + case "File": { + /** @var File $resource */ + if (\file_exists($this->path . '/files/' . $resource->getFileName())) { + \unlink($this->path . '/files/' . $resource->getFileName()); + } + break; + } } - } - $callback( - new Progress( - Transfer::RESOURCE_FILES, - time(), - $fileCounters['total'], - $fileCounters['current'], - $fileCounters['failed'], - $fileCounters['skipped'] - ) - ); - - $this->syncFile(); + $this->data[$group][$resource->getName()][] = $resource->asArray(); + $resource->setStatus(Resource::STATUS_SUCCESS); + $this->resourceCache->update($resource); + $this->syncFile(); + } } } diff --git a/src/Transfer/Log.php b/src/Transfer/Log.php deleted file mode 100644 index d0cab36..0000000 --- a/src/Transfer/Log.php +++ /dev/null @@ -1,91 +0,0 @@ -message = $message; - $this->timestamp = $timestamp ?? \time(); - $this->resource = $resource; - } - /** - * Get Message - * - * @return string - */ - public function getMessage(): string - { - return $this->message; - } - - /** - * Set Message - * - * @param string $message - * @return self - */ - public function setMessage(string $message): self - { - $this->message = $message; - return $this; - } - - /** - * Get Timestamp - * - * @return int - */ - public function getTimestamp(): int - { - return $this->timestamp; - } - - /** - * Set Timestamp - * - * @param int $timestamp - * @return self - */ - public function setTimestamp(int $timestamp): self - { - $this->timestamp = $timestamp; - return $this; - } - - /** - * Get Resource - * - * @return Resource|null - */ - public function getResource(): ?Resource - { - return $this->resource; - } - - /** - * As Array - * - * @return array - */ - public function asArray(): array - { - return [ - 'message' => $this->message, - 'timestamp' => $this->timestamp, - 'resource' => $this->resource ? $this->resource->asArray() : null, - ]; - } -} diff --git a/src/Transfer/Resource.php b/src/Transfer/Resource.php index 617dfa2..9ca662e 100644 --- a/src/Transfer/Resource.php +++ b/src/Transfer/Resource.php @@ -4,11 +4,49 @@ abstract class Resource { + const STATUS_PENDING = 'PENDING'; + const STATUS_SUCCESS = 'SUCCESS'; + const STATUS_ERROR = 'ERROR'; + const STATUS_SKIPPED = 'SKIP'; + const STATUS_PROCESSING = 'PROCESSING'; + const STATUS_WARNING = 'WARNING'; + + const TYPE_ATTRIBUTE = 'Attribute'; + const TYPE_BUCKET = 'Bucket'; + const TYPE_COLLECTION = 'Collection'; + const TYPE_DATABASE = 'Database'; + const TYPE_DOCUMENT = 'Document'; + const TYPE_FILE = 'File'; + const TYPE_FILEDATA = 'FileData'; + const TYPE_FUNCTION = 'Function'; + const TYPE_HASH = 'Hash'; + const TYPE_INDEX = 'Index'; + const TYPE_PROJECT = 'Project'; + const TYPE_USER = 'User'; + const TYPE_ENVVAR = 'EnvVar'; + const TYPE_TEAM = 'Team'; + const TYPE_TEAM_MEMBERSHIP = 'TeamMembership'; + /** - * ID of the resource, if any. + * ID of the resource */ protected string $id = ''; + /** + * Internal ID + */ + protected string $internalId = ''; + + /** + * Status of the resource + */ + protected string $status = self::STATUS_PENDING; + + /** + * Reason for the status + */ + protected string $reason = ''; + /** * Gets the name of the adapter. * @@ -16,6 +54,13 @@ abstract class Resource */ abstract public function getName(): string; + /** + * Get Parent Group + * + * @return + */ + abstract public function getGroup(): string; + /** * Get ID * @@ -38,6 +83,52 @@ public function setId(string $id): self return $this; } + /** + * Get Internal ID + * + * @return string + */ + public function getInternalId(): string + { + return $this->internalId; + } + + /** + * Set Internal ID + * + * @param string $internalId + * @return self + */ + public function setInternalId(string $internalId): self + { + $this->internalId = $internalId; + return $this; + } + + /** + * Get Status + * + * @return string + */ + public function getStatus(): string + { + return $this->status; + } + + /** + * Set Status + * + * @param string $status + * @param string $reason + * @return self + */ + public function setStatus(string $status, string $reason = ''): self + { + $this->status = $status; + $this->reason = $reason; + return $this; + } + /** * As Array * diff --git a/src/Transfer/ResourceCache.php b/src/Transfer/ResourceCache.php new file mode 100644 index 0000000..c5cec51 --- /dev/null +++ b/src/Transfer/ResourceCache.php @@ -0,0 +1,83 @@ +resourceCache = []; + } + + public function add($resource) + { + $resourceUUID = uniqid(); + $resource->setInternalId($resourceUUID); // Assign each resource a unique ID + $this->resourceCache[get_class($resource)][$resourceUUID] = $resource; + } + + public function addAll($resources) + { + foreach ($resources as $resource) { + $this->add($resource); + } + } + + public function update($resource) + { + if (!in_array($resource, $this->resourceCache[get_class($resource)])) { + throw new \Exception('Resource does not exist in cache'); + } + + $this->resourceCache[get_class($resource)][$resource->getInternalId()] = $resource; + } + + public function updateAll($resources) + { + foreach ($resources as $resource) { + $this->update($resource); + } + } + + public function remove($resource) + { + if (!in_array($resource, $this->resourceCache[get_class($resource)])) { + throw new \Exception('Resource does not exist in cache'); + } + + unset($this->resourceCache[get_class($resource)][$resource->getInternalId()]); + } + + /** + * Get Resources + * + * @param string|Resource $resourceType + * + * @return Resource[] + */ + public function get($resource) + { + if (is_string($resource)) { + return $this->resourceCache[$resource] ?? []; + } else { + return $this->resourceCache[get_class($resource)] ?? []; + } + } + + public function getAll() + { + return $this->resourceCache; + } + + public function clear() + { + $this->resourceCache = []; + } +} diff --git a/src/Transfer/Resources/Hash.php b/src/Transfer/Resources/Auth/Hash.php similarity index 96% rename from src/Transfer/Resources/Hash.php rename to src/Transfer/Resources/Auth/Hash.php index 5aa8b9f..aaaf1a8 100644 --- a/src/Transfer/Resources/Hash.php +++ b/src/Transfer/Resources/Auth/Hash.php @@ -1,8 +1,9 @@ id = $id; + $this->name = $name; + $this->preferences = $preferences; + $this->members = $members; + } + + function getName(): string + { + return Resource::TYPE_TEAM; + } + + function getGroup(): string + { + return Transfer::GROUP_AUTH; + } + + function getTeamName(): string + { + return $this->name; + } + + function setTeamName(string $name): self + { + $this->name = $name; + return $this; + } + + function getId(): string + { + return $this->id; + } + + function setId(string $id): self + { + $this->id = $id; + return $this; + } + + function getPreferences(): array + { + return $this->preferences; + } + + function setPreferences(array $preferences): self + { + $this->preferences = $preferences; + return $this; + } + + function getMembers(): array + { + return $this->members; + } + + /** + * @param User[] $members + */ + function setMembers(array $members): self + { + $this->members = $members; + return $this; + } + + function asArray(): array + { + return [ + 'id' => $this->id, + 'name' => $this->name, + 'preferences' => $this->preferences, + ]; + } +} diff --git a/src/Transfer/Resources/Auth/TeamMembership.php b/src/Transfer/Resources/Auth/TeamMembership.php new file mode 100644 index 0000000..f8889e0 --- /dev/null +++ b/src/Transfer/Resources/Auth/TeamMembership.php @@ -0,0 +1,85 @@ +team = $team; + $this->userId = $userId; + $this->roles = $roles; + $this->active = $active; + } + + function getName(): string + { + return Resource::TYPE_TEAM_MEMBERSHIP; + } + + function getGroup(): string + { + return Transfer::GROUP_AUTH; + } + + function getTeam(): Team + { + return $this->team; + } + + function setTeam(Team $team): self + { + $this->team = $team; + return $this; + } + + function getUserId(): string + { + return $this->userId; + } + + function setUserId(string $userId): self + { + $this->userId = $userId; + return $this; + } + + function getRoles(): array + { + return $this->roles; + } + + function setRoles(array $roles): self + { + $this->roles = $roles; + return $this; + } + + function getActive(): bool + { + return $this->active; + } + + function setActive(bool $active): self + { + $this->active = $active; + return $this; + } + + function asArray(): array + { + return [ + 'userId' => $this->userId, + 'roles' => $this->roles, + 'active' => $this->active, + ]; + } +} diff --git a/src/Transfer/Resources/User.php b/src/Transfer/Resources/Auth/User.php similarity index 95% rename from src/Transfer/Resources/User.php rename to src/Transfer/Resources/Auth/User.php index a190622..eb9ddda 100644 --- a/src/Transfer/Resources/User.php +++ b/src/Transfer/Resources/Auth/User.php @@ -1,9 +1,10 @@ $this->id, 'email' => $this->email, 'username' => $this->username, - 'passwordHash' => $this->passwordHash->asArray(), + 'passwordHash' => $this->passwordHash ? $this->passwordHash->asArray() : null, 'phone' => $this->phone, 'types' => $this->types, 'oauthProvider' => $this->oauthProvider, diff --git a/src/Transfer/Resources/Attribute.php b/src/Transfer/Resources/Database/Attribute.php similarity index 70% rename from src/Transfer/Resources/Attribute.php rename to src/Transfer/Resources/Database/Attribute.php index ed832ab..e8cdd8e 100644 --- a/src/Transfer/Resources/Attribute.php +++ b/src/Transfer/Resources/Database/Attribute.php @@ -1,10 +1,11 @@ key = $key; $this->required = $required; $this->array = $array; + $this->collection = $collection; } public function getName(): string { - return 'attribute'; + return Resource::TYPE_ATTRIBUTE; + } + + abstract public function getTypeName(): string; + + public function getGroup(): string + { + return Transfer::GROUP_DATABASES; } public function getKey(): string @@ -50,6 +60,17 @@ public function setKey(string $key): self return $this; } + public function getCollection(): Collection + { + return $this->collection; + } + + public function setCollection(Collection $collection) + { + $this->collection = $collection; + return $this; + } + public function getRequired(): bool { return $this->required; diff --git a/src/Transfer/Resources/Attributes/BoolAttribute.php b/src/Transfer/Resources/Database/Attributes/BoolAttribute.php similarity index 61% rename from src/Transfer/Resources/Attributes/BoolAttribute.php rename to src/Transfer/Resources/Database/Attributes/BoolAttribute.php index 5d42f40..1547f0f 100644 --- a/src/Transfer/Resources/Attributes/BoolAttribute.php +++ b/src/Transfer/Resources/Database/Attributes/BoolAttribute.php @@ -1,8 +1,9 @@ default = $default; } - public function getName(): string + public function getTypeName(): string { return 'boolAttribute'; } diff --git a/src/Transfer/Resources/Attributes/DateTimeAttribute.php b/src/Transfer/Resources/Database/Attributes/DateTimeAttribute.php similarity index 53% rename from src/Transfer/Resources/Attributes/DateTimeAttribute.php rename to src/Transfer/Resources/Database/Attributes/DateTimeAttribute.php index 5a00685..b94d4f9 100644 --- a/src/Transfer/Resources/Attributes/DateTimeAttribute.php +++ b/src/Transfer/Resources/Database/Attributes/DateTimeAttribute.php @@ -1,8 +1,9 @@ default = $default; } @@ -30,7 +32,7 @@ public function setDefault(string $default): void $this->default = $default; } - public function getName(): string + public function getTypeName(): string { return 'dateTimeAttribute'; } diff --git a/src/Transfer/Resources/Attributes/EmailAttribute.php b/src/Transfer/Resources/Database/Attributes/EmailAttribute.php similarity index 52% rename from src/Transfer/Resources/Attributes/EmailAttribute.php rename to src/Transfer/Resources/Database/Attributes/EmailAttribute.php index 969f1a1..bd1ae66 100644 --- a/src/Transfer/Resources/Attributes/EmailAttribute.php +++ b/src/Transfer/Resources/Database/Attributes/EmailAttribute.php @@ -1,8 +1,9 @@ default = $default; } @@ -30,7 +32,7 @@ public function setDefault(string $default): void $this->default = $default; } - public function getName(): string + public function getTypeName(): string { return 'emailAttribute'; } diff --git a/src/Transfer/Resources/Attributes/EnumAttribute.php b/src/Transfer/Resources/Database/Attributes/EnumAttribute.php similarity index 68% rename from src/Transfer/Resources/Attributes/EnumAttribute.php rename to src/Transfer/Resources/Database/Attributes/EnumAttribute.php index 0207322..ad527f5 100644 --- a/src/Transfer/Resources/Attributes/EnumAttribute.php +++ b/src/Transfer/Resources/Database/Attributes/EnumAttribute.php @@ -1,8 +1,9 @@ default = $default; $this->elements = $elements; } - public function getName(): string + public function getTypeName(): string { return 'enumAttribute'; } diff --git a/src/Transfer/Resources/Attributes/FloatAttribute.php b/src/Transfer/Resources/Database/Attributes/FloatAttribute.php similarity index 72% rename from src/Transfer/Resources/Attributes/FloatAttribute.php rename to src/Transfer/Resources/Database/Attributes/FloatAttribute.php index 7bd9e3e..f4d62a0 100644 --- a/src/Transfer/Resources/Attributes/FloatAttribute.php +++ b/src/Transfer/Resources/Database/Attributes/FloatAttribute.php @@ -1,8 +1,9 @@ default = $default; $this->min = $min; $this->max = $max; } - public function getName(): string + public function getTypeName(): string { return 'floatAttribute'; } diff --git a/src/Transfer/Resources/Attributes/IPAttribute.php b/src/Transfer/Resources/Database/Attributes/IPAttribute.php similarity index 52% rename from src/Transfer/Resources/Attributes/IPAttribute.php rename to src/Transfer/Resources/Database/Attributes/IPAttribute.php index 4f73a54..b4eb26e 100644 --- a/src/Transfer/Resources/Attributes/IPAttribute.php +++ b/src/Transfer/Resources/Database/Attributes/IPAttribute.php @@ -1,8 +1,9 @@ default = $default; } @@ -30,7 +32,7 @@ public function setDefault(string $default): void $this->default = $default; } - public function getName(): string + public function getTypeName(): string { return 'IPAttribute'; } diff --git a/src/Transfer/Resources/Attributes/IntAttribute.php b/src/Transfer/Resources/Database/Attributes/IntAttribute.php similarity index 72% rename from src/Transfer/Resources/Attributes/IntAttribute.php rename to src/Transfer/Resources/Database/Attributes/IntAttribute.php index a1c1d8f..ec5175d 100644 --- a/src/Transfer/Resources/Attributes/IntAttribute.php +++ b/src/Transfer/Resources/Database/Attributes/IntAttribute.php @@ -1,8 +1,9 @@ default = $default; $this->min = $min; $this->max = $max; } - public function getName(): string + public function getTypeName(): string { return 'intAttribute'; } diff --git a/src/Transfer/Resources/Attributes/RelationshipAttribute.php b/src/Transfer/Resources/Database/Attributes/RelationshipAttribute.php similarity index 78% rename from src/Transfer/Resources/Attributes/RelationshipAttribute.php rename to src/Transfer/Resources/Database/Attributes/RelationshipAttribute.php index dd65147..34b99c0 100644 --- a/src/Transfer/Resources/Attributes/RelationshipAttribute.php +++ b/src/Transfer/Resources/Database/Attributes/RelationshipAttribute.php @@ -1,8 +1,9 @@ relatedCollection = $relatedCollection; $this->relationType = $relationType; $this->twoWay = $twoWay; @@ -35,7 +37,7 @@ public function __construct(string $key, bool $required = false, bool $array = f $this->side = $side; } - public function getName(): string + public function getTypeName(): string { return 'relationshipAttribute'; } diff --git a/src/Transfer/Resources/Attributes/StringAttribute.php b/src/Transfer/Resources/Database/Attributes/StringAttribute.php similarity index 66% rename from src/Transfer/Resources/Attributes/StringAttribute.php rename to src/Transfer/Resources/Database/Attributes/StringAttribute.php index f7fff7b..210c68d 100644 --- a/src/Transfer/Resources/Attributes/StringAttribute.php +++ b/src/Transfer/Resources/Database/Attributes/StringAttribute.php @@ -1,8 +1,9 @@ default = $default; $this->size = $size; } - public function getName(): string + public function getTypeName(): string { return 'stringAttribute'; } diff --git a/src/Transfer/Resources/Attributes/URLAttribute.php b/src/Transfer/Resources/Database/Attributes/URLAttribute.php similarity index 52% rename from src/Transfer/Resources/Attributes/URLAttribute.php rename to src/Transfer/Resources/Database/Attributes/URLAttribute.php index b427161..f1bf6c2 100644 --- a/src/Transfer/Resources/Attributes/URLAttribute.php +++ b/src/Transfer/Resources/Database/Attributes/URLAttribute.php @@ -1,8 +1,9 @@ default = $default; } @@ -30,7 +32,7 @@ public function setDefault(string $default): void $this->default = $default; } - public function getName(): string + public function getTypeName(): string { return 'URLAttribute'; } diff --git a/src/Transfer/Resources/Collection.php b/src/Transfer/Resources/Database/Collection.php similarity index 67% rename from src/Transfer/Resources/Collection.php rename to src/Transfer/Resources/Database/Collection.php index a34d4b6..126abb1 100644 --- a/src/Transfer/Resources/Collection.php +++ b/src/Transfer/Resources/Database/Collection.php @@ -1,8 +1,9 @@ database = $database; $this->name = $name; $this->id = $id; $this->documentSecurity = $documentSecurity; @@ -47,7 +51,23 @@ public function __construct(string $name = '', string $id = '', bool $documentSe public function getName(): string { - return 'collection'; + return Resource::TYPE_COLLECTION; + } + + public function getGroup(): string + { + return Transfer::GROUP_DATABASES; + } + + public function getDatabase(): Database + { + return $this->database; + } + + public function setDatabase(Database $database): self + { + $this->database = $database; + return $this; } public function getCollectionName(): string @@ -94,47 +114,11 @@ public function setPermissions(array $permissions): self return $this; } - public function getAttributes(): array - { - return $this->columns; - } - - /** - * @param list $columns - * @return self - */ - public function setAttributes(array $columns): self - { - $this->columns = $columns; - return $this; - } - - public function getIndexes(): array - { - return $this->indexes; - } - - /** - * @param list $indexes - * @return self - */ - public function setIndexes(array $indexes): self - { - $this->indexes = $indexes; - return $this; - } - public function asArray(): array { return [ 'name' => $this->name, 'id' => $this->id, - 'columns' => array_map(function ($column) { - return $column->asArray(); - }, $this->columns), - 'indexes' => array_map(function ($index) { - return $index->asArray(); - }, $this->indexes), 'permissions' => $this->permissions, 'documentSecurity' => $this->documentSecurity, ]; diff --git a/src/Transfer/Resources/Database.php b/src/Transfer/Resources/Database/Database.php similarity index 90% rename from src/Transfer/Resources/Database.php rename to src/Transfer/Resources/Database/Database.php index 38cb23f..a03b1ee 100644 --- a/src/Transfer/Resources/Database.php +++ b/src/Transfer/Resources/Database/Database.php @@ -1,8 +1,9 @@ $attributes * @param array $orders */ - public function __construct(string $id, string $key, string $type = '', array $attributes = [], array $orders = []) + public function __construct(string $id, string $key, Collection $collection, string $type = '', array $attributes = [], array $orders = []) { $this->id = $id; $this->key = $key; $this->type = $type; $this->attributes = $attributes; $this->orders = $orders; + $this->collection = $collection; } public function getName(): string { - return 'index'; + return Resource::TYPE_INDEX; + } + + public function getGroup(): string + { + return Transfer::GROUP_DATABASES; } public function getKey(): string @@ -47,6 +56,17 @@ public function setKey(string $key): self return $this; } + public function getCollection(): Collection + { + return $this->collection; + } + + public function setCollection(Collection $collection): self + { + $this->collection = $collection; + return $this; + } + public function getType(): string { return $this->type; diff --git a/src/Transfer/Resources/Functions/EnvVar.php b/src/Transfer/Resources/Functions/EnvVar.php new file mode 100644 index 0000000..9db7c42 --- /dev/null +++ b/src/Transfer/Resources/Functions/EnvVar.php @@ -0,0 +1,72 @@ +func = $func; + $this->key = $key; + $this->value = $value; + } + + public function getName(): string + { + return Resource::TYPE_ENVVAR; + } + + public function getGroup(): string + { + return Transfer::GROUP_FUNCTIONS; + } + + public function getFunc(): Func + { + return $this->func; + } + + public function setFunc(Func $func): self + { + $this->func = $func; + return $this; + } + + public function getKey(): string + { + return $this->key; + } + + public function setKey(string $key): self + { + $this->key = $key; + return $this; + } + + public function getValue(): string + { + return $this->value; + } + + public function setValue(string $value): self + { + $this->value = $value; + return $this; + } + + public function asArray(): array + { + return [ + 'func' => $this->func->getId(), + 'key' => $this->key, + 'value' => $this->value, + ]; + } +} diff --git a/src/Transfer/Resources/Functions/Func.php b/src/Transfer/Resources/Functions/Func.php new file mode 100644 index 0000000..ff27fd3 --- /dev/null +++ b/src/Transfer/Resources/Functions/Func.php @@ -0,0 +1,136 @@ +name = $name; + $this->id = $id; + $this->execute = $execute; + $this->enabled = $enabled; + $this->runtime = $runtime; + $this->events = $events; + $this->schedule = $schedule; + $this->timeout = $timeout; + } + + public function getName(): string + { + return Resource::TYPE_FUNCTION; + } + + public function getGroup(): string + { + return Transfer::GROUP_FUNCTIONS; + } + + public function getFunctionName(): string + { + return $this->name; + } + + public function getId(): string + { + return $this->id; + } + + public function setId(string $id): self + { + $this->id = $id; + return $this; + } + + public function getExecute(): array + { + return $this->execute; + } + + public function setExecute(array $execute): self + { + $this->execute = $execute; + return $this; + } + + public function getEnabled(): bool + { + return $this->enabled; + } + + public function setEnabled(bool $enabled): self + { + $this->enabled = $enabled; + return $this; + } + + public function getRuntime(): string + { + return $this->runtime; + } + + public function setRuntime(string $runtime): self + { + $this->runtime = $runtime; + return $this; + } + + public function getEvents(): array + { + return $this->events; + } + + public function setEvents(array $events): self + { + $this->events = $events; + return $this; + } + + public function getSchedule(): string + { + return $this->schedule; + } + + public function setSchedule(string $schedule): self + { + $this->schedule = $schedule; + return $this; + } + + public function getTimeout(): int + { + return $this->timeout; + } + + public function setTimeout(int $timeout): self + { + $this->timeout = $timeout; + return $this; + } + + public function asArray(): array + { + return [ + 'name' => $this->name, + 'id' => $this->id, + 'execute' => $this->execute, + 'enabled' => $this->enabled, + 'runtime' => $this->runtime, + 'events' => $this->events, + 'schedule' => $this->schedule, + 'timeout' => $this->timeout, + ]; + } +} diff --git a/src/Transfer/Resources/Project.php b/src/Transfer/Resources/Project.php index 9bab657..b0aabf3 100644 --- a/src/Transfer/Resources/Project.php +++ b/src/Transfer/Resources/Project.php @@ -3,6 +3,7 @@ namespace Utopia\Transfer\Resources; use Utopia\Transfer\Resource; +use Utopia\Transfer\Transfer; class Project extends Resource { @@ -17,7 +18,12 @@ public function __construct(string $name = '', string $id = '') public function getName(): string { - return 'project'; + return Resource::TYPE_PROJECT; + } + + public function getGroup(): string + { + return Transfer::GROUP_GENERAL; } public function getId(): string diff --git a/src/Transfer/Resources/Bucket.php b/src/Transfer/Resources/Storage/Bucket.php similarity index 95% rename from src/Transfer/Resources/Bucket.php rename to src/Transfer/Resources/Storage/Bucket.php index 926ef2e..bee9ae7 100644 --- a/src/Transfer/Resources/Bucket.php +++ b/src/Transfer/Resources/Storage/Bucket.php @@ -1,8 +1,9 @@ '', - ]; - - /** - * Logs - * - * @var array $logs - */ - protected $logs = []; - - /** - * Resource Cache - * - * @var array $resourceCache - */ - protected $resourceCache = [ - Transfer::RESOURCE_DATABASES => [], - Transfer::RESOURCE_DOCUMENTS => [], - Transfer::RESOURCE_FILES => [], - Transfer::RESOURCE_FUNCTIONS => [], - Transfer::RESOURCE_USERS => [], - ]; - - /** - * Counters - * - * @var array $counters - */ - protected $counters = []; - - /** - * Endpoint - * - * @var string $endpoint - */ - protected $endpoint = ''; - - /** - * Get Resource Counters - * - * @param string $resource = null - * - * @return array - */ - public function &getCounter(string $resource = null): array - { - if ($resource && $this->counters[$resource]) { - return $this->counters[$resource]; - } else { - $this->counters[$resource] = [ - 'total' => 0, - 'current' => 0, - 'failed' => 0, - 'skipped' => 0, - ]; - - return $this->counters[$resource]; - } - } - - /** - * Gets the name of the adapter. - * - * @return string - */ - abstract public function getName(): string; - - /** - * Get Supported Resources - * - * @return array - */ - abstract public function getSupportedResources(): array; - - /** - * Register Logs Array + * Transfer Groups into destination * - * @param array &$logs - */ - public function registerLogs(array &$logs): void - { - $this->logs = &$logs; - } - - /** - * Register Transfer Hooks - * - * @param array &$cache - * @param array &$counters - * - * @return void - */ - public function registerTransferHooks(array &$cache, array &$counters): void - { - $this->resourceCache = &$cache; - $this->counters = &$counters; - } - - /** - * Transfer Resources into destination - * - * @param array $resources + * @param array $groups * @param callable $callback */ - public function run(array $resources, callable $callback): void + public function run(array $groups, callable $callback): void { - foreach ($resources as $resource) { - if (!in_array($resource, $this->getSupportedResources())) { - throw new \Exception("Cannot Transfer unsupported resource: '" . $resource . "'"); - } + //TODO: Check we have no unsupported groups. - switch ($resource) { - case Transfer::RESOURCE_USERS: - $this->exportUsers(100, function (array $users) use ($callback) { - $this->resourceCache[Transfer::RESOURCE_USERS] = array_merge($this->resourceCache[Transfer::RESOURCE_USERS], $users); - $callback(Transfer::RESOURCE_USERS, $users); - }); - break; - case Transfer::RESOURCE_DATABASES: - $this->exportDatabases(100, function (array $databases) use ($callback) { - $this->resourceCache[Transfer::RESOURCE_DATABASES] = array_merge($this->resourceCache[Transfer::RESOURCE_DATABASES], $databases); - $callback(Transfer::RESOURCE_DATABASES, $databases); - }); - break; - case Transfer::RESOURCE_DOCUMENTS: - $this->exportDocuments(100, function (array $documents) use ($callback) { - $this->resourceCache[Transfer::RESOURCE_DOCUMENTS] = array_merge($this->resourceCache[Transfer::RESOURCE_DOCUMENTS], $documents); - $callback(Transfer::RESOURCE_DOCUMENTS, $documents); - }); - break; - case Transfer::RESOURCE_FILES: - $this->exportFiles(5, function (array $files) use ($callback) { - if (!$files[0] instanceof FileData) { - $this->resourceCache[Transfer::RESOURCE_FILES] = array_merge($this->resourceCache[Transfer::RESOURCE_FILES], $files); - } - $callback(Transfer::RESOURCE_FILES, $files); - }); - break; - case Transfer::RESOURCE_FUNCTIONS: - $this->exportFunctions(100, function (array $functions) use ($callback) { - $this->resourceCache[Transfer::RESOURCE_FUNCTIONS] = array_merge($this->resourceCache, $functions); - $callback(Transfer::RESOURCE_FUNCTIONS, $functions); - }); - break; - } + if (in_array(Transfer::GROUP_AUTH, $groups)) { + $this->exportAuth(100, function (array $users) use ($callback) { + $this->resourceCache->addAll($users); + $callback(Transfer::GROUP_AUTH, $users); + }); } - } - /** - * Check Requirements - * Performs a suite of API Checks, Resource Checks, etc... to ensure the adapter is ready to be used. - * This is highly recommended to be called before any other method after initialization. - * - * If no resources are provided, the method should check all resources. - * Returns a object with all the keys of the resources provided and a true|string value if the resource is available or not. - * If the resource is not available, the value should be a string with the error message. - * - * @string[] $resources - * - * @return string[] - */ - abstract public function check(array $resources = []): array; - - /** - * Call - * - * Make an API call - * - * @param string $method - * @param string $path - * @param array $params - * @param array $headers - * @return array|string - * @throws \Exception - */ - public function call(string $method, string $path = '', array $headers = array(), array $params = array()): array|string - { - $headers = array_merge($this->headers, $headers); - $ch = curl_init((str_contains($path, 'http') ? $path : $this->endpoint . $path . (($method == 'GET' && !empty($params)) ? '?' . http_build_query($params) : ''))); - $responseHeaders = []; - $responseStatus = -1; - $responseType = ''; - $responseBody = ''; - - switch ($headers['Content-Type']) { - case 'application/json': - $query = json_encode($params); - break; - - case 'multipart/form-data': - $query = $this->flatten($params); - break; - - default: - $query = http_build_query($params); - break; - } - - foreach ($headers as $i => $header) { - $headers[] = $i . ':' . $header; - unset($headers[$i]); + if (in_array(Transfer::GROUP_DATABASES, $groups)) { + $this->exportDatabases(100, function (array $databases) use ($callback) { + $this->resourceCache->addAll($databases); + $callback(Transfer::GROUP_DATABASES, $databases); + }); } - curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); - curl_setopt($ch, CURLOPT_USERAGENT, php_uname('s') . '-' . php_uname('r') . ':php-' . phpversion()); - curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); - curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); - curl_setopt($ch, CURLOPT_HEADERFUNCTION, function ($curl, $header) use (&$responseHeaders) { - $len = strlen($header); - $header = explode(':', strtolower($header), 2); - - if (count($header) < 2) { // ignore invalid headers - return $len; - } - - $responseHeaders[strtolower(trim($header[0]))] = trim($header[1]); - - return $len; - }); - - if ($method != 'GET') { - curl_setopt($ch, CURLOPT_POSTFIELDS, $query); + if (in_array(Transfer::GROUP_DOCUMENTS, $groups)) { + $this->exportDocuments(100, function (array $documents) use ($callback) { + $this->resourceCache->addAll($documents); + $callback(Transfer::GROUP_DOCUMENTS, $documents); + }); } - $responseBody = curl_exec($ch); - - $responseType = $responseHeaders['Content-Type'] ?? ''; - $responseStatus = curl_getinfo($ch, CURLINFO_HTTP_CODE); - - switch (substr($responseType, 0, strpos($responseType, ';'))) { - case 'application/json': - $responseBody = json_decode($responseBody, true); - break; + if (in_array(Transfer::GROUP_STORAGE, $groups)) { + $this->exportFiles(100, function (array $files) use ($callback) { + $this->resourceCache->addAll($files); + $callback(Transfer::GROUP_STORAGE, $files); + }); } - if (curl_errno($ch)) { - throw new \Exception(curl_error($ch)); + if (in_array(Transfer::GROUP_FUNCTIONS, $groups)) { + $this->exportFunctions(100, function (array $functions) use ($callback) { + $this->resourceCache->addAll($functions); + $callback(Transfer::GROUP_FUNCTIONS, $functions); + }); } - - curl_close($ch); - - if ($responseStatus >= 400) { - if (is_array($responseBody)) { - throw new \Exception(json_encode($responseBody)); - } else { - throw new \Exception($responseStatus . ': ' . $responseBody); - } - } - - return $responseBody; - } - - /** - * Flatten params array to PHP multiple format - * - * @param array $data - * @param string $prefix - * @return array - */ - protected function flatten(array $data, string $prefix = ''): array - { - $output = []; - - foreach ($data as $key => $value) { - $finalKey = $prefix ? "{$prefix}[{$key}]" : $key; - - if (is_array($value)) { - $output += $this->flatten($value, $finalKey); // @todo: handle name collision here if needed - } else { - $output[$finalKey] = $value; - } - } - - return $output; } /** @@ -298,10 +58,7 @@ protected function flatten(array $data, string $prefix = ''): array * * @return void */ - public function exportUsers(int $batchSize, callable $callback): void - { - throw new Exception('Unimplemented, Please check if your source adapter supports this method.'); - } + abstract public function exportAuth(int $batchSize, callable $callback): void; /** * Export Databases @@ -311,10 +68,7 @@ public function exportUsers(int $batchSize, callable $callback): void * * @return void */ - public function exportDatabases(int $batchSize, callable $callback): void - { - throw new Exception('Unimplemented, Please check if your source adapter supports this method.'); - } + abstract public function exportDatabases(int $batchSize, callable $callback): void; /** * Export Documents @@ -324,10 +78,7 @@ public function exportDatabases(int $batchSize, callable $callback): void * * @return void */ - public function exportDocuments(int $batchSize, callable $callback): void - { - throw new Exception('Unimplemented, Please check if your source adapter supports this method.'); - } + abstract public function exportDocuments(int $batchSize, callable $callback): void; /** * Export Files @@ -337,10 +88,7 @@ public function exportDocuments(int $batchSize, callable $callback): void * * @return void */ - public function exportFiles(int $batchSize, callable $callback): void - { - throw new Exception('Unimplemented, Please check if your source adapter supports this method.'); - } + abstract public function exportFiles(int $batchSize, callable $callback): void; /** * Export Functions @@ -350,8 +98,5 @@ public function exportFiles(int $batchSize, callable $callback): void * * @return void */ - public function exportFunctions(int $batchSize, callable $callback): void - { - throw new Exception('Unimplemented, Please check if your source adapter supports this method.'); - } + abstract public function exportFunctions(int $batchSize, callable $callback): void; } diff --git a/src/Transfer/Sources/Appwrite.php b/src/Transfer/Sources/Appwrite.php index 42a2733..086e228 100644 --- a/src/Transfer/Sources/Appwrite.php +++ b/src/Transfer/Sources/Appwrite.php @@ -6,30 +6,35 @@ use Appwrite\Client; use Appwrite\Query; use Appwrite\Services\Databases; +use Appwrite\Services\Functions; use Appwrite\Services\Storage; +use Appwrite\Services\Teams; use Appwrite\Services\Users; -use Utopia\Transfer\Log; -use Utopia\Transfer\Resources\Attribute; -use Utopia\Transfer\Resources\User; +use Utopia\Transfer\Resources\Database\Attribute; use Utopia\Transfer\Transfer; -use Utopia\Transfer\Resources\Attributes\BoolAttribute; -use Utopia\Transfer\Resources\Attributes\DateTimeAttribute; -use Utopia\Transfer\Resources\Attributes\EmailAttribute; -use Utopia\Transfer\Resources\Attributes\EnumAttribute; -use Utopia\Transfer\Resources\Attributes\FloatAttribute; -use Utopia\Transfer\Resources\Attributes\IntAttribute; -use Utopia\Transfer\Resources\Attributes\IPAttribute; -use Utopia\Transfer\Resources\Attributes\StringAttribute; -use Utopia\Transfer\Resources\Attributes\URLAttribute; -use Utopia\Transfer\Resources\Attributes\RelationshipAttribute; -use Utopia\Transfer\Resources\Collection; -use Utopia\Transfer\Resources\Database; -use Utopia\Transfer\Resources\Document; -use Utopia\Transfer\Resources\Hash; -use Utopia\Transfer\Resources\Index; -use Utopia\Transfer\Resources\Bucket; -use Utopia\Transfer\Resources\File; -use Utopia\Transfer\Resources\FileData; +use Utopia\Transfer\Resources\Database\Attributes\BoolAttribute; +use Utopia\Transfer\Resources\Database\Attributes\DateTimeAttribute; +use Utopia\Transfer\Resources\Database\Attributes\EmailAttribute; +use Utopia\Transfer\Resources\Database\Attributes\EnumAttribute; +use Utopia\Transfer\Resources\Database\Attributes\FloatAttribute; +use Utopia\Transfer\Resources\Database\Attributes\IntAttribute; +use Utopia\Transfer\Resources\Database\Attributes\IPAttribute; +use Utopia\Transfer\Resources\Database\Attributes\StringAttribute; +use Utopia\Transfer\Resources\Database\Attributes\URLAttribute; +use Utopia\Transfer\Resources\Database\Attributes\RelationshipAttribute; +use Utopia\Transfer\Resources\Database\Collection; +use Utopia\Transfer\Resources\Database\Database; +use Utopia\Transfer\Resources\Database\Document; +use Utopia\Transfer\Resources\Auth\User; +use Utopia\Transfer\Resources\Auth\Hash; +use Utopia\Transfer\Resources\Database\Index; +use Utopia\Transfer\Resources\Storage\Bucket; +use Utopia\Transfer\Resources\Functions\EnvVar; +use Utopia\Transfer\Resources\Storage\File; +use Utopia\Transfer\Resources\Storage\FileData; +use Utopia\Transfer\Resources\Functions\Func; +use Utopia\Transfer\Resources\Auth\Team; +use Utopia\Transfer\Resources\Auth\TeamMembership; class Appwrite extends Source { @@ -87,10 +92,11 @@ public function getName(): string public function getSupportedResources(): array { return [ - Transfer::RESOURCE_USERS, - Transfer::RESOURCE_DATABASES, - Transfer::RESOURCE_DOCUMENTS, - Transfer::RESOURCE_FILES, + Transfer::GROUP_AUTH, + Transfer::GROUP_DATABASES, + Transfer::GROUP_DOCUMENTS, + Transfer::GROUP_STORAGE, + Transfer::GROUP_FUNCTIONS ]; } @@ -113,7 +119,7 @@ public function check(array $resources = []): array foreach ($resources as $resource) { switch ($resource) { - case Transfer::RESOURCE_DATABASES: + case Transfer::GROUP_DATABASES: $databases = new Databases($this->client); try { $databases->list(); @@ -124,7 +130,7 @@ public function check(array $resources = []): array } } break; - case Transfer::RESOURCE_USERS: + case Transfer::GROUP_AUTH: $auth = new Users($this->client); try { $auth->list(); @@ -135,7 +141,7 @@ public function check(array $resources = []): array } } break; - case Transfer::RESOURCE_DOCUMENTS: + case Transfer::GROUP_DOCUMENTS: $databases = new Databases($this->client); try { $databases->list(); @@ -240,19 +246,21 @@ public function check(array $resources = []): array } /** - * Export Users + * Export Auth Resources * * @param int $batchSize Max 100 * @param callable $callback Callback function to be called after each batch, $callback(user[] $batch); * * @return void */ - public function exportUsers(int $batchSize, callable $callback): void + public function exportAuth(int $batchSize, callable $callback): void { $usersClient = new Users($this->client); + $teamsClient = new Teams($this->client); $lastDocument = null; + // Export Users while (true) { $users = []; @@ -264,12 +272,16 @@ public function exportUsers(int $batchSize, callable $callback): void $response = $usersClient->list($queries); + if ($response["total"] == 0) { + break; + } + foreach ($response["users"] as $user) { $users[] = new User( $user['$id'], $user["email"], $user["name"], - new Hash($user["password"], $user["hash"]), + $user["password"] ? new Hash($user["password"], $user["hash"]) : null, $user["phone"], $this->calculateTypes($user), "", @@ -288,15 +300,91 @@ public function exportUsers(int $batchSize, callable $callback): void break; } } + + $lastDocument = null; + + // Export Teams + while (true) { + $teams = []; + + $queries = [Query::limit($batchSize)]; + + if ($lastDocument) { + $queries[] = Query::cursorAfter($lastDocument); + } + + $response = $teamsClient->list($queries); + + if ($response["total"] == 0) { + break; + } + + foreach ($response["teams"] as $team) { + $teams[] = new Team( + $team['$id'], + $team["name"], + $team["prefs"], + ); + + $lastDocument = $team['$id']; + } + + $callback($teams); + + if (count($teams) < $batchSize) { + break; + } + } + + $lastDocument = null; + + // Export Memberships + $cacheTeams = $this->resourceCache->get(Team::class); + + foreach ($cacheTeams as $team) { + while (true) { + $memberships = []; + + $queries = [Query::limit($batchSize)]; + + if ($lastDocument) { + $queries[] = Query::cursorAfter($lastDocument); + } + + $response = $teamsClient->listMemberships($team->getId(), $queries); + + if ($response["total"] == 0) { + break; + } + + foreach ($response["memberships"] as $membership) { + $memberships[] = new TeamMembership( + $team, + $membership['userId'], + $membership['roles'], + $membership['confirm'] + ); + + $lastDocument = $membership['$id']; + } + + $callback($memberships); + + if (count($memberships) < $batchSize) { + break; + } + } + } } - public function convertAttribute(array $value): Attribute + public function convertAttribute(array $value, Collection $collection): Attribute { switch ($value["type"]) { case "string": if (!isset($value["format"])) { return new StringAttribute( $value["key"], + $collection, $value["required"], $value["array"], $value["default"], @@ -308,6 +396,7 @@ public function convertAttribute(array $value): Attribute case "email": return new EmailAttribute( $value["key"], + $collection, $value["required"], $value["array"], $value["default"] @@ -315,6 +404,7 @@ public function convertAttribute(array $value): Attribute case "enum": return new EnumAttribute( $value["key"], + $collection, $value["elements"], $value["required"], $value["array"], @@ -323,6 +413,7 @@ public function convertAttribute(array $value): Attribute case "url": return new URLAttribute( $value["key"], + $collection, $value["required"], $value["array"], $value["default"] @@ -330,6 +421,7 @@ public function convertAttribute(array $value): Attribute case "ip": return new IPAttribute( $value["key"], + $collection, $value["required"], $value["array"], $value["default"] @@ -337,6 +429,7 @@ public function convertAttribute(array $value): Attribute case "datetime": return new DateTimeAttribute( $value["key"], + $collection, $value["required"], $value["array"], $value["default"] @@ -344,6 +437,7 @@ public function convertAttribute(array $value): Attribute default: return new StringAttribute( $value["key"], + $collection, $value["required"], $value["array"], $value["default"], @@ -353,6 +447,7 @@ public function convertAttribute(array $value): Attribute case "boolean": return new BoolAttribute( $value["key"], + $collection, $value["required"], $value["array"], $value["default"] @@ -360,6 +455,7 @@ public function convertAttribute(array $value): Attribute case "integer": return new IntAttribute( $value["key"], + $collection, $value["required"], $value["array"], $value["default"], @@ -369,6 +465,7 @@ public function convertAttribute(array $value): Attribute case "double": return new FloatAttribute( $value["key"], + $collection, $value["required"], $value["array"], $value["default"], @@ -378,6 +475,7 @@ public function convertAttribute(array $value): Attribute case "relationship": return new RelationshipAttribute( $value["key"], + $collection, $value["required"], $value["array"], $value["relatedCollection"], @@ -396,7 +494,7 @@ public function convertAttribute(array $value): Attribute * Export Databases * * @param int $batchSize Max 100 - * @param callable $callback Callback function to be called after each database, $callback(database[] $batch); + * @param callable $callback Callback function to be called after each database, $callback(Resource $batch); * * @return void */ @@ -406,9 +504,9 @@ public function exportDatabases(int $batchSize, callable $callback): void $lastDocument = null; + // Transfer Databases while (true) { $queries = [Query::limit($batchSize)]; - $databases = []; if ($lastDocument) { @@ -423,52 +521,119 @@ public function exportDatabases(int $batchSize, callable $callback): void $database['$id'] ); - $collections = $databaseClient->listCollections( - $database['$id'] + $databases[] = $newDatabase; + } + + $callback($databases); + + if (count($databases) < $batchSize) { + break; + } + } + + // Transfer Collections + $lastDocument = null; + + $databases = $this->resourceCache->get(Database::class); + foreach ($databases as $database) { + while (true) { + $queries = [Query::limit($batchSize)]; + $collections = []; + + if ($lastDocument) { + $queries[] = Query::cursorAfter($lastDocument); + } + + $response = $databaseClient->listCollections( + $database->getId(), + $queries ); - $generalCollections = []; - foreach ($collections["collections"] as $collection) { + foreach ($response["collections"] as $collection) { $newCollection = new Collection( + $database, $collection["name"], $collection['$id'], $collection["documentSecurity"], $collection['$permissions'] ); - $attributes = []; - $indexes = []; + $collections[] = $newCollection; + } - foreach ($collection["attributes"] as $attribute) { - $attributes[] = $this->convertAttribute($attribute); - } + $callback($collections); - foreach ($collection["indexes"] as $index) { - $indexes[] = new Index( - "unique()", - $index["key"], - $index["type"], - $index["attributes"], - $index["orders"] - ); - } + if (count($collections) < $batchSize) { + break; + } + } + } + + // Transfer Attributes + $lastDocument = null; + $collections = $this->resourceCache->get(Collection::class); + /** @var Collection[] $collections */ - $newCollection->setAttributes($attributes); - $newCollection->setIndexes($indexes); + foreach ($collections as $collection) { + while (true) { + $queries = [Query::limit($batchSize)]; + $attributes = []; - $generalCollections[] = $newCollection; + if ($lastDocument) { + $queries[] = Query::cursorAfter($lastDocument); } - $newDatabase->setCollections($generalCollections); - $databases[] = $newDatabase; + $response = $databaseClient->listAttributes( + $collection->getDatabase()->getId(), + $collection->getId(), + $queries + ); + + foreach ($response["attributes"] as $attribute) { + $attributes[] = $this->convertAttribute($attribute, $collection); + } + + $callback($attributes); - $lastDocument = $database['$id']; + if (count($attributes) < $batchSize) { + break; + } } + } - $callback($databases); + // Transfer Indexes + $lastDocument = null; + foreach ($collections as $collection) { + while (true) { + $queries = [Query::limit($batchSize)]; + $indexes = []; - if (count($response["databases"]) < $batchSize) { - break; + if ($lastDocument) { + $queries[] = Query::cursorAfter($lastDocument); + } + + $response = $databaseClient->listIndexes( + $collection->getDatabase()->getId(), + $collection->getId(), + $queries + ); + + foreach ($response["indexes"] as $index) { + $indexes[] = new Index( + "unique()", + $index["key"], + $collection, + $index["type"], + $index["attributes"], + $index["orders"] + ); + } + + $callback($indexes); + + if (count($indexes) < $batchSize) { + break; + } } } } @@ -477,64 +642,58 @@ public function exportDatabases(int $batchSize, callable $callback): void * Export Documents * * @param int $batchSize Max 100 - * @param callable $callback Callback function to be called after each batch, $callback(document[] $batch); + * @param callable $callback Callback function to be called after each batch, $callback(Document[] $batch); * * @return void */ public function exportDocuments(int $batchSize, callable $callback): void { $databaseClient = new Databases($this->client); + $collections = $this->resourceCache->get(Collection::class); - $databases = $this->resourceCache[Transfer::RESOURCE_DATABASES]; - - foreach ($databases as $database) { - /** @var Database $database */ - $collections = $database->getCollections(); + foreach ($collections as $collection) { + /** @var Collection $collection */ + $lastDocument = null; - foreach ($collections as $collection) { - /** @var Collection $collection */ - $lastDocument = null; + while (true) { + $queries = [Query::limit($batchSize)]; - while (true) { - $queries = [Query::limit($batchSize)]; + $documents = []; - $documents = []; + if ($lastDocument) { + $queries[] = Query::cursorAfter($lastDocument); + } - if ($lastDocument) { - $queries[] = Query::cursorAfter($lastDocument); - } + $response = $databaseClient->listDocuments( + $collection->getDatabase()->getId(), + $collection->getId(), + $queries + ); - $response = $databaseClient->listDocuments( - $database->getId(), - $collection->getId(), - $queries + foreach ($response["documents"] as $document) { + $id = $document['$id']; + $permissions = $document['$permissions']; + unset($document['$id']); + unset($document['$permissions']); + unset($document['$collectionId']); + unset($document['$updatedAt']); + unset($document['$createdAt']); + unset($document['$databaseId']); + + $documents[] = new Document( + $id, + $collection->getDatabase(), + $collection, + $document, + $permissions ); + $lastDocument = $id; + } - foreach ($response["documents"] as $document) { - $id = $document['$id']; - $permissions = $document['$permissions']; - unset($document['$id']); - unset($document['$permissions']); - unset($document['$collectionId']); - unset($document['$updatedAt']); - unset($document['$createdAt']); - unset($document['$databaseId']); - - $documents[] = new Document( - $id, - $database, - $collection, - $document, - $permissions - ); - $lastDocument = $id; - } - - $callback($documents); + $callback($documents); - if (count($response["documents"]) < $batchSize) { - break; - } + if (count($response["documents"]) < $batchSize) { + break; } } } @@ -628,6 +787,7 @@ public function exportFiles(int $batchSize, callable $callback): void $file['$permissions'], $file['sizeOriginal'], ); + $lastDocument = $file['$id']; } @@ -654,9 +814,8 @@ public function exportFiles(int $batchSize, callable $callback): void protected function streamFile(File $file, callable $callback): void { // Set the chunk size (5MB) - $chunkSize = 5 * 1024 * 1024; $start = 0; - $end = $chunkSize - 1; + $end = Transfer::STORAGE_MAX_CHUNK_SIZE - 1; // Get the file size $fileSize = $file->getSize(); @@ -684,7 +843,6 @@ protected function streamFile(File $file, callable $callback): void $status = curl_getinfo($ch, CURLINFO_HTTP_CODE); if ($status !== 200 && $status !== 206) { - $this->logs[Log::FATAL][] = new Log('Failed to download file, Error: ' . $chunkData); throw new \Exception('Failed to download file, Error: ' . $chunkData); } @@ -697,8 +855,8 @@ protected function streamFile(File $file, callable $callback): void )]); // Update the range - $start += $chunkSize; - $end += $chunkSize; + $start += Transfer::STORAGE_MAX_CHUNK_SIZE; + $end += Transfer::STORAGE_MAX_CHUNK_SIZE; if ($end > $fileSize) { $end = $fileSize - 1; @@ -708,4 +866,48 @@ protected function streamFile(File $file, callable $callback): void // Close cURL curl_close($ch); } + + /** + * Export Functions + * + * @param int $batchSize Max 5 + * @param callable $callback Callback function to be called after each batch, $callback(Function[] $batch); + */ + public function exportFunctions(int $batchSize, callable $callback): void + { + $functionsClient = new Functions($this->client); + + $functions = $functionsClient->list(); + + if ($functions['total'] === 0) { + return; + } + + $convertedResources = []; + + foreach ($functions['functions'] as $function) { + $convertedFunc = new Func( + $function['name'], + $function['$id'], + $function['runtime'], + $function['execute'], + $function['enabled'], + $function['events'], + $function['schedule'], + $function['timeout'] + ); + + $convertedResources[] = $convertedFunc; + + foreach ($function['vars'] as $var) { + $convertedResources[] = new EnvVar( + $convertedFunc, + $var['key'], + $var['value'], + ); + } + } + + $callback($convertedResources); + } } diff --git a/src/Transfer/Sources/Firebase.php b/src/Transfer/Sources/Firebase.php index 7bd37a6..60b088a 100644 --- a/src/Transfer/Sources/Firebase.php +++ b/src/Transfer/Sources/Firebase.php @@ -9,15 +9,15 @@ use Utopia\Transfer\Resources\User; use Utopia\Transfer\Transfer; use Utopia\Transfer\Log; -use Utopia\Transfer\Resources\Attribute as ResourcesAttribute; -use Utopia\Transfer\Resources\Attributes\BoolAttribute; -use Utopia\Transfer\Resources\Attributes\DateTimeAttribute; -use Utopia\Transfer\Resources\Attributes\FloatAttribute; -use Utopia\Transfer\Resources\Attributes\IntAttribute; -use Utopia\Transfer\Resources\Attributes\StringAttribute; -use Utopia\Transfer\Resources\Collection; +use Utopia\Transfer\Resources\Database\Attribute as ResourcesAttribute; +use Utopia\Transfer\Resources\Database\Attributes\BoolAttribute; +use Utopia\Transfer\Resources\Database\Attributes\DateTimeAttribute; +use Utopia\Transfer\Resources\Database\Attributes\FloatAttribute; +use Utopia\Transfer\Resources\Database\Attributes\IntAttribute; +use Utopia\Transfer\Resources\Database\Attributes\StringAttribute; +use Utopia\Transfer\Resources\Database\Collection; use Utopia\Transfer\Resources\Database; -use Utopia\Transfer\Resources\Hash; +use Utopia\Transfer\Resources\Auth\Hash; class Firebase extends Source { @@ -74,8 +74,8 @@ public function getName(): string public function getSupportedResources(): array { return [ - Transfer::RESOURCE_USERS, - Transfer::RESOURCE_DATABASES + Transfer::GROUP_AUTH, + Transfer::GROUP_DATABASES ]; } @@ -135,7 +135,7 @@ public function getProject(): Project|null * * @return void */ - public function exportUsers(int $batchSize, callable $callback): void + public function exportAuth(int $batchSize, callable $callback): void { if (!$this->project || !$this->project->getId()) { $this->logs[Log::FATAL][] = new Log('Project not set'); @@ -277,7 +277,7 @@ public function calculateType(string $key, \Google\Service\Firestore\Value $fiel } /** - * Predict Schema + * Calculate Schema * * @param int $batchSize Max 500 * @param $collection Collection @@ -285,7 +285,7 @@ public function calculateType(string $key, \Google\Service\Firestore\Value $fiel * * @return list **/ - public function predictSchema(int $batchSize, Collection $collection, array &$newCollections) + public function calculateSchema(int $batchSize, Collection $collection, array &$newCollections) { $attributes = []; @@ -315,7 +315,7 @@ public function predictSchema(int $batchSize, Collection $collection, array &$ne return $name . '/' . $subcollection; }, $subcollections); - $newCollections = array_merge($newCollections, $this->handleCollections($subcollections)); + $newCollections = array_merge($newCollections, $this->handleCollections($subcollections, $collection->getDatabase())); } return $attributes; @@ -325,17 +325,18 @@ public function predictSchema(int $batchSize, Collection $collection, array &$ne * Handle Collections * * @param string[] $collectionIDs + * @param Database $database * * @return Collection[] */ - public function handleCollections(array $collectionIDs): array + public function handleCollections(array $collectionIDs, Database $database): array { $collections = []; foreach ($collectionIDs as $collectionID) { - $collection = new Collection($collectionID, $collectionID); + $collection = new Collection($database, $collectionID, $collectionID); - $collection->setAttributes($this->predictSchema(500, $collection, $collections)); + $collection->setAttributes($this->calculateSchema(500, $collection, $collections)); $collections[] = $collection; } @@ -370,7 +371,7 @@ public function exportDatabases(int $batchSize, callable $callback): void $database = new Database('Default', 'Default', Database::DB_NON_RELATIONAL); - $database->setCollections($this->handleCollections($request['collectionIds'])); + $database->setCollections($this->handleCollections($request['collectionIds'], $database)); $callback([$database]); } @@ -428,7 +429,7 @@ public function check(array $resources = []): array foreach ($resources as $resource) { switch ($resource) { - case Transfer::RESOURCE_USERS: + case Transfer::GROUP_AUTH: $firebase = new \Google\Service\FirebaseManagement($this->googleClient); $request = $firebase->projects->listProjects(); @@ -452,9 +453,9 @@ public function check(array $resources = []): array return $report; } - $completedResources[] = Transfer::RESOURCE_USERS; + $completedResources[] = Transfer::GROUP_AUTH; break; - case Transfer::RESOURCE_DATABASES: + case Transfer::GROUP_DATABASES: $firestore = new \Google\Service\Firestore($this->googleClient); $request = $firestore->projects_databases_documents->listDocuments('projects/' . $this->project->getId() . '/databases/(default)/documents', '', [ @@ -471,4 +472,19 @@ public function check(array $resources = []): array return $report; } + + public function exportDocuments(int $batchSize, callable $callback): void + { + throw new \Exception('Not Implemented'); + } + + public function exportFiles(int $batchSize, callable $callback): void + { + throw new \Exception('Not Implemented'); + } + + public function exportFunctions(int $batchSize, callable $callback): void + { + throw new \Exception('Not Implemented'); + } } diff --git a/src/Transfer/Sources/FirebaseG2.php b/src/Transfer/Sources/FirebaseG2.php new file mode 100644 index 0000000..5668102 --- /dev/null +++ b/src/Transfer/Sources/FirebaseG2.php @@ -0,0 +1,190 @@ +serviceAccount = $serviceAccount; + $this->projectID = $projectID; + } + + public function getName(): string + { + return 'Firebase'; + } + + function base64url_encode($data) + { + return str_replace(['+', '/', '='], ['-', '_', ''], base64_encode($data)); + } + + function calculateJWT(): string + { + $jwtClaim = [ + 'iss' => $this->serviceAccount['client_email'], + 'scope' => 'https://www.googleapis.com/auth/firebase https://www.googleapis.com/auth/cloud-platform', + 'exp' => time() + 3600, + 'iat' => time(), + 'aud' => 'https://oauth2.googleapis.com/token' + ]; + + $jwtHeader = [ + 'alg' => 'RS256', + 'typ' => 'JWT' + ]; + + $jwtPayload = $this->base64url_encode(json_encode($jwtHeader)) . '.' . $this->base64url_encode(json_encode($jwtClaim)); + + $jwtSignature = ''; + openssl_sign($jwtPayload, $jwtSignature, $this->serviceAccount['private_key'], 'sha256'); + $jwtSignature = $this->base64url_encode($jwtSignature); + + return $jwtPayload . '.' . $jwtSignature; + } + + /** + * Computes the JWT then fetches an auth token from the Google OAuth2 API which is valid for an hour + */ + function authenticate() + { + if (time() < $this->tokenExpires) { + return; + } + + $response = $this->call('POST', 'https://oauth2.googleapis.com/token', [ + 'Content-Type' => 'application/x-www-form-urlencoded', + ], [ + 'grant_type' => 'urn:ietf:params:oauth:grant-type:jwt-bearer', + 'assertion' => $this->calculateJWT() + ]); + + $this->currentToken = $response['access_token']; + $this->tokenExpires = time() + $response['expires_in']; + $this->headers['Authorization'] = 'Bearer ' . $this->currentToken; + } + + public function getSupportedResources(): array + { + return [ + Transfer::GROUP_AUTH + ]; + } + + public function check(array $resources = []): array + { + throw new \Exception('Not implemented'); + } + + public function exportAuth(int $batchSize, callable $callback): void + { + $this->authenticate(); + + // Fetch our Hash Config + $hashConfig = ($this->call('GET', 'https://identitytoolkit.googleapis.com/admin/v2/projects/' . $this->projectID . '/config'))["signIn"]["hashConfig"]; + + $nextPageToken = null; + + // Transfer Users + while (true) { + $users = []; + + $request = [ + "targetProjectId" => $this->projectID, + "maxResults" => $batchSize, + ]; + + if ($nextPageToken) { + $request["nextPageToken"] = $nextPageToken; + } + + $response = $this->call('POST', 'https://identitytoolkit.googleapis.com/identitytoolkit/v3/relyingparty/downloadAccount', [ + 'Content-Type' => 'application/json', + ], $request); + + $result = $response["users"]; + $nextPageToken = $response["nextPageToken"] ?? null; + + foreach ($result as $user) { + $users[] = new User( + $user["localId"] ?? '', + $user["email"] ?? '', + $user["displayName"] ?? $user["email"] ?? '', + new Hash($user["passwordHash"] ?? '', $user["salt"] ?? '', Hash::SCRYPT_MODIFIED, $hashConfig["saltSeparator"], $hashConfig["signerKey"]), + $user["phoneNumber"] ?? '', + $this->calculateUserType($user['providerUserInfo'] ?? []), + '', + $user["emailVerified"], + false, // Can't get phone number status on firebase :/ + $user["disabled"] + ); + } + + $callback($users); + + if (count($result) < $batchSize) { + break; + } + } + } + + public function calculateUserType(array $providerData): array + { + if (count($providerData) === 0) { + return [User::TYPE_ANONYMOUS]; + } + + $types = []; + + foreach ($providerData as $provider) { + switch ($provider["providerId"]) { + case 'password': + $types[] = User::TYPE_EMAIL; + break; + case 'phone': + $types[] = User::TYPE_PHONE; + break; + default: + $types[] = User::TYPE_OAUTH; + break; + } + } + + return $types; + } + + public function exportFunctions(int $batchSize, callable $callback): void + { + throw new \Exception('Not implemented'); + } + + public function exportFiles(int $batchSize, callable $callback): void + { + throw new \Exception('Not implemented'); + } + + public function exportDatabases(int $batchSize, callable $callback): void + { + $this->authenticate(); + + // Transfer Databases + $databases = []; + + } + + public function exportDocuments(int $batchSize, callable $callback): void + { + throw new \Exception('Not implemented'); + } +} diff --git a/src/Transfer/Sources/NHost.php b/src/Transfer/Sources/NHost.php index 034722a..f282935 100644 --- a/src/Transfer/Sources/NHost.php +++ b/src/Transfer/Sources/NHost.php @@ -3,20 +3,19 @@ namespace Utopia\Transfer\Sources; use Utopia\Transfer\Source; -use Utopia\Transfer\Resources\User; +use Utopia\Transfer\Resources\Auth\User; use Utopia\Transfer\Transfer; -use Utopia\Transfer\Log; -use Utopia\Transfer\Resources\Attribute; -use Utopia\Transfer\Resources\Database; -use Utopia\Transfer\Resources\Hash; -use Utopia\Transfer\Resources\Attributes\BoolAttribute; -use Utopia\Transfer\Resources\Attributes\DateTimeAttribute; -use Utopia\Transfer\Resources\Attributes\FloatAttribute; -use Utopia\Transfer\Resources\Attributes\IntAttribute; -use Utopia\Transfer\Resources\Attributes\StringAttribute; -use Utopia\Transfer\Resources\Collection; -use Utopia\Transfer\Resources\Document; -use Utopia\Transfer\Resources\Index; +use Utopia\Transfer\Resources\Database\Attribute; +use Utopia\Transfer\Resources\Database\Database; +use Utopia\Transfer\Resources\Auth\Hash; +use Utopia\Transfer\Resources\Database\Attributes\BoolAttribute; +use Utopia\Transfer\Resources\Database\Attributes\DateTimeAttribute; +use Utopia\Transfer\Resources\Database\Attributes\FloatAttribute; +use Utopia\Transfer\Resources\Database\Attributes\IntAttribute; +use Utopia\Transfer\Resources\Database\Attributes\StringAttribute; +use Utopia\Transfer\Resources\Database\Collection; +use Utopia\Transfer\Resources\Database\Document; +use Utopia\Transfer\Resources\Database\Index; class NHost extends Source { @@ -72,7 +71,7 @@ public function __construct(string $host, string $databaseName, string $username try { $this->pdo = new \PDO("pgsql:host=" . $this->host . ";port=" . $this->port . ";dbname=" . $this->databaseName, $this->username, $this->password); } catch (\PDOException $e) { - $this->logs[Log::ERROR] = new Log('Failed to connect to database: ' . $e->getMessage(), \time()); + throw new \Exception('Failed to connect to database: ' . $e->getMessage()); } } @@ -84,9 +83,9 @@ public function getName(): string public function getSupportedResources(): array { return [ - Transfer::RESOURCE_USERS, - Transfer::RESOURCE_DATABASES, - Transfer::RESOURCE_DOCUMENTS, + Transfer::GROUP_AUTH, + Transfer::GROUP_DATABASES, + Transfer::GROUP_DOCUMENTS, ]; } @@ -98,7 +97,7 @@ public function getSupportedResources(): array * * @return User[] */ - public function exportUsers(int $batchSize, callable $callback): void + public function exportAuth(int $batchSize, callable $callback): void { $total = $this->pdo->query('SELECT COUNT(*) FROM auth.users')->fetchColumn(); @@ -136,53 +135,14 @@ public function exportUsers(int $batchSize, callable $callback): void } } - /** - * Convert Collection - * - * @param string $tableName - * @return Collection - */ - public function convertCollection(string $tableName): Collection - { - $statement = $this->pdo->prepare('SELECT * FROM information_schema."columns" where "table_name" = :tableName'); - $statement->bindValue(':tableName', $tableName, \PDO::PARAM_STR); - $statement->execute(); - $databaseCollection = $statement->fetchAll(\PDO::FETCH_ASSOC); - - $convertedCollection = new Collection($tableName, $tableName); - - $attributes = []; - - foreach ($databaseCollection as $column) { - $attributes[] = $this->convertAttribute($column); - } - $convertedCollection->setAttributes($attributes); - - // Handle Indexes - - $indexStatement = $this->pdo->prepare('SELECT indexname, indexdef FROM pg_indexes WHERE tablename = :tableName'); - $indexStatement->bindValue(':tableName', $tableName, \PDO::PARAM_STR); - $indexStatement->execute(); - - $databaseIndexes = $indexStatement->fetchAll(\PDO::FETCH_ASSOC); - $indexes = []; - foreach ($databaseIndexes as $index) { - $result = $this->convertIndex($index); - - $indexes[] = $result; - } - $convertedCollection->setIndexes($indexes); - - return $convertedCollection; - } - /** * Convert Attribute * * @param array $column + * @param Collection $collection * @return Attribute */ - public function convertAttribute(array $column): Attribute + public function convertAttribute(array $column, Collection $collection): Attribute { $isArray = $column['data_type'] === 'ARRAY'; @@ -190,24 +150,24 @@ public function convertAttribute(array $column): Attribute // Numbers case 'boolean': case 'bool': - return new BoolAttribute($column['column_name'], $column['is_nullable'] === 'NO', $isArray, $column['column_default']); + return new BoolAttribute($column['column_name'], $collection, $column['is_nullable'] === 'NO', $isArray, $column['column_default']); case 'smallint': case 'int2': - return new IntAttribute($column['column_name'], $column['is_nullable'] === 'NO', $isArray, $column['column_default'], -32768, 32767); + return new IntAttribute($column['column_name'], $collection, $column['is_nullable'] === 'NO', $isArray, $column['column_default'], -32768, 32767); case 'integer': case 'int4': - return new IntAttribute($column['column_name'], $column['is_nullable'] === 'NO', $isArray, $column['column_default'], -2147483648, 2147483647); + return new IntAttribute($column['column_name'], $collection, $column['is_nullable'] === 'NO', $isArray, $column['column_default'], -2147483648, 2147483647); case 'bigint': case 'int8': case 'numeric': - return new IntAttribute($column['column_name'], $column['is_nullable'] === 'NO', $isArray, $column['column_default']); + return new IntAttribute($column['column_name'], $collection, $column['is_nullable'] === 'NO', $isArray, $column['column_default']); case 'decimal': case 'real': case 'double precision': case 'float4': case 'float8': case 'money': - return new FloatAttribute($column['column_name'], $column['is_nullable'] === 'NO', $isArray, $column['column_default']); + return new FloatAttribute($column['column_name'], $collection, $column['is_nullable'] === 'NO', $isArray, $column['column_default']); // Time (Conversion happens with documents) case 'timestamp with time zone': case 'date': @@ -218,7 +178,7 @@ public function convertAttribute(array $column): Attribute case 'time': case 'timetz': case 'interval': - return new DateTimeAttribute($column['column_name'], $column['is_nullable'] === 'NO', $isArray, null); + return new DateTimeAttribute($column['column_name'], $collection, $column['is_nullable'] === 'NO', $isArray, null); break; // Strings and Objects case 'uuid': @@ -231,6 +191,7 @@ public function convertAttribute(array $column): Attribute case 'bytea': return new StringAttribute( $column['column_name'], + $collection, $column['is_nullable'] === 'NO', $isArray, $column['column_default'], @@ -238,9 +199,10 @@ public function convertAttribute(array $column): Attribute ); break; default: - $this->logs[Log::WARNING][] = new Log('Unknown data type: ' . $column['data_type'] . ' for column: ' . $column['column_name'] . ' Falling back to string.', \time()); + // $this->logs[Log::WARNING][] = new Log('Unknown data type: ' . $column['data_type'] . ' for column: ' . $column['column_name'] . ' Falling back to string.', \time()); TODO: Figure out how to deal with warnings return new StringAttribute( $column['column_name'], + $collection, $column['is_nullable'] === 'NO', $isArray, $column['column_default'], @@ -254,17 +216,18 @@ public function convertAttribute(array $column): Attribute * Convert Index * * @param string $table + * @param Collection $collection * @return Index|false */ - public function convertIndex(array $index): Index|false + public function convertIndex(array $index, Collection $collection): Index|false { $pattern = "/CREATE (?\w+)? INDEX (?\w+) ON (?
\w+\.\w+) USING (?\w+) \((?\w+)\)/"; if (\preg_match($pattern, $index['indexdef'], $matches)) { // We only support BTree indexes if ($matches['method'] !== 'btree') { - $this->logs[Log::ERROR][] = new Log('Skipping index due to unsupported type: ' . $matches['method'] . ' for index: ' . $matches['name'] . '. Transfers only support BTree.', \time()); - + //TODO: Figure out how to deal with warnings + // Add warning here for unsupported index type return false; } @@ -294,9 +257,10 @@ public function convertIndex(array $index): Index|false } } - return new Index($matches['name'], $matches['name'], $type, $attributes, $order); + return new Index($matches['name'], $matches['name'], $collection, $type, $attributes, $order); } else { - $this->logs[Log::ERROR][] = new Log('Skipping index due to unsupported format: ' . $index['indexdef'] . ' for index: ' . $index['indexname'] . '. Transfers only support BTree.', \time()); + // $this->logs[Log::ERROR][] = new Log('Skipping index due to unsupported format: ' . $index['indexdef'] . ' for index: ' . $index['indexname'] . '. Transfers only support BTree.', \time()); + // Add error here for unsupported index format return false; } @@ -320,7 +284,9 @@ public function exportDatabases(int $batchSize, callable $callback): void //TODO: Handle edge cases where there are user created databases and data. $transferDatabase = new Database('public', 'public'); + $callback([$transferDatabase]); + // Transfer Tables while ($offset < $total) { $statement = $this->pdo->prepare('SELECT table_name FROM information_schema.tables WHERE table_schema = \'public\' order by table_name LIMIT :limit OFFSET :offset'); $statement->bindValue(':limit', $batchSize, \PDO::PARAM_INT); @@ -334,13 +300,45 @@ public function exportDatabases(int $batchSize, callable $callback): void $transferCollections = []; foreach ($tables as $table) { - $transferCollections[] = $this->convertCollection($table['table_name']); + $transferCollections[] = new Collection($transferDatabase, $table['table_name'], $table['table_name']); } - $transferDatabase->setCollections($transferCollections); + $callback($transferCollections); } - $callback([$transferDatabase]); + // Transfer Attributes and Indexes + $collections = $this->resourceCache->get(Collection::class); + + foreach ($collections as $collection) { + /** @var Collection $collection */ + $statement = $this->pdo->prepare('SELECT * FROM information_schema."columns" where "table_name" = :tableName'); + $statement->bindValue(':tableName', $collection->getCollectionName(), \PDO::PARAM_STR); + $statement->execute(); + $databaseCollection = $statement->fetchAll(\PDO::FETCH_ASSOC); + + $attributes = []; + + foreach ($databaseCollection as $column) { + $attributes[] = $this->convertAttribute($column, $collection); + } + + $callback($attributes); + + // Transfer Indexes + $indexStatement = $this->pdo->prepare('SELECT indexname, indexdef FROM pg_indexes WHERE tablename = :tableName'); + $indexStatement->bindValue(':tableName', $collection->getCollectionName(), \PDO::PARAM_STR); + $indexStatement->execute(); + + $databaseIndexes = $indexStatement->fetchAll(\PDO::FETCH_ASSOC); + $indexes = []; + foreach ($databaseIndexes as $index) { + $result = $this->convertIndex($index, $collection); + + $indexes[] = $result; + } + + $callback($indexes); + } } /** @@ -353,7 +351,7 @@ public function exportDatabases(int $batchSize, callable $callback): void */ public function exportDocuments(int $batchSize, callable $callback): void { - $databases = $this->resourceCache[Transfer::RESOURCE_DATABASES]; + $databases = $this->resourceCache->get(Database::class); foreach ($databases as $database) { /** @var Database $database */ @@ -376,12 +374,17 @@ public function exportDocuments(int $batchSize, callable $callback): void $transferDocuments = []; + $attributes = $this->resourceCache->get(Attribute::class); + $collectionAttributes = array_filter($attributes, function (Attribute $attribute) use ($collection) { + return $attribute->getId() === $collection->getId(); + }); + foreach ($documents as $document) { $data = json_decode($document['row_to_json'], true); $processedData = []; - foreach ($collection->getAttributes() as $attribute) { - /* @var Attribute $attribute */ + foreach ($collectionAttributes as $attribute) { + /** @var Attribute $attribute */ if (!$attribute->getArray() && \is_array($data[$attribute->getKey()])) { $processedData[$attribute->getKey()] = json_encode($data[$attribute->getKey()]); } else { @@ -443,7 +446,7 @@ public function check(array $resources = []): array foreach ($resources as $resource) { switch ($resource) { - case Transfer::RESOURCE_USERS: + case Transfer::GROUP_AUTH: $statement = $this->pdo->prepare('SELECT COUNT(*) FROM auth.users'); $statement->execute(); @@ -452,7 +455,7 @@ public function check(array $resources = []): array } break; - case Transfer::RESOURCE_DATABASES: + case Transfer::GROUP_DATABASES: $statement = $this->pdo->prepare('SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = \'public\''); $statement->execute(); @@ -461,8 +464,8 @@ public function check(array $resources = []): array } break; - case Transfer::RESOURCE_DOCUMENTS: - if (!in_array(Transfer::RESOURCE_DATABASES, $resources)) { + case Transfer::GROUP_DOCUMENTS: + if (!in_array(Transfer::GROUP_DATABASES, $resources)) { $report['Documents'][] = 'Documents resource requires Databases resource to be enabled.'; } } @@ -470,4 +473,14 @@ public function check(array $resources = []): array return $report; } + + public function exportFiles(int $batchSize, callable $callback): void + { + throw new \Exception('Not Implemented'); + } + + public function exportFunctions(int $batchSize, callable $callback): void + { + throw new \Exception('Not Implemented'); + } } diff --git a/src/Transfer/Sources/Supabase.php b/src/Transfer/Sources/Supabase.php index a8350eb..c81d170 100644 --- a/src/Transfer/Sources/Supabase.php +++ b/src/Transfer/Sources/Supabase.php @@ -2,9 +2,14 @@ namespace Utopia\Transfer\Sources; -use Utopia\Transfer\Resources\User; +use Utopia\Transfer\Resources\Auth\User; +use Utopia\Transfer\Resources\Auth\Hash; +use Utopia\Transfer\Resources\Storage\Bucket; +use Utopia\Transfer\Resources\Storage\File; +use Utopia\Transfer\Resources\Storage\FileData; use Utopia\Transfer\Transfer; -use Utopia\Transfer\Resources\Hash; + +use function PHPUnit\Framework\callback; class Supabase extends NHost { @@ -13,6 +18,40 @@ public function getName(): string return 'Supabase'; } + protected string $key; + + /** + * Constructor + * + * @param string $endpoint + * @param string $key + * @param string $host + * @param string $databaseName + * @param string $username + * @param string $password + * @param string $port + * + * @return self + */ + public function __construct(string $endpoint, string $key, string $host, string $databaseName, string $username, string $password, string $port = '5432') + { + $this->endpoint = $endpoint; + $this->key = $key; + $this->host = $host; + $this->databaseName = $databaseName; + $this->username = $username; + $this->password = $password; + $this->port = $port; + + $this->headers['Authorization'] = 'Bearer ' . $this->key; + + try { + $this->pdo = new \PDO("pgsql:host=" . $this->host . ";port=" . $this->port . ";dbname=" . $this->databaseName, $this->username, $this->password); + } catch (\PDOException $e) { + throw new \Exception('Failed to connect to database: ' . $e->getMessage()); + } + } + /** * Export Users * @@ -21,7 +60,7 @@ public function getName(): string * * @return User[] */ - public function exportUsers(int $batchSize, callable $callback): void + public function exportAuth(int $batchSize, callable $callback): void { $total = $this->pdo->query('SELECT COUNT(*) FROM auth.users')->fetchColumn(); @@ -77,4 +116,110 @@ private function calculateAuthTypes(array $user): array return $types; } + + public function exportFiles(int $batchSize, callable $callback): void + { + // Transfer Buckets + $statement = $this->pdo->prepare('SELECT * FROM storage.buckets order by created_at'); + $statement->execute(); + + $buckets = $statement->fetchAll(\PDO::FETCH_ASSOC); + + $transferBuckets = []; + + foreach ($buckets as $bucket) { + $transferBuckets[] = new Bucket( + $bucket['id'], + [], + false, + $bucket['name'], + true, + $bucket['file_size_limit'] ?? 0, + [], // $bucket['allowed_mime_type'], //TODO: Need to convert this to file extensions + ); + } + + $callback($transferBuckets); + + // Transfer Files + foreach ($transferBuckets as $bucket) { + /** @var Bucket $bucket */ + $totalStatement = $this->pdo->prepare('SELECT COUNT(*) FROM storage.objects WHERE bucket_id=:bucketId'); + $totalStatement->execute([':bucketId' => $bucket->getId()]); + $total = $totalStatement->fetchColumn(); + + $offset = 0; + while ($offset < $total) { + $statement = $this->pdo->prepare('SELECT * FROM storage.objects WHERE bucket_id=:bucketId ORDER BY created_at LIMIT :limit OFFSET :offset'); + $statement->execute([ + ':bucketId' => $bucket->getId(), + ':limit' => $batchSize, + ':offset' => $offset + ]); + + $files = $statement->fetchAll(\PDO::FETCH_ASSOC); + + $offset += $batchSize; + + foreach ($files as $file) { + $metadata = json_decode($file['metadata'], true); + + $this->handleFileTransfer(new File( + $file['id'], + $bucket, + $file['name'], + '', + $metadata['mimetype'], + [], + $metadata['size'] + ), $callback); + } + } + } + } + + /** + * Handle File Transfer + * Streams a file to the destination + * + * @param File $file + * @param callable $callback (array $data) + * + * @return void + */ + protected function handleFileTransfer(File $file, callable $callback): void + { + // Set the chunk size (5MB) + $start = 0; + $end = Transfer::STORAGE_MAX_CHUNK_SIZE - 1; + + // Get the file size + $fileSize = $file->getSize(); + + // Loop until the entire file is downloaded + while ($start < $fileSize) { + $chunkData = $this->call( + 'GET', + '/storage/v1/object/' . + rawurlencode($file->getBucket()->getId()) . '/' . rawurlencode($file->getFileName()), + ['range' => "bytes=$start-$end"] + ); + + // Send the chunk to the callback function + $callback([new FileData( + $chunkData, + $start, + $end, + $file + )]); + + // Update the range + $start += Transfer::STORAGE_MAX_CHUNK_SIZE; + $end += Transfer::STORAGE_MAX_CHUNK_SIZE; + + if ($end > $fileSize) { + $end = $fileSize - 1; + } + } + } } diff --git a/src/Transfer/Target.php b/src/Transfer/Target.php new file mode 100644 index 0000000..c198c65 --- /dev/null +++ b/src/Transfer/Target.php @@ -0,0 +1,196 @@ + '', + ]; + + /** + * Resource Cache + * + * @var ResourceCache $resourceCache + */ + protected $resourceCache; + + /** + * Endpoint + * + * @var string $endpoint + */ + protected $endpoint = ''; + + /** + * Gets the name of the adapter. + * + * @return string + */ + abstract public function getName(): string; + + /** + * Get Supported Resources + * + * @return array + */ + abstract public function getSupportedResources(): array; + + /** + * Register Transfer Cache + * + * @param ResourceCache &$cache + * + * @return void + */ + //TODO: Pretty sure there is a better way to do this instead of passing by reference + public function registerTransferCache(ResourceCache &$cache): void + { + $this->resourceCache = &$cache; + } + + /** + * Run Transfer + * + * @param array $resources + * @param callable $callback + */ + abstract public function run(array $resources, callable $callback): void; + + /** + * Check Requirements + * Performs a suite of API Checks, Resource Checks, etc... to ensure the adapter is ready to be used. + * This is highly recommended to be called before any other method after initialization. + * + * If no resources are provided, the method should check all resources. + * Returns a object with all the keys of the resources provided and a true|string value if the resource is available or not. + * If the resource is not available, the value should be a string with the error message. + * + * @string[] $resources + * + * @return string[] + */ + abstract public function check(array $resources = []): array; + + /** + * Call + * + * Make an API call + * + * @param string $method + * @param string $path + * @param array $params + * @param array $headers + * @return array|string + * @throws \Exception + */ + public function call(string $method, string $path = '', array $headers = array(), array $params = array()): array|string + { + $headers = array_merge($this->headers, $headers); + $ch = curl_init((str_contains($path, 'http') ? $path : $this->endpoint . $path . (($method == 'GET' && !empty($params)) ? '?' . http_build_query($params) : ''))); + $responseHeaders = []; + $responseStatus = -1; + $responseType = ''; + $responseBody = ''; + + switch ($headers['Content-Type']) { + case 'application/json': + $query = json_encode($params); + break; + + case 'multipart/form-data': + $query = $this->flatten($params); + break; + + default: + $query = http_build_query($params); + break; + } + + foreach ($headers as $i => $header) { + $headers[] = $i . ':' . $header; + unset($headers[$i]); + } + + curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($ch, CURLOPT_USERAGENT, php_uname('s') . '-' . php_uname('r') . ':php-' . phpversion()); + curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); + curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); + curl_setopt($ch, CURLOPT_HEADERFUNCTION, function ($curl, $header) use (&$responseHeaders) { + $len = strlen($header); + $header = explode(':', strtolower($header), 2); + + if (count($header) < 2) { // ignore invalid headers + return $len; + } + + $responseHeaders[strtolower(trim($header[0]))] = trim($header[1]); + + return $len; + }); + + if ($method != 'GET') { + curl_setopt($ch, CURLOPT_POSTFIELDS, $query); + } + + $responseBody = curl_exec($ch); + + $responseType = $responseHeaders['Content-Type'] ?? $responseHeaders['content-type'] ?? ''; + $responseStatus = curl_getinfo($ch, CURLINFO_HTTP_CODE); + + switch (substr($responseType, 0, strpos($responseType, ';'))) { + case 'application/json': + $responseBody = json_decode($responseBody, true); + break; + } + + if (curl_errno($ch)) { + var_dump(curl_errno($ch)); + throw new \Exception(curl_error($ch)); + } + + curl_close($ch); + + if ($responseStatus >= 400) { + if (is_array($responseBody)) { + throw new \Exception(json_encode($responseBody)); + } else { + throw new \Exception($responseStatus . ': ' . $responseBody); + } + } + + return $responseBody; + } + + /** + * Flatten params array to PHP multiple format + * + * @param array $data + * @param string $prefix + * @return array + */ + protected function flatten(array $data, string $prefix = ''): array + { + $output = []; + + foreach ($data as $key => $value) { + $finalKey = $prefix ? "{$prefix}[{$key}]" : $key; + + if (is_array($value)) { + $output += $this->flatten($value, $finalKey); // @todo: handle name collision here if needed + } else { + $output[$finalKey] = $value; + } + } + + return $output; + } +} diff --git a/src/Transfer/Transfer.php b/src/Transfer/Transfer.php index b03ab58..fb82285 100644 --- a/src/Transfer/Transfer.php +++ b/src/Transfer/Transfer.php @@ -2,16 +2,20 @@ namespace Utopia\Transfer; +use Utopia\Transfer\ResourceCache; use Utopia\Transfer\Destination; use Utopia\Transfer\Source; class Transfer { - public const RESOURCE_USERS = 'Users'; - public const RESOURCE_FILES = 'Files'; - public const RESOURCE_FUNCTIONS = 'Functions'; - public const RESOURCE_DATABASES = 'Databases'; - public const RESOURCE_DOCUMENTS = 'Documents'; + public const GROUP_GENERAL = 'General'; // for things that don't belong to any group + public const GROUP_AUTH = 'Auth'; + public const GROUP_STORAGE = 'Storage'; + public const GROUP_FUNCTIONS = 'Functions'; + public const GROUP_DATABASES = 'Databases'; + public const GROUP_DOCUMENTS = 'Documents'; + + public const STORAGE_MAX_CHUNK_SIZE = 1024 * 1024 * 5; // 5MB /** * @param Source $source @@ -23,11 +27,11 @@ public function __construct(Source $source, Destination $destination) { $this->source = $source; $this->destination = $destination; + $this->resourceCache = new ResourceCache(); - $this->source->registerLogs($this->logs); - $this->source->registerTransferHooks($this->resources, $this->counters); - $this->destination->registerLogs($this->logs); - $this->destination->registerTransferHooks($this->resources, $this->counters); + $this->source->registerTransferCache($this->resourceCache); + $this->destination->registerTransferCache($this->resourceCache); + $this->destination->setSource($source); return $this; } @@ -47,73 +51,18 @@ public function __construct(Source $source, Destination $destination) */ protected string $currentResource; - /** - * Counters - * - * @var array $counter - */ - protected $counters = [ - Transfer::RESOURCE_USERS => [ - 'total' => 0, - 'current' => 0, - 'failed' => 0, - 'skipped' => 0, - ], - Transfer::RESOURCE_FILES => [ - 'total' => 0, - 'current' => 0, - 'failed' => 0, - 'skipped' => 0, - ], - Transfer::RESOURCE_FUNCTIONS => [ - 'total' => 0, - 'current' => 0, - 'failed' => 0, - 'skipped' => 0, - ], - Transfer::RESOURCE_DATABASES => [ - 'total' => 0, - 'current' => 0, - 'failed' => 0, - 'skipped' => 0, - ], - Transfer::RESOURCE_DOCUMENTS => [ - 'total' => 0, - 'current' => 0, - 'failed' => 0, - 'skipped' => 0, - ] - ]; - /** * A local cache of resources that were transferred. * - * @var array + * @var ResourceCache */ - protected array $resources = [ - self::RESOURCE_DOCUMENTS => [], - self::RESOURCE_DATABASES => [], - self::RESOURCE_FILES => [], - self::RESOURCE_FUNCTIONS => [], - self::RESOURCE_USERS => [] - ]; + protected ResourceCache $resourceCache; /** * @var array */ protected array $options = []; - /** - * @var array - */ - protected array $logs = [ - Log::ERROR => [], - Log::WARNING => [], - Log::INFO => [], - Log::FATAL => [], - Log::SUCCESS => [] - ]; - /** * @var array */ @@ -133,46 +82,21 @@ public function __construct(Source $source, Destination $destination) public function run(array $resources, callable $callback): void { $this->destination->run($resources, function (Progress $progress) use ($callback) { + //TODO: Rewrite to use ResourceCache to calculate this $this->currentResource = $progress->getResourceType(); $callback($progress); }, $this->source); } - /** - * Get Logs - * - * If no level is provided then the function returns all logs combined ordered by timestamp. - * - * @param string $level - * - * @return array - */ - public function getLogs($level = ''): array - { - if (!empty($level)) { - return $this->logs[$level]; - } - - $mergedLogs = array_merge($this->logs[Log::ERROR], $this->logs[Log::WARNING], $this->logs[Log::INFO], $this->logs[Log::FATAL]); - - $timestamps = []; - foreach ($mergedLogs as $key => $log) { - $timestamps[$key] = $log->getTimestamp(); - } - array_multisort($timestamps, SORT_ASC, $mergedLogs); - - return $mergedLogs; - } - /** * Get Resource Cache * - * @return array + * @return ResourceCache */ - public function getResourceCache(): array + public function getResourceCache(): ResourceCache { - return $this->resources; + return $this->resourceCache; } /** diff --git a/tests/Transfer/Destinations/AppwriteTest.php b/tests/Transfer/Destinations/AppwriteTest.php index 35e9763..c6bd548 100644 --- a/tests/Transfer/Destinations/AppwriteTest.php +++ b/tests/Transfer/Destinations/AppwriteTest.php @@ -16,8 +16,8 @@ use PHPUnit\Framework\TestCase; use Utopia\Transfer\Destinations\Appwrite; -use Utopia\Transfer\Resources\Hash; -use Utopia\Transfer\Resources\User; +use Utopia\Transfer\Resources\Auth\Hash; +use Utopia\Transfer\Resources\Auth\User; use Appwrite\Client; use Appwrite\Services\Users; diff --git a/tests/Transfer/ProviderTest.phpignore b/tests/Transfer/ProviderTest.phpignore index 438fea7..3f04552 100644 --- a/tests/Transfer/ProviderTest.phpignore +++ b/tests/Transfer/ProviderTest.phpignore @@ -18,11 +18,11 @@ class ProviderTest extends TestCase $this->assertNotEmpty($this->provider->getSupportedResources()); } - public function testExportUsers() + public function testexportAuth() { $result = []; - $this->provider->exportUsers(100, function (array $users) use (&$result) { + $this->provider->exportAuth(100, function (array $users) use (&$result) { $result = array_merge($result, $users); }); @@ -55,15 +55,15 @@ class ProviderTest extends TestCase public function testCheckUsers() { - $result = $this->provider->check([Transfer::RESOURCE_USERS]); + $result = $this->provider->check([Transfer::GROUP_AUTH]); - $this->assertEquals($result, [Transfer::RESOURCE_USERS]); + $this->assertEquals($result, [Transfer::GROUP_AUTH]); } public function testCheckDatabases() { - $result = $this->provider->check([Transfer::RESOURCE_DATABASES]); + $result = $this->provider->check([Transfer::GROUP_DATABASES]); - $this->assertEquals($result, [Transfer::RESOURCE_DATABASES]); + $this->assertEquals($result, [Transfer::GROUP_DATABASES]); } } \ No newline at end of file diff --git a/tests/Transfer/Sources/AppwriteSourceTest.php b/tests/Transfer/Sources/AppwriteSourceTest.php index f5402fb..43e936b 100644 --- a/tests/Transfer/Sources/AppwriteSourceTest.php +++ b/tests/Transfer/Sources/AppwriteSourceTest.php @@ -45,7 +45,7 @@ public function testGetUsers(): void { $result = []; - $this->appwrite->exportUsers( + $this->appwrite->exportAuth( 100, public function (array $users) use (&$result) { $result = array_merge($result, $users); diff --git a/tests/Transfer/Sources/FirebaseTest.php b/tests/Transfer/Sources/FirebaseTest.php index 40251d1..dca822a 100644 --- a/tests/Transfer/Sources/FirebaseTest.php +++ b/tests/Transfer/Sources/FirebaseTest.php @@ -84,7 +84,7 @@ public function testGetUsers(): void $result = []; - $this->firebase->exportUsers( + $this->firebase->exportAuth( 500, public function (array $users) use (&$result) { $result = array_merge($result, $users); diff --git a/tests/Transfer/Sources/NHostTest.php b/tests/Transfer/Sources/NHostTest.php index 69f2c5b..48db340 100644 --- a/tests/Transfer/Sources/NHostTest.php +++ b/tests/Transfer/Sources/NHostTest.php @@ -41,7 +41,7 @@ public function testGetUsers(): array { $result = []; - $this->nhost->exportUsers( + $this->nhost->exportAuth( 500, public function (array $users) use (&$result) { $result = array_merge($result, $users); diff --git a/tests/Transfer/Sources/SupabaseTest.php b/tests/Transfer/Sources/SupabaseTest.php index d945126..b601360 100644 --- a/tests/Transfer/Sources/SupabaseTest.php +++ b/tests/Transfer/Sources/SupabaseTest.php @@ -41,7 +41,7 @@ public function testGetUsers(): array { $result = []; - $this->supabase->exportUsers( + $this->supabase->exportAuth( 500, public function (array $users) use (&$result) { $result = array_merge($result, $users); diff --git a/tests/Transfer/Transfers/FirebaseToAppwriteTest.php b/tests/Transfer/Transfers/FirebaseToAppwriteTest.php index 6ee97e5..851f438 100644 --- a/tests/Transfer/Transfers/FirebaseToAppwriteTest.php +++ b/tests/Transfer/Transfers/FirebaseToAppwriteTest.php @@ -74,7 +74,7 @@ public function setUp(): void public function testTransferUsers(): void { $this->transfer->run( - [Transfer::RESOURCE_USERS], + [Transfer::GROUP_AUTH], public function () { } ); @@ -92,7 +92,7 @@ public function testVerifyUsers(): void $assertedUsers = false; - $this->firebase->exportUsers( + $this->firebase->exportAuth( 500, public function (array $users) use ($userClient, &$assertedUsers) { foreach ($users as $user) { diff --git a/tests/Transfer/Transfers/SupabaseToAppwriteTest.php b/tests/Transfer/Transfers/SupabaseToAppwriteTest.php index d93a075..9508fb2 100644 --- a/tests/Transfer/Transfers/SupabaseToAppwriteTest.php +++ b/tests/Transfer/Transfers/SupabaseToAppwriteTest.php @@ -72,7 +72,7 @@ public function setUp(): void public function testTransferUsers(): void { $this->transfer->run( - [Transfer::RESOURCE_USERS], + [Transfer::GROUP_AUTH], public function () { } ); @@ -91,7 +91,7 @@ public function testVerifyUsers(): void $assertedUsers = false; - $this->supabase->exportUsers( + $this->supabase->exportAuth( 500, public function (array $users) use ($userClient, &$assertedUsers) { foreach ($users as $user) { From 9dcbdea52f8e0759ebd175bd2e495258a9860b7a Mon Sep 17 00:00:00 2001 From: Bradley Schofield Date: Wed, 26 Apr 2023 14:28:34 +0900 Subject: [PATCH 34/70] Update Target.php --- src/Transfer/Target.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Transfer/Target.php b/src/Transfer/Target.php index c198c65..80d18b3 100644 --- a/src/Transfer/Target.php +++ b/src/Transfer/Target.php @@ -153,7 +153,6 @@ public function call(string $method, string $path = '', array $headers = array() } if (curl_errno($ch)) { - var_dump(curl_errno($ch)); throw new \Exception(curl_error($ch)); } From bc47b313d9c39bea70466016cc3c6b58752fc3e3 Mon Sep 17 00:00:00 2001 From: Bradley Schofield Date: Wed, 26 Apr 2023 14:49:18 +0900 Subject: [PATCH 35/70] Rework Appwrite File Transfer Client --- src/Transfer/Sources/Appwrite.php | 55 ++++++++++--------------------- src/Transfer/Sources/Supabase.php | 8 +++-- 2 files changed, 23 insertions(+), 40 deletions(-) diff --git a/src/Transfer/Sources/Appwrite.php b/src/Transfer/Sources/Appwrite.php index 086e228..46b0f9f 100644 --- a/src/Transfer/Sources/Appwrite.php +++ b/src/Transfer/Sources/Appwrite.php @@ -72,6 +72,8 @@ public function __construct(string $project, string $endpoint, string $key) $this->endpoint = $endpoint; $this->project = $project; $this->key = $key; + $this->headers['X-Appwrite-Project'] = $this->project; + $this->headers['X-Appwrite-Key'] = $this->key; } /** @@ -766,8 +768,6 @@ public function exportFiles(int $batchSize, callable $callback): void while (true) { $queries = [Query::limit($batchSize)]; - $files = []; - if ($lastDocument) { $queries[] = Query::cursorAfter($lastDocument); } @@ -778,7 +778,7 @@ public function exportFiles(int $batchSize, callable $callback): void ); foreach ($response["files"] as $file) { - $files[] = new File( + $this->handleFileDataTransfer(new File( $file['$id'], $bucket, $file['name'], @@ -786,13 +786,9 @@ public function exportFiles(int $batchSize, callable $callback): void $file['mimeType'], $file['$permissions'], $file['sizeOriginal'], - ); - - $lastDocument = $file['$id']; - } + ), $callback); - foreach ($files as $file) { - $this->streamFile($file, $callback); + $lastDocument = $file['$id']; } if (count($response["files"]) < $batchSize) { @@ -803,48 +799,34 @@ public function exportFiles(int $batchSize, callable $callback): void } /** - * Stream File + * Handle File Data Transfer * Streams a file to the destination * * @param File $file * @param callable $callback (array $data) - * + * * @return void */ - protected function streamFile(File $file, callable $callback): void + protected function handleFileDataTransfer(File $file, callable $callback): void { // Set the chunk size (5MB) $start = 0; $end = Transfer::STORAGE_MAX_CHUNK_SIZE - 1; + if ($end > $file->getSize()) { + $end = $file->getSize() - 1; + } + // Get the file size $fileSize = $file->getSize(); - // Initialize cURL - $ch = curl_init("{$this->endpoint}/storage/buckets/{$file->getBucket()->getId()}/files/{$file->getId()}/download"); - // Loop until the entire file is downloaded while ($start < $fileSize) { - // Set the Range header - $range = "Range: bytes=$start-$end"; - curl_setopt($ch, CURLOPT_HTTPHEADER, [ - $range, - 'X-Appwrite-key: ' . $this->key, - 'X-Appwrite-Project: ' . $this->project, - ]); - - // Set cURL options - curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); - - // Download the chunk - $chunkData = curl_exec($ch); - - $status = curl_getinfo($ch, CURLINFO_HTTP_CODE); - - if ($status !== 200 && $status !== 206) { - throw new \Exception('Failed to download file, Error: ' . $chunkData); - } + $chunkData = $this->call( + 'GET', + "/storage/buckets/{$file->getBucket()->getId()}/files/{$file->getId()}/download", + ['range' => "bytes=$start-$end"] + ); // Send the chunk to the callback function $callback([new FileData( @@ -862,9 +844,6 @@ protected function streamFile(File $file, callable $callback): void $end = $fileSize - 1; } } - - // Close cURL - curl_close($ch); } /** diff --git a/src/Transfer/Sources/Supabase.php b/src/Transfer/Sources/Supabase.php index c81d170..6a35c04 100644 --- a/src/Transfer/Sources/Supabase.php +++ b/src/Transfer/Sources/Supabase.php @@ -164,7 +164,7 @@ public function exportFiles(int $batchSize, callable $callback): void foreach ($files as $file) { $metadata = json_decode($file['metadata'], true); - $this->handleFileTransfer(new File( + $this->handleFileDataTransfer(new File( $file['id'], $bucket, $file['name'], @@ -187,7 +187,7 @@ public function exportFiles(int $batchSize, callable $callback): void * * @return void */ - protected function handleFileTransfer(File $file, callable $callback): void + protected function handleFileDataTransfer(File $file, callable $callback): void { // Set the chunk size (5MB) $start = 0; @@ -196,6 +196,10 @@ protected function handleFileTransfer(File $file, callable $callback): void // Get the file size $fileSize = $file->getSize(); + if ($end > $fileSize) { + $end = $fileSize - 1; + } + // Loop until the entire file is downloaded while ($start < $fileSize) { $chunkData = $this->call( From 04e2e080eb5572cb1ae2fca6ff71e3b7502ca97e Mon Sep 17 00:00:00 2001 From: Bradley Schofield Date: Wed, 10 May 2023 13:20:48 +0900 Subject: [PATCH 36/70] Continue Refactor --- README.md | 16 +- phpunit.xml | 6 +- playground.php | 29 +- src/Transfer/Destination.php | 7 +- src/Transfer/Destinations/Appwrite.php | 125 ++-- src/Transfer/Destinations/Local.php | 55 +- src/Transfer/Resource.php | 9 +- src/Transfer/ResourceCache.php | 36 +- src/Transfer/Resources/Auth/Hash.php | 2 +- src/Transfer/Resources/Auth/Team.php | 2 +- .../Resources/Auth/TeamMembership.php | 2 +- src/Transfer/Resources/Auth/User.php | 2 +- src/Transfer/Resources/Database/Attribute.php | 2 +- .../Resources/Database/Collection.php | 2 +- src/Transfer/Resources/Database/Database.php | 20 +- src/Transfer/Resources/Database/Document.php | 4 +- src/Transfer/Resources/Database/Index.php | 2 +- src/Transfer/Resources/Functions/EnvVar.php | 2 +- src/Transfer/Resources/Functions/Func.php | 2 +- src/Transfer/Resources/Project.php | 4 +- src/Transfer/Resources/Storage/Bucket.php | 2 +- src/Transfer/Resources/Storage/File.php | 2 +- src/Transfer/Resources/Storage/FileData.php | 2 +- src/Transfer/Source.php | 122 ++-- src/Transfer/Sources/Appwrite.php | 386 +++++++----- src/Transfer/Sources/Firebase.php | 577 +++++++----------- src/Transfer/Sources/FirebaseG2.php | 190 ------ src/Transfer/Sources/NHost.php | 447 +++++++------- src/Transfer/Sources/Supabase.php | 86 ++- src/Transfer/Target.php | 3 +- src/Transfer/Transfer.php | 24 +- tests/Transfer/Destinations/AppwriteTest.php | 85 --- tests/Transfer/ProviderTest.phpignore | 69 --- tests/Transfer/Sources/AppwriteSourceTest.php | 85 --- tests/Transfer/Sources/FirebaseTest.php | 106 ---- tests/Transfer/Sources/NHostTest.php | 92 --- tests/Transfer/Sources/SupabaseTest.php | 99 --- .../Transfers/FirebaseToAppwriteTest.php | 136 ----- .../Transfers/SupabaseToAppwriteTest.php | 139 ----- tests/e2e/adapters/MockDestination.php | 32 + tests/e2e/adapters/MockSource.php | 32 + tests/unit/ResourceCacheTest.php | 135 ++++ 42 files changed, 1185 insertions(+), 1995 deletions(-) delete mode 100644 src/Transfer/Sources/FirebaseG2.php delete mode 100644 tests/Transfer/Destinations/AppwriteTest.php delete mode 100644 tests/Transfer/ProviderTest.phpignore delete mode 100644 tests/Transfer/Sources/AppwriteSourceTest.php delete mode 100644 tests/Transfer/Sources/FirebaseTest.php delete mode 100644 tests/Transfer/Sources/NHostTest.php delete mode 100644 tests/Transfer/Sources/SupabaseTest.php delete mode 100644 tests/Transfer/Transfers/FirebaseToAppwriteTest.php delete mode 100644 tests/Transfer/Transfers/SupabaseToAppwriteTest.php create mode 100644 tests/e2e/adapters/MockDestination.php create mode 100644 tests/e2e/adapters/MockSource.php create mode 100644 tests/unit/ResourceCacheTest.php diff --git a/README.md b/README.md index 390735a..6f5984d 100644 --- a/README.md +++ b/README.md @@ -47,18 +47,18 @@ $transfer->run( ## Supported Resources Chart Sources: -| | Users | Databases | Documents | Files | Functions | -|----------|-------|-----------|-----------|-------|-----------| -| Appwrite | ✅ | ✅ | ✅ | ✅ | ✅ | -| Supabase | ✅ | ✅ | ✅ | | | +| | Auth | Databases | Storage | Functions | Settings | +|----------|-------|-----------|-------|-----------|-----------| +| Appwrite | ✅ | ✅ | ✅ | ✅ | | +| Supabase | ✅ | ✅ | ✅ | ✅ | | | NHost | ✅ | ✅ | ✅ | | | | Firebase | ✅ | ✅ | | | | Destinations: -| | Users | Databases | Documents | Files | Functions | -|----------|-------|-----------|-----------|-------|-----------| -| Appwrite | ✅ | ✅ | ✅ | ✅ | ✅ | -| Local | ✅ | ✅ | ✅ | ✅ | ✅ | +| | Auth | Databases | Storage | Functions | Settings | +|----------|-------|-----------|-------|-----------|-----------| +| Appwrite | ✅ | ✅ | ✅ | ✅ | | +| Local | ✅ | ✅ | ✅ | ✅ | ✅ | > **Warning** > The Local destination should be used for testing purposes only. It is not recommended to use this destination in production or as a backup. The local destination is there to confirm that a source is working correctly and to test the transfer process with needing a target destination instance. diff --git a/phpunit.xml b/phpunit.xml index d686f8a..0658ab9 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -9,10 +9,8 @@ stopOnFailure="false" > - - ./tests/Transfer/Sources - ./tests/Transfer/Destinations - ./tests/Transfer/Transfers + + ./tests/Unit \ No newline at end of file diff --git a/playground.php b/playground.php index 70773d7..9cc3c94 100644 --- a/playground.php +++ b/playground.php @@ -16,7 +16,7 @@ use Utopia\Transfer\Sources\NHost; use Utopia\Transfer\Sources\Supabase; use Dotenv\Dotenv; -use Utopia\Transfer\Sources\FirebaseG2; +use Utopia\Transfer\Resource; $dotenv = Dotenv::createImmutable(__DIR__); $dotenv->load(); @@ -31,11 +31,6 @@ ); $sourceFirebase = new Firebase( - json_decode($_ENV["FIREBASE_TEST_ACCOUNT"], true), - Firebase::AUTH_SERVICEACCOUNT -); - -$sourceFirebaseG2 = new FirebaseG2( json_decode($_ENV["FIREBASE_TEST_ACCOUNT"], true), "amadeus-a3730" ); @@ -47,14 +42,14 @@ // $_ENV["NHOST_TEST_PASSWORD"] ?? '', // ); -$sourceSupabase = new Supabase( - $_ENV['SUPABASE_TEST_ENDPOINT'] ?? '', - $_ENV['SUPABASE_TEST_KEY'] ?? '', - $_ENV["SUPABASE_TEST_HOST"] ?? '', - $_ENV["SUPABASE_TEST_DATABASE"] ?? '', - $_ENV["SUPABASE_TEST_USERNAME"] ?? '', - $_ENV["SUPABASE_TEST_PASSWORD"] ?? '', -); +// $sourceSupabase = new Supabase( +// $_ENV['SUPABASE_TEST_ENDPOINT'] ?? '', +// $_ENV['SUPABASE_TEST_KEY'] ?? '', +// $_ENV["SUPABASE_TEST_HOST"] ?? '', +// $_ENV["SUPABASE_TEST_DATABASE"] ?? '', +// $_ENV["SUPABASE_TEST_USERNAME"] ?? '', +// $_ENV["SUPABASE_TEST_PASSWORD"] ?? '', +// ); /** * Initialise All Destination Adapters @@ -71,10 +66,8 @@ * Initialise Transfer Class */ -$sourceFirebase->setProject($sourceFirebase->getProjects()[0]); - $transfer = new Transfer( - $sourceSupabase, + $sourceFirebase, $destinationLocal ); @@ -83,7 +76,7 @@ */ $transfer->run( [ - Transfer::GROUP_STORAGE + Resource::TYPE_USER, ], function () { } ); \ No newline at end of file diff --git a/src/Transfer/Destination.php b/src/Transfer/Destination.php index 3ed667f..58bafe3 100644 --- a/src/Transfer/Destination.php +++ b/src/Transfer/Destination.php @@ -42,8 +42,8 @@ public function setSource(Source $source): self */ public function run(array $resources, callable $callback): void { - $this->source->run($resources, function (string $group, array $resources) use ($callback) { - $this->importResources($resources, $callback, $group); + $this->source->run($resources, function (array $resources) use ($callback) { + $this->importResources($resources, $callback); }); } @@ -52,8 +52,7 @@ public function run(array $resources, callable $callback): void * * @param array $resources * @param callable $callback (Progress $progress) - * @param string $group * */ - abstract public function importResources(array $resources, callable $callback, string $group): void; + abstract public function importResources(array $resources, callable $callback): void; } diff --git a/src/Transfer/Destinations/Appwrite.php b/src/Transfer/Destinations/Appwrite.php index d5060bd..ac00596 100644 --- a/src/Transfer/Destinations/Appwrite.php +++ b/src/Transfer/Destinations/Appwrite.php @@ -3,6 +3,7 @@ namespace Utopia\Transfer\Destinations; use Appwrite\Client; +use Appwrite\InputFile; use Appwrite\Services\Users; use Appwrite\Services\Databases; use Appwrite\Services\Functions; @@ -58,7 +59,7 @@ public function __construct(string $project, string $endpoint, string $key) * * @return string */ - public function getName(): string + static function getName(): string { return 'Appwrite'; } @@ -73,20 +74,20 @@ public function getSupportedResources(): array return [ Transfer::GROUP_AUTH, Transfer::GROUP_DATABASES, - Transfer::GROUP_DOCUMENTS, Transfer::GROUP_STORAGE, - Transfer::GROUP_FUNCTIONS + Transfer::GROUP_FUNCTIONS, + Transfer::GROUP_SETTINGS ]; } public function check(array $resources = []): array { $report = [ - 'Users' => [], - 'Databases' => [], - 'Documents' => [], - 'Files' => [], - 'Functions' => [] + Transfer::GROUP_AUTH => [], + Transfer::GROUP_DATABASES => [], + Transfer::GROUP_STORAGE => [], + Transfer::GROUP_FUNCTIONS => [], + Transfer::GROUP_SETTINGS => [], ]; if (empty($resources)) { @@ -103,108 +104,125 @@ public function check(array $resources = []): array $databases->list(); } catch (\Throwable $e) { if ($e->getCode() == 401) { - $report['Databases'][] = 'API Key is missing scope: databases.read'; + $report[Transfer::GROUP_DATABASES][] = 'API Key is missing scope: databases.read'; } } - break; - case Transfer::GROUP_AUTH: - $auth = new Users($this->client); + try { - $auth->list(); + $databases->create('', ''); } catch (\Throwable $e) { if ($e->getCode() == 401) { - $report['Users'][] = 'API Key is missing scope: users.read'; + $report[Transfer::GROUP_DATABASES][] = 'API Key is missing scope: databases.write'; } } - break; - case Transfer::GROUP_DOCUMENTS: - $databases = new Databases($this->client); + try { - $databases->list(); + $databases->listCollections('', [], ''); } catch (\Throwable $e) { if ($e->getCode() == 401) { - $report['Databases'][] = 'API Key is missing scope: databases.read'; + $report[Transfer::GROUP_DATABASES][] = 'API Key is missing scope: collections.write'; } } try { - $databases->create('', ''); + $databases->createCollection('', '', '', []); } catch (\Throwable $e) { if ($e->getCode() == 401) { - $report['Databases'][] = 'API Key is missing scope: databases.write'; + $report[Transfer::GROUP_DATABASES][] = 'API Key is missing scope: collections.write'; } } try { - $databases->listCollections('', [], ''); + $databases->listDocuments('', '', []); } catch (\Throwable $e) { if ($e->getCode() == 401) { - $report['Databases'][] = 'API Key is missing scope: collections.write'; + $report[Transfer::GROUP_DATABASES][] = 'API Key is missing scope: documents.write'; } } try { - $databases->createCollection('', '', '', []); + $databases->createDocument('', '', '', [], []); } catch (\Throwable $e) { if ($e->getCode() == 401) { - $report['Databases'][] = 'API Key is missing scope: collections.write'; + $report[Transfer::GROUP_DATABASES][] = 'API Key is missing scope: documents.write'; } } try { - $databases->listDocuments('', '', []); + $databases->listIndexes('', ''); } catch (\Throwable $e) { if ($e->getCode() == 401) { - $report['Databases'][] = 'API Key is missing scope: documents.write'; + $report[Transfer::GROUP_DATABASES][] = 'API Key is missing scope: indexes.read'; } } try { - $databases->createDocument('', '', '', [], []); + $databases->createIndex('', '', '', '', [], []); } catch (\Throwable $e) { if ($e->getCode() == 401) { - $report['Documents'][] = 'API Key is missing scope: documents.write'; + $report[Transfer::GROUP_DATABASES][] = 'API Key is missing scope: indexes.write'; } } try { - $databases->listIndexes('', ''); + $databases->listAttributes('', ''); } catch (\Throwable $e) { if ($e->getCode() == 401) { - $report['Databases'][] = 'API Key is missing scope: indexes.read'; + $report[Transfer::GROUP_DATABASES][] = 'API Key is missing scope: attributes.read'; } } try { - $databases->createIndex('', '', '', '', [], []); + $databases->createStringAttribute('', '', '', 0, false, false); + } catch (\Throwable $e) { + if ($e->getCode() == 401) { + $report[Transfer::GROUP_DATABASES][] = 'API Key is missing scope: attributes.write'; + } + } + break; + case Transfer::GROUP_AUTH: + $auth = new Users($this->client); + try { + $auth->list(); } catch (\Throwable $e) { if ($e->getCode() == 401) { - $report['Databases'][] = 'API Key is missing scope: indexes.write'; + $report[Transfer::GROUP_AUTH][] = 'API Key is missing scope: users.read'; } } try { - $databases->listAttributes('', ''); + $auth->create('', '', '', ''); } catch (\Throwable $e) { if ($e->getCode() == 401) { - $report['Databases'][] = 'API Key is missing scope: attributes.read'; + $report[Transfer::GROUP_AUTH][] = 'API Key is missing scope: users.write'; + } + } + break; + case Transfer::GROUP_STORAGE: + $storage = new Storage($this->client); + try { + $storage->listFiles(''); + } catch (\Throwable $e) { + if ($e->getCode() == 401) { + $report[Transfer::GROUP_STORAGE][] = 'API Key is missing scope: files.read'; } } try { - $databases->createStringAttribute('', '', '', 0, false, false); + $storage->createFile('', '', new InputFile()); } catch (\Throwable $e) { if ($e->getCode() == 401) { - $report['Databases'][] = 'API Key is missing scope: attributes.write'; + $report[Transfer::GROUP_STORAGE][] = 'API Key is missing scope: files.write'; } } + break; } } return $report; } - function importResources(array $resources, callable $callback, string $group): void + function importResources(array $resources, callable $callback): void { foreach ($resources as $resource) { /** @var Resource $resource */ @@ -212,9 +230,6 @@ function importResources(array $resources, callable $callback, string $group): v case Transfer::GROUP_DATABASES: $responseResource = $this->importDatabaseResource($resource); break; - case Transfer::GROUP_DOCUMENTS: - $responseResource = $this->importDocumentResource($resource); - break; case Transfer::GROUP_STORAGE: $responseResource = $this->importFileResource($resource); break; @@ -271,7 +286,7 @@ public function importDatabaseResource(Resource $resource): Resource break; case Resource::TYPE_DOCUMENT: /** @var Document $resource */ - $response = $databaseService->createDocument( + $databaseService->createDocument( $resource->getDatabase()->getId(), $resource->getCollection()->getId(), $resource->getId(), @@ -369,32 +384,6 @@ public function awaitAttributeCreation(Attribute $attribute, int $timeout): bool throw new \Exception('Attribute creation timeout'); } - public function importDocumentResource(Resource $resource): Resource - { - $databaseService = new Databases($this->client); - - try { - switch ($resource->getName()) { - case Resource::TYPE_DOCUMENT: - /** @var Document $resource */ - $databaseService->createDocument( - $resource->getDatabase()->getId(), - $resource->getCollection()->getId(), - $resource->getId(), - $resource->getData(), - $resource->getPermissions() - ); - break; - } - - $resource->setStatus(Resource::STATUS_SUCCESS); - } catch (\Exception $e) { - $resource->setStatus(Resource::STATUS_ERROR, $e->getMessage()); - } finally { - return $resource; - } - } - public function importFileResource(Resource $resource): Resource { $storageService = new Storage($this->client); diff --git a/src/Transfer/Destinations/Local.php b/src/Transfer/Destinations/Local.php index 2dd5f5b..57a1aa4 100644 --- a/src/Transfer/Destinations/Local.php +++ b/src/Transfer/Destinations/Local.php @@ -35,7 +35,7 @@ public function __construct(string $path) * * @return string */ - public function getName(): string + static function getName(): string { return 'Local'; } @@ -50,7 +50,6 @@ public function getSupportedResources(): array return [ Transfer::GROUP_AUTH, Transfer::GROUP_DATABASES, - Transfer::GROUP_DOCUMENTS, Transfer::GROUP_STORAGE, Transfer::GROUP_FUNCTIONS ]; @@ -65,11 +64,10 @@ public function getSupportedResources(): array public function check(array $resources = []): array { $report = [ - 'Users' => [], - 'Databases' => [], - 'Documents' => [], - 'Files' => [], - 'Functions' => [] + Transfer::GROUP_AUTH => [], + Transfer::GROUP_DATABASES => [], + Transfer::GROUP_STORAGE => [], + Transfer::GROUP_FUNCTIONS => [] ]; if (empty($resources)) { @@ -78,7 +76,7 @@ public function check(array $resources = []): array // Check we can write to the file if (!\is_writable($this->path . '/backup.json')) { - $report['Databases'][] = 'Unable to write to file: ' . $this->path; + $report[Transfer::GROUP_DATABASES][] = 'Unable to write to file: ' . $this->path; throw new \Exception('Unable to write to file: ' . $this->path); } @@ -87,10 +85,16 @@ public function check(array $resources = []): array public function syncFile(): void { + $jsonEncodedData = \json_encode($this->data, JSON_PRETTY_PRINT); + + if ($jsonEncodedData === false) { + throw new \Exception('Unable to encode data to JSON'); + } + \file_put_contents($this->path . '/backup.json', \json_encode($this->data, JSON_PRETTY_PRINT)); } - public function importResources(array $resources, callable $callback, string $group): void + public function importResources(array $resources, callable $callback): void { foreach ($resources as $resource) { /** @var Resource $resource */ @@ -99,32 +103,35 @@ public function importResources(array $resources, callable $callback, string $gr /** @var FileData $resource */ // Handle folders - if (str_contains($resource->getFile()->getFileName(), '/')) { - $folders = explode('/', $resource->getFile()->getFileName()); - $folderPath = $this->path . '/files'; + if (str_contains($resource->getFile()->getFileName(), '/')) { + $folders = explode('/', $resource->getFile()->getFileName()); + $folderPath = $this->path . '/files'; - foreach ($folders as $folder) { - $folderPath .= '/' . $folder; + foreach ($folders as $folder) { + $folderPath .= '/' . $folder; - if (!\file_exists($folderPath) && str_contains($folder, '.') === false) { - mkdir($folderPath, 0777, true); - } + if (!\file_exists($folderPath) && str_contains($folder, '.') === false) { + mkdir($folderPath, 0777, true); } } + } file_put_contents($this->path . '/files/' . $resource->getFile()->getFileName(), $resource->getData(), FILE_APPEND); - break; - } + break; + } case "File": { /** @var File $resource */ - if (\file_exists($this->path . '/files/' . $resource->getFileName())) { - \unlink($this->path . '/files/' . $resource->getFileName()); - } - break; + if (\file_exists($this->path . '/files/' . $resource->getFileName())) { + \unlink($this->path . '/files/' . $resource->getFileName()); } + break; + } + } + + if ($resource->getName() !== Resource::TYPE_FILEDATA) { + $this->data[$resource->getGroup()][$resource->getName()][] = $resource->asArray(); } - $this->data[$group][$resource->getName()][] = $resource->asArray(); $resource->setStatus(Resource::STATUS_SUCCESS); $this->resourceCache->update($resource); $this->syncFile(); diff --git a/src/Transfer/Resource.php b/src/Transfer/Resource.php index 9ca662e..50cbe3f 100644 --- a/src/Transfer/Resource.php +++ b/src/Transfer/Resource.php @@ -10,6 +10,13 @@ abstract class Resource const STATUS_SKIPPED = 'SKIP'; const STATUS_PROCESSING = 'PROCESSING'; const STATUS_WARNING = 'WARNING'; + /** + * For some transfers (namely Firebase) we have to keep resources in cache that do not necessarily need to be Transferred + * This status is used to mark resources that are not going to be transferred but are still needed for the transfer to work + * e.g Documents are required for Database transfers because of schema tracing in firebase + */ + const STATUS_DISREGARDED = 'DISREGARDED'; + const TYPE_ATTRIBUTE = 'Attribute'; const TYPE_BUCKET = 'Bucket'; @@ -52,7 +59,7 @@ abstract class Resource * * @return string */ - abstract public function getName(): string; + abstract static function getName(): string; /** * Get Parent Group diff --git a/src/Transfer/ResourceCache.php b/src/Transfer/ResourceCache.php index c5cec51..e38eb71 100644 --- a/src/Transfer/ResourceCache.php +++ b/src/Transfer/ResourceCache.php @@ -20,10 +20,10 @@ public function add($resource) { $resourceUUID = uniqid(); $resource->setInternalId($resourceUUID); // Assign each resource a unique ID - $this->resourceCache[get_class($resource)][$resourceUUID] = $resource; + $this->resourceCache[$resource->getName()][$resourceUUID] = $resource; } - public function addAll($resources) + public function addAll(array $resources) { foreach ($resources as $resource) { $this->add($resource); @@ -32,11 +32,11 @@ public function addAll($resources) public function update($resource) { - if (!in_array($resource, $this->resourceCache[get_class($resource)])) { + if (!in_array($resource, $this->resourceCache[$resource->getName()])) { throw new \Exception('Resource does not exist in cache'); } - $this->resourceCache[get_class($resource)][$resource->getInternalId()] = $resource; + $this->resourceCache[$resource->getName()][$resource->getInternalId()] = $resource; } public function updateAll($resources) @@ -48,18 +48,18 @@ public function updateAll($resources) public function remove($resource) { - if (!in_array($resource, $this->resourceCache[get_class($resource)])) { + if (!in_array($resource, $this->resourceCache[$resource->getName()])) { throw new \Exception('Resource does not exist in cache'); } - unset($this->resourceCache[get_class($resource)][$resource->getInternalId()]); + unset($this->resourceCache[$resource->getName()][$resource->getInternalId()]); } /** * Get Resources * * @param string|Resource $resourceType - * + * * @return Resource[] */ public function get($resource) @@ -67,7 +67,7 @@ public function get($resource) if (is_string($resource)) { return $this->resourceCache[$resource] ?? []; } else { - return $this->resourceCache[get_class($resource)] ?? []; + return $this->resourceCache[$resource->getName()] ?? []; } } @@ -76,7 +76,25 @@ public function getAll() return $this->resourceCache; } - public function clear() + public function getStatusCounters() + { + $status = [ + Resource::STATUS_DISREGARDED => 0, + Resource::STATUS_SUCCESS => 0, + Resource::STATUS_ERROR => 0, + Resource::STATUS_SKIPPED => 0, + ]; + + foreach ($this->resourceCache as $resources) { + foreach ($resources as $resource) { + $status[$resource->getStatus()]++; + } + } + + return $status; + } + + public function wipe() { $this->resourceCache = []; } diff --git a/src/Transfer/Resources/Auth/Hash.php b/src/Transfer/Resources/Auth/Hash.php index aaaf1a8..60de7a5 100644 --- a/src/Transfer/Resources/Auth/Hash.php +++ b/src/Transfer/Resources/Auth/Hash.php @@ -42,7 +42,7 @@ public function __construct(string $hash, string $salt = '', string $algorithm = $this->passwordLength = $passwordLength; } - public function getName(): string + static function getName(): string { return Resource::TYPE_HASH; } diff --git a/src/Transfer/Resources/Auth/Team.php b/src/Transfer/Resources/Auth/Team.php index 5f67297..0cd97d4 100644 --- a/src/Transfer/Resources/Auth/Team.php +++ b/src/Transfer/Resources/Auth/Team.php @@ -21,7 +21,7 @@ function __construct(string $id, string $name, array $preferences = [], array $m $this->members = $members; } - function getName(): string + static function getName(): string { return Resource::TYPE_TEAM; } diff --git a/src/Transfer/Resources/Auth/TeamMembership.php b/src/Transfer/Resources/Auth/TeamMembership.php index f8889e0..5d23dfa 100644 --- a/src/Transfer/Resources/Auth/TeamMembership.php +++ b/src/Transfer/Resources/Auth/TeamMembership.php @@ -20,7 +20,7 @@ function __construct(Team $team, string $userId, array $roles = [], bool $active $this->active = $active; } - function getName(): string + static function getName(): string { return Resource::TYPE_TEAM_MEMBERSHIP; } diff --git a/src/Transfer/Resources/Auth/User.php b/src/Transfer/Resources/Auth/User.php index eb9ddda..d8c778b 100644 --- a/src/Transfer/Resources/Auth/User.php +++ b/src/Transfer/Resources/Auth/User.php @@ -57,7 +57,7 @@ public function __construct( * * @return string */ - public function getName(): string + static function getName(): string { return Resource::TYPE_USER; } diff --git a/src/Transfer/Resources/Database/Attribute.php b/src/Transfer/Resources/Database/Attribute.php index e8cdd8e..df48742 100644 --- a/src/Transfer/Resources/Database/Attribute.php +++ b/src/Transfer/Resources/Database/Attribute.php @@ -37,7 +37,7 @@ public function __construct(string $key, Collection $collection, bool $required $this->collection = $collection; } - public function getName(): string + static function getName(): string { return Resource::TYPE_ATTRIBUTE; } diff --git a/src/Transfer/Resources/Database/Collection.php b/src/Transfer/Resources/Database/Collection.php index 126abb1..b085241 100644 --- a/src/Transfer/Resources/Database/Collection.php +++ b/src/Transfer/Resources/Database/Collection.php @@ -49,7 +49,7 @@ public function __construct(Database $database, string $name, string $id, bool $ $this->permissions = $permissions; } - public function getName(): string + static function getName(): string { return Resource::TYPE_COLLECTION; } diff --git a/src/Transfer/Resources/Database/Database.php b/src/Transfer/Resources/Database/Database.php index a03b1ee..7e78171 100644 --- a/src/Transfer/Resources/Database/Database.php +++ b/src/Transfer/Resources/Database/Database.php @@ -15,9 +15,6 @@ class Database extends Resource { - public const DB_RELATIONAL = 'relational'; - public const DB_NON_RELATIONAL = 'non-relational'; - /** * @var list $collections */ @@ -25,16 +22,14 @@ class Database extends Resource protected string $name; protected string $id; - protected string $type; - public function __construct(string $name = '', string $id = '', string $type = self::DB_RELATIONAL) + public function __construct(string $name = '', string $id = '') { $this->name = $name; $this->id = $id; - $this->type = $type; } - public function getName(): string + static function getName(): string { return Resource::TYPE_DATABASE; } @@ -60,17 +55,6 @@ public function setId(string $id): self return $this; } - public function getType(): string - { - return $this->type; - } - - public function setType(string $type): self - { - $this->type = $type; - return $this; - } - /** * @return list */ diff --git a/src/Transfer/Resources/Database/Document.php b/src/Transfer/Resources/Database/Document.php index 5bef040..31af490 100644 --- a/src/Transfer/Resources/Database/Document.php +++ b/src/Transfer/Resources/Database/Document.php @@ -22,14 +22,14 @@ public function __construct(string $id, Database $database, Collection $collecti $this->permissions = $permissions; } - public function getName(): string + static function getName(): string { return Resource::TYPE_DOCUMENT; } public function getGroup(): string { - return Transfer::GROUP_DOCUMENTS; + return Transfer::GROUP_DATABASES; } public function getId(): string diff --git a/src/Transfer/Resources/Database/Index.php b/src/Transfer/Resources/Database/Index.php index d47ac50..2859daa 100644 --- a/src/Transfer/Resources/Database/Index.php +++ b/src/Transfer/Resources/Database/Index.php @@ -35,7 +35,7 @@ public function __construct(string $id, string $key, Collection $collection, str $this->collection = $collection; } - public function getName(): string + static function getName(): string { return Resource::TYPE_INDEX; } diff --git a/src/Transfer/Resources/Functions/EnvVar.php b/src/Transfer/Resources/Functions/EnvVar.php index 9db7c42..e08395d 100644 --- a/src/Transfer/Resources/Functions/EnvVar.php +++ b/src/Transfer/Resources/Functions/EnvVar.php @@ -18,7 +18,7 @@ public function __construct(Func $func, string $key, string $value) $this->value = $value; } - public function getName(): string + static function getName(): string { return Resource::TYPE_ENVVAR; } diff --git a/src/Transfer/Resources/Functions/Func.php b/src/Transfer/Resources/Functions/Func.php index ff27fd3..dbeff41 100644 --- a/src/Transfer/Resources/Functions/Func.php +++ b/src/Transfer/Resources/Functions/Func.php @@ -28,7 +28,7 @@ public function __construct(string $name, string $id, string $runtime, array $ex $this->timeout = $timeout; } - public function getName(): string + static function getName(): string { return Resource::TYPE_FUNCTION; } diff --git a/src/Transfer/Resources/Project.php b/src/Transfer/Resources/Project.php index b0aabf3..71e4114 100644 --- a/src/Transfer/Resources/Project.php +++ b/src/Transfer/Resources/Project.php @@ -16,14 +16,14 @@ public function __construct(string $name = '', string $id = '') $this->id = $id; } - public function getName(): string + static function getName(): string { return Resource::TYPE_PROJECT; } public function getGroup(): string { - return Transfer::GROUP_GENERAL; + return Transfer::GROUP_SETTINGS; } public function getId(): string diff --git a/src/Transfer/Resources/Storage/Bucket.php b/src/Transfer/Resources/Storage/Bucket.php index bee9ae7..7ea786c 100644 --- a/src/Transfer/Resources/Storage/Bucket.php +++ b/src/Transfer/Resources/Storage/Bucket.php @@ -32,7 +32,7 @@ public function __construct(string $id = '', array $permissions = [], bool $file $this->antiVirus = $antiVirus; } - public function getName(): string + static function getName(): string { return Resource::TYPE_BUCKET; } diff --git a/src/Transfer/Resources/Storage/File.php b/src/Transfer/Resources/Storage/File.php index 62b89c6..03af00f 100644 --- a/src/Transfer/Resources/Storage/File.php +++ b/src/Transfer/Resources/Storage/File.php @@ -26,7 +26,7 @@ public function __construct(string $id = '', Bucket $bucket = null, string $name $this->size = $size; } - public function getName(): string + static function getName(): string { return Resource::TYPE_FILE; } diff --git a/src/Transfer/Resources/Storage/FileData.php b/src/Transfer/Resources/Storage/FileData.php index 98fd0ed..57b3652 100644 --- a/src/Transfer/Resources/Storage/FileData.php +++ b/src/Transfer/Resources/Storage/FileData.php @@ -20,7 +20,7 @@ public function __construct(string $data, int $start, int $end, File $file) $this->file = $file; } - public function getName(): string + static function getName(): string { return Resource::TYPE_FILEDATA; } diff --git a/src/Transfer/Source.php b/src/Transfer/Source.php index 339e16a..acd723e 100644 --- a/src/Transfer/Source.php +++ b/src/Transfer/Source.php @@ -4,99 +4,113 @@ abstract class Source extends Target { + protected $transferCallback; + + public function callback(array $resources): void + { + ($this->transferCallback)($resources); + } + /** - * Transfer Groups into destination + * Transfer Resources into destination * - * @param array $groups + * @param array $resources * @param callable $callback */ - public function run(array $groups, callable $callback): void + public function run(array $resources, callable $callback): void { - //TODO: Check we have no unsupported groups. - - if (in_array(Transfer::GROUP_AUTH, $groups)) { - $this->exportAuth(100, function (array $users) use ($callback) { - $this->resourceCache->addAll($users); - $callback(Transfer::GROUP_AUTH, $users); - }); - } - - if (in_array(Transfer::GROUP_DATABASES, $groups)) { - $this->exportDatabases(100, function (array $databases) use ($callback) { - $this->resourceCache->addAll($databases); - $callback(Transfer::GROUP_DATABASES, $databases); - }); - } - - if (in_array(Transfer::GROUP_DOCUMENTS, $groups)) { - $this->exportDocuments(100, function (array $documents) use ($callback) { - $this->resourceCache->addAll($documents); - $callback(Transfer::GROUP_DOCUMENTS, $documents); - }); - } - - if (in_array(Transfer::GROUP_STORAGE, $groups)) { - $this->exportFiles(100, function (array $files) use ($callback) { - $this->resourceCache->addAll($files); - $callback(Transfer::GROUP_STORAGE, $files); - }); - } + $this->transferCallback = function (array $resources) use ($callback) { + $this->resourceCache->addAll($resources); + $callback($resources); + }; - if (in_array(Transfer::GROUP_FUNCTIONS, $groups)) { - $this->exportFunctions(100, function (array $functions) use ($callback) { - $this->resourceCache->addAll($functions); - $callback(Transfer::GROUP_FUNCTIONS, $functions); - }); - } + $this->exportResources($resources, 100); } /** - * Export Users + * Export Resources * + * @param string[] $resources * @param int $batchSize - * @param callable $callback Callback function to be called after each batch, $callback(user[] $batch); * * @return void */ - abstract public function exportAuth(int $batchSize, callable $callback): void; + public function exportResources(array $resources, int $batchSize) + { + // Convert Resources back into their relevant groups + $groups = []; + foreach ($resources as $resource) { + if (in_array($resource, Transfer::GROUP_AUTH_RESOURCES)) { + $groups[Transfer::GROUP_AUTH][] = $resource; + } elseif (in_array($resource, Transfer::GROUP_DATABASES_RESOURCES)) { + $groups[Transfer::GROUP_DATABASES][] = $resource; + } elseif (in_array($resource, Transfer::GROUP_STORAGE_RESOURCES)) { + $groups[Transfer::GROUP_STORAGE][] = $resource; + } elseif (in_array($resource, Transfer::GROUP_FUNCTIONS_RESOURCES)) { + $groups[Transfer::GROUP_FUNCTIONS][] = $resource; + } + } + + if (empty($groups)) { + return; + } + + // Send each group to the relevant export function + foreach ($groups as $group => $resources) { + switch ($group) { + case Transfer::GROUP_AUTH: + $this->exportAuthGroup($batchSize, $resources); + break; + case Transfer::GROUP_DATABASES: + $this->exportDatabasesGroup($batchSize, $resources); + break; + case Transfer::GROUP_STORAGE: + $this->exportStorageGroup($batchSize, $resources); + break; + case Transfer::GROUP_FUNCTIONS: + $this->exportFunctionsGroup($batchSize, $resources); + break; + } + } + } /** - * Export Databases + * Export Auth Group * - * @param int $batchSize Max 100 - * @param callable $callback Callback function to be called after each database, $callback(database[] $batch); + * @param int $batchSize + * @param array $resources Resources to export * * @return void */ - abstract public function exportDatabases(int $batchSize, callable $callback): void; + abstract public function exportAuthGroup(int $batchSize, array $resources); /** - * Export Documents + * Export Databases Group * * @param int $batchSize Max 100 - * @param callable $callback Callback function to be called after each document, $callback(document[] $batch); + * @param array $resources Resources to export * * @return void */ - abstract public function exportDocuments(int $batchSize, callable $callback): void; + abstract public function exportDatabasesGroup(int $batchSize, array $resources); /** - * Export Files + * Export Storage Group * * @param int $batchSize Max 5 - * @param callable $callback Callback function to be called after each batch, $callback(File[]|Bucket[] $batch); + * @param array $resources Resources to export * * @return void */ - abstract public function exportFiles(int $batchSize, callable $callback): void; + abstract public function exportStorageGroup(int $batchSize, array $resources); /** - * Export Functions + * Export Functions Group * * @param int $batchSize Max 100 - * @param callable $callback Callback function to be called after each function, $callback(function[] $batch); + * @param array $resources Resources to export * * @return void */ - abstract public function exportFunctions(int $batchSize, callable $callback): void; + abstract public function exportFunctionsGroup(int $batchSize, array $resources); } diff --git a/src/Transfer/Sources/Appwrite.php b/src/Transfer/Sources/Appwrite.php index 46b0f9f..74edbb2 100644 --- a/src/Transfer/Sources/Appwrite.php +++ b/src/Transfer/Sources/Appwrite.php @@ -10,6 +10,7 @@ use Appwrite\Services\Storage; use Appwrite\Services\Teams; use Appwrite\Services\Users; +use Utopia\Transfer\Resource; use Utopia\Transfer\Resources\Database\Attribute; use Utopia\Transfer\Transfer; use Utopia\Transfer\Resources\Database\Attributes\BoolAttribute; @@ -81,7 +82,7 @@ public function __construct(string $project, string $endpoint, string $key) * * @return string */ - public function getName(): string + static function getName(): string { return "Appwrite"; } @@ -96,7 +97,6 @@ public function getSupportedResources(): array return [ Transfer::GROUP_AUTH, Transfer::GROUP_DATABASES, - Transfer::GROUP_DOCUMENTS, Transfer::GROUP_STORAGE, Transfer::GROUP_FUNCTIONS ]; @@ -105,11 +105,10 @@ public function getSupportedResources(): array public function check(array $resources = []): array { $report = [ - "Users" => [], - "Databases" => [], - "Documents" => [], - "Files" => [], - "Functions" => [], + Transfer::GROUP_AUTH => [], + Transfer::GROUP_DATABASES => [], + Transfer::GROUP_STORAGE => [], + Transfer::GROUP_FUNCTIONS => [] ]; if (empty($resources)) { @@ -127,29 +126,7 @@ public function check(array $resources = []): array $databases->list(); } catch (\Throwable $e) { if ($e->getCode() == 401) { - $report["Databases"][] = - "API Key is missing scope: databases.read"; - } - } - break; - case Transfer::GROUP_AUTH: - $auth = new Users($this->client); - try { - $auth->list(); - } catch (\Throwable $e) { - if ($e->getCode() == 401) { - $report["Users"][] = - "API Key is missing scope: users.read"; - } - } - break; - case Transfer::GROUP_DOCUMENTS: - $databases = new Databases($this->client); - try { - $databases->list(); - } catch (\Throwable $e) { - if ($e->getCode() == 401) { - $report["Databases"][] = + $report[Transfer::GROUP_DATABASES][] = "API Key is missing scope: databases.read"; } } @@ -158,7 +135,7 @@ public function check(array $resources = []): array $databases->create("", ""); } catch (\Throwable $e) { if ($e->getCode() == 401) { - $report["Databases"][] = + $report[Transfer::GROUP_DATABASES][] = "API Key is missing scope: databases.write"; } } @@ -167,7 +144,7 @@ public function check(array $resources = []): array $databases->listCollections("", [], ""); } catch (\Throwable $e) { if ($e->getCode() == 401) { - $report["Databases"][] = + $report[Transfer::GROUP_DATABASES][] = "API Key is missing scope: collections.write"; } } @@ -176,7 +153,7 @@ public function check(array $resources = []): array $databases->createCollection("", "", "", []); } catch (\Throwable $e) { if ($e->getCode() == 401) { - $report["Databases"][] = + $report[Transfer::GROUP_DATABASES][] = "API Key is missing scope: collections.write"; } } @@ -185,7 +162,7 @@ public function check(array $resources = []): array $databases->listDocuments("", "", []); } catch (\Throwable $e) { if ($e->getCode() == 401) { - $report["Databases"][] = + $report[Transfer::GROUP_DATABASES][] = "API Key is missing scope: documents.write"; } } @@ -203,7 +180,7 @@ public function check(array $resources = []): array $databases->listIndexes("", ""); } catch (\Throwable $e) { if ($e->getCode() == 401) { - $report["Databases"][] = + $report[Transfer::GROUP_DATABASES][] = "API Key is missing scope: indexes.read"; } } @@ -212,7 +189,7 @@ public function check(array $resources = []): array $databases->createIndex("", "", "", "", [], []); } catch (\Throwable $e) { if ($e->getCode() == 401) { - $report["Databases"][] = + $report[Transfer::GROUP_DATABASES][] = "API Key is missing scope: indexes.write"; } } @@ -221,7 +198,7 @@ public function check(array $resources = []): array $databases->listAttributes("", ""); } catch (\Throwable $e) { if ($e->getCode() == 401) { - $report["Databases"][] = + $report[Transfer::GROUP_DATABASES][] = "API Key is missing scope: attributes.read"; } } @@ -237,10 +214,52 @@ public function check(array $resources = []): array ); } catch (\Throwable $e) { if ($e->getCode() == 401) { - $report["Databases"][] = + $report[Transfer::GROUP_DATABASES][] = "API Key is missing scope: attributes.write"; } } + break; + case Transfer::GROUP_AUTH: + $auth = new Users($this->client); + try { + $auth->list(); + } catch (\Throwable $e) { + if ($e->getCode() == 401) { + $report[Transfer::GROUP_AUTH][] = + "API Key is missing scope: users.read"; + } + } + break; + case Transfer::GROUP_STORAGE: + $storage = new Storage($this->client); + try { + $storage->listFiles(''); + } catch (\Throwable $e) { + if ($e->getCode() == 401) { + $report[Transfer::GROUP_STORAGE][] = + "API Key is missing scope: files.read"; + } + } + case Transfer::GROUP_FUNCTIONS: + $functions = new Functions($this->client); + try { + $functions->list(); + } catch (\Throwable $e) { + if ($e->getCode() == 401) { + $report[Transfer::GROUP_FUNCTIONS][] = + "API Key is missing scope: functions.read"; + } + } + + try { + $functions->listExecutions(''); + } catch (\Throwable $e) { + if ($e->getCode() == 401) { + $report[Transfer::GROUP_FUNCTIONS][] = + "API Key is missing scope: executions.read"; + } + } + break; } } @@ -251,15 +270,27 @@ public function check(array $resources = []): array * Export Auth Resources * * @param int $batchSize Max 100 - * @param callable $callback Callback function to be called after each batch, $callback(user[] $batch); * * @return void */ - public function exportAuth(int $batchSize, callable $callback): void + public function exportAuthGroup(int $batchSize, array $resources) { - $usersClient = new Users($this->client); - $teamsClient = new Teams($this->client); + if (in_array(Resource::TYPE_USER, $resources)) { + $this->exportUsers($batchSize); + } + + if (in_array(Resource::TYPE_TEAM, $resources)) { + $this->exportTeams($batchSize); + } + + if (in_array(Resource::TYPE_TEAM_MEMBERSHIP, $resources)) { + $this->exportTeamMemberships($batchSize); + } + } + function exportUsers(int $batchSize) + { + $usersClient = new Users($this->client); $lastDocument = null; // Export Users @@ -296,13 +327,17 @@ public function exportAuth(int $batchSize, callable $callback): void $lastDocument = $user['$id']; } - $callback($users); + $this->callback($users); if (count($users) < $batchSize) { break; } } + } + function exportTeams(int $batchSize) + { + $teamsClient = new Teams($this->client); $lastDocument = null; // Export Teams @@ -331,17 +366,22 @@ public function exportAuth(int $batchSize, callable $callback): void $lastDocument = $team['$id']; } - $callback($teams); + $this->callback($teams); if (count($teams) < $batchSize) { break; } } + } + + function exportTeamMemberships(int $batchSize) + { + $teamsClient = new Teams($this->client); $lastDocument = null; // Export Memberships - $cacheTeams = $this->resourceCache->get(Team::class); + $cacheTeams = $this->resourceCache->get(Team::getName()); foreach ($cacheTeams as $team) { while (true) { @@ -370,7 +410,7 @@ public function exportAuth(int $batchSize, callable $callback): void $lastDocument = $membership['$id']; } - $callback($memberships); + $this->callback($memberships); if (count($memberships) < $batchSize) { break; @@ -379,7 +419,84 @@ public function exportAuth(int $batchSize, callable $callback): void } } - public function convertAttribute(array $value, Collection $collection): Attribute + public function exportDatabasesGroup(int $batchSize, array $resources) + { + if (in_array(Resource::TYPE_DATABASE, $resources)) { + $this->exportDatabases($batchSize); + } + + if (in_array(Resource::TYPE_COLLECTION, $resources)) { + $this->exportCollections($batchSize); + } + + if (in_array(Resource::TYPE_ATTRIBUTE, $resources)) { + $this->exportAttributes($batchSize); + } + + if (in_array(Resource::TYPE_INDEX, $resources)) { + $this->exportIndexes($batchSize); + } + + if (in_array(Resource::TYPE_DOCUMENT, $resources)) { + $this->exportDocuments($batchSize); + } + } + + + function exportDocuments(int $batchSize) + { + $databaseClient = new Databases($this->client); + $collections = $this->resourceCache->get(Collection::getName()); + + foreach ($collections as $collection) { + /** @var Collection $collection */ + $lastDocument = null; + + while (true) { + $queries = [Query::limit($batchSize)]; + + $documents = []; + + if ($lastDocument) { + $queries[] = Query::cursorAfter($lastDocument); + } + + $response = $databaseClient->listDocuments( + $collection->getDatabase()->getId(), + $collection->getId(), + $queries + ); + + foreach ($response["documents"] as $document) { + $id = $document['$id']; + $permissions = $document['$permissions']; + unset($document['$id']); + unset($document['$permissions']); + unset($document['$collectionId']); + unset($document['$updatedAt']); + unset($document['$createdAt']); + unset($document['$databaseId']); + + $documents[] = new Document( + $id, + $collection->getDatabase(), + $collection, + $document, + $permissions + ); + $lastDocument = $id; + } + + $this->callback($documents); + + if (count($response["documents"]) < $batchSize) { + break; + } + } + } + } + + function convertAttribute(array $value, Collection $collection): Attribute { switch ($value["type"]) { case "string": @@ -492,15 +609,7 @@ public function convertAttribute(array $value, Collection $collection): Attribut throw new \Exception("Unknown attribute type: " . $value["type"]); } - /** - * Export Databases - * - * @param int $batchSize Max 100 - * @param callable $callback Callback function to be called after each database, $callback(Resource $batch); - * - * @return void - */ - public function exportDatabases(int $batchSize, callable $callback): void + function exportDatabases(int $batchSize) { $databaseClient = new Databases($this->client); @@ -526,17 +635,22 @@ public function exportDatabases(int $batchSize, callable $callback): void $databases[] = $newDatabase; } - $callback($databases); + $this->callback($databases); if (count($databases) < $batchSize) { break; } } + } + + function exportCollections(int $batchSize) + { + $databaseClient = new Databases($this->client); // Transfer Collections $lastDocument = null; - $databases = $this->resourceCache->get(Database::class); + $databases = $this->resourceCache->get(Database::getName()); foreach ($databases as $database) { while (true) { $queries = [Query::limit($batchSize)]; @@ -563,17 +677,22 @@ public function exportDatabases(int $batchSize, callable $callback): void $collections[] = $newCollection; } - $callback($collections); + $this->callback($collections); if (count($collections) < $batchSize) { break; } } } + } + + function exportAttributes(int $batchSize) + { + $databaseClient = new Databases($this->client); // Transfer Attributes $lastDocument = null; - $collections = $this->resourceCache->get(Collection::class); + $collections = $this->resourceCache->get(Collection::getName()); /** @var Collection[] $collections */ foreach ($collections as $collection) { @@ -595,17 +714,25 @@ public function exportDatabases(int $batchSize, callable $callback): void $attributes[] = $this->convertAttribute($attribute, $collection); } - $callback($attributes); + $this->callback($attributes); if (count($attributes) < $batchSize) { break; } } } + } + + function exportIndexes(int $batchSize) + { + $databaseClient = new Databases($this->client); + + $collections = $this->resourceCache->get(Resource::TYPE_COLLECTION); // Transfer Indexes $lastDocument = null; foreach ($collections as $collection) { + /** @var Collection $collection */ while (true) { $queries = [Query::limit($batchSize)]; $indexes = []; @@ -631,7 +758,7 @@ public function exportDatabases(int $batchSize, callable $callback): void ); } - $callback($indexes); + $this->callback($indexes); if (count($indexes) < $batchSize) { break; @@ -640,75 +767,7 @@ public function exportDatabases(int $batchSize, callable $callback): void } } - /** - * Export Documents - * - * @param int $batchSize Max 100 - * @param callable $callback Callback function to be called after each batch, $callback(Document[] $batch); - * - * @return void - */ - public function exportDocuments(int $batchSize, callable $callback): void - { - $databaseClient = new Databases($this->client); - $collections = $this->resourceCache->get(Collection::class); - - foreach ($collections as $collection) { - /** @var Collection $collection */ - $lastDocument = null; - - while (true) { - $queries = [Query::limit($batchSize)]; - - $documents = []; - - if ($lastDocument) { - $queries[] = Query::cursorAfter($lastDocument); - } - - $response = $databaseClient->listDocuments( - $collection->getDatabase()->getId(), - $collection->getId(), - $queries - ); - - foreach ($response["documents"] as $document) { - $id = $document['$id']; - $permissions = $document['$permissions']; - unset($document['$id']); - unset($document['$permissions']); - unset($document['$collectionId']); - unset($document['$updatedAt']); - unset($document['$createdAt']); - unset($document['$databaseId']); - - $documents[] = new Document( - $id, - $collection->getDatabase(), - $collection, - $document, - $permissions - ); - $lastDocument = $id; - } - - $callback($documents); - - if (count($response["documents"]) < $batchSize) { - break; - } - } - } - } - - /** - * Calculate Types - * - * @param array $user - * - * @return array - */ - protected function calculateTypes(array $user): array + function calculateTypes(array $user): array { if (empty($user["email"]) && empty($user["phone"])) { return [User::TYPE_ANONYMOUS]; @@ -727,14 +786,20 @@ protected function calculateTypes(array $user): array return $types; } - /** - * Export Files - * - * @param int $batchSize Max 5 - * @param callable $callback Callback function to be called after each batch, $callback(File[]|Bucket[] $batch); - */ - public function exportFiles(int $batchSize, callable $callback): void + public function exportStorageGroup(int $batchSize, array $resources) { + if (in_array(Resource::TYPE_BUCKET, $resources)) { + $this->exportBuckets($batchSize); + } + + if (in_array(Resource::TYPE_FILE, $resources)) { + $this->exportFiles($batchSize); + } + } + + function exportBuckets(int $batchSize) + { + //TODO: Impl batching $storageClient = new Storage($this->client); $buckets = $storageClient->listBuckets(); @@ -760,9 +825,15 @@ public function exportFiles(int $batchSize, callable $callback): void return; } - $callback($convertedBuckets); + $this->callback($convertedBuckets); + } - foreach ($convertedBuckets as $bucket) { + function exportFiles(int $batchSize) + { + $storageClient = new Storage($this->client); + + $buckets = $this->resourceCache->get(Bucket::getName()); + foreach ($buckets as $bucket) { $lastDocument = null; while (true) { @@ -778,7 +849,7 @@ public function exportFiles(int $batchSize, callable $callback): void ); foreach ($response["files"] as $file) { - $this->handleFileDataTransfer(new File( + $this->handleDataTransfer(new File( $file['$id'], $bucket, $file['name'], @@ -786,7 +857,7 @@ public function exportFiles(int $batchSize, callable $callback): void $file['mimeType'], $file['$permissions'], $file['sizeOriginal'], - ), $callback); + )); $lastDocument = $file['$id']; } @@ -798,16 +869,7 @@ public function exportFiles(int $batchSize, callable $callback): void } } - /** - * Handle File Data Transfer - * Streams a file to the destination - * - * @param File $file - * @param callable $callback (array $data) - * - * @return void - */ - protected function handleFileDataTransfer(File $file, callable $callback): void + function handleDataTransfer(File $file) { // Set the chunk size (5MB) $start = 0; @@ -829,7 +891,7 @@ protected function handleFileDataTransfer(File $file, callable $callback): void ); // Send the chunk to the callback function - $callback([new FileData( + $this->callback([new FileData( $chunkData, $start, $end, @@ -846,14 +908,16 @@ protected function handleFileDataTransfer(File $file, callable $callback): void } } - /** - * Export Functions - * - * @param int $batchSize Max 5 - * @param callable $callback Callback function to be called after each batch, $callback(Function[] $batch); - */ - public function exportFunctions(int $batchSize, callable $callback): void + public function exportFunctionsGroup(int $batchSize, array $resources) + { + if (in_array(Resource::TYPE_FUNCTION, $resources)) { + $this->exportFunctions($batchSize); + } + } + + public function exportFunctions(int $batchSize) { + //TODO: Implement batching $functionsClient = new Functions($this->client); $functions = $functionsClient->list(); @@ -887,6 +951,6 @@ public function exportFunctions(int $batchSize, callable $callback): void } } - $callback($convertedResources); + $this->callback($convertedResources); } } diff --git a/src/Transfer/Sources/Firebase.php b/src/Transfer/Sources/Firebase.php index 60b088a..e6dc4c9 100644 --- a/src/Transfer/Sources/Firebase.php +++ b/src/Transfer/Sources/Firebase.php @@ -2,168 +2,132 @@ namespace Utopia\Transfer\Sources; +use Utopia\Transfer\Resource; use Utopia\Transfer\Source; -use Google\Client; -use Google\Service\Firestore\ListCollectionIdsRequest; -use Utopia\Transfer\Resources\Project; -use Utopia\Transfer\Resources\User; use Utopia\Transfer\Transfer; -use Utopia\Transfer\Log; -use Utopia\Transfer\Resources\Database\Attribute as ResourcesAttribute; +use Utopia\Transfer\Resources\Auth\Hash; +use Utopia\Transfer\Resources\Auth\User; +use Utopia\Transfer\Resources\Database\Attribute; use Utopia\Transfer\Resources\Database\Attributes\BoolAttribute; use Utopia\Transfer\Resources\Database\Attributes\DateTimeAttribute; use Utopia\Transfer\Resources\Database\Attributes\FloatAttribute; use Utopia\Transfer\Resources\Database\Attributes\IntAttribute; use Utopia\Transfer\Resources\Database\Attributes\StringAttribute; use Utopia\Transfer\Resources\Database\Collection; -use Utopia\Transfer\Resources\Database; -use Utopia\Transfer\Resources\Auth\Hash; +use Utopia\Transfer\Resources\Database\Database; class Firebase extends Source { - public const TYPE_OAUTH = 'oauth'; - public const AUTH_SERVICEACCOUNT = 'serviceaccount'; - - /** - * @var array{object: array, type: string} - */ - protected $authentication = [ - 'type' => self::AUTH_SERVICEACCOUNT, - 'object' => [] - ]; - - /** - * @var Client|null - */ - protected $googleClient = null; - - /** - * @var Project|null - */ - protected $project; + private array $serviceAccount; + private string $projectID; + private string $currentToken = ''; + private int $tokenExpires = 0; - /** - * Constructor - * - * @param array $authObject Service Account Credentials for AUTH_SERVICEACCOUNT - * @param string $authType Can be either Firebase::TYPE_OAUTH or Firebase::AUTH_APIKEY - */ - public function __construct(array $authObject = [], string $authType = self::AUTH_SERVICEACCOUNT) + public function __construct(array $serviceAccount, string $projectID) { - if (!in_array($authType, [self::TYPE_OAUTH, self::AUTH_SERVICEACCOUNT])) { - throw new \Exception('Invalid authentication type'); - } - - $this->googleClient = new Client(); - - if ($authType === self::TYPE_OAUTH) { - $this->googleClient->setAccessToken($authObject); - } elseif ($authType === self::AUTH_SERVICEACCOUNT) { - $this->googleClient->setAuthConfig($authObject); - } - - $this->googleClient->addScope('https://www.googleapis.com/auth/firebase'); - $this->googleClient->addScope('https://www.googleapis.com/auth/cloud-platform'); + $this->serviceAccount = $serviceAccount; + $this->projectID = $projectID; } - public function getName(): string + static function getName(): string { return 'Firebase'; } - public function getSupportedResources(): array + function base64url_encode($data) { - return [ - Transfer::GROUP_AUTH, - Transfer::GROUP_DATABASES - ]; + return str_replace(['+', '/', '='], ['-', '_', ''], base64_encode($data)); } - public function getProjects(): array + function calculateJWT(): string { - $projects = []; + $jwtClaim = [ + 'iss' => $this->serviceAccount['client_email'], + 'scope' => 'https://www.googleapis.com/auth/firebase https://www.googleapis.com/auth/cloud-platform https://www.googleapis.com/auth/datastore', + 'exp' => time() + 3600, + 'iat' => time(), + 'aud' => 'https://oauth2.googleapis.com/token' + ]; - if (!$this->googleClient) { - throw new \Exception('Google Client not initialized'); - } + $jwtHeader = [ + 'alg' => 'RS256', + 'typ' => 'JWT' + ]; - $firebase = new \Google\Service\FirebaseManagement($this->googleClient); + $jwtPayload = $this->base64url_encode(json_encode($jwtHeader)) . '.' . $this->base64url_encode(json_encode($jwtClaim)); - $request = $firebase->projects->listProjects(); + $jwtSignature = ''; + openssl_sign($jwtPayload, $jwtSignature, $this->serviceAccount['private_key'], 'sha256'); + $jwtSignature = $this->base64url_encode($jwtSignature); - if ($request['results']) { - foreach ($request['results'] as $project) { - $projects[] = new Project( - $project['displayName'], - $project['projectId'] - ); - } - } - - return $projects; + return $jwtPayload . '.' . $jwtSignature; } /** - * Set Project - * - * @param Project|string $project + * Computes the JWT then fetches an auth token from the Google OAuth2 API which is valid for an hour */ - public function setProject(Project|string $project): void + function authenticate() { - if (is_string($project)) { - $project = new Project($project, $project); + if (time() < $this->tokenExpires) { + return; } - $this->project = $project; + try { + $response = parent::call('POST', 'https://oauth2.googleapis.com/token', [ + 'Content-Type' => 'application/x-www-form-urlencoded', + ], [ + 'grant_type' => 'urn:ietf:params:oauth:grant-type:jwt-bearer', + 'assertion' => $this->calculateJWT() + ]); + + $this->currentToken = $response['access_token']; + $this->tokenExpires = time() + $response['expires_in']; + $this->headers['Authorization'] = 'Bearer ' . $this->currentToken; + } catch (\Exception $e) { + throw new \Exception('Failed to authenticate with Firebase: ' . $e->getMessage()); + } } - /** - * Get Project - * - * @return Project|null - */ - public function getProject(): Project|null + public function call(string $method, string $path = '', array $headers = array(), array $params = array()): array|string { - return $this->project; + $this->authenticate(); + + return parent::call($method, $path, $headers, $params); } - /** - * Export Users - * - * @param int $batchSize Max 500 - * @param callable $callback Callback function to be called after each batch, $callback(user[] $batch); - * - * @return void - */ - public function exportAuth(int $batchSize, callable $callback): void + public function getSupportedResources(): array { - if (!$this->project || !$this->project->getId()) { - $this->logs[Log::FATAL][] = new Log('Project not set'); - throw new \Exception('Project not set'); - } - - if ($batchSize > 500) { - $this->logs[Log::FATAL][] = new Log('Batch size cannot be greater than 500'); - throw new \Exception('Batch size cannot be greater than 500'); - } - - // Fetch our hash config - $httpClient = $this->googleClient->authorize(); + return [ + Transfer::GROUP_AUTH, + Transfer::GROUP_DATABASES + ]; + } - $hashConfig = json_decode($httpClient->request('GET', 'https://identitytoolkit.googleapis.com/admin/v2/projects/' . $this->project->getId() . '/config')->getBody()->getContents(), true)["signIn"]["hashConfig"]; + public function check(array $resources = []): array + { + throw new \Exception('Not implemented'); + } - if (!$hashConfig) { - $this->logs[Log::FATAL][] = new Log('Unable to fetch hash config'); - throw new \Exception('Unable to fetch hash config'); + public function exportAuthGroup(int $batchSize, array $resources) + { + if (in_array(Resource::TYPE_USER, $resources)) { + $this->exportUsers($batchSize); } + } + + function exportUsers(int $batchSize) + { + // Fetch our Hash Config + $hashConfig = ($this->call('GET', 'https://identitytoolkit.googleapis.com/admin/v2/projects/' . $this->projectID . '/config'))["signIn"]["hashConfig"]; $nextPageToken = null; + // Transfer Users while (true) { $users = []; $request = [ - "targetProjectId" => $this->project->getId(), + "targetProjectId" => $this->projectID, "maxResults" => $batchSize, ]; @@ -171,28 +135,21 @@ public function exportAuth(int $batchSize, callable $callback): void $request["nextPageToken"] = $nextPageToken; } - $response = json_decode($httpClient->request('POST', 'https://identitytoolkit.googleapis.com/identitytoolkit/v3/relyingparty/downloadAccount', [ - 'json' => $request - ])->getBody()->getContents(), true); - - if (!$response) { - $this->logs[Log::FATAL][] = new Log('Unable to fetch users'); - throw new \Exception('Unable to fetch users'); - } + $response = $this->call('POST', 'https://identitytoolkit.googleapis.com/identitytoolkit/v3/relyingparty/downloadAccount', [ + 'Content-Type' => 'application/json', + ], $request); $result = $response["users"]; $nextPageToken = $response["nextPageToken"] ?? null; foreach ($result as $user) { - /** @var array $user */ - $users[] = new User( $user["localId"] ?? '', $user["email"] ?? '', $user["displayName"] ?? $user["email"] ?? '', new Hash($user["passwordHash"] ?? '', $user["salt"] ?? '', Hash::SCRYPT_MODIFIED, $hashConfig["saltSeparator"], $hashConfig["signerKey"]), $user["phoneNumber"] ?? '', - $this->calculateTypes($user['providerUserInfo'] ?? []), + $this->calculateUserType($user['providerUserInfo'] ?? []), '', $user["emailVerified"], false, // Can't get phone number status on firebase :/ @@ -200,7 +157,7 @@ public function exportAuth(int $batchSize, callable $callback): void ); } - $callback($users); + $this->callback($users); if (count($result) < $batchSize) { break; @@ -208,175 +165,7 @@ public function exportAuth(int $batchSize, callable $callback): void } } - /** - * Calculate Array Type - * - * @param string $key - * @param \Google\Service\Firestore\ArrayValue $data - * - * @return ResourcesAttribute - */ - public function calculateArrayType(string $key, \Google\Service\Firestore\ArrayValue $data): ResourcesAttribute - { - $isSameType = true; - $previousType = null; - - foreach ($data["values"] as $field) { - if (!$previousType) { - $previousType = $this->calculateType($key, $field); - } elseif ($previousType->getName() != ($this->calculateType($key, $field))->getName()) { - $isSameType = false; - break; - } - } - - if ($isSameType) { - $previousType->setArray(true); - return $previousType; - } else { - return new StringAttribute($key, false, true, null, 1000000); - } - } - - /** - * Calculate Type - * - * @param string $key - * @param \Google\Service\Firestore\Value $field - * - * @return ResourcesAttribute - */ - public function calculateType(string $key, \Google\Service\Firestore\Value $field): ResourcesAttribute - { - if (isset($field["booleanValue"])) { - return new BoolAttribute($key, false, false, null); - } elseif (isset($field["bytesValue"])) { - return new StringAttribute($key, false, false, null, 1000000); - } elseif (isset($field["doubleValue"])) { - return new FloatAttribute($key, false, false, null); - } elseif (isset($field["integerValue"])) { - return new IntAttribute($key, false, false, null); - } elseif (isset($field["mapValue"])) { - return new StringAttribute($key, false, false, null, 1000000); - } elseif (isset($field["nullValue"])) { - return new StringAttribute($key, false, false, null, 1000000); - } elseif (isset($field["referenceValue"])) { - return new StringAttribute($key, false, false, null, 1000000); - } elseif (isset($field["stringValue"])) { - return new StringAttribute($key, false, false, null, 1000000); - } elseif (isset($field["timestampValue"])) { - return new DateTimeAttribute($key, false, false, null); - } elseif (isset($field["geoPointValue"])) { - return new StringAttribute($key, false, false, null, 1000000); - } elseif (isset($field["arrayValue"])) { - return $this->calculateArrayType($key, $field["arrayValue"]); - } else { - $this->logs[Log::WARNING][] = new Log('Failed to determine data type for: ' . $key . ' Falling back to string.', \time()); - return new StringAttribute($key, false, false, null, 1000000); - } - } - - /** - * Calculate Schema - * - * @param int $batchSize Max 500 - * @param $collection Collection - * @param &Collection[] $newCollections - * - * @return list - **/ - public function calculateSchema(int $batchSize, Collection $collection, array &$newCollections) - { - $attributes = []; - - $firestore = new \Google\Service\Firestore($this->googleClient); - - $documents = $firestore->projects_databases_documents->listDocuments('projects/' . $this->project->getId() . '/databases/(default)/documents/' . $collection->getId(), '', [ - 'pageSize' => $batchSize - ]); - - foreach ($documents as $document) { - foreach ($document["fields"] as $key => $value) { - $attributes[] = $this->calculateType($key, $value); - } - - $requestOptions = new ListCollectionIdsRequest(); - $requestOptions->setPageSize(500); - - // Handle subcollections - $subcollections = $firestore->projects_databases_documents->listCollectionIds($document["name"], $requestOptions, [])["collectionIds"]; - - if ($subcollections == null) { - continue; - } - - $subcollections = array_map(function ($subcollection) use ($document, $collection) { - $name = str_replace("projects/" . $this->getProject()->getId() . "/databases/(default)/documents/", "", $document["name"]); - return $name . '/' . $subcollection; - }, $subcollections); - - $newCollections = array_merge($newCollections, $this->handleCollections($subcollections, $collection->getDatabase())); - } - - return $attributes; - } - - /** - * Handle Collections - * - * @param string[] $collectionIDs - * @param Database $database - * - * @return Collection[] - */ - public function handleCollections(array $collectionIDs, Database $database): array - { - $collections = []; - - foreach ($collectionIDs as $collectionID) { - $collection = new Collection($database, $collectionID, $collectionID); - - $collection->setAttributes($this->calculateSchema(500, $collection, $collections)); - - $collections[] = $collection; - } - - return $collections; - } - - /** - * Export Databases - * - * @param int $batchSize Max 500 - * @param callable $callback Callback function to be called after each batch, $callback(database[] $batch); - * - * @return void - */ - public function exportDatabases(int $batchSize, callable $callback): void - { - if (!$this->project || !$this->project->getId()) { - $this->logs[Log::FATAL][] = new Log('Project not set'); - throw new \Exception('Project not set'); - } - - if ($batchSize > 500) { - $this->logs[Log::FATAL][] = new Log('Batch size cannot be greater than 500'); - throw new \Exception('Batch size cannot be greater than 500'); - } - - $firestore = new \Google\Service\Firestore($this->googleClient); - - // Let's grab the root collections. (google's params technically doesn't allow this, however they do it in their own console) - $request = $firestore->projects_databases_documents->listCollectionIds('projects/' . $this->project->getId() . '/databases/(default)/documents', new ListCollectionIdsRequest()); - - $database = new Database('Default', 'Default', Database::DB_NON_RELATIONAL); - - $database->setCollections($this->handleCollections($request['collectionIds'], $database)); - - $callback([$database]); - } - - public function calculateTypes(array $providerData): array + function calculateUserType(array $providerData): array { if (count($providerData) === 0) { return [User::TYPE_ANONYMOUS]; @@ -401,90 +190,178 @@ public function calculateTypes(array $providerData): array return $types; } - public function check(array $resources = []): array + public function exportDatabasesGroup(int $batchSize, array $resources) { - $report = [ - 'Users' => [], - 'Databases' => [], - 'Documents' => [], - 'Files' => [], - 'Functions' => [] - ]; + if (in_array(Resource::TYPE_DATABASE, $resources)) { + $database = new Database('default', '(default)'); + $this->callback([$database]); + } - if (empty($resources)) { - $resources = $this->getSupportedResources(); + if (in_array(Resource::TYPE_COLLECTION, $resources)) { + $this->traceDBResource($database, 'projects/' . $this->projectID . '/databases/' . $database->getId() . '/documents/', $batchSize); } - if (!$this->googleClient) { - $this->logs[Log::FATAL][] = new Log('Google Client not initialized'); - $report['Users'][] = 'Google Client not initialized'; - return $report; + if (in_array(Resource::TYPE_DOCUMENT, $resources)) { + $this->exportDocuments($batchSize); } + } + + function exportDocuments(int $batchSize) + { + // Not Implemented + return; + } - if (!$this->project || !$this->project->getId()) { - $this->logs[Log::FATAL][] = new Log('Project not set'); - $report['Users'][] = 'Project not set'; - return $report; + function convertAttribute(Collection $collection, string $key, array $field): Attribute + { + if (array_key_exists("booleanValue", $field)) { + return new BoolAttribute($key, $collection, false, false, null); + } elseif (array_key_exists("bytesValue", $field)) { + return new StringAttribute($key, $collection, false, false, null, 1000000); + } elseif (array_key_exists("doubleValue", $field)) { + return new FloatAttribute($key, $collection, false, false, null); + } elseif (array_key_exists("integerValue", $field)) { + return new IntAttribute($key, $collection, false, false, null); + } elseif (array_key_exists("mapValue", $field)) { + return new StringAttribute($key, $collection, false, false, null, 1000000); + } elseif (array_key_exists("nullValue", $field)) { + return new StringAttribute($key, $collection, false, false, null, 1000000); + } elseif (array_key_exists("referenceValue", $field)) { + return new StringAttribute($key, $collection, false, false, null, 1000000); //TODO: This should be a reference attribute + } elseif (array_key_exists("stringValue", $field)) { + return new StringAttribute($key, $collection, false, false, null, 1000000); + } elseif (array_key_exists("timestampValue", $field)) { + return new DateTimeAttribute($key, $collection, false, false, null); + } elseif (array_key_exists("geoPointValue", $field)) { + return new StringAttribute($key, $collection, false, false, null, 1000000); + } elseif (array_key_exists("arrayValue", $field)) { + return $this->calculateArrayType($collection, $key, $field["arrayValue"]); + } else { + var_dump($field); + throw new \Exception('Unknown field type'); } + } - foreach ($resources as $resource) { - switch ($resource) { - case Transfer::GROUP_AUTH: - $firebase = new \Google\Service\FirebaseManagement($this->googleClient); + function calculateArrayType(Collection $collection, string $key, array $data): Attribute + { + $isSameType = true; + $previousType = null; - $request = $firebase->projects->listProjects(); + foreach ($data["values"] as $field) { + if (!$previousType) { + $previousType = $this->convertAttribute($collection, $key, $field); + } elseif ($previousType->getName() != ($this->convertAttribute($collection, $key, $field))->getName()) { + $isSameType = false; + break; + } + } - if (!$request['results']) { - $report['Users'][] = 'Unable to fetch projects'; - return $report; - } + if ($isSameType) { + $previousType->setArray(true); + return $previousType; + } else { + return new StringAttribute($key, $collection, false, true, null, 1000000); + } + } - $found = false; + function handleCollection(Collection $collection, int $batchSize) + { + $resourceURL = 'https://firestore.googleapis.com/v1/projects/' . $this->projectID . '/databases/' . $collection->getDatabase()->getId() . '/documents/' . $collection->getId(); - foreach ($request['results'] as $project) { - if ($project['projectId'] === $this->project->getId()) { - $found = true; - break; - } - } + $nextPageToken = null; - if (!$found) { - $report['Users'][] = 'Project not found'; - return $report; - } + $knownSchema = []; - $completedResources[] = Transfer::GROUP_AUTH; - break; - case Transfer::GROUP_DATABASES: - $firestore = new \Google\Service\Firestore($this->googleClient); + // Transfer Documents and Calculate Schemas + while (true) { + $documents = []; + + $result = $this->call('GET', $resourceURL, [ + 'Content-Type' => 'application/json', + ], [ + 'pageSize' => $batchSize, + 'pageToken' => $nextPageToken + ]); + + if (!empty($result)) { + break; + } - $request = $firestore->projects_databases_documents->listDocuments('projects/' . $this->project->getId() . '/databases/(default)/documents', '', [ - 'pageSize' => 1 - ]); + // Calculate Schema and handle subcollections + $documentSchema = []; + foreach ($result['documents'] as $document) { + if (!isset($document['fields'])) { + continue; //TODO: Transfer Empty Documents + } - if (!$request['documents']) { - $report['Databases'][] = 'Unable to fetch documents'; - return $report; + foreach ($document['fields'] as $key => $field) { + if (!isset($documentSchema[$key])) { + $documentSchema[$key] = $this->convertAttribute($collection, $key, $field); } - break; + } + + $this->traceDBResource($collection->getDatabase(), $document['name'], $batchSize); } - } - return $report; + // Transfer Documents + // $callback($documents); + + if (count($result['documents']) < $batchSize) { + break; + } + + $nextPageToken = $result['nextPageToken'] ?? null; + } } - public function exportDocuments(int $batchSize, callable $callback): void + function traceDBResource(Database $database, string $resource, int $batchSize) { - throw new \Exception('Not Implemented'); + $baseURL = 'https://firestore.googleapis.com/v1/' . $resource; + + $nextPageToken = null; + $allCollections = []; + while (true) { + $collections = []; + + $result = $this->call('POST', $baseURL . ':listCollectionIds', [ + 'Content-Type' => 'application/json', + ], [ + 'pageSize' => $batchSize, + 'pageToken' => $nextPageToken + ]); + + // Transfer Collections + foreach ($result['collectionIds'] as $collection) { + $collections[] = new Collection($database, $collection, $collection); + } + + if (count($collections) !== 0) { + $allCollections = array_merge($allCollections, $collections); + $this->callback($collections); + } else { + return; + } + + // Transfer Documents and Calculate Schema + foreach ($collections as $collection) { + $this->handleCollection($collection, $batchSize); + } + + if (count($result['collectionIds']) < $batchSize) { + break; + } + + $nextPageToken = $result['nextPageToken'] ?? null; + } } - public function exportFiles(int $batchSize, callable $callback): void + public function exportFunctionsGroup(int $batchSize, array $resources) { - throw new \Exception('Not Implemented'); + throw new \Exception('Not implemented'); } - public function exportFunctions(int $batchSize, callable $callback): void + public function exportStorageGroup(int $batchSize, array $resources) { - throw new \Exception('Not Implemented'); + throw new \Exception('Not implemented'); } } diff --git a/src/Transfer/Sources/FirebaseG2.php b/src/Transfer/Sources/FirebaseG2.php deleted file mode 100644 index 5668102..0000000 --- a/src/Transfer/Sources/FirebaseG2.php +++ /dev/null @@ -1,190 +0,0 @@ -serviceAccount = $serviceAccount; - $this->projectID = $projectID; - } - - public function getName(): string - { - return 'Firebase'; - } - - function base64url_encode($data) - { - return str_replace(['+', '/', '='], ['-', '_', ''], base64_encode($data)); - } - - function calculateJWT(): string - { - $jwtClaim = [ - 'iss' => $this->serviceAccount['client_email'], - 'scope' => 'https://www.googleapis.com/auth/firebase https://www.googleapis.com/auth/cloud-platform', - 'exp' => time() + 3600, - 'iat' => time(), - 'aud' => 'https://oauth2.googleapis.com/token' - ]; - - $jwtHeader = [ - 'alg' => 'RS256', - 'typ' => 'JWT' - ]; - - $jwtPayload = $this->base64url_encode(json_encode($jwtHeader)) . '.' . $this->base64url_encode(json_encode($jwtClaim)); - - $jwtSignature = ''; - openssl_sign($jwtPayload, $jwtSignature, $this->serviceAccount['private_key'], 'sha256'); - $jwtSignature = $this->base64url_encode($jwtSignature); - - return $jwtPayload . '.' . $jwtSignature; - } - - /** - * Computes the JWT then fetches an auth token from the Google OAuth2 API which is valid for an hour - */ - function authenticate() - { - if (time() < $this->tokenExpires) { - return; - } - - $response = $this->call('POST', 'https://oauth2.googleapis.com/token', [ - 'Content-Type' => 'application/x-www-form-urlencoded', - ], [ - 'grant_type' => 'urn:ietf:params:oauth:grant-type:jwt-bearer', - 'assertion' => $this->calculateJWT() - ]); - - $this->currentToken = $response['access_token']; - $this->tokenExpires = time() + $response['expires_in']; - $this->headers['Authorization'] = 'Bearer ' . $this->currentToken; - } - - public function getSupportedResources(): array - { - return [ - Transfer::GROUP_AUTH - ]; - } - - public function check(array $resources = []): array - { - throw new \Exception('Not implemented'); - } - - public function exportAuth(int $batchSize, callable $callback): void - { - $this->authenticate(); - - // Fetch our Hash Config - $hashConfig = ($this->call('GET', 'https://identitytoolkit.googleapis.com/admin/v2/projects/' . $this->projectID . '/config'))["signIn"]["hashConfig"]; - - $nextPageToken = null; - - // Transfer Users - while (true) { - $users = []; - - $request = [ - "targetProjectId" => $this->projectID, - "maxResults" => $batchSize, - ]; - - if ($nextPageToken) { - $request["nextPageToken"] = $nextPageToken; - } - - $response = $this->call('POST', 'https://identitytoolkit.googleapis.com/identitytoolkit/v3/relyingparty/downloadAccount', [ - 'Content-Type' => 'application/json', - ], $request); - - $result = $response["users"]; - $nextPageToken = $response["nextPageToken"] ?? null; - - foreach ($result as $user) { - $users[] = new User( - $user["localId"] ?? '', - $user["email"] ?? '', - $user["displayName"] ?? $user["email"] ?? '', - new Hash($user["passwordHash"] ?? '', $user["salt"] ?? '', Hash::SCRYPT_MODIFIED, $hashConfig["saltSeparator"], $hashConfig["signerKey"]), - $user["phoneNumber"] ?? '', - $this->calculateUserType($user['providerUserInfo'] ?? []), - '', - $user["emailVerified"], - false, // Can't get phone number status on firebase :/ - $user["disabled"] - ); - } - - $callback($users); - - if (count($result) < $batchSize) { - break; - } - } - } - - public function calculateUserType(array $providerData): array - { - if (count($providerData) === 0) { - return [User::TYPE_ANONYMOUS]; - } - - $types = []; - - foreach ($providerData as $provider) { - switch ($provider["providerId"]) { - case 'password': - $types[] = User::TYPE_EMAIL; - break; - case 'phone': - $types[] = User::TYPE_PHONE; - break; - default: - $types[] = User::TYPE_OAUTH; - break; - } - } - - return $types; - } - - public function exportFunctions(int $batchSize, callable $callback): void - { - throw new \Exception('Not implemented'); - } - - public function exportFiles(int $batchSize, callable $callback): void - { - throw new \Exception('Not implemented'); - } - - public function exportDatabases(int $batchSize, callable $callback): void - { - $this->authenticate(); - - // Transfer Databases - $databases = []; - - } - - public function exportDocuments(int $batchSize, callable $callback): void - { - throw new \Exception('Not implemented'); - } -} diff --git a/src/Transfer/Sources/NHost.php b/src/Transfer/Sources/NHost.php index f282935..ebba389 100644 --- a/src/Transfer/Sources/NHost.php +++ b/src/Transfer/Sources/NHost.php @@ -2,6 +2,7 @@ namespace Utopia\Transfer\Sources; +use Utopia\Transfer\Resource; use Utopia\Transfer\Source; use Utopia\Transfer\Resources\Auth\User; use Utopia\Transfer\Transfer; @@ -75,7 +76,7 @@ public function __construct(string $host, string $databaseName, string $username } } - public function getName(): string + static function getName(): string { return 'NHost'; } @@ -84,20 +85,70 @@ public function getSupportedResources(): array { return [ Transfer::GROUP_AUTH, - Transfer::GROUP_DATABASES, - Transfer::GROUP_DOCUMENTS, + Transfer::GROUP_DATABASES ]; } - /** - * Export Users - * - * @param int $batchSize Max 500 - * @param callable $callback Callback function to be called after each batch, $callback(user[] $batch); - * - * @return User[] - */ - public function exportAuth(int $batchSize, callable $callback): void + public function check(array $resources = []): array + { + $report = [ + Transfer::GROUP_AUTH => [], + Transfer::GROUP_DATABASES => [], + Transfer::GROUP_STORAGE => [], + Transfer::GROUP_FUNCTIONS => [] + ]; + + if (empty($resources)) { + $resources = $this->getSupportedResources(); + } + + try { + $this->pdo = new \PDO("pgsql:host=" . $this->host . ";port=" . $this->port . ";dbname=" . $this->databaseName, $this->username, $this->password); + } catch (\PDOException $e) { + $report[Transfer::GROUP_DATABASES][] = 'Failed to connect to database. PDO Code: ' . $e->getCode() . ' Error: ' . $e->getMessage(); + } + + if (!empty($this->pdo->errorCode())) { + $report[Transfer::GROUP_DATABASES][] = 'Failed to connect to database. PDO Code: ' . $this->pdo->errorCode() . (empty($this->pdo->errorInfo()[2]) ? '' : ' Error: ' . $this->pdo->errorInfo()[2]); + } + + foreach ($resources as $resource) { + switch ($resource) { + case Transfer::GROUP_AUTH: + $statement = $this->pdo->prepare('SELECT COUNT(*) FROM auth.users'); + $statement->execute(); + + if ($statement->errorCode() !== '00000') { + $report['Users'][] = 'Failed to access users table. Error: ' . $statement->errorInfo()[2]; + } + + break; + case Transfer::GROUP_DATABASES: + $statement = $this->pdo->prepare('SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = \'public\''); + $statement->execute(); + + if ($statement->errorCode() !== '00000') { + $report[Transfer::GROUP_DATABASES][] = 'Failed to access tables table. Error: ' . $statement->errorInfo()[2]; + } + + break; + case Transfer::GROUP_STORAGE: + //TODO: Attempt to access API + break; + } + } + + return $report; + } + + public function exportAuthGroup(int $batchSize, array $resources) + { + if (in_array(Resource::TYPE_USER, $resources)) { + $this->exportUsers($batchSize); + } + } + + function exportUsers(int $batchSize) { $total = $this->pdo->query('SELECT COUNT(*) FROM auth.users')->fetchColumn(); @@ -131,18 +182,168 @@ public function exportAuth(int $batchSize, callable $callback): void ); } - $callback($transferUsers); + $this->callback($transferUsers); } } - /** - * Convert Attribute - * - * @param array $column - * @param Collection $collection - * @return Attribute - */ - public function convertAttribute(array $column, Collection $collection): Attribute + public function exportDatabasesGroup(int $batchSize, array $resources) + { + if (in_array(Resource::TYPE_DATABASE, $resources)) { + $this->exportDatabases($batchSize); + } + + if (in_array(Resource::TYPE_COLLECTION, $resources)) { + $this->exportCollections($batchSize); + } + + if (in_array(Resource::TYPE_ATTRIBUTE, $resources)) { + $this->exportAttributes($batchSize); + } + + if (in_array(Resource::TYPE_DOCUMENT, $resources)) { + $this->exportDocuments($batchSize); + } + + if (in_array(Resource::TYPE_INDEX, $resources)) { + $this->exportIndexes($batchSize); + } + } + + function exportDatabases(int $batchSize): void + { + // We'll only transfer the public database for now, since it's the only one that exists by default. + //TODO: Handle edge cases where there are user created databases and data. + $transferDatabase = new Database('public', 'public'); + $this->callback([$transferDatabase]); + } + + function exportCollections(int $batchSize) + { + $databases = $this->resourceCache->get(Database::getName()); + + foreach ($databases as $database) { + $statement = $this->pdo->prepare('SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = :database'); + $statement->execute([':database' => $database->getName()]); //TODO: Maybe add "getOriginalName"? + $total = $statement->fetchColumn(); + + $offset = 0; + + while ($offset < $total) { + $statement = $this->pdo->prepare('SELECT table_name FROM information_schema.tables WHERE table_schema = \'public\' order by table_name LIMIT :limit OFFSET :offset'); + $statement->execute([':limit' => $batchSize, ':offset' => $offset]); + + $tables = $statement->fetchAll(\PDO::FETCH_ASSOC); + + $offset += $batchSize; + + $transferCollections = []; + + foreach ($tables as $table) { + $transferCollections[] = new Collection($database, $table['table_name'], $table['table_name']); + } + + $this->callback($transferCollections); + } + } + } + + function exportAttributes(int $batchSize) + { + $collections = $this->resourceCache->get(Collection::getName()); + + foreach ($collections as $collection) { + /** @var Collection $collection */ + $statement = $this->pdo->prepare('SELECT * FROM information_schema."columns" where "table_name" = :tableName'); + $statement->bindValue(':tableName', $collection->getCollectionName(), \PDO::PARAM_STR); + $statement->execute(); + $databaseCollection = $statement->fetchAll(\PDO::FETCH_ASSOC); + + $attributes = []; + + foreach ($databaseCollection as $column) { + $attributes[] = $this->convertAttribute($column, $collection); + } + + $this->callback($attributes); + } + } + + function exportIndexes(int $batchSize) + { + $collections = $this->resourceCache->get(Collection::getName()); + + foreach ($collections as $collection) { + /** @var Collection $collection */ + + $indexStatement = $this->pdo->prepare('SELECT indexname, indexdef FROM pg_indexes WHERE tablename = :tableName'); + $indexStatement->bindValue(':tableName', $collection->getCollectionName(), \PDO::PARAM_STR); + $indexStatement->execute(); + + $databaseIndexes = $indexStatement->fetchAll(\PDO::FETCH_ASSOC); + $indexes = []; + foreach ($databaseIndexes as $index) { + $result = $this->convertIndex($index, $collection); + + $indexes[] = $result; + } + + $this->callback($indexes); + } + } + + function exportDocuments(int $batchSize) + { + $databases = $this->resourceCache->get(Database::getName()); + + foreach ($databases as $database) { + /** @var Database $database */ + $collections = $database->getCollections(); + + foreach ($collections as $collection) { + $total = $this->pdo->query('SELECT COUNT(*) FROM ' . $collection->getCollectionName())->fetchColumn(); + + $offset = 0; + + while ($offset < $total) { + $statement = $this->pdo->prepare('SELECT row_to_json(t) FROM (SELECT * FROM ' . $collection->getCollectionName() . ' LIMIT :limit OFFSET :offset) t;'); + $statement->bindValue(':limit', $batchSize, \PDO::PARAM_INT); + $statement->bindValue(':offset', $offset, \PDO::PARAM_INT); + $statement->execute(); + + $documents = $statement->fetchAll(\PDO::FETCH_ASSOC); + + $offset += $batchSize; + + $transferDocuments = []; + + $attributes = $this->resourceCache->get(Attribute::getName()); + $collectionAttributes = array_filter($attributes, function (Attribute $attribute) use ($collection) { + return $attribute->getId() === $collection->getId(); + }); + + foreach ($documents as $document) { + $data = json_decode($document['row_to_json'], true); + + $processedData = []; + foreach ($collectionAttributes as $attribute) { + /** @var Attribute $attribute */ + if (!$attribute->getArray() && \is_array($data[$attribute->getKey()])) { + $processedData[$attribute->getKey()] = json_encode($data[$attribute->getKey()]); + } else { + $processedData[$attribute->getKey()] = $data[$attribute->getKey()]; + } + } + + $transferDocuments[] = new Document('unique()', $database, $collection, $processedData); + } + + $this->callback($transferDocuments); + } + } + } + } + + function convertAttribute(array $column, Collection $collection): Attribute { $isArray = $column['data_type'] === 'ARRAY'; @@ -212,14 +413,7 @@ public function convertAttribute(array $column, Collection $collection): Attribu } } - /** - * Convert Index - * - * @param string $table - * @param Collection $collection - * @return Index|false - */ - public function convertIndex(array $index, Collection $collection): Index|false + function convertIndex(array $index, Collection $collection): Index|false { $pattern = "/CREATE (?\w+)? INDEX (?\w+) ON (?
\w+\.\w+) USING (?\w+) \((?\w+)\)/"; @@ -266,142 +460,7 @@ public function convertIndex(array $index, Collection $collection): Index|false } } - /** - * Export Databases - * - * @param int $batchSize Max 100 - * @param callable $callback Callback function to be called after each database, $callback(database[] $batch); - * - * @return void - */ - public function exportDatabases(int $batchSize, callable $callback): void - { - $total = $this->pdo->query('SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = \'public\'')->fetchColumn(); - - $offset = 0; - - // We'll only transfer the public database for now, since it's the only one that exists by default. - //TODO: Handle edge cases where there are user created databases and data. - - $transferDatabase = new Database('public', 'public'); - $callback([$transferDatabase]); - - // Transfer Tables - while ($offset < $total) { - $statement = $this->pdo->prepare('SELECT table_name FROM information_schema.tables WHERE table_schema = \'public\' order by table_name LIMIT :limit OFFSET :offset'); - $statement->bindValue(':limit', $batchSize, \PDO::PARAM_INT); - $statement->bindValue(':offset', $offset, \PDO::PARAM_INT); - $statement->execute(); - - $tables = $statement->fetchAll(\PDO::FETCH_ASSOC); - - $offset += $batchSize; - - $transferCollections = []; - - foreach ($tables as $table) { - $transferCollections[] = new Collection($transferDatabase, $table['table_name'], $table['table_name']); - } - - $callback($transferCollections); - } - - // Transfer Attributes and Indexes - $collections = $this->resourceCache->get(Collection::class); - - foreach ($collections as $collection) { - /** @var Collection $collection */ - $statement = $this->pdo->prepare('SELECT * FROM information_schema."columns" where "table_name" = :tableName'); - $statement->bindValue(':tableName', $collection->getCollectionName(), \PDO::PARAM_STR); - $statement->execute(); - $databaseCollection = $statement->fetchAll(\PDO::FETCH_ASSOC); - - $attributes = []; - - foreach ($databaseCollection as $column) { - $attributes[] = $this->convertAttribute($column, $collection); - } - - $callback($attributes); - - // Transfer Indexes - $indexStatement = $this->pdo->prepare('SELECT indexname, indexdef FROM pg_indexes WHERE tablename = :tableName'); - $indexStatement->bindValue(':tableName', $collection->getCollectionName(), \PDO::PARAM_STR); - $indexStatement->execute(); - - $databaseIndexes = $indexStatement->fetchAll(\PDO::FETCH_ASSOC); - $indexes = []; - foreach ($databaseIndexes as $index) { - $result = $this->convertIndex($index, $collection); - - $indexes[] = $result; - } - - $callback($indexes); - } - } - - /** - * Export Documents - * - * @param int $batchSize Max 100 - * @param callable $callback Callback function to be called after each batch, $callback(document[] $batch); - * - * @return void - */ - public function exportDocuments(int $batchSize, callable $callback): void - { - $databases = $this->resourceCache->get(Database::class); - - foreach ($databases as $database) { - /** @var Database $database */ - $collections = $database->getCollections(); - - foreach ($collections as $collection) { - $total = $this->pdo->query('SELECT COUNT(*) FROM ' . $collection->getCollectionName())->fetchColumn(); - - $offset = 0; - - while ($offset < $total) { - $statement = $this->pdo->prepare('SELECT row_to_json(t) FROM (SELECT * FROM ' . $collection->getCollectionName() . ' LIMIT :limit OFFSET :offset) t;'); - $statement->bindValue(':limit', $batchSize, \PDO::PARAM_INT); - $statement->bindValue(':offset', $offset, \PDO::PARAM_INT); - $statement->execute(); - - $documents = $statement->fetchAll(\PDO::FETCH_ASSOC); - - $offset += $batchSize; - - $transferDocuments = []; - - $attributes = $this->resourceCache->get(Attribute::class); - $collectionAttributes = array_filter($attributes, function (Attribute $attribute) use ($collection) { - return $attribute->getId() === $collection->getId(); - }); - - foreach ($documents as $document) { - $data = json_decode($document['row_to_json'], true); - - $processedData = []; - foreach ($collectionAttributes as $attribute) { - /** @var Attribute $attribute */ - if (!$attribute->getArray() && \is_array($data[$attribute->getKey()])) { - $processedData[$attribute->getKey()] = json_encode($data[$attribute->getKey()]); - } else { - $processedData[$attribute->getKey()] = $data[$attribute->getKey()]; - } - } - - $transferDocuments[] = new Document('unique()', $database, $collection, $processedData); - } - - $callback($transferDocuments); - } - } - } - } - - private function calculateUserTypes(array $user): array + function calculateUserTypes(array $user): array { if (empty($user['password_hash']) && empty($user['phone_number'])) { return [User::TYPE_ANONYMOUS]; @@ -420,66 +479,12 @@ private function calculateUserTypes(array $user): array return $types; } - public function check(array $resources = []): array - { - $report = [ - 'Users' => [], - 'Databases' => [], - 'Documents' => [], - 'Files' => [], - 'Functions' => [] - ]; - - if (empty($resources)) { - $resources = $this->getSupportedResources(); - } - - try { - $this->pdo = new \PDO("pgsql:host=" . $this->host . ";port=" . $this->port . ";dbname=" . $this->databaseName, $this->username, $this->password); - } catch (\PDOException $e) { - $report['Databases'][] = 'Failed to connect to database. PDO Code: ' . $e->getCode() . ' Error: ' . $e->getMessage(); - } - - if (!empty($this->pdo->errorCode())) { - $report['Databases'][] = 'Failed to connect to database. PDO Code: ' . $this->pdo->errorCode() . (empty($this->pdo->errorInfo()[2]) ? '' : ' Error: ' . $this->pdo->errorInfo()[2]); - } - - foreach ($resources as $resource) { - switch ($resource) { - case Transfer::GROUP_AUTH: - $statement = $this->pdo->prepare('SELECT COUNT(*) FROM auth.users'); - $statement->execute(); - - if ($statement->errorCode() !== '00000') { - $report['Users'][] = 'Failed to access users table. Error: ' . $statement->errorInfo()[2]; - } - - break; - case Transfer::GROUP_DATABASES: - $statement = $this->pdo->prepare('SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = \'public\''); - $statement->execute(); - - if ($statement->errorCode() !== '00000') { - $report['Databases'][] = 'Failed to access tables table. Error: ' . $statement->errorInfo()[2]; - } - - break; - case Transfer::GROUP_DOCUMENTS: - if (!in_array(Transfer::GROUP_DATABASES, $resources)) { - $report['Documents'][] = 'Documents resource requires Databases resource to be enabled.'; - } - } - } - - return $report; - } - - public function exportFiles(int $batchSize, callable $callback): void + public function exportStorageGroup(int $batchSize, array $resources) { throw new \Exception('Not Implemented'); } - public function exportFunctions(int $batchSize, callable $callback): void + public function exportFunctionsGroup(int $batchSize, array $resources) { throw new \Exception('Not Implemented'); } diff --git a/src/Transfer/Sources/Supabase.php b/src/Transfer/Sources/Supabase.php index 6a35c04..f0d701f 100644 --- a/src/Transfer/Sources/Supabase.php +++ b/src/Transfer/Sources/Supabase.php @@ -2,6 +2,7 @@ namespace Utopia\Transfer\Sources; +use Utopia\Transfer\Resource; use Utopia\Transfer\Resources\Auth\User; use Utopia\Transfer\Resources\Auth\Hash; use Utopia\Transfer\Resources\Storage\Bucket; @@ -9,11 +10,11 @@ use Utopia\Transfer\Resources\Storage\FileData; use Utopia\Transfer\Transfer; -use function PHPUnit\Framework\callback; +const MIME_MAP = ['video/3gpp2' => '3g2', 'video/3gp' => '3gp', 'video/3gpp' => '3gp', 'application/x-compressed' => '7zip', 'audio/x-acc' => 'aac', 'audio/ac3' => 'ac3', 'application/postscript' => 'ai', 'audio/x-aiff' => 'aif', 'audio/aiff' => 'aif', 'audio/x-au' => 'au', 'video/x-msvideo' => 'avi', 'video/msvideo' => 'avi', 'video/avi' => 'avi', 'application/x-troff-msvideo' => 'avi', 'application/macbinary' => 'bin', 'application/mac-binary' => 'bin', 'application/x-binary' => 'bin', 'application/x-macbinary' => 'bin', 'image/bmp' => 'bmp', 'image/x-bmp' => 'bmp', 'image/x-bitmap' => 'bmp', 'image/x-xbitmap' => 'bmp', 'image/x-win-bitmap' => 'bmp', 'image/x-windows-bmp' => 'bmp', 'image/ms-bmp' => 'bmp', 'image/x-ms-bmp' => 'bmp', 'application/bmp' => 'bmp', 'application/x-bmp' => 'bmp', 'application/x-win-bitmap' => 'bmp', 'application/cdr' => 'cdr', 'application/coreldraw' => 'cdr', 'application/x-cdr' => 'cdr', 'application/x-coreldraw' => 'cdr', 'image/cdr' => 'cdr', 'image/x-cdr' => 'cdr', 'zz-application/zz-winassoc-cdr' => 'cdr', 'application/mac-compactpro' => 'cpt', 'application/pkix-crl' => 'crl', 'application/pkcs-crl' => 'crl', 'application/x-x509-ca-cert' => 'crt', 'application/pkix-cert' => 'crt', 'text/css' => 'css', 'text/x-comma-separated-values' => 'csv', 'text/comma-separated-values' => 'csv', 'application/vnd.msexcel' => 'csv', 'application/x-director' => 'dcr', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' => 'docx', 'application/x-dvi' => 'dvi', 'message/rfc822' => 'eml', 'application/x-msdownload' => 'exe', 'video/x-f4v' => 'f4v', 'audio/x-flac' => 'flac', 'video/x-flv' => 'flv', 'image/gif' => 'gif', 'application/gpg-keys' => 'gpg', 'application/x-gtar' => 'gtar', 'application/x-gzip' => 'gzip', 'application/mac-binhex40' => 'hqx', 'application/mac-binhex' => 'hqx', 'application/x-binhex40' => 'hqx', 'application/x-mac-binhex40' => 'hqx', 'text/html' => 'html', 'image/x-icon' => 'ico', 'image/x-ico' => 'ico', 'image/vnd.microsoft.icon' => 'ico', 'text/calendar' => 'ics', 'application/java-archive' => 'jar', 'application/x-java-application' => 'jar', 'application/x-jar' => 'jar', 'image/jp2' => 'jp2', 'video/mj2' => 'jp2', 'image/jpx' => 'jp2', 'image/jpm' => 'jp2', 'image/jpeg' => 'jpeg', 'image/jpg' => 'jpeg', 'image/pjpeg' => 'jpeg', 'application/x-javascript' => 'js', 'application/json' => 'json', 'text/json' => 'json', 'application/vnd.google-earth.kml+xml' => 'kml', 'application/vnd.google-earth.kmz' => 'kmz', 'text/x-log' => 'log', 'audio/x-m4a' => 'm4a', 'audio/mp4' => 'm4a', 'application/vnd.mpegurl' => 'm4u', 'audio/midi' => 'mid', 'application/vnd.mif' => 'mif', 'video/quicktime' => 'mov', 'video/x-sgi-movie' => 'movie', 'audio/mpeg' => 'mp3', 'audio/mpg' => 'mp3', 'audio/mpeg3' => 'mp3', 'audio/mp3' => 'mp3', 'video/mp4' => 'mp4', 'video/mpeg' => 'mpeg', 'application/oda' => 'oda', 'audio/ogg' => 'ogg', 'video/ogg' => 'ogg', 'application/ogg' => 'ogg', 'font/otf' => 'otf', 'application/x-pkcs10' => 'p10', 'application/pkcs10' => 'p10', 'application/x-pkcs12' => 'p12', 'application/x-pkcs7-signature' => 'p7a', 'application/pkcs7-mime' => 'p7c', 'application/x-pkcs7-mime' => 'p7c', 'application/x-pkcs7-certreqresp' => 'p7r', 'application/pkcs7-signature' => 'p7s', 'application/pdf' => 'pdf', 'application/octet-stream' => 'pdf', 'application/x-x509-user-cert' => 'pem', 'application/x-pem-file' => 'pem', 'application/pgp' => 'pgp', 'application/x-httpd-php' => 'php', 'application/php' => 'php', 'application/x-php' => 'php', 'text/php' => 'php', 'text/x-php' => 'php', 'application/x-httpd-php-source' => 'php', 'image/png' => 'png', 'image/x-png' => 'png', 'application/powerpoint' => 'ppt', 'application/vnd.ms-powerpoint' => 'ppt', 'application/vnd.ms-office' => 'ppt', 'application/msword' => 'doc', 'application/vnd.openxmlformats-officedocument.presentationml.presentation' => 'pptx', 'application/x-photoshop' => 'psd', 'image/vnd.adobe.photoshop' => 'psd', 'audio/x-realaudio' => 'ra', 'audio/x-pn-realaudio' => 'ram', 'application/x-rar' => 'rar', 'application/rar' => 'rar', 'application/x-rar-compressed' => 'rar', 'audio/x-pn-realaudio-plugin' => 'rpm', 'application/x-pkcs7' => 'rsa', 'text/rtf' => 'rtf', 'text/richtext' => 'rtx', 'video/vnd.rn-realvideo' => 'rv', 'application/x-stuffit' => 'sit', 'application/smil' => 'smil', 'text/srt' => 'srt', 'image/svg+xml' => 'svg', 'application/x-shockwave-flash' => 'swf', 'application/x-tar' => 'tar', 'application/x-gzip-compressed' => 'tgz', 'image/tiff' => 'tiff', 'font/ttf' => 'ttf', 'text/plain' => 'txt', 'text/x-vcard' => 'vcf', 'application/videolan' => 'vlc', 'text/vtt' => 'vtt', 'audio/x-wav' => 'wav', 'audio/wave' => 'wav', 'audio/wav' => 'wav', 'application/wbxml' => 'wbxml', 'video/webm' => 'webm', 'image/webp' => 'webp', 'audio/x-ms-wma' => 'wma', 'application/wmlc' => 'wmlc', 'video/x-ms-wmv' => 'wmv', 'video/x-ms-asf' => 'wmv', 'font/woff' => 'woff', 'font/woff2' => 'woff2', 'application/xhtml+xml' => 'xhtml', 'application/excel' => 'xl', 'application/msexcel' => 'xls', 'application/x-msexcel' => 'xls', 'application/x-ms-excel' => 'xls', 'application/x-excel' => 'xls', 'application/x-dos_ms_excel' => 'xls', 'application/xls' => 'xls', 'application/x-xls' => 'xls', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' => 'xlsx', 'application/vnd.ms-excel' => 'xlsx', 'application/xml' => 'xml', 'text/xml' => 'xml', 'text/xsl' => 'xsl', 'application/xspf+xml' => 'xspf', 'application/x-compress' => 'z', 'application/x-zip' => 'zip', 'application/zip' => 'zip', 'application/x-zip-compressed' => 'zip', 'application/s-compressed' => 'zip', 'multipart/x-zip' => 'zip', 'text/x-scriptzsh' => 'zsh',]; class Supabase extends NHost { - public function getName(): string + static function getName(): string { return 'Supabase'; } @@ -52,15 +53,14 @@ public function __construct(string $endpoint, string $key, string $host, string } } - /** - * Export Users - * - * @param int $batchSize Max 500 - * @param callable $callback Callback function to be called after each batch, $callback(user[] $batch); - * - * @return User[] - */ - public function exportAuth(int $batchSize, callable $callback): void + public function exportAuthGroup(int $batchSize, array $resources) + { + if (in_array(Resource::TYPE_USER, $resources)) { + $this->exportUsers($batchSize); + } + } + + function exportUsers(int $batchSize) { $total = $this->pdo->query('SELECT COUNT(*) FROM auth.users')->fetchColumn(); @@ -94,11 +94,22 @@ public function exportAuth(int $batchSize, callable $callback): void ); } - $callback($transferUsers); + $this->callback($transferUsers); } } - private function calculateAuthTypes(array $user): array + function convertMimes(array $mimes): array + { + $extensions = []; + + foreach ($mimes as $mime) { + $extensions[] = MIME_MAP[$mime] ?? ''; + } + + return $extensions; + } + + function calculateAuthTypes(array $user): array { if (empty($user['encrypted_password']) && empty($user['phone'])) { return [User::TYPE_ANONYMOUS]; @@ -117,9 +128,19 @@ private function calculateAuthTypes(array $user): array return $types; } - public function exportFiles(int $batchSize, callable $callback): void + public function exportStorageGroup(int $batchSize, array $resources) + { + if (in_array(Resource::TYPE_BUCKET, $resources)) { + $this->exportBuckets($batchSize); + } + + if (in_array(Resource::TYPE_FILE, $resources)) { + $this->exportFiles($batchSize); + } + } + + function exportBuckets(int $batchSize) { - // Transfer Buckets $statement = $this->pdo->prepare('SELECT * FROM storage.buckets order by created_at'); $statement->execute(); @@ -135,14 +156,24 @@ public function exportFiles(int $batchSize, callable $callback): void $bucket['name'], true, $bucket['file_size_limit'] ?? 0, - [], // $bucket['allowed_mime_type'], //TODO: Need to convert this to file extensions + $bucket['allowed_mime_types'] ? $this->convertMimes($bucket['allowed_mime_types']) : [], ); } - $callback($transferBuckets); + $this->callback($transferBuckets); + } + + function exportFiles(int $batchSize) + { + /** + * TODO: Supabase has folders, with enough folders within folders this could cause us to hit the max name length + * Need to figure out a solution to this. + */ // Transfer Files - foreach ($transferBuckets as $bucket) { + $buckets = $this->resourceCache->get(Bucket::getName()); + + foreach ($buckets as $bucket) { /** @var Bucket $bucket */ $totalStatement = $this->pdo->prepare('SELECT COUNT(*) FROM storage.objects WHERE bucket_id=:bucketId'); $totalStatement->execute([':bucketId' => $bucket->getId()]); @@ -164,7 +195,7 @@ public function exportFiles(int $batchSize, callable $callback): void foreach ($files as $file) { $metadata = json_decode($file['metadata'], true); - $this->handleFileDataTransfer(new File( + $this->handleDataTransfer(new File( $file['id'], $bucket, $file['name'], @@ -172,22 +203,13 @@ public function exportFiles(int $batchSize, callable $callback): void $metadata['mimetype'], [], $metadata['size'] - ), $callback); + )); } } } } - /** - * Handle File Transfer - * Streams a file to the destination - * - * @param File $file - * @param callable $callback (array $data) - * - * @return void - */ - protected function handleFileDataTransfer(File $file, callable $callback): void + function handleDataTransfer(File $file) { // Set the chunk size (5MB) $start = 0; @@ -205,12 +227,12 @@ protected function handleFileDataTransfer(File $file, callable $callback): void $chunkData = $this->call( 'GET', '/storage/v1/object/' . - rawurlencode($file->getBucket()->getId()) . '/' . rawurlencode($file->getFileName()), + rawurlencode($file->getBucket()->getId()) . '/' . rawurlencode($file->getFileName()), ['range' => "bytes=$start-$end"] ); // Send the chunk to the callback function - $callback([new FileData( + $this->callback([new FileData( $chunkData, $start, $end, diff --git a/src/Transfer/Target.php b/src/Transfer/Target.php index 80d18b3..946d2c2 100644 --- a/src/Transfer/Target.php +++ b/src/Transfer/Target.php @@ -34,7 +34,7 @@ abstract class Target * * @return string */ - abstract public function getName(): string; + abstract static function getName(): string; /** * Get Supported Resources @@ -50,7 +50,6 @@ abstract public function getSupportedResources(): array; * * @return void */ - //TODO: Pretty sure there is a better way to do this instead of passing by reference public function registerTransferCache(ResourceCache &$cache): void { $this->resourceCache = &$cache; diff --git a/src/Transfer/Transfer.php b/src/Transfer/Transfer.php index fb82285..6385e98 100644 --- a/src/Transfer/Transfer.php +++ b/src/Transfer/Transfer.php @@ -8,13 +8,19 @@ class Transfer { - public const GROUP_GENERAL = 'General'; // for things that don't belong to any group + public const GROUP_GENERAL = 'General'; public const GROUP_AUTH = 'Auth'; public const GROUP_STORAGE = 'Storage'; public const GROUP_FUNCTIONS = 'Functions'; public const GROUP_DATABASES = 'Databases'; - public const GROUP_DOCUMENTS = 'Documents'; - + public const GROUP_SETTINGS = 'Settings'; + + public const GROUP_AUTH_RESOURCES = [Resource::TYPE_USER, Resource::TYPE_TEAM, Resource::TYPE_TEAM_MEMBERSHIP, Resource::TYPE_HASH]; + public const GROUP_STORAGE_RESOURCES = [Resource::TYPE_FILE, Resource::TYPE_BUCKET, Resource::TYPE_FILEDATA]; + public const GROUP_FUNCTIONS_RESOURCES = [Resource::TYPE_FUNCTION, Resource::TYPE_ENVVAR]; + public const GROUP_DATABASES_RESOURCES = [Resource::TYPE_DATABASE, Resource::TYPE_COLLECTION, Resource::TYPE_INDEX, Resource::TYPE_ATTRIBUTE, Resource::TYPE_DOCUMENT]; + public const GROUP_SETTINGS_RESOURCES = [Resource::TYPE_PROJECT]; + public const STORAGE_MAX_CHUNK_SIZE = 1024 * 1024 * 5; // 5MB /** @@ -81,7 +87,17 @@ public function __construct(Source $source, Destination $destination) */ public function run(array $resources, callable $callback): void { - $this->destination->run($resources, function (Progress $progress) use ($callback) { + // Allows you to push entire groups if you want. + $computedResources = []; + foreach ($resources as $resource) { + if (is_array($resource)) { + $computedResources = array_merge($computedResources, $resource); + } else { + $computedResources[] = $resource; + } + } + + $this->destination->run($computedResources, function (Progress $progress) use ($callback) { //TODO: Rewrite to use ResourceCache to calculate this $this->currentResource = $progress->getResourceType(); diff --git a/tests/Transfer/Destinations/AppwriteTest.php b/tests/Transfer/Destinations/AppwriteTest.php deleted file mode 100644 index c6bd548..0000000 --- a/tests/Transfer/Destinations/AppwriteTest.php +++ /dev/null @@ -1,85 +0,0 @@ - - * @version 1.0 RC1 - * @license The MIT License (MIT) - */ - -namespace Utopia\Tests; - -use PHPUnit\Framework\TestCase; -use Utopia\Transfer\Destinations\Appwrite; -use Utopia\Transfer\Resources\Auth\Hash; -use Utopia\Transfer\Resources\Auth\User; -use Appwrite\Client; -use Appwrite\Services\Users; - -class AppwriteTest extends TestCase -{ - /** - * @var Appwrite - */ - public $appwrite; - - /** - * @var Client - */ - public $client; - - public function setUp(): void - { - $this->appwrite = new Appwrite( - getenv("DESTINATION_APPWRITE_TEST_PROJECT"), - getenv("DESTINATION_APPWRITE_TEST_ENDPOINT"), - getenv("DESTINATION_APPWRITE_TEST_KEY") - ); - - $this->client = new Client(); - $this->client - ->setEndpoint(getenv("DESTINATION_APPWRITE_TEST_ENDPOINT")) - ->setProject(getenv("DESTINATION_APPWRITE_TEST_PROJECT")) - ->setKey(getenv("DESTINATION_APPWRITE_TEST_KEY")); - } - - public function testGetSupportedResources(): void - { - $this->assertIsArray($this->appwrite->getSupportedResources()); - $this->assertNotEmpty($this->appwrite->getSupportedResources()); - } - - public function testImportUserPassword(): void - { - $users = new Users($this->client); - - /** - * Hash: SHA256, - * Password: 'password' - */ - $user = new User( - '123456789', - 'test@user.com', - "Walter O'brien", - new Hash('5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8'), - '', - [User::TYPE_EMAIL] - ); - - $this->appwrite->importPasswordUser($user); - - // Check User Exists in Appwrite - $response = $users->get($user->getId()); - $this->assertEquals($user->getId(), $response['$id']); - $this->assertEquals($user->getEmail(), $response['email']); - $this->assertEquals($user->getPasswordHash()->getHash(), $response['password']); - - // Cleanup - $users->delete($user->getId()); - } -} diff --git a/tests/Transfer/ProviderTest.phpignore b/tests/Transfer/ProviderTest.phpignore deleted file mode 100644 index 3f04552..0000000 --- a/tests/Transfer/ProviderTest.phpignore +++ /dev/null @@ -1,69 +0,0 @@ -assertIsObject($this->provider); - $this->assertNotEmpty($this->provider->getSupportedResources()); - } - - public function testexportAuth() - { - $result = []; - - $this->provider->exportAuth(100, function (array $users) use (&$result) { - $result = array_merge($result, $users); - }); - - foreach ($result as $user) { - /** @var User $user */ - $this->assertIsObject($user); - } - - $this->assertIsArray($result); - $this->assertNotEmpty($result); - } - - public function testExportDatabases() - { - $result = []; - - $this->provider->exportDatabases(100, function (array $databases) use (&$result) { - $result = array_merge($result, $databases); - }); - - foreach ($result as $database) { - /** @var Database $database */ - $this->assertIsObject($database); - $this->assertNotEmpty($database->getCollections()); - } - - $this->assertIsArray($result); - $this->assertNotEmpty($result); - } - - public function testCheckUsers() - { - $result = $this->provider->check([Transfer::GROUP_AUTH]); - - $this->assertEquals($result, [Transfer::GROUP_AUTH]); - } - - public function testCheckDatabases() - { - $result = $this->provider->check([Transfer::GROUP_DATABASES]); - - $this->assertEquals($result, [Transfer::GROUP_DATABASES]); - } -} \ No newline at end of file diff --git a/tests/Transfer/Sources/AppwriteSourceTest.php b/tests/Transfer/Sources/AppwriteSourceTest.php deleted file mode 100644 index 43e936b..0000000 --- a/tests/Transfer/Sources/AppwriteSourceTest.php +++ /dev/null @@ -1,85 +0,0 @@ - - * @version 1.0 RC1 - * @license The MIT License (MIT) - */ - -namespace Utopia\Tests; - -use PHPUnit\Framework\TestCase; -use Utopia\App; -use Utopia\Transfer\Sources\Appwrite; -use Utopia\Transfer\Resources\Project; - -class AppwriteSourceTest extends TestCase -{ - /** - * @var Appwrite - */ - public $appwrite; - - /** - * @var array - */ - public $serviceAccount; - - public function setUp(): void - { - $this->appwrite = new Appwrite( - getenv('SOURCE_APPWRITE_TEST_PROJECT'), - getenv('SOURCE_APPWRITE_TEST_ENDPOINT'), - getenv('SOURCE_APPWRITE_TEST_KEY') - ); - } - - - public function testGetUsers(): void - { - $result = []; - - $this->appwrite->exportAuth( - 100, - public function (array $users) use (&$result) { - $result = array_merge($result, $users); - } - ); - - foreach ($result as $user) { - /** - * @var User $user -*/ - $this->assertIsObject($user); - } - - $this->assertIsArray($result); - $this->assertNotEmpty($result); - } - - public function testGetDatabases(): void - { - $result = []; - - $this->appwrite->exportDatabases( - 100, - public function (array $databases) use (&$result) { - $result = array_merge($result, $databases); - } - ); - - foreach ($result as $database) { - /** - * @var Database $database -*/ - $this->assertIsObject($database); - $this->assertNotEmpty($database->getCollections()); - } - } -} diff --git a/tests/Transfer/Sources/FirebaseTest.php b/tests/Transfer/Sources/FirebaseTest.php deleted file mode 100644 index dca822a..0000000 --- a/tests/Transfer/Sources/FirebaseTest.php +++ /dev/null @@ -1,106 +0,0 @@ - - * @version 1.0 RC1 - * @license The MIT License (MIT) - */ - -namespace Utopia\Tests; - -use PHPUnit\Framework\TestCase; -use Utopia\App; -use Utopia\Transfer\Sources\Firebase; -use Utopia\Transfer\Resources\Project; - -class FirebaseTest extends TestCase -{ - /** - * @var Firebase - */ - public $firebase; - - /** - * @var array - */ - public $serviceAccount; - - public function setUp(): void - { - $this->serviceAccount = json_decode(getEnv("FIREBASE_TEST_ACCOUNT"), true); - - $this->firebase = new Firebase( - $this->serviceAccount, - Firebase::AUTH_SERVICEACCOUNT - ); - } - - public function testGetProjects(): void - { - $projects = $this->firebase->getProjects(); - - $this->assertIsArray($projects); - $this->assertNotEmpty($projects); - } - - public function testSetProject(): void - { - $projects = $this->firebase->getProjects(); - - /** - * @var Project $testProject - */ - $testProject = null; - - foreach ($projects as $project) { - /** - * @var Project $project -*/ - if ($project->getId() == $this->serviceAccount['project_id']) { - $testProject = $project; - break; - } - } - - $this->assertIsObject($testProject); - - $this->firebase->setProject($testProject); - - $this->assertEquals($projects[0], $this->firebase->getProject()); - } - - - public function testGetUsers(): void - { - $projects = $this->firebase->getProjects(); - - $this->firebase->setProject($projects[0]); - - $result = []; - - $this->firebase->exportAuth( - 500, - public function (array $users) use (&$result) { - $result = array_merge($result, $users); - } - ); - - foreach ($result as $user) { - /** - * @var User $user -*/ - $this->assertIsObject($user); - $this->assertNotEmpty($user->getPasswordHash()); - $this->assertNotEmpty($user->getPasswordHash()->getHash()); - } - - $this->assertIsArray($result); - $this->assertNotEmpty($result); - } -} diff --git a/tests/Transfer/Sources/NHostTest.php b/tests/Transfer/Sources/NHostTest.php deleted file mode 100644 index 48db340..0000000 --- a/tests/Transfer/Sources/NHostTest.php +++ /dev/null @@ -1,92 +0,0 @@ - - * @version 1.0 RC1 - * @license The MIT License (MIT) - */ - -namespace Utopia\Tests; - -use PHPUnit\Framework\TestCase; -use Utopia\App; -use Utopia\Transfer\Sources\NHost; -use Utopia\Transfer\Resources\Project; -use Utopia\Transfer\Resources\User; - -class NHostTest extends TestCase -{ - /** - * @var NHost - */ - public $nhost; - - public function setUp(): void - { - $this->nhost = new NHost( - getEnv("NHOST_TEST_HOST") ?? '', - getEnv("NHOST_TEST_DATABASE") ?? '', - getEnv("NHOST_TEST_USERNAME") ?? '', - getEnv("NHOST_TEST_PASSWORD") ?? '', - ); - } - - public function testGetUsers(): array - { - $result = []; - - $this->nhost->exportAuth( - 500, - public function (array $users) use (&$result) { - $result = array_merge($result, $users); - } - ); - - foreach ($result as $user) { - /** - * @var User $user -*/ - $this->assertIsObject($user); - $this->assertNotEmpty($user->getPasswordHash()); - $this->assertNotEmpty($user->getPasswordHash()->getHash()); - } - - $this->assertIsArray($result); - $this->assertNotEmpty($result); - - return $result; - } - - /** - * @depends testGetUsers - */ - public function testVerifyUsers(array $users): void - { - $assertedUsers = 0; - - foreach ($users as $user) { - /** - * @var User $user -*/ - if (in_array(User::TYPE_ANONYMOUS, $user->getTypes())) { - continue; - } - - try { - $assertedUsers++; - - $this->assertNotEmpty($user); - } catch (\Exception $e) { - throw $e; - } - } - - $this->assertGreaterThan(1, $assertedUsers); - } -} diff --git a/tests/Transfer/Sources/SupabaseTest.php b/tests/Transfer/Sources/SupabaseTest.php deleted file mode 100644 index b601360..0000000 --- a/tests/Transfer/Sources/SupabaseTest.php +++ /dev/null @@ -1,99 +0,0 @@ - - * @version 1.0 RC1 - * @license The MIT License (MIT) - */ - -namespace Utopia\Tests; - -use PHPUnit\Framework\TestCase; -use Utopia\App; -use Utopia\Transfer\Sources\Supabase; -use Utopia\Transfer\Resources\Project; -use Utopia\Transfer\Resources\User; - -class SupabaseTest extends TestCase -{ - /** - * @var Supabase - */ - public $supabase; - - public function setUp(): void - { - $this->supabase = new Supabase( - getEnv("SUPABASE_TEST_HOST") ?? '', - getEnv("SUPABASE_TEST_DATABASE") ?? '', - getEnv("SUPABASE_TEST_USERNAME") ?? '', - getEnv("SUPABASE_TEST_PASSWORD") ?? '', - ); - } - - public function testGetUsers(): array - { - $result = []; - - $this->supabase->exportAuth( - 500, - public function (array $users) use (&$result) { - $result = array_merge($result, $users); - } - ); - - foreach ($result as $user) { - /** - * @var User $user -*/ - $this->assertIsObject($user); - $this->assertNotEmpty($user->getPasswordHash()); - $this->assertNotEmpty($user->getPasswordHash()->getHash()); - } - - $this->assertIsArray($result); - $this->assertNotEmpty($result); - - return $result; - } - - /** - * @depends testGetUsers - */ - public function testVerifyUsers(array $users): void - { - $assertedUsers = 0; - - foreach ($users as $user) { - /** - * @var User $user -*/ - if (in_array(User::TYPE_ANONYMOUS, $user->getTypes())) { - continue; - } - - try { - $userFound = $this->supabase->pdo->query('SELECT * FROM auth.users WHERE id = \'' . $user->getId() . '\'')->fetch(); - $assertedUsers++; - - $this->assertNotEmpty($userFound); - $this->assertEquals($user->getId(), $userFound['id']); - $this->assertEquals($user->getEmail(), $userFound['email']); - $this->assertEquals($user->getPhone(), $userFound['phone']); - $this->assertEquals($user->getPasswordHash()->getHash(), $userFound['encrypted_password']); - $this->assertEquals($user->getEmailVerified(), !empty($userFound['email_confirmed_at'])); - $this->assertEquals($user->getPhoneVerified(), !empty($userFound['phone_confirmed_at'])); - } catch (\Exception $e) { - throw $e; - } - } - - $this->assertGreaterThan(1, $assertedUsers); - } -} diff --git a/tests/Transfer/Transfers/FirebaseToAppwriteTest.php b/tests/Transfer/Transfers/FirebaseToAppwriteTest.php deleted file mode 100644 index 851f438..0000000 --- a/tests/Transfer/Transfers/FirebaseToAppwriteTest.php +++ /dev/null @@ -1,136 +0,0 @@ - - * @version 1.0 RC1 - * @license The MIT License (MIT) - */ - -namespace Utopia\Tests; - -use PHPUnit\Framework\TestCase; -use Utopia\Transfer\Destinations\Appwrite; -use Utopia\Transfer\Log; -use Utopia\Transfer\Sources\Firebase; -use Utopia\Transfer\Transfer; -use Utopia\Transfer\Resources\User; -use Appwrite\Client as AppwriteClient; -use Appwrite\Services\Users; - -class FirebaseToAppwriteTest extends TestCase -{ - /** - * @var Firebase - */ - public $firebase; - - /** - * @var Appwrite - */ - public $appwrite; - - /** - * @var AppwriteClient - */ - public $appwriteClient; - - /** - * @var Transfer - */ - public $transfer; - - public function setUp(): void - { - $serviceAccount = json_decode(getenv("FIREBASE_TEST_ACCOUNT"), true); - - $this->firebase = new Firebase( - $serviceAccount, - Firebase::AUTH_SERVICEACCOUNT - ); - - $this->firebase->setProject($serviceAccount['project_id']); - - $this->appwrite = new Appwrite( - getenv("DESTINATION_APPWRITE_TEST_PROJECT"), - getenv("DESTINATION_APPWRITE_TEST_ENDPOINT"), - getenv("DESTINATION_APPWRITE_TEST_KEY") - ); - - $this->appwriteClient = new AppwriteClient(); - $this->appwriteClient - ->setEndpoint(getenv("DESTINATION_APPWRITE_TEST_ENDPOINT")) - ->setProject(getenv("DESTINATION_APPWRITE_TEST_PROJECT")) - ->setKey(getenv("DESTINATION_APPWRITE_TEST_KEY")); - - $this->transfer = new Transfer($this->firebase, $this->appwrite); - } - - public function testTransferUsers(): void - { - $this->transfer->run( - [Transfer::GROUP_AUTH], - public function () { - } - ); - - // Check for Fatal Errors in Transfer Log - $this->assertEmpty($this->transfer->getLogs(Log::FATAL)); - } - - /** - * @depends testTransferUsers - */ - public function testVerifyUsers(): void - { - $userClient = new Users($this->appwriteClient); - - $assertedUsers = false; - - $this->firebase->exportAuth( - 500, - public function (array $users) use ($userClient, &$assertedUsers) { - foreach ($users as $user) { - /** - * @var User $user - */ - if (in_array(User::TYPE_ANONYMOUS, $user->getTypes())) { - continue; - } - - try { - $userFound = $userClient->get($user->getId()); - } catch (\Exception $e) { - throw $e; - } - $this->assertNotEmpty($userFound); - - $this->assertEquals($user->getEmail(), $userFound['email']); - $this->assertEquals($user->getPhone(), $userFound['phone']); - $assertedUsers = true; - } - } - ); - - $this->assertTrue($assertedUsers); - } - - public function testCleanupUsers(): void - { - $userClient = new Users($this->appwriteClient); - $appwriteUsers = $userClient->list(); - - $deletedUsers = 0; - - foreach ($appwriteUsers["users"] as $user) { - $userClient->delete($user['$id']); - $this->assertTrue(true); - $deletedUsers++; - } - } -} diff --git a/tests/Transfer/Transfers/SupabaseToAppwriteTest.php b/tests/Transfer/Transfers/SupabaseToAppwriteTest.php deleted file mode 100644 index 9508fb2..0000000 --- a/tests/Transfer/Transfers/SupabaseToAppwriteTest.php +++ /dev/null @@ -1,139 +0,0 @@ - - * @version 1.0 RC1 - * @license The MIT License (MIT) - */ - -namespace Utopia\Tests; - -use PHPUnit\Framework\TestCase; -use Utopia\Transfer\Destinations\Appwrite; -use Utopia\Transfer\Log; -use Utopia\Transfer\Sources\Supabase; -use Utopia\Transfer\Transfer; -use Utopia\Transfer\Resources\User; -use Appwrite\Client as AppwriteClient; -use Appwrite\Services\Users; - -class SupabaseToAppwriteTest extends TestCase -{ - /** - * @var Supabase - */ - public $supabase; - - /** - * @var Appwrite - */ - public $appwrite; - - /** - * @var AppwriteClient - */ - public $appwriteClient; - - /** - * @var Transfer - */ - public $transfer; - - public function setUp(): void - { - $this->supabase = new Supabase( - getEnv("SUPABASE_TEST_HOST"), - getEnv("SUPABASE_TEST_DATABASE"), - getEnv("SUPABASE_TEST_USERNAME"), - getEnv("SUPABASE_TEST_PASSWORD"), - ); - - $this->appwrite = new Appwrite( - getenv("DESTINATION_APPWRITE_TEST_PROJECT"), - getenv("DESTINATION_APPWRITE_TEST_ENDPOINT"), - getenv("DESTINATION_APPWRITE_TEST_KEY") - ); - - $this->appwriteClient = new AppwriteClient(); - $this->appwriteClient - ->setEndpoint(getenv("DESTINATION_APPWRITE_TEST_ENDPOINT")) - ->setProject(getenv("DESTINATION_APPWRITE_TEST_PROJECT")) - ->setKey(getenv("DESTINATION_APPWRITE_TEST_KEY")); - - $this->transfer = new Transfer($this->supabase, $this->appwrite); - } - - public function testTransferUsers(): void - { - $this->transfer->run( - [Transfer::GROUP_AUTH], - public function () { - } - ); - - // Check for Fatal Errors in Transfer Log - $this->assertEmpty($this->transfer->getLogs(Log::FATAL)); - } - - - /** - * @depends testTransferUsers - */ - public function testVerifyUsers(): void - { - $userClient = new Users($this->appwriteClient); - - $assertedUsers = false; - - $this->supabase->exportAuth( - 500, - public function (array $users) use ($userClient, &$assertedUsers) { - foreach ($users as $user) { - /** - * @var User $user - */ - if (in_array(User::TYPE_ANONYMOUS, $user->getTypes())) { - continue; - } - - try { - $userFound = $userClient->get($user->getId()); - } catch (\Exception $e) { - throw $e; - } - $this->assertNotEmpty($userFound); - - $this->assertEquals($user->getId(), $userFound['$id']); - $this->assertEquals($user->getEmail(), $userFound['email']); - $this->assertEquals($user->getPhone(), $userFound['phone']); - $this->assertEquals($user->getPasswordHash()->getHash(), $userFound['password']); - $this->assertEquals($user->getEmailVerified(), $userFound['emailVerification']); - $this->assertEquals($user->getPhoneVerified(), $userFound['phoneVerification']); - $assertedUsers = true; - } - } - ); - - $this->assertTrue($assertedUsers); - } - - public function testCleanupUsers(): void - { - $userClient = new Users($this->appwriteClient); - $appwriteUsers = $userClient->list(); - - $deletedUsers = 0; - - foreach ($appwriteUsers["users"] as $user) { - $userClient->delete($user['$id']); - $this->assertTrue(true); - $deletedUsers++; - } - } -} diff --git a/tests/e2e/adapters/MockDestination.php b/tests/e2e/adapters/MockDestination.php new file mode 100644 index 0000000..64f2224 --- /dev/null +++ b/tests/e2e/adapters/MockDestination.php @@ -0,0 +1,32 @@ +add($resource); + + $this->assertArrayHasKey($resource->getName(), $cache->getAll()); + $this->assertArrayHasKey($resource->getInternalId(), $cache->getAll()[$resource->getName()]); + $this->assertEquals($resource, $cache->getAll()[$resource->getName()][$resource->getInternalId()]); + + return $cache; + } + + /** + * @depends testAdd + */ + public function testRemove(ResourceCache $cache) + { + $resources = $cache->get(ConcreteResource::getName()); + $resource = $resources[array_keys($resources)[0]]; + + $cache->remove($resource); + + $this->assertArrayNotHasKey($resource->getInternalId(), $cache->getAll()[$resource->getName()]); + + return $cache; + } + + /** + * @depends testRemove + */ + public function testWipe(ResourceCache $cache) + { + $resource = new ConcreteResource(); + $cache->add($resource); + + $resource2 = new ConcreteResource(); + $cache->add($resource2); + + $cache->wipe(); + + $this->assertEmpty($cache->getAll()); + + return $cache; + } + + /** + * @depends testWipe + */ + public function testStatusCountersAdd(ResourceCache $cache) + { + $resource1 = new ConcreteResource(); + $resource2 = new ConcreteResource(); + $resource3 = new ConcreteResource(); + + $resource1->setStatus(Resource::STATUS_SUCCESS); + $resource2->setStatus(Resource::STATUS_ERROR); + $resource3->setStatus(Resource::STATUS_SKIPPED); + + $cache->add($resource1); + $cache->add($resource2); + $cache->add($resource3); + + $this->assertEquals(1, $cache->getStatusCounters()[Resource::STATUS_SUCCESS]); + $this->assertEquals(1, $cache->getStatusCounters()[Resource::STATUS_ERROR]); + $this->assertEquals(1, $cache->getStatusCounters()[Resource::STATUS_SKIPPED]); + + return $cache; + } + + /** + * @depends testStatusCountersAdd + */ + public function testStatusCountersUpdate(ResourceCache $cache) + { + $resources = $cache->get(ConcreteResource::getName()); + $resource = $resources[array_keys($resources)[0]]; + + $resource->setStatus(Resource::STATUS_ERROR); + $cache->update($resource); + + $resourceStatus = $cache->getStatusCounters(); + + $this->assertEquals(2, $resourceStatus[Resource::STATUS_ERROR]); + $this->assertEquals(0, $resourceStatus[Resource::STATUS_SUCCESS]); + $this->assertEquals(1, $resourceStatus[Resource::STATUS_SKIPPED]); + + return $cache; + } + + /** + * @depends testStatusCountersUpdate + */ + public function testStatusCountersRemove(ResourceCache $cache) + { + $resources = $cache->get(ConcreteResource::getName()); + $resource = $resources[array_keys($resources)[0]]; + + $cache->remove($resource); + + $statusCounters = $cache->getStatusCounters(); + + $this->assertEquals(1, $statusCounters[Resource::STATUS_ERROR]); + $this->assertEquals(0, $statusCounters[Resource::STATUS_SUCCESS]); + $this->assertEquals(1, $statusCounters[Resource::STATUS_SKIPPED]); + } +} From 417018c43cd2d88588cbd894709aa6a98d1b4113 Mon Sep 17 00:00:00 2001 From: Bradley Schofield Date: Thu, 11 May 2023 16:44:54 +0900 Subject: [PATCH 37/70] Start implementing tests and change check with report --- README.md | 4 +- playground.php | 38 +- src/Transfer/Destinations/Appwrite.php | 315 +++++++++------ src/Transfer/Destinations/Local.php | 79 ++-- src/Transfer/Resource.php | 20 +- src/Transfer/ResourceCache.php | 11 +- src/Transfer/Resources/Auth/Hash.php | 1 + .../Resources/Functions/Deployment.php | 142 +++++++ src/Transfer/Resources/Project.php | 47 --- src/Transfer/Resources/Storage/FileData.php | 1 - src/Transfer/Source.php | 16 +- src/Transfer/Sources/Appwrite.php | 373 +++++++++++------- src/Transfer/Sources/Firebase.php | 71 +++- src/Transfer/Sources/NHost.php | 307 ++++++++++---- src/Transfer/Sources/Supabase.php | 108 ++++- src/Transfer/Target.php | 24 +- src/Transfer/Transfer.php | 4 +- tests/e2e/Sources/Appwrite.php | 24 ++ tests/e2e/Sources/SourceTest.php | 44 +++ tests/e2e/adapters/MockDestination.php | 24 +- tests/e2e/adapters/MockSource.php | 2 +- 21 files changed, 1169 insertions(+), 486 deletions(-) create mode 100644 src/Transfer/Resources/Functions/Deployment.php delete mode 100644 src/Transfer/Resources/Project.php create mode 100644 tests/e2e/Sources/Appwrite.php create mode 100644 tests/e2e/Sources/SourceTest.php diff --git a/README.md b/README.md index 6f5984d..6055062 100644 --- a/README.md +++ b/README.md @@ -50,7 +50,7 @@ Sources: | | Auth | Databases | Storage | Functions | Settings | |----------|-------|-----------|-------|-----------|-----------| | Appwrite | ✅ | ✅ | ✅ | ✅ | | -| Supabase | ✅ | ✅ | ✅ | ✅ | | +| Supabase | ✅ | ✅ | ✅ | | | | NHost | ✅ | ✅ | ✅ | | | | Firebase | ✅ | ✅ | | | | @@ -61,7 +61,7 @@ Destinations: | Local | ✅ | ✅ | ✅ | ✅ | ✅ | > **Warning** -> The Local destination should be used for testing purposes only. It is not recommended to use this destination in production or as a backup. The local destination is there to confirm that a source is working correctly and to test the transfer process with needing a target destination instance. +> The Local destination should be used for testing purposes only. It is not recommended to use this destination in production or as a backup. The local destination is there to confirm that a source is working correctly and to test the transfer process with needing a target destination instance. This may change in the future however as the library matures. diff --git a/playground.php b/playground.php index 9cc3c94..2c25f0f 100644 --- a/playground.php +++ b/playground.php @@ -30,26 +30,30 @@ $_ENV['SOURCE_APPWRITE_TEST_KEY'] ); +$firebase = json_decode($_ENV["FIREBASE_TEST_ACCOUNT"], true); + $sourceFirebase = new Firebase( - json_decode($_ENV["FIREBASE_TEST_ACCOUNT"], true), - "amadeus-a3730" + $firebase, + $firebase['project_id'] ?? '', ); -// $sourceNHost = new NHost( -// $_ENV["NHOST_TEST_HOST"] ?? '', -// $_ENV["NHOST_TEST_DATABASE"] ?? '', -// $_ENV["NHOST_TEST_USERNAME"] ?? '', -// $_ENV["NHOST_TEST_PASSWORD"] ?? '', -// ); +$sourceNHost = new NHost( + $_ENV['NHOST_TEST_SUBDOMAIN'] ?? '', + $_ENV["NHOST_TEST_REGION"] ?? '', + $_ENV['NHOST_TEST_SECRET'] ?? '', + $_ENV["NHOST_TEST_DATABASE"] ?? '', + $_ENV["NHOST_TEST_USERNAME"] ?? '', + $_ENV["NHOST_TEST_PASSWORD"] ?? '', +); -// $sourceSupabase = new Supabase( -// $_ENV['SUPABASE_TEST_ENDPOINT'] ?? '', -// $_ENV['SUPABASE_TEST_KEY'] ?? '', -// $_ENV["SUPABASE_TEST_HOST"] ?? '', -// $_ENV["SUPABASE_TEST_DATABASE"] ?? '', -// $_ENV["SUPABASE_TEST_USERNAME"] ?? '', -// $_ENV["SUPABASE_TEST_PASSWORD"] ?? '', -// ); +$sourceSupabase = new Supabase( + $_ENV['SUPABASE_TEST_ENDPOINT'] ?? '', + $_ENV['SUPABASE_TEST_KEY'] ?? '', + $_ENV["SUPABASE_TEST_HOST"] ?? '', + $_ENV["SUPABASE_TEST_DATABASE"] ?? '', + $_ENV["SUPABASE_TEST_USERNAME"] ?? '', + $_ENV["SUPABASE_TEST_PASSWORD"] ?? '', +); /** * Initialise All Destination Adapters @@ -76,7 +80,7 @@ */ $transfer->run( [ - Resource::TYPE_USER, + Transfer::GROUP_DATABASES_RESOURCES, ], function () { } ); \ No newline at end of file diff --git a/src/Transfer/Destinations/Appwrite.php b/src/Transfer/Destinations/Appwrite.php index ac00596..7eff3dc 100644 --- a/src/Transfer/Destinations/Appwrite.php +++ b/src/Transfer/Destinations/Appwrite.php @@ -34,6 +34,7 @@ use Utopia\Transfer\Resources\Database\Attributes\RelationshipAttribute; use Utopia\Transfer\Resources\Database\Document; use Utopia\Transfer\Resource; +use Utopia\Transfer\Resources\Functions\Deployment; class Appwrite extends Destination { @@ -72,154 +73,149 @@ static function getName(): string public function getSupportedResources(): array { return [ - Transfer::GROUP_AUTH, - Transfer::GROUP_DATABASES, - Transfer::GROUP_STORAGE, - Transfer::GROUP_FUNCTIONS, - Transfer::GROUP_SETTINGS + // Auth + Resource::TYPE_USER, + Resource::TYPE_TEAM, + Resource::TYPE_TEAM_MEMBERSHIP, + + // Database + Resource::TYPE_DATABASE, + Resource::TYPE_COLLECTION, + Resource::TYPE_ATTRIBUTE, + Resource::TYPE_INDEX, + Resource::TYPE_DOCUMENT, + + // Storage + Resource::TYPE_BUCKET, + Resource::TYPE_FILE, + Resource::TYPE_FILEDATA, + + // Functions + Resource::TYPE_FUNCTION, + Resource::TYPE_DEPLOYMENT, + Resource::TYPE_ENVVAR, + + // Settings ]; } - public function check(array $resources = []): array + public function report(array $resources = []): array { - $report = [ - Transfer::GROUP_AUTH => [], - Transfer::GROUP_DATABASES => [], - Transfer::GROUP_STORAGE => [], - Transfer::GROUP_FUNCTIONS => [], - Transfer::GROUP_SETTINGS => [], - ]; - if (empty($resources)) { $resources = $this->getSupportedResources(); } + $databases = new Databases($this->client); + $functions = new Functions($this->client); + $storage = new Storage($this->client); + $teams = new Teams($this->client); + $users = new Users($this->client); + + $currentPermission = ''; // Most of these API calls are purposely wrong. Appwrite will throw a 403 before a 400. // We want to make sure the API key has full read and write access to the project. - foreach ($resources as $resource) { - switch ($resource) { - case Transfer::GROUP_DATABASES: - $databases = new Databases($this->client); - try { - $databases->list(); - } catch (\Throwable $e) { - if ($e->getCode() == 401) { - $report[Transfer::GROUP_DATABASES][] = 'API Key is missing scope: databases.read'; - } - } - try { - $databases->create('', ''); - } catch (\Throwable $e) { - if ($e->getCode() == 401) { - $report[Transfer::GROUP_DATABASES][] = 'API Key is missing scope: databases.write'; - } - } + try { + // Auth + if (in_array(Resource::TYPE_USER, $resources)) { + $currentPermission = 'users.read'; + $users->list(); - try { - $databases->listCollections('', [], ''); - } catch (\Throwable $e) { - if ($e->getCode() == 401) { - $report[Transfer::GROUP_DATABASES][] = 'API Key is missing scope: collections.write'; - } - } + $currentPermission = 'users.write'; + $users->create('', '', ''); + } - try { - $databases->createCollection('', '', '', []); - } catch (\Throwable $e) { - if ($e->getCode() == 401) { - $report[Transfer::GROUP_DATABASES][] = 'API Key is missing scope: collections.write'; - } - } + if (in_array(Resource::TYPE_TEAM, $resources)) { + $currentPermission = 'teams.read'; + $teams->list(); - try { - $databases->listDocuments('', '', []); - } catch (\Throwable $e) { - if ($e->getCode() == 401) { - $report[Transfer::GROUP_DATABASES][] = 'API Key is missing scope: documents.write'; - } - } + $currentPermission = 'teams.write'; + $teams->create('', ''); + } - try { - $databases->createDocument('', '', '', [], []); - } catch (\Throwable $e) { - if ($e->getCode() == 401) { - $report[Transfer::GROUP_DATABASES][] = 'API Key is missing scope: documents.write'; - } - } + if (in_array(Resource::TYPE_TEAM_MEMBERSHIP, $resources)) { + $currentPermission = 'memberships.read'; + $teams->listMemberships(''); - try { - $databases->listIndexes('', ''); - } catch (\Throwable $e) { - if ($e->getCode() == 401) { - $report[Transfer::GROUP_DATABASES][] = 'API Key is missing scope: indexes.read'; - } - } + $currentPermission = 'memberships.write'; + $teams->createMembership('', [], ''); + } - try { - $databases->createIndex('', '', '', '', [], []); - } catch (\Throwable $e) { - if ($e->getCode() == 401) { - $report[Transfer::GROUP_DATABASES][] = 'API Key is missing scope: indexes.write'; - } - } + // Database + if (in_array(Resource::TYPE_DATABASE, $resources)) { + $currentPermission = 'database.read'; + $databases->list(); - try { - $databases->listAttributes('', ''); - } catch (\Throwable $e) { - if ($e->getCode() == 401) { - $report[Transfer::GROUP_DATABASES][] = 'API Key is missing scope: attributes.read'; - } - } + $currentPermission = 'database.write'; + $databases->create('', ''); + } - try { - $databases->createStringAttribute('', '', '', 0, false, false); - } catch (\Throwable $e) { - if ($e->getCode() == 401) { - $report[Transfer::GROUP_DATABASES][] = 'API Key is missing scope: attributes.write'; - } - } - break; - case Transfer::GROUP_AUTH: - $auth = new Users($this->client); - try { - $auth->list(); - } catch (\Throwable $e) { - if ($e->getCode() == 401) { - $report[Transfer::GROUP_AUTH][] = 'API Key is missing scope: users.read'; - } - } + if (in_array(Resource::TYPE_COLLECTION, $resources)) { + $currentPermission = 'collections.read'; + $databases->listCollections(''); - try { - $auth->create('', '', '', ''); - } catch (\Throwable $e) { - if ($e->getCode() == 401) { - $report[Transfer::GROUP_AUTH][] = 'API Key is missing scope: users.write'; - } - } - break; - case Transfer::GROUP_STORAGE: - $storage = new Storage($this->client); - try { - $storage->listFiles(''); - } catch (\Throwable $e) { - if ($e->getCode() == 401) { - $report[Transfer::GROUP_STORAGE][] = 'API Key is missing scope: files.read'; - } - } + $currentPermission = 'collections.write'; + $databases->createCollection('', '', ''); + } - try { - $storage->createFile('', '', new InputFile()); - } catch (\Throwable $e) { - if ($e->getCode() == 401) { - $report[Transfer::GROUP_STORAGE][] = 'API Key is missing scope: files.write'; - } - } - break; + if (in_array(Resource::TYPE_ATTRIBUTE, $resources)) { + $currentPermission = 'attributes.read'; + $databases->listAttributes('', ''); + + $currentPermission = 'attributes.write'; + $databases->createStringAttribute('', '', '', 0, false); + } + + if (in_array(Resource::TYPE_INDEX, $resources)) { + $currentPermission = 'indexes.read'; + $databases->listIndexes('', ''); + + $currentPermission = 'indexes.write'; + $databases->createIndex('', '', '', '', []); + } + + if (in_array(Resource::TYPE_DOCUMENT, $resources)) { + $currentPermission = 'documents.read'; + $databases->listDocuments('', ''); + + $currentPermission = 'documents.write'; + $databases->createDocument('', '', '', []); + } + + // Storage + if (in_array(Resource::TYPE_BUCKET, $resources)) { + $currentPermission = 'storage.read'; + $storage->listBuckets(); + + $currentPermission = 'storage.write'; + $storage->createBucket('', ''); + } + + if (in_array(Resource::TYPE_FILE, $resources)) { + $currentPermission = 'files.read'; + $storage->listFiles(''); + + $currentPermission = 'files.write'; + $storage->createFile('', '', new InputFile()); } - } - return $report; + // Functions + if (in_array(Resource::TYPE_FUNCTION, $resources)) { + $currentPermission = 'functions.read'; + $functions->list(); + + $currentPermission = 'functions.write'; + $functions->create('', '', ''); + } + + return []; + } catch (\Exception $exception) { + if ($exception->getCode() === 403) { + throw new \Exception('Missing permission: ' . $currentPermission); + } else { + throw $exception; + } + } } function importResources(array $resources, callable $callback): void @@ -610,6 +606,15 @@ public function importPasswordUser(User $user): array|null $user->getUsername() ); break; + case Hash::PLAINTEXT: + $result = $auth->create( + $user->getId(), + $user->getEmail(), + $user->getPhone(), + $hash->getHash(), + $user->getUsername() + ); + break; } return $result; @@ -633,6 +638,7 @@ public function importFunctionResource(Resource $resource): Resource $resource->getTimeout(), $resource->getEnabled() ); + break; case Resource::TYPE_ENVVAR: /** @var EnvVar $resource */ $functions->createVariable( @@ -640,13 +646,72 @@ public function importFunctionResource(Resource $resource): Resource $resource->getKey(), $resource->getValue() ); + break; + case Resource::TYPE_DEPLOYMENT: + return $this->importDeployment($resource); + break; } $resource->setStatus(Resource::STATUS_SUCCESS); + return $resource; } catch (\Exception $e) { $resource->setStatus(Resource::STATUS_ERROR, $e->getMessage()); } finally { return $resource; } } + + function importDeployment(Deployment $deployment): Resource + { + $functionId = $deployment->getFunction()->getId(); + + $response = null; + + if ($deployment->getSize() <= Transfer::STORAGE_MAX_CHUNK_SIZE) { + $response = $this->client->call( + 'POST', + "/v1/functions/{$functionId}/deployments", + [ + 'content-type' => 'multipart/form-data', + ], + [ + 'functionId' => $functionId, + 'code' => new \CurlFile('data://application/gzip;base64,' . base64_encode($deployment->getData()), 'application/gzip', 'deployment.tar.gz'), + 'activate' => $deployment->getActivated() ? 'true' : 'false', + 'entrypoint' => $deployment->getEntrypoint() + ] + ); + + $deployment->setStatus(Resource::STATUS_SUCCESS); + return $deployment; + } + + $response = $this->client->call( + 'POST', + "/v1/functions/{$functionId}/deployments", + [ + 'content-type' => 'multipart/form-data', + 'content-range' => 'bytes ' . ($deployment->getStart()) . '-' . ($deployment->getEnd() == ($deployment->getSize() - 1) ? $deployment->getSize() : $deployment->getEnd()) . '/' . $deployment->getSize(), + 'x-appwrite-id' => $deployment->getId(), + ], + [ + 'functionId' => $functionId, + 'code' => new \CurlFile('data://application/gzip;base64,' . base64_encode($deployment->getData()), 'application/gzip', 'deployment.tar.gz'), + 'activate' => $deployment->getActivated(), + 'entrypoint' => $deployment->getEntrypoint() + ] + ); + + if ($deployment->getStart() === 0) { + $deployment->setId($response['$id']); + } + + if ($deployment->getEnd() == ($deployment->getSize() - 1)) { + $deployment->setStatus(Resource::STATUS_SUCCESS); + } else { + $deployment->setStatus(Resource::STATUS_PENDING); + } + + return $deployment; + } } diff --git a/src/Transfer/Destinations/Local.php b/src/Transfer/Destinations/Local.php index 57a1aa4..a2af71c 100644 --- a/src/Transfer/Destinations/Local.php +++ b/src/Transfer/Destinations/Local.php @@ -5,6 +5,7 @@ use Utopia\Transfer\Destination; use Utopia\Transfer\Resources\Storage\File; use Utopia\Transfer\Resources\Storage\FileData; +use Utopia\Transfer\Resources\Functions\Deployment; use Utopia\Transfer\Resource; use Utopia\Transfer\Transfer; @@ -27,6 +28,7 @@ public function __construct(string $path) if (!\file_exists($this->path)) { mkdir($this->path, 0777, true); mkdir($this->path . '/files', 0777, true); + mkdir($this->path . '/deployments', 0777, true); } } @@ -48,27 +50,27 @@ static function getName(): string public function getSupportedResources(): array { return [ - Transfer::GROUP_AUTH, - Transfer::GROUP_DATABASES, - Transfer::GROUP_STORAGE, - Transfer::GROUP_FUNCTIONS + Resource::TYPE_ATTRIBUTE, + Resource::TYPE_BUCKET, + Resource::TYPE_COLLECTION, + Resource::TYPE_DATABASE, + Resource::TYPE_DOCUMENT, + Resource::TYPE_FILE, + Resource::TYPE_FILEDATA, + Resource::TYPE_FUNCTION, + Resource::TYPE_DEPLOYMENT, + Resource::TYPE_HASH, + Resource::TYPE_INDEX, + Resource::TYPE_USER, + Resource::TYPE_ENVVAR, + Resource::TYPE_TEAM, + Resource::TYPE_TEAM_MEMBERSHIP, ]; } - /** - * Check if destination is valid - * - * @param array $resources - * @return array - */ - public function check(array $resources = []): array + public function report(array $resources = []): array { - $report = [ - Transfer::GROUP_AUTH => [], - Transfer::GROUP_DATABASES => [], - Transfer::GROUP_STORAGE => [], - Transfer::GROUP_FUNCTIONS => [] - ]; + $report = []; if (empty($resources)) { $resources = $this->getSupportedResources(); @@ -88,7 +90,7 @@ public function syncFile(): void $jsonEncodedData = \json_encode($this->data, JSON_PRETTY_PRINT); if ($jsonEncodedData === false) { - throw new \Exception('Unable to encode data to JSON'); + throw new \Exception('Unable to encode data to JSON, Are you accidentally encoding binary data?'); } \file_put_contents($this->path . '/backup.json', \json_encode($this->data, JSON_PRETTY_PRINT)); @@ -99,37 +101,46 @@ public function importResources(array $resources, callable $callback): void foreach ($resources as $resource) { /** @var Resource $resource */ switch ($resource->getName()) { + case "Deployment": { + /** @var Deployment $resource */ + if ($resource->getStart() === 0) { + $this->data[$resource->getGroup()][$resource->getName()][$resource->getInternalId()] = $resource->asArray(); + } + + file_put_contents($this->path . 'deployments/'.$resource->getId().'.tar.gz', $resource->getData(), FILE_APPEND); + } + break; case "FileData": { /** @var FileData $resource */ // Handle folders - if (str_contains($resource->getFile()->getFileName(), '/')) { - $folders = explode('/', $resource->getFile()->getFileName()); - $folderPath = $this->path . '/files'; + if (str_contains($resource->getFile()->getFileName(), '/')) { + $folders = explode('/', $resource->getFile()->getFileName()); + $folderPath = $this->path . '/files'; - foreach ($folders as $folder) { - $folderPath .= '/' . $folder; + foreach ($folders as $folder) { + $folderPath .= '/' . $folder; - if (!\file_exists($folderPath) && str_contains($folder, '.') === false) { - mkdir($folderPath, 0777, true); + if (!\file_exists($folderPath) && str_contains($folder, '.') === false) { + mkdir($folderPath, 0777, true); + } } } - } file_put_contents($this->path . '/files/' . $resource->getFile()->getFileName(), $resource->getData(), FILE_APPEND); - break; - } + break; + } case "File": { /** @var File $resource */ - if (\file_exists($this->path . '/files/' . $resource->getFileName())) { - \unlink($this->path . '/files/' . $resource->getFileName()); + if (\file_exists($this->path . '/files/' . $resource->getFileName())) { + \unlink($this->path . '/files/' . $resource->getFileName()); + } + break; } - break; - } } - if ($resource->getName() !== Resource::TYPE_FILEDATA) { - $this->data[$resource->getGroup()][$resource->getName()][] = $resource->asArray(); + if ($resource->getName() !== Resource::TYPE_FILEDATA && $resource->getName() !== Resource::TYPE_DEPLOYMENT) { + $this->data[$resource->getGroup()][$resource->getName()][$resource->getInternalId()] = $resource->asArray(); } $resource->setStatus(Resource::STATUS_SUCCESS); diff --git a/src/Transfer/Resource.php b/src/Transfer/Resource.php index 50cbe3f..36869ef 100644 --- a/src/Transfer/Resource.php +++ b/src/Transfer/Resource.php @@ -26,14 +26,32 @@ abstract class Resource const TYPE_FILE = 'File'; const TYPE_FILEDATA = 'FileData'; const TYPE_FUNCTION = 'Function'; + const TYPE_DEPLOYMENT = 'Deployment'; const TYPE_HASH = 'Hash'; const TYPE_INDEX = 'Index'; - const TYPE_PROJECT = 'Project'; const TYPE_USER = 'User'; const TYPE_ENVVAR = 'EnvVar'; const TYPE_TEAM = 'Team'; const TYPE_TEAM_MEMBERSHIP = 'TeamMembership'; + const ALL_RESOURCES = [ + self::TYPE_ATTRIBUTE, + self::TYPE_BUCKET, + self::TYPE_COLLECTION, + self::TYPE_DATABASE, + self::TYPE_DOCUMENT, + self::TYPE_FILE, + self::TYPE_FILEDATA, + self::TYPE_FUNCTION, + self::TYPE_DEPLOYMENT, + self::TYPE_HASH, + self::TYPE_INDEX, + self::TYPE_USER, + self::TYPE_ENVVAR, + self::TYPE_TEAM, + self::TYPE_TEAM_MEMBERSHIP + ]; + /** * ID of the resource */ diff --git a/src/Transfer/ResourceCache.php b/src/Transfer/ResourceCache.php index e38eb71..a91c05a 100644 --- a/src/Transfer/ResourceCache.php +++ b/src/Transfer/ResourceCache.php @@ -18,9 +18,14 @@ public function __construct() public function add($resource) { - $resourceUUID = uniqid(); - $resource->setInternalId($resourceUUID); // Assign each resource a unique ID - $this->resourceCache[$resource->getName()][$resourceUUID] = $resource; + if (!$resource->getInternalId()) { + $resourceId = uniqid(); + if (isset($this->resourceCache[$resource->getName()][$resourceId])) { + $resourceId = uniqid(); + } + $resource->setInternalId(uniqid()); + } + $this->resourceCache[$resource->getName()][$resource->getInternalId()] = $resource; } public function addAll(array $resources) diff --git a/src/Transfer/Resources/Auth/Hash.php b/src/Transfer/Resources/Auth/Hash.php index 60de7a5..b2934a1 100644 --- a/src/Transfer/Resources/Auth/Hash.php +++ b/src/Transfer/Resources/Auth/Hash.php @@ -18,6 +18,7 @@ class Hash extends Resource public const SHA256 = 'SHA256'; public const PHPASS = 'PHPass'; public const SCRYPT = 'Scrypt'; + public const PLAINTEXT = 'PlainText'; private string $hash; private string $salt = ''; diff --git a/src/Transfer/Resources/Functions/Deployment.php b/src/Transfer/Resources/Functions/Deployment.php new file mode 100644 index 0000000..89000bd --- /dev/null +++ b/src/Transfer/Resources/Functions/Deployment.php @@ -0,0 +1,142 @@ +id = $id; + $this->func = $func; + $this->size = $size; + $this->entrypoint = $entrypoint; + $this->start = $start; + $this->end = $end; + $this->data = $data; + $this->activated = $activated; + } + + static function getName(): string + { + return Resource::TYPE_DEPLOYMENT; + } + + public function getGroup(): string + { + return Transfer::GROUP_FUNCTIONS; + } + + public function getId(): string + { + return $this->id; + } + + public function setId(string $id): self + { + $this->id = $id; + return $this; + } + + public function getFunction(): Func + { + return $this->func; + } + + public function setFunction(Func $func): self + { + $this->func = $func; + return $this; + } + + public function setSize(int $size): self + { + $this->size = $size; + return $this; + } + + public function getSize(): int + { + return $this->size; + } + + public function setEntrypoint(string $entrypoint): self + { + $this->entrypoint = $entrypoint; + return $this; + } + + public function getEntrypoint(): string + { + return $this->entrypoint; + } + + public function setStart(int $start): self + { + $this->start = $start; + return $this; + } + + public function getStart(): int + { + return $this->start; + } + + public function setEnd(int $end): self + { + $this->end = $end; + return $this; + } + + public function getEnd(): int + { + return $this->end; + } + + public function setData(string $data): self + { + $this->data = $data; + return $this; + } + + public function getData(): string + { + return $this->data; + } + + public function setActivated(bool $activated): self + { + $this->activated = $activated; + return $this; + } + + public function getActivated(): bool + { + return $this->activated; + } + + public function asArray(): array + { + return [ + 'id' => $this->id, + 'func' => $this->func->asArray(), + 'size' => $this->size, + 'entrypoint' => $this->entrypoint, + 'start' => $this->start, + 'end' => $this->end, + 'activated' => $this->activated, + ]; + } +} diff --git a/src/Transfer/Resources/Project.php b/src/Transfer/Resources/Project.php deleted file mode 100644 index 71e4114..0000000 --- a/src/Transfer/Resources/Project.php +++ /dev/null @@ -1,47 +0,0 @@ -name = $name; - $this->id = $id; - } - - static function getName(): string - { - return Resource::TYPE_PROJECT; - } - - public function getGroup(): string - { - return Transfer::GROUP_SETTINGS; - } - - public function getId(): string - { - return $this->id; - } - - public function setId(string $id): self - { - $this->id = $id; - return $this; - } - - public function asArray(): array - { - return [ - 'name' => $this->name, - 'id' => $this->id, - ]; - } -} diff --git a/src/Transfer/Resources/Storage/FileData.php b/src/Transfer/Resources/Storage/FileData.php index 57b3652..3d81523 100644 --- a/src/Transfer/Resources/Storage/FileData.php +++ b/src/Transfer/Resources/Storage/FileData.php @@ -53,7 +53,6 @@ public function getFile(): File public function asArray(): array { return [ - 'data' => $this->data, 'start' => $this->start, 'end' => $this->end, 'file' => $this->file->asArray(), diff --git a/src/Transfer/Source.php b/src/Transfer/Source.php index acd723e..ac4d22b 100644 --- a/src/Transfer/Source.php +++ b/src/Transfer/Source.php @@ -19,9 +19,19 @@ public function callback(array $resources): void */ public function run(array $resources, callable $callback): void { - $this->transferCallback = function (array $resources) use ($callback) { - $this->resourceCache->addAll($resources); - $callback($resources); + $this->transferCallback = function (array $returnedResources) use ($callback, $resources) { + $prunedResurces = []; + foreach ($returnedResources as $resource) { + /** @var Resource $resource */ + if (!in_array($resource->getName(), $resources)) { + $resource->setStatus(Resource::STATUS_SKIPPED); + } else { + $prunedResurces[] = $resource; + } + } + + $this->resourceCache->addAll($returnedResources); + $callback($prunedResurces); }; $this->exportResources($resources, 100); diff --git a/src/Transfer/Sources/Appwrite.php b/src/Transfer/Sources/Appwrite.php index 74edbb2..b92c8d6 100644 --- a/src/Transfer/Sources/Appwrite.php +++ b/src/Transfer/Sources/Appwrite.php @@ -36,6 +36,7 @@ use Utopia\Transfer\Resources\Functions\Func; use Utopia\Transfer\Resources\Auth\Team; use Utopia\Transfer\Resources\Auth\TeamMembership; +use Utopia\Transfer\Resources\Functions\Deployment; class Appwrite extends Source { @@ -95,175 +96,158 @@ static function getName(): string public function getSupportedResources(): array { return [ - Transfer::GROUP_AUTH, - Transfer::GROUP_DATABASES, - Transfer::GROUP_STORAGE, - Transfer::GROUP_FUNCTIONS + // Auth + Resource::TYPE_USER, + Resource::TYPE_TEAM, + Resource::TYPE_TEAM_MEMBERSHIP, + + // Database + Resource::TYPE_DATABASE, + Resource::TYPE_COLLECTION, + Resource::TYPE_ATTRIBUTE, + Resource::TYPE_INDEX, + Resource::TYPE_DOCUMENT, + + // Storage + Resource::TYPE_BUCKET, + Resource::TYPE_FILE, + Resource::TYPE_FILEDATA, + + // Functions + Resource::TYPE_FUNCTION, + Resource::TYPE_DEPLOYMENT, + Resource::TYPE_ENVVAR, + + // Settings ]; } - public function check(array $resources = []): array + public function report(array $resources = []): array { - $report = [ - Transfer::GROUP_AUTH => [], - Transfer::GROUP_DATABASES => [], - Transfer::GROUP_STORAGE => [], - Transfer::GROUP_FUNCTIONS => [] - ]; + $report = []; + $currentPermission = ''; if (empty($resources)) { $resources = $this->getSupportedResources(); } - // Most of these API calls are purposely wrong. Appwrite will throw a 403 before a 400. - // We want to make sure the API key has the correct permissions. - - foreach ($resources as $resource) { - switch ($resource) { - case Transfer::GROUP_DATABASES: - $databases = new Databases($this->client); - try { - $databases->list(); - } catch (\Throwable $e) { - if ($e->getCode() == 401) { - $report[Transfer::GROUP_DATABASES][] = - "API Key is missing scope: databases.read"; - } - } + $usersClient = new Users($this->client); + $teamsClient = new Teams($this->client); + $databaseClient = new Databases($this->client); + $storageClient = new Storage($this->client); + $functionsClient = new Functions($this->client); - try { - $databases->create("", ""); - } catch (\Throwable $e) { - if ($e->getCode() == 401) { - $report[Transfer::GROUP_DATABASES][] = - "API Key is missing scope: databases.write"; - } - } + // Auth + try { + $currentPermission = 'users.read'; + if (in_array(Resource::TYPE_USER, $resources)) + $report[Resource::TYPE_USER] = $usersClient->list()['total']; + + $currentPermission = 'teams.read'; + if (in_array(Resource::TYPE_TEAM, $resources)) + $report[Resource::TYPE_TEAM] = $teamsClient->list()['total']; + + if (in_array(Resource::TYPE_TEAM_MEMBERSHIP, $resources)) { + $report[Resource::TYPE_TEAM_MEMBERSHIP] = 0; + $teams = $teamsClient->list()['teams']; + foreach ($teams as $team) { + $report[Resource::TYPE_TEAM_MEMBERSHIP] += $teamsClient->listMemberships($team['$id'])['total']; + } + } - try { - $databases->listCollections("", [], ""); - } catch (\Throwable $e) { - if ($e->getCode() == 401) { - $report[Transfer::GROUP_DATABASES][] = - "API Key is missing scope: collections.write"; - } - } + // Databases + $currentPermission = 'databases.read'; + if (in_array(Resource::TYPE_DATABASE, $resources)) + $report[Resource::TYPE_DATABASE] = $databaseClient->list()['total']; + + $currentPermission = 'collections.read'; + if (in_array(Resource::TYPE_COLLECTION, $resources)) { + $report[Resource::TYPE_COLLECTION] = 0; + $databases = $databaseClient->list()['databases']; + foreach ($databases as $database) { + $report[Resource::TYPE_COLLECTION] += $databaseClient->listCollections($database['$id'])['total']; + } + } - try { - $databases->createCollection("", "", "", []); - } catch (\Throwable $e) { - if ($e->getCode() == 401) { - $report[Transfer::GROUP_DATABASES][] = - "API Key is missing scope: collections.write"; - } + $currentPermission = 'documents.read'; + if (in_array(Resource::TYPE_DOCUMENT, $resources)) { + $report[Resource::TYPE_DOCUMENT] = 0; + $databases = $databaseClient->list()['databases']; + foreach ($databases as $database) { + $collections = $databaseClient->listCollections($database['$id'])['collections']; + foreach ($collections as $collection) { + $report[Resource::TYPE_DOCUMENT] += $databaseClient->listDocuments($database['$id'], $collection['$id'])['total']; } + } + } - try { - $databases->listDocuments("", "", []); - } catch (\Throwable $e) { - if ($e->getCode() == 401) { - $report[Transfer::GROUP_DATABASES][] = - "API Key is missing scope: documents.write"; - } + $currentPermission = 'attributes.read'; + if (in_array(Resource::TYPE_ATTRIBUTE, $resources)) { + $report[Resource::TYPE_ATTRIBUTE] = 0; + $databases = $databaseClient->list()['databases']; + foreach ($databases as $database) { + $collections = $databaseClient->listCollections($database['$id'])['collections']; + foreach ($collections as $collection) { + $report[Resource::TYPE_ATTRIBUTE] += $databaseClient->listAttributes($database['$id'], $collection['$id'])['total']; } + } + } - try { - $databases->createDocument("", "", "", [], []); - } catch (\Throwable $e) { - if ($e->getCode() == 401) { - $report["Documents"][] = - "API Key is missing scope: documents.write"; - } + $currentPermission = 'indexes.read'; + if (in_array(Resource::TYPE_INDEX, $resources)) { + $report[Resource::TYPE_INDEX] = 0; + $databases = $databaseClient->list()['databases']; + foreach ($databases as $database) { + $collections = $databaseClient->listCollections($database['$id'])['collections']; + foreach ($collections as $collection) { + $report[Resource::TYPE_INDEX] += $databaseClient->listIndexes($database['$id'], $collection['$id'])['total']; } + } + } - try { - $databases->listIndexes("", ""); - } catch (\Throwable $e) { - if ($e->getCode() == 401) { - $report[Transfer::GROUP_DATABASES][] = - "API Key is missing scope: indexes.read"; - } - } + // Storage + $currentPermission = 'buckets.read'; + if (in_array(Resource::TYPE_BUCKET, $resources)) + $report[Resource::TYPE_BUCKET] = $storageClient->listBuckets()['total']; + + $currentPermission = 'files.read'; + if (in_array(Resource::TYPE_FILE, $resources)) { + $report[Resource::TYPE_FILE] = 0; + $buckets = $storageClient->listBuckets()['buckets']; + foreach ($buckets as $bucket) { + $report[Resource::TYPE_FILE] += $storageClient->listFiles($bucket['$id'])['total']; + } + } - try { - $databases->createIndex("", "", "", "", [], []); - } catch (\Throwable $e) { - if ($e->getCode() == 401) { - $report[Transfer::GROUP_DATABASES][] = - "API Key is missing scope: indexes.write"; - } - } + // Functions + $currentPermission = 'functions.read'; + if (in_array(Resource::TYPE_FUNCTION, $resources)) + $report[Resource::TYPE_FUNCTION] = $functionsClient->list()['total']; - try { - $databases->listAttributes("", ""); - } catch (\Throwable $e) { - if ($e->getCode() == 401) { - $report[Transfer::GROUP_DATABASES][] = - "API Key is missing scope: attributes.read"; - } - } + if (in_array(Resource::TYPE_DEPLOYMENT, $resources)) { + $report[Resource::TYPE_DEPLOYMENT] = 0; + $functions = $functionsClient->list()['functions']; + foreach ($functions as $function) { + $report[Resource::TYPE_DEPLOYMENT] += $functionsClient->listDeployments($function['$id'])['total']; + } + } - try { - $databases->createStringAttribute( - "", - "", - "", - 0, - false, - false - ); - } catch (\Throwable $e) { - if ($e->getCode() == 401) { - $report[Transfer::GROUP_DATABASES][] = - "API Key is missing scope: attributes.write"; - } - } - break; - case Transfer::GROUP_AUTH: - $auth = new Users($this->client); - try { - $auth->list(); - } catch (\Throwable $e) { - if ($e->getCode() == 401) { - $report[Transfer::GROUP_AUTH][] = - "API Key is missing scope: users.read"; - } - } - break; - case Transfer::GROUP_STORAGE: - $storage = new Storage($this->client); - try { - $storage->listFiles(''); - } catch (\Throwable $e) { - if ($e->getCode() == 401) { - $report[Transfer::GROUP_STORAGE][] = - "API Key is missing scope: files.read"; - } - } - case Transfer::GROUP_FUNCTIONS: - $functions = new Functions($this->client); - try { - $functions->list(); - } catch (\Throwable $e) { - if ($e->getCode() == 401) { - $report[Transfer::GROUP_FUNCTIONS][] = - "API Key is missing scope: functions.read"; - } - } + if (in_array(Resource::TYPE_ENVVAR, $resources)) { + $report[Resource::TYPE_ENVVAR] = 0; + $functions = $functionsClient->list()['functions']; + foreach ($functions as $function) { + $report[Resource::TYPE_ENVVAR] += $functionsClient->listVariables($function['$id'])['total']; + } + } - try { - $functions->listExecutions(''); - } catch (\Throwable $e) { - if ($e->getCode() == 401) { - $report[Transfer::GROUP_FUNCTIONS][] = - "API Key is missing scope: executions.read"; - } - } - break; + return $report; + } catch (\Exception $e) { + if ($e->getCode() === 403) { + throw new \Exception("Missing Permission: {$currentPermission}."); + } else { + throw new \Exception($e->getMessage()); } } - - return $report; } /** @@ -875,13 +859,13 @@ function handleDataTransfer(File $file) $start = 0; $end = Transfer::STORAGE_MAX_CHUNK_SIZE - 1; - if ($end > $file->getSize()) { - $end = $file->getSize() - 1; - } - // Get the file size $fileSize = $file->getSize(); + if ($end > $fileSize) { + $end = $fileSize - 1; + } + // Loop until the entire file is downloaded while ($start < $fileSize) { $chunkData = $this->call( @@ -910,9 +894,11 @@ function handleDataTransfer(File $file) public function exportFunctionsGroup(int $batchSize, array $resources) { - if (in_array(Resource::TYPE_FUNCTION, $resources)) { + if (in_array(Resource::TYPE_FUNCTION, $resources)) $this->exportFunctions($batchSize); - } + + if (in_array(Resource::TYPE_DEPLOYMENT, $resources)) + $this->exportDeployments($batchSize); } public function exportFunctions(int $batchSize) @@ -953,4 +939,89 @@ public function exportFunctions(int $batchSize) $this->callback($convertedResources); } + + public function exportDeployments(int $batchSize) + { + $functionsClient = new Functions($this->client); + + $functions = $this->resourceCache->get(Func::getName()); + + foreach ($functions as $func) { + /** @var Func $func */ + + $lastDocument = null; + while (true) { + $queries = [Query::limit($batchSize)]; + + if ($lastDocument) { + $queries[] = Query::cursorAfter($lastDocument); + } + + $response = $functionsClient->listDeployments( + $func->getId(), + $queries + ); + + foreach ($response["deployments"] as $deployment) { + $this->handleDeploymentData($func, $deployment); + + $lastDocument = $deployment['$id']; + } + + if (count($response["deployments"]) < $batchSize) { + break; + } + } + } + } + + public function handleDeploymentData(Func $func, array $deployment) + { + // Set the chunk size (5MB) + $start = 0; + $end = Transfer::STORAGE_MAX_CHUNK_SIZE - 1; + + // Get the file size + $fileSize = $deployment['size']; + + if ($end > $fileSize) { + $end = $fileSize - 1; + } + + $deployment = new Deployment( + $deployment['$id'], + $func, + $fileSize, + $deployment['entrypoint'], + $start, + $end, + '', + $deployment['activate'] + ); + + $deployment->setInternalId($deployment->getId()); + + // Loop until the entire file is downloaded + while ($start < $fileSize) { + $chunkData = $this->call( + 'GET', + "/functions/{$func->getId()}/deployments/{$deployment->getInternalId()}/download", + ['range' => "bytes=$start-$end"] + ); + + // Send the chunk to the callback function + $deployment->setData($chunkData); + $deployment->setStart($start); + $deployment->setEnd($end); + $this->callback([$deployment]); + + // Update the range + $start += Transfer::STORAGE_MAX_CHUNK_SIZE; + $end += Transfer::STORAGE_MAX_CHUNK_SIZE; + + if ($end > $fileSize) { + $end = $fileSize - 1; + } + } + } } diff --git a/src/Transfer/Sources/Firebase.php b/src/Transfer/Sources/Firebase.php index e6dc4c9..88347a8 100644 --- a/src/Transfer/Sources/Firebase.php +++ b/src/Transfer/Sources/Firebase.php @@ -15,6 +15,7 @@ use Utopia\Transfer\Resources\Database\Attributes\StringAttribute; use Utopia\Transfer\Resources\Database\Collection; use Utopia\Transfer\Resources\Database\Database; +use Utopia\Transfer\Resources\Database\Document; class Firebase extends Source { @@ -95,15 +96,26 @@ public function call(string $method, string $path = '', array $headers = array() return parent::call($method, $path, $headers, $params); } + /** + * Get Supported Resources + * + * @return array + */ public function getSupportedResources(): array { return [ - Transfer::GROUP_AUTH, - Transfer::GROUP_DATABASES + // Auth + Resource::TYPE_USER, + + // Database + Resource::TYPE_DATABASE, + Resource::TYPE_COLLECTION, + Resource::TYPE_ATTRIBUTE, + Resource::TYPE_DOCUMENT, ]; } - public function check(array $resources = []): array + public function report(array $resources = []): array { throw new \Exception('Not implemented'); } @@ -208,8 +220,11 @@ public function exportDatabasesGroup(int $batchSize, array $resources) function exportDocuments(int $batchSize) { - // Not Implemented - return; + $collections = $this->resourceCache->get(Collection::getName()); + + foreach ($collections as $collection) { + + } } function convertAttribute(Collection $collection, string $key, array $field): Attribute @@ -237,7 +252,6 @@ function convertAttribute(Collection $collection, string $key, array $field): At } elseif (array_key_exists("arrayValue", $field)) { return $this->calculateArrayType($collection, $key, $field["arrayValue"]); } else { - var_dump($field); throw new \Exception('Unknown field type'); } } @@ -283,7 +297,7 @@ function handleCollection(Collection $collection, int $batchSize) 'pageToken' => $nextPageToken ]); - if (!empty($result)) { + if (empty($result)) { break; } @@ -301,10 +315,11 @@ function handleCollection(Collection $collection, int $batchSize) } $this->traceDBResource($collection->getDatabase(), $document['name'], $batchSize); + $documents[] = $this->convertDocument($collection, $document); } - // Transfer Documents - // $callback($documents); + // Transfer Documents + $this->callback($documents); if (count($result['documents']) < $batchSize) { break; @@ -314,6 +329,44 @@ function handleCollection(Collection $collection, int $batchSize) } } + function calculateValue(array $field) { + if (array_key_exists("booleanValue", $field)) { + return $field['booleanValue']; + } elseif (array_key_exists("bytesValue", $field)) { + return $field['bytesValue']; + } elseif (array_key_exists("doubleValue", $field)) { + return $field['doubleValue']; + } elseif (array_key_exists("integerValue", $field)) { + return $field['integerValue']; + } elseif (array_key_exists("mapValue", $field)) { + return $field['mapValue']; + } elseif (array_key_exists("nullValue", $field)) { + return $field['nullValue']; + } elseif (array_key_exists("referenceValue", $field)) { + return $field['referenceValue']; //TODO: This should be a reference attribute + } elseif (array_key_exists("stringValue", $field)) { + return $field['stringValue']; + } elseif (array_key_exists("timestampValue", $field)) { + return $field['timestampValue']; + } elseif (array_key_exists("geoPointValue", $field)) { + return $field['geoPointValue']; + } elseif (array_key_exists("arrayValue", $field)) { + //TODO: + } else { + throw new \Exception('Unknown field type'); + } + } + + function convertDocument(Collection $collection, array $document): Document + { + $data = []; + foreach ($document['fields'] as $key => $field) { + $data[$key] = $this->calculateValue($field); + } + + return new Document($document['name'], $collection->getDatabase(), $collection, $data, []); + } + function traceDBResource(Database $database, string $resource, int $batchSize) { $baseURL = 'https://firestore.googleapis.com/v1/' . $resource; diff --git a/src/Transfer/Sources/NHost.php b/src/Transfer/Sources/NHost.php index ebba389..2876311 100644 --- a/src/Transfer/Sources/NHost.php +++ b/src/Transfer/Sources/NHost.php @@ -17,6 +17,9 @@ use Utopia\Transfer\Resources\Database\Collection; use Utopia\Transfer\Resources\Database\Document; use Utopia\Transfer\Resources\Database\Index; +use Utopia\Transfer\Resources\Storage\Bucket; +use Utopia\Transfer\Resources\Storage\File; +use Utopia\Transfer\Resources\Storage\FileData; class NHost extends Source { @@ -25,52 +28,26 @@ class NHost extends Source */ public $pdo; - /** - * @var string - */ - public string $host; - - /** - * @var string - */ + public string $subdomain; + public string $region; public string $databaseName; - - /** - * @var string - */ public string $username; - - /** - * @var string - */ public string $password; - - /** - * @var string - */ public string $port; + public string $adminSecret; - /** - * Constructor - * - * @param string $host - * @param string $databaseName - * @param string $username - * @param string $password - * @param string $port - * - * @return self - */ - public function __construct(string $host, string $databaseName, string $username, string $password, string $port = '5432') + public function __construct(string $subdomain, string $region, string $adminSecret, string $databaseName, string $username, string $password, string $port = '5432') { - $this->host = $host; + $this->subdomain = $subdomain; + $this->region = $region; + $this->adminSecret = $adminSecret; $this->databaseName = $databaseName; $this->username = $username; $this->password = $password; $this->port = $port; try { - $this->pdo = new \PDO("pgsql:host=" . $this->host . ";port=" . $this->port . ";dbname=" . $this->databaseName, $this->username, $this->password); + $this->pdo = new \PDO("pgsql:host=" . $this->subdomain . '.db.' . $this->region . '.nhost.run' . ";port=" . $this->port . ";dbname=" . $this->databaseName, $this->username, $this->password); } catch (\PDOException $e) { throw new \Exception('Failed to connect to database: ' . $e->getMessage()); } @@ -81,61 +58,130 @@ static function getName(): string return 'NHost'; } + /** + * Get Supported Resources + * + * @return array + */ public function getSupportedResources(): array { return [ - Transfer::GROUP_AUTH, - Transfer::GROUP_DATABASES + // Auth + Resource::TYPE_USER, + + // Database + Resource::TYPE_DATABASE, + Resource::TYPE_COLLECTION, + Resource::TYPE_ATTRIBUTE, + Resource::TYPE_INDEX, + Resource::TYPE_DOCUMENT, + + // Storage + Resource::TYPE_BUCKET, + Resource::TYPE_FILE, + Resource::TYPE_FILEDATA, ]; } - public function check(array $resources = []): array + public function report(array $resources = []): array { - $report = [ - Transfer::GROUP_AUTH => [], - Transfer::GROUP_DATABASES => [], - Transfer::GROUP_STORAGE => [], - Transfer::GROUP_FUNCTIONS => [] - ]; + $report = []; if (empty($resources)) { $resources = $this->getSupportedResources(); } try { - $this->pdo = new \PDO("pgsql:host=" . $this->host . ";port=" . $this->port . ";dbname=" . $this->databaseName, $this->username, $this->password); + $this->pdo = new \PDO("pgsql:host=" . $this->subdomain . '.db.' . $this->region . '.nhost.run' . ";port=" . $this->port . ";dbname=" . $this->databaseName, $this->username, $this->password); } catch (\PDOException $e) { - $report[Transfer::GROUP_DATABASES][] = 'Failed to connect to database. PDO Code: ' . $e->getCode() . ' Error: ' . $e->getMessage(); + throw new \Exception('Failed to connect to database. PDO Code: ' . $e->getCode() . ' Error: ' . $e->getMessage()); } if (!empty($this->pdo->errorCode())) { - $report[Transfer::GROUP_DATABASES][] = 'Failed to connect to database. PDO Code: ' . $this->pdo->errorCode() . (empty($this->pdo->errorInfo()[2]) ? '' : ' Error: ' . $this->pdo->errorInfo()[2]); + throw new \Exception('Failed to connect to database. PDO Code: ' . $this->pdo->errorCode() . (empty($this->pdo->errorInfo()[2]) ? '' : ' Error: ' . $this->pdo->errorInfo()[2])); } - foreach ($resources as $resource) { - switch ($resource) { - case Transfer::GROUP_AUTH: - $statement = $this->pdo->prepare('SELECT COUNT(*) FROM auth.users'); - $statement->execute(); + // Auth + if (in_array(Resource::TYPE_USER, $resources)) { + $statement = $this->pdo->prepare('SELECT COUNT(*) FROM auth.users'); + $statement->execute(); - if ($statement->errorCode() !== '00000') { - $report['Users'][] = 'Failed to access users table. Error: ' . $statement->errorInfo()[2]; - } + if ($statement->errorCode() !== '00000') { + throw new \Exception('Failed to access users table. Error: ' . $statement->errorInfo()[2]); + } - break; - case Transfer::GROUP_DATABASES: - $statement = $this->pdo->prepare('SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = \'public\''); - $statement->execute(); + $report[Resource::TYPE_USER] = $statement->fetchColumn(); + } - if ($statement->errorCode() !== '00000') { - $report[Transfer::GROUP_DATABASES][] = 'Failed to access tables table. Error: ' . $statement->errorInfo()[2]; - } + // Databases + if (in_array(Resource::TYPE_DATABASE, $resources)) + $report[Resource::TYPE_DATABASE] = 1; - break; - case Transfer::GROUP_STORAGE: - //TODO: Attempt to access API - break; + if (in_array(Resource::TYPE_COLLECTION, $resources)) { + $statement = $this->pdo->prepare('SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = \'public\''); + $statement->execute(); + + if ($statement->errorCode() !== '00000') { + throw new \Exception('Failed to access tables table. Error: ' . $statement->errorInfo()[2]); } + + $report[Resource::TYPE_COLLECTION] = $statement->fetchColumn(); + } + + if (in_array(Resource::TYPE_ATTRIBUTE, $resources)) { + $statement = $this->pdo->prepare('SELECT COUNT(*) FROM information_schema.columns WHERE table_schema = \'public\''); + $statement->execute(); + + if ($statement->errorCode() !== '00000') { + throw new \Exception('Failed to access columns table. Error: ' . $statement->errorInfo()[2]); + } + + $report[Resource::TYPE_ATTRIBUTE] = $statement->fetchColumn(); + } + + if (in_array(Resource::TYPE_INDEX, $resources)) { + $statement = $this->pdo->prepare('SELECT COUNT(*) FROM pg_indexes WHERE schemaname = \'public\''); + $statement->execute(); + + if ($statement->errorCode() !== '00000') { + throw new \Exception('Failed to access indexes table. Error: ' . $statement->errorInfo()[2]); + } + + $report[Resource::TYPE_INDEX] = $statement->fetchColumn(); + } + + if (in_array(Resource::TYPE_DOCUMENT, $resources)) { + $statement = $this->pdo->prepare('SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = \'public\''); + $statement->execute(); + + if ($statement->errorCode() !== '00000') { + throw new \Exception('Failed to access tables table. Error: ' . $statement->errorInfo()[2]); + } + + $report[Resource::TYPE_DOCUMENT] = $statement->fetchColumn(); + } + + // Storage + if (in_array(Resource::TYPE_BUCKET, $resources)) { + $statement = $this->pdo->prepare('SELECT COUNT(*) FROM storage.buckets'); + $statement->execute(); + + if ($statement->errorCode() !== '00000') { + throw new \Exception('Failed to access buckets table. Error: ' . $statement->errorInfo()[2]); + } + + $report[Resource::TYPE_BUCKET] = $statement->fetchColumn(); + } + + if (in_array(Resource::TYPE_FILE, $resources)) { + $statement = $this->pdo->prepare('SELECT COUNT(*) FROM storage.files'); + $statement->execute(); + + if ($statement->errorCode() !== '00000') { + throw new \Exception('Failed to access files table. Error: ' . $statement->errorInfo()[2]); + } + + $report[Resource::TYPE_FILE] = $statement->fetchColumn(); } return $report; @@ -223,7 +269,7 @@ function exportCollections(int $batchSize) foreach ($databases as $database) { $statement = $this->pdo->prepare('SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = :database'); - $statement->execute([':database' => $database->getName()]); //TODO: Maybe add "getOriginalName"? + $statement->execute([':database' => $database->getName()]); $total = $statement->fetchColumn(); $offset = 0; @@ -481,7 +527,134 @@ function calculateUserTypes(array $user): array public function exportStorageGroup(int $batchSize, array $resources) { - throw new \Exception('Not Implemented'); + if (in_array(Resource::TYPE_BUCKET, $resources)) + $this->exportBuckets($batchSize); + + if (in_array(Resource::TYPE_FILE, $resources)) + $this->exportFiles($batchSize); + } + + public function exportBuckets(int $batchSize) + { + $total = $this->pdo->query('SELECT COUNT(*) FROM storage.buckets')->fetchColumn(); + + $offset = 0; + + while ($offset < $total) { + $statement = $this->pdo->prepare('SELECT * FROM storage.buckets order by created_at LIMIT :limit OFFSET :offset'); + $statement->bindValue(':limit', $batchSize, \PDO::PARAM_INT); + $statement->bindValue(':offset', $offset, \PDO::PARAM_INT); + $statement->execute(); + + $buckets = $statement->fetchAll(\PDO::FETCH_ASSOC); + + $offset += $batchSize; + + $transferBuckets = []; + + foreach ($buckets as $bucket) { + $transferBuckets[] = new Bucket( + $bucket['id'], + [], + false, + $bucket['id'], + true, + $bucket['max_upload_file_size'], + [], + '', + false, + false + ); + } + + $this->callback($transferBuckets); + } + } + + public function exportFiles(int $batchSize) + { + $buckets = $this->resourceCache->get(Bucket::getName()); + + foreach ($buckets as $bucket) { + $totalStatement = $this->pdo->prepare('SELECT COUNT(*) FROM storage.files WHERE bucket_id=:bucketId'); + $totalStatement->execute([':bucketId' => $bucket->getId()]); + $total = $totalStatement->fetchColumn(); + + $offset = 0; + while ($offset < $total) { + $statement = $this->pdo->prepare('SELECT * FROM storage.files WHERE bucket_id=:bucketId order by created_at LIMIT :limit OFFSET :offset'); + $statement->bindValue(':limit', $batchSize, \PDO::PARAM_INT); + $statement->bindValue(':offset', $offset, \PDO::PARAM_INT); + $statement->bindValue(':bucketId', $bucket->getId(), \PDO::PARAM_STR); + $statement->execute(); + + $files = $statement->fetchAll(\PDO::FETCH_ASSOC); + + $offset += $batchSize; + + foreach ($files as $file) { + $this->handleDataTransfer(new File( + $file['id'], + $bucket, + $file['name'], + '', + $file['mime_type'], + [], + $file['size'], + )); + } + } + } + } + + public function handleDataTransfer(File $file) + { + $url = "https://{$this->subdomain}.storage.{$this->region}.nhost.run"; + $start = 0; + $end = Transfer::STORAGE_MAX_CHUNK_SIZE - 1; + + $fileSize = $file->getSize(); + $response = $this->call("GET", $url."/v1/files/{$file->getId()}/presignedurl", [ + 'X-Hasura-Admin-Secret' => $this->adminSecret, + ]); + + $fileUrl = $response['url']; + $refreshTime = \time() + $response['expiration']; + + if ($end > $fileSize) { + $end = $fileSize - 1; + } + + while ($start < $fileSize) { + if (\time() > $refreshTime) { + $response = $this->call("GET", "/v1/files/{$file->getId()}/presignedurl", [ + 'X-Hasura-Admin-Secret' => $this->adminSecret, + ]); + + $fileUrl = $response['url']; + $refreshTime = \time() + $response['expiration']; + } + + $chunkData = $this->call( + 'GET', + $fileUrl, + ['range' => "bytes=$start-$end"] + ); + + $this->callback([new FileData( + $chunkData, + $start, + $end, + $file + )]); + + $start += Transfer::STORAGE_MAX_CHUNK_SIZE; + $end += Transfer::STORAGE_MAX_CHUNK_SIZE; + + if ($end > $fileSize) { + $end = $fileSize - 1; + } + }; } public function exportFunctionsGroup(int $batchSize, array $resources) diff --git a/src/Transfer/Sources/Supabase.php b/src/Transfer/Sources/Supabase.php index f0d701f..eb6c124 100644 --- a/src/Transfer/Sources/Supabase.php +++ b/src/Transfer/Sources/Supabase.php @@ -20,6 +20,7 @@ static function getName(): string } protected string $key; + protected string $host; /** * Constructor @@ -53,6 +54,110 @@ public function __construct(string $endpoint, string $key, string $host, string } } + public function report(array $resources = []): array + { + $report = []; + + if (empty($resources)) { + $resources = $this->getSupportedResources(); + } + + try { + $this->pdo = new \PDO("pgsql:host=" . $this->host . ";port=" . $this->port . ";dbname=" . $this->databaseName, $this->username, $this->password); + } catch (\PDOException $e) { + throw new \Exception('Failed to connect to database. PDO Code: ' . $e->getCode() . ' Error: ' . $e->getMessage()); + } + + if (!empty($this->pdo->errorCode())) { + throw new \Exception('Failed to connect to database. PDO Code: ' . $this->pdo->errorCode() . (empty($this->pdo->errorInfo()[2]) ? '' : ' Error: ' . $this->pdo->errorInfo()[2])); + } + + // Auth + if (in_array(Resource::TYPE_USER, $resources)) { + $statement = $this->pdo->prepare('SELECT COUNT(*) FROM auth.users'); + $statement->execute(); + + if ($statement->errorCode() !== '00000') { + throw new \Exception('Failed to access users table. Error: ' . $statement->errorInfo()[2]); + } + + $report[Resource::TYPE_USER] = $statement->fetchColumn(); + } + + // Databases + if (in_array(Resource::TYPE_DATABASE, $resources)) + $report[Resource::TYPE_DATABASE] = 1; + + if (in_array(Resource::TYPE_COLLECTION, $resources)) { + $statement = $this->pdo->prepare('SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = \'public\''); + $statement->execute(); + + if ($statement->errorCode() !== '00000') { + throw new \Exception('Failed to access tables table. Error: ' . $statement->errorInfo()[2]); + } + + $report[Resource::TYPE_COLLECTION] = $statement->fetchColumn(); + } + + if (in_array(Resource::TYPE_ATTRIBUTE, $resources)) { + $statement = $this->pdo->prepare('SELECT COUNT(*) FROM information_schema.columns WHERE table_schema = \'public\''); + $statement->execute(); + + if ($statement->errorCode() !== '00000') { + throw new \Exception('Failed to access columns table. Error: ' . $statement->errorInfo()[2]); + } + + $report[Resource::TYPE_ATTRIBUTE] = $statement->fetchColumn(); + } + + if (in_array(Resource::TYPE_INDEX, $resources)) { + $statement = $this->pdo->prepare('SELECT COUNT(*) FROM pg_indexes WHERE schemaname = \'public\''); + $statement->execute(); + + if ($statement->errorCode() !== '00000') { + throw new \Exception('Failed to access indexes table. Error: ' . $statement->errorInfo()[2]); + } + + $report[Resource::TYPE_INDEX] = $statement->fetchColumn(); + } + + if (in_array(Resource::TYPE_DOCUMENT, $resources)) { + $statement = $this->pdo->prepare('SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = \'public\''); + $statement->execute(); + + if ($statement->errorCode() !== '00000') { + throw new \Exception('Failed to access tables table. Error: ' . $statement->errorInfo()[2]); + } + + $report[Resource::TYPE_DOCUMENT] = $statement->fetchColumn(); + } + + // Storage + if (in_array(Resource::TYPE_BUCKET, $resources)) { + $statement = $this->pdo->prepare('SELECT COUNT(*) FROM storage.buckets'); + $statement->execute(); + + if ($statement->errorCode() !== '00000') { + throw new \Exception('Failed to access buckets table. Error: ' . $statement->errorInfo()[2]); + } + + $report[Resource::TYPE_BUCKET] = $statement->fetchColumn(); + } + + if (in_array(Resource::TYPE_FILE, $resources)) { + $statement = $this->pdo->prepare('SELECT COUNT(*) FROM storage.objects'); + $statement->execute(); + + if ($statement->errorCode() !== '00000') { + throw new \Exception('Failed to access files table. Error: ' . $statement->errorInfo()[2]); + } + + $report[Resource::TYPE_FILE] = $statement->fetchColumn(); + } + + return $report; + } + public function exportAuthGroup(int $batchSize, array $resources) { if (in_array(Resource::TYPE_USER, $resources)) { @@ -170,7 +275,6 @@ function exportFiles(int $batchSize) * Need to figure out a solution to this. */ - // Transfer Files $buckets = $this->resourceCache->get(Bucket::getName()); foreach ($buckets as $bucket) { @@ -211,11 +315,9 @@ function exportFiles(int $batchSize) function handleDataTransfer(File $file) { - // Set the chunk size (5MB) $start = 0; $end = Transfer::STORAGE_MAX_CHUNK_SIZE - 1; - // Get the file size $fileSize = $file->getSize(); if ($end > $fileSize) { diff --git a/src/Transfer/Target.php b/src/Transfer/Target.php index 946d2c2..6aabee0 100644 --- a/src/Transfer/Target.php +++ b/src/Transfer/Target.php @@ -20,7 +20,7 @@ abstract class Target * * @var ResourceCache $resourceCache */ - protected $resourceCache; + public $resourceCache; /** * Endpoint @@ -64,19 +64,17 @@ public function registerTransferCache(ResourceCache &$cache): void abstract public function run(array $resources, callable $callback): void; /** - * Check Requirements - * Performs a suite of API Checks, Resource Checks, etc... to ensure the adapter is ready to be used. - * This is highly recommended to be called before any other method after initialization. - * - * If no resources are provided, the method should check all resources. - * Returns a object with all the keys of the resources provided and a true|string value if the resource is available or not. - * If the resource is not available, the value should be a string with the error message. - * - * @string[] $resources - * - * @return string[] + * Report Resources + * + * This function performs a count of all resources that are available for transfer. + * It also serves a secondary purpose of checking if the API is available for the given adapter. + * + * On Destinations, this function should just return nothing but still check if the API is available. + * If any issues are found then an exception should be thrown with an error message. + * + * @param array $resources */ - abstract public function check(array $resources = []): array; + abstract public function report(array $resources = []): array; /** * Call diff --git a/src/Transfer/Transfer.php b/src/Transfer/Transfer.php index 6385e98..2b8fc8a 100644 --- a/src/Transfer/Transfer.php +++ b/src/Transfer/Transfer.php @@ -17,9 +17,9 @@ class Transfer public const GROUP_AUTH_RESOURCES = [Resource::TYPE_USER, Resource::TYPE_TEAM, Resource::TYPE_TEAM_MEMBERSHIP, Resource::TYPE_HASH]; public const GROUP_STORAGE_RESOURCES = [Resource::TYPE_FILE, Resource::TYPE_BUCKET, Resource::TYPE_FILEDATA]; - public const GROUP_FUNCTIONS_RESOURCES = [Resource::TYPE_FUNCTION, Resource::TYPE_ENVVAR]; + public const GROUP_FUNCTIONS_RESOURCES = [Resource::TYPE_FUNCTION, Resource::TYPE_ENVVAR, Resource::TYPE_DEPLOYMENT]; public const GROUP_DATABASES_RESOURCES = [Resource::TYPE_DATABASE, Resource::TYPE_COLLECTION, Resource::TYPE_INDEX, Resource::TYPE_ATTRIBUTE, Resource::TYPE_DOCUMENT]; - public const GROUP_SETTINGS_RESOURCES = [Resource::TYPE_PROJECT]; + public const GROUP_SETTINGS_RESOURCES = []; public const STORAGE_MAX_CHUNK_SIZE = 1024 * 1024 * 5; // 5MB diff --git a/tests/e2e/Sources/Appwrite.php b/tests/e2e/Sources/Appwrite.php new file mode 100644 index 0000000..dfbf95a --- /dev/null +++ b/tests/e2e/Sources/Appwrite.php @@ -0,0 +1,24 @@ +source->report(); + + $this->assertIsArray($report); + $this->validateReport($report); + } +} \ No newline at end of file diff --git a/tests/e2e/Sources/SourceTest.php b/tests/e2e/Sources/SourceTest.php new file mode 100644 index 0000000..5dc9d95 --- /dev/null +++ b/tests/e2e/Sources/SourceTest.php @@ -0,0 +1,44 @@ +assertNotEmpty($this->source::getName()); + } + + public function testGetSupportedResources(): void + { + $this->assertNotEmpty($this->source->getSupportedResources()); + + foreach ($this->source->getSupportedResources() as $resource) { + $this->assertContains($resource, Resource::ALL_RESOURCES); + } + } + + public function testTransferCache(): void + { + $this->source->registerTransferCache($this->createMock(\Utopia\Transfer\ResourceCache::class)); + + $this->assertNotNull($this->source->resourceCache); + } + + public abstract function testReport(): void; + + public function validateReport(array $report) { + foreach ($report as $resource => $amount) { + $this->assertContains($resource, Resource::ALL_RESOURCES); + $this->assertIsInt($amount); + } + } + + public abstract function testExportResources(): void; +} \ No newline at end of file diff --git a/tests/e2e/adapters/MockDestination.php b/tests/e2e/adapters/MockDestination.php index 64f2224..4e98215 100644 --- a/tests/e2e/adapters/MockDestination.php +++ b/tests/e2e/adapters/MockDestination.php @@ -1,6 +1,7 @@ Date: Fri, 12 May 2023 12:31:18 +0900 Subject: [PATCH 38/70] Update progress tracking system --- playground.php | 17 +- src/Transfer/Destination.php | 2 +- src/Transfer/Destinations/Appwrite.php | 2 + src/Transfer/Destinations/Local.php | 2 + src/Transfer/Progress.php | 216 ------------------------- src/Transfer/ResourceCache.php | 18 --- src/Transfer/Transfer.php | 55 ++++++- tests/unit/ResourceCacheTest.php | 61 ------- 8 files changed, 61 insertions(+), 312 deletions(-) delete mode 100644 src/Transfer/Progress.php diff --git a/playground.php b/playground.php index 2c25f0f..80642f9 100644 --- a/playground.php +++ b/playground.php @@ -23,7 +23,7 @@ /** * Initialise All Source Adapters -*/ + */ $sourceAppwrite = new Appwrite( $_ENV['SOURCE_APPWRITE_TEST_PROJECT'], $_ENV['SOURCE_APPWRITE_TEST_ENDPOINT'], @@ -57,7 +57,7 @@ /** * Initialise All Destination Adapters -*/ + */ $destinationAppwrite = new AppwriteDestination( $_ENV['DESTINATION_APPWRITE_TEST_PROJECT'], $_ENV['DESTINATION_APPWRITE_TEST_ENDPOINT'], @@ -68,19 +68,18 @@ /** * Initialise Transfer Class -*/ + */ $transfer = new Transfer( - $sourceFirebase, + $sourceSupabase, $destinationLocal ); /** * Run Transfer -*/ + */ $transfer->run( - [ - Transfer::GROUP_DATABASES_RESOURCES, - ], function () { + [Transfer::GROUP_DATABASES_RESOURCES], + function (array $resources) use ($transfer) { } -); \ No newline at end of file +); diff --git a/src/Transfer/Destination.php b/src/Transfer/Destination.php index 58bafe3..245f7f3 100644 --- a/src/Transfer/Destination.php +++ b/src/Transfer/Destination.php @@ -51,7 +51,7 @@ public function run(array $resources, callable $callback): void * Import Resources * * @param array $resources - * @param callable $callback (Progress $progress) + * @param callable $callback (array $resources) * */ abstract public function importResources(array $resources, callable $callback): void; diff --git a/src/Transfer/Destinations/Appwrite.php b/src/Transfer/Destinations/Appwrite.php index 7eff3dc..7dd9fdf 100644 --- a/src/Transfer/Destinations/Appwrite.php +++ b/src/Transfer/Destinations/Appwrite.php @@ -239,6 +239,8 @@ function importResources(array $resources, callable $callback): void $this->resourceCache->update($responseResource); } + + $callback($resources); } public function importDatabaseResource(Resource $resource): Resource diff --git a/src/Transfer/Destinations/Local.php b/src/Transfer/Destinations/Local.php index a2af71c..45250e8 100644 --- a/src/Transfer/Destinations/Local.php +++ b/src/Transfer/Destinations/Local.php @@ -147,5 +147,7 @@ public function importResources(array $resources, callable $callback): void $this->resourceCache->update($resource); $this->syncFile(); } + + $callback($resources); } } diff --git a/src/Transfer/Progress.php b/src/Transfer/Progress.php deleted file mode 100644 index 79e4f4a..0000000 --- a/src/Transfer/Progress.php +++ /dev/null @@ -1,216 +0,0 @@ -resourceType = $resourceType; - $this->timestamp = $timestamp ?? \time(); - $this->total = $total; - $this->current = $current; - $this->failed = $failed; - $this->skipped = $skipped; - } - - /** - * Get Resource Type - * - * @return string - */ - public function getResourceType(): string - { - return $this->resourceType; - } - - /** - * Set Resource Type - * - * @param string $resourceType - * @return self - */ - - public function setResourceType(string $resourceType): self - { - $this->resourceType = $resourceType; - return $this; - } - - /** - * Get Timestamp - * - * @return int - */ - public function getTimestamp(): int - { - return $this->timestamp; - } - - /** - * Set Timestamp - * - * @param int $timestamp - * @return self - */ - public function setTimestamp(int $timestamp): self - { - $this->timestamp = $timestamp; - return $this; - } - - /** - * Get Total - * - * @return int - */ - public function getTotal(): int - { - return $this->total; - } - - /** - * Set Total - * - * @param int $total - * @return self - */ - public function setTotal(int $total): self - { - $this->total = $total; - return $this; - } - - /** - * Get Current - * - * @return int - */ - public function getCurrent(): int - { - return $this->current; - } - - /** - * Set Current - * - * @param int $current - * @return self - */ - public function setCurrent(int $current): self - { - $this->current = $current; - return $this; - } - - /** - * Get Failed - * - * @return int - */ - public function getFailed(): int - { - return $this->failed; - } - - /** - * Set Failed - * - * @param int $failed - * @return self - */ - public function setFailed(int $failed): self - { - $this->failed = $failed; - return $this; - } - - /** - * Get Skipped - * - * @return int - */ - public function getSkipped(): int - { - return $this->skipped; - } - - /** - * Set Skipped - * - * @param int $skipped - * @return self - */ - public function setSkipped(int $skipped): self - { - $this->skipped = $skipped; - return $this; - } - - /** - * Get Progress - * - * @return float - */ - public function getProgress(): float - { - if ($this->total === 0) { - return 0; - } - - return ($this->current / $this->total) * 100; - } - - /** - * Get Remaining - * - * @return int - */ - public function getRemaining(): int - { - return $this->total - $this->current; - } - - /** - * Get ETA - * - * @return int - */ - public function getETA(): int - { - return 0; - } - - /** - * As Array - * - * @return array - */ - public function asArray(): array - { - return [ - 'resourceType' => $this->resourceType, - 'timestamp' => $this->timestamp, - 'total' => $this->total, - 'current' => $this->current, - 'failed' => $this->failed, - 'skipped' => $this->skipped, - 'progress' => $this->getProgress(), - 'remaining' => $this->getRemaining(), - 'eta' => $this->getETA(), - ]; - } -} diff --git a/src/Transfer/ResourceCache.php b/src/Transfer/ResourceCache.php index a91c05a..a606e77 100644 --- a/src/Transfer/ResourceCache.php +++ b/src/Transfer/ResourceCache.php @@ -81,24 +81,6 @@ public function getAll() return $this->resourceCache; } - public function getStatusCounters() - { - $status = [ - Resource::STATUS_DISREGARDED => 0, - Resource::STATUS_SUCCESS => 0, - Resource::STATUS_ERROR => 0, - Resource::STATUS_SKIPPED => 0, - ]; - - foreach ($this->resourceCache as $resources) { - foreach ($resources as $resource) { - $status[$resource->getStatus()]++; - } - } - - return $status; - } - public function wipe() { $this->resourceCache = []; diff --git a/src/Transfer/Transfer.php b/src/Transfer/Transfer.php index 2b8fc8a..06fbe9b 100644 --- a/src/Transfer/Transfer.php +++ b/src/Transfer/Transfer.php @@ -79,11 +79,36 @@ public function __construct(Source $source, Destination $destination) */ protected array $events = []; + public function getStatusCounters() + { + $status = []; + + foreach ($this->resourceCache as $resources) { + foreach ($resources as $resource) { + /** @var Resource $resource */ + if (!array_key_exists($resource->getGroup(), $status)) { + $status[$resource->getGroup()] = [ + Resource::STATUS_PENDING => 0, + Resource::STATUS_SUCCESS => 0, + Resource::STATUS_ERROR => 0, + Resource::STATUS_SKIPPED => 0, + Resource::STATUS_PROCESSING => 0, + Resource::STATUS_WARNING => 0, + ]; + } + + $status[$resource->getGroup()][$resource->getStatus()]++; + } + } + + return $status; + } + /** * Transfer Resources between adapters * * @param array $resources - * @param callable $callback (Progress $progress) + * @param callable $callback (array $resources) */ public function run(array $resources, callable $callback): void { @@ -97,12 +122,7 @@ public function run(array $resources, callable $callback): void } } - $this->destination->run($computedResources, function (Progress $progress) use ($callback) { - //TODO: Rewrite to use ResourceCache to calculate this - $this->currentResource = $progress->getResourceType(); - - $callback($progress); - }, $this->source); + $this->destination->run($computedResources, $callback, $this->source); } /** @@ -125,4 +145,25 @@ public function getCurrentResource(): string { return $this->currentResource; } + + /** + * Get Transfer Report + */ + public function getReport(): array + { + $report = []; + + $resourceCache = $this->resourceCache->getAll(); + + foreach ($resourceCache as $resource) { + $report[] = [ + 'resource' => $resource->getType(), + 'id' => $resource->getId(), + 'status' => $resource->getStatus(), + 'message' => $resource->getMessage(), + ]; + } + + return $report; + } } diff --git a/tests/unit/ResourceCacheTest.php b/tests/unit/ResourceCacheTest.php index 0c79680..0b1b0db 100644 --- a/tests/unit/ResourceCacheTest.php +++ b/tests/unit/ResourceCacheTest.php @@ -71,65 +71,4 @@ public function testWipe(ResourceCache $cache) return $cache; } - - /** - * @depends testWipe - */ - public function testStatusCountersAdd(ResourceCache $cache) - { - $resource1 = new ConcreteResource(); - $resource2 = new ConcreteResource(); - $resource3 = new ConcreteResource(); - - $resource1->setStatus(Resource::STATUS_SUCCESS); - $resource2->setStatus(Resource::STATUS_ERROR); - $resource3->setStatus(Resource::STATUS_SKIPPED); - - $cache->add($resource1); - $cache->add($resource2); - $cache->add($resource3); - - $this->assertEquals(1, $cache->getStatusCounters()[Resource::STATUS_SUCCESS]); - $this->assertEquals(1, $cache->getStatusCounters()[Resource::STATUS_ERROR]); - $this->assertEquals(1, $cache->getStatusCounters()[Resource::STATUS_SKIPPED]); - - return $cache; - } - - /** - * @depends testStatusCountersAdd - */ - public function testStatusCountersUpdate(ResourceCache $cache) - { - $resources = $cache->get(ConcreteResource::getName()); - $resource = $resources[array_keys($resources)[0]]; - - $resource->setStatus(Resource::STATUS_ERROR); - $cache->update($resource); - - $resourceStatus = $cache->getStatusCounters(); - - $this->assertEquals(2, $resourceStatus[Resource::STATUS_ERROR]); - $this->assertEquals(0, $resourceStatus[Resource::STATUS_SUCCESS]); - $this->assertEquals(1, $resourceStatus[Resource::STATUS_SKIPPED]); - - return $cache; - } - - /** - * @depends testStatusCountersUpdate - */ - public function testStatusCountersRemove(ResourceCache $cache) - { - $resources = $cache->get(ConcreteResource::getName()); - $resource = $resources[array_keys($resources)[0]]; - - $cache->remove($resource); - - $statusCounters = $cache->getStatusCounters(); - - $this->assertEquals(1, $statusCounters[Resource::STATUS_ERROR]); - $this->assertEquals(0, $statusCounters[Resource::STATUS_SUCCESS]); - $this->assertEquals(1, $statusCounters[Resource::STATUS_SKIPPED]); - } } From 32a602b02a84d6eaa720f2b5c5ec5dda5ad432c5 Mon Sep 17 00:00:00 2001 From: Bradley Schofield Date: Fri, 12 May 2023 13:41:10 +0900 Subject: [PATCH 39/70] Update Transfer.php --- src/Transfer/Transfer.php | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/Transfer/Transfer.php b/src/Transfer/Transfer.php index 06fbe9b..77f2c82 100644 --- a/src/Transfer/Transfer.php +++ b/src/Transfer/Transfer.php @@ -148,14 +148,23 @@ public function getCurrentResource(): string /** * Get Transfer Report + * + * @param string $statusLevel + * If no status level is provided, all status types will be returned. + * + * @return array */ - public function getReport(): array + public function getReport(string $statusLevel = ''): array { $report = []; $resourceCache = $this->resourceCache->getAll(); foreach ($resourceCache as $resource) { + if ($statusLevel && $resource->getStatus() !== $statusLevel) { + continue; + } + $report[] = [ 'resource' => $resource->getType(), 'id' => $resource->getId(), From f56c3208ce43610ca1e8ef562d921e851f113e95 Mon Sep 17 00:00:00 2001 From: Bradley Schofield Date: Fri, 12 May 2023 14:16:01 +0900 Subject: [PATCH 40/70] Update Firebase.php --- src/Transfer/Sources/Firebase.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Transfer/Sources/Firebase.php b/src/Transfer/Sources/Firebase.php index 88347a8..7f25cfc 100644 --- a/src/Transfer/Sources/Firebase.php +++ b/src/Transfer/Sources/Firebase.php @@ -24,10 +24,10 @@ class Firebase extends Source private string $currentToken = ''; private int $tokenExpires = 0; - public function __construct(array $serviceAccount, string $projectID) + public function __construct(array $serviceAccount) { $this->serviceAccount = $serviceAccount; - $this->projectID = $projectID; + $this->projectID = $serviceAccount['project_id']; } static function getName(): string From a8f4c12aa4098eb09791e76ee40449f8b0ab3a3a Mon Sep 17 00:00:00 2001 From: Bradley Schofield Date: Fri, 12 May 2023 17:10:35 +0900 Subject: [PATCH 41/70] Update Transfer.php --- src/Transfer/Transfer.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/Transfer/Transfer.php b/src/Transfer/Transfer.php index 77f2c82..d9f21e6 100644 --- a/src/Transfer/Transfer.php +++ b/src/Transfer/Transfer.php @@ -20,6 +20,13 @@ class Transfer public const GROUP_FUNCTIONS_RESOURCES = [Resource::TYPE_FUNCTION, Resource::TYPE_ENVVAR, Resource::TYPE_DEPLOYMENT]; public const GROUP_DATABASES_RESOURCES = [Resource::TYPE_DATABASE, Resource::TYPE_COLLECTION, Resource::TYPE_INDEX, Resource::TYPE_ATTRIBUTE, Resource::TYPE_DOCUMENT]; public const GROUP_SETTINGS_RESOURCES = []; + public const ALL_RESOURCES = array_merge( + self::GROUP_AUTH_RESOURCES, + self::GROUP_STORAGE_RESOURCES, + self::GROUP_FUNCTIONS_RESOURCES, + self::GROUP_DATABASES_RESOURCES, + self::GROUP_SETTINGS_RESOURCES + ); public const STORAGE_MAX_CHUNK_SIZE = 1024 * 1024 * 5; // 5MB From fec14703d80cfe3c2b3a3f1cac79dbecfbdffd36 Mon Sep 17 00:00:00 2001 From: Bradley Schofield Date: Fri, 12 May 2023 17:22:29 +0900 Subject: [PATCH 42/70] Update Transfer.php --- src/Transfer/Transfer.php | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/Transfer/Transfer.php b/src/Transfer/Transfer.php index d9f21e6..5f45f9f 100644 --- a/src/Transfer/Transfer.php +++ b/src/Transfer/Transfer.php @@ -20,13 +20,15 @@ class Transfer public const GROUP_FUNCTIONS_RESOURCES = [Resource::TYPE_FUNCTION, Resource::TYPE_ENVVAR, Resource::TYPE_DEPLOYMENT]; public const GROUP_DATABASES_RESOURCES = [Resource::TYPE_DATABASE, Resource::TYPE_COLLECTION, Resource::TYPE_INDEX, Resource::TYPE_ATTRIBUTE, Resource::TYPE_DOCUMENT]; public const GROUP_SETTINGS_RESOURCES = []; - public const ALL_RESOURCES = array_merge( - self::GROUP_AUTH_RESOURCES, - self::GROUP_STORAGE_RESOURCES, - self::GROUP_FUNCTIONS_RESOURCES, - self::GROUP_DATABASES_RESOURCES, - self::GROUP_SETTINGS_RESOURCES - ); + public const ALL_PUBLIC_RESOURCES = [ + Resource::TYPE_USER, Resource::TYPE_TEAM, + Resource::TYPE_TEAM_MEMBERSHIP,Resource::TYPE_FILE, + Resource::TYPE_BUCKET, Resource::TYPE_FUNCTION, + Resource::TYPE_ENVVAR, Resource::TYPE_DEPLOYMENT, + Resource::TYPE_DATABASE, Resource::TYPE_COLLECTION, + Resource::TYPE_INDEX, Resource::TYPE_ATTRIBUTE, + Resource::TYPE_DOCUMENT + ]; public const STORAGE_MAX_CHUNK_SIZE = 1024 * 1024 * 5; // 5MB From f1d41d3f0b2072fd12d1dafdb80d5ea0d0ffd251 Mon Sep 17 00:00:00 2001 From: Bradley Schofield Date: Wed, 17 May 2023 15:59:03 +0900 Subject: [PATCH 43/70] add storage to firebase and change database system --- playground.php | 4 +- src/Transfer/Resource.php | 10 ++ src/Transfer/Sources/Firebase.php | 184 +++++++++++++++++++++++------- src/Transfer/Target.php | 2 +- src/Transfer/Transfer.php | 40 ++++--- 5 files changed, 176 insertions(+), 64 deletions(-) diff --git a/playground.php b/playground.php index 80642f9..6b8eb3a 100644 --- a/playground.php +++ b/playground.php @@ -71,7 +71,7 @@ */ $transfer = new Transfer( - $sourceSupabase, + $sourceFirebase, $destinationLocal ); @@ -79,7 +79,7 @@ * Run Transfer */ $transfer->run( - [Transfer::GROUP_DATABASES_RESOURCES], + [Transfer::GROUP_STORAGE_RESOURCES], function (array $resources) use ($transfer) { } ); diff --git a/src/Transfer/Resource.php b/src/Transfer/Resource.php index 36869ef..07bb2c4 100644 --- a/src/Transfer/Resource.php +++ b/src/Transfer/Resource.php @@ -154,6 +154,16 @@ public function setStatus(string $status, string $reason = ''): self return $this; } + /** + * Get Reason + * + * @return string + */ + public function getReason(): string + { + return $this->reason; + } + /** * As Array * diff --git a/src/Transfer/Sources/Firebase.php b/src/Transfer/Sources/Firebase.php index 7f25cfc..380c047 100644 --- a/src/Transfer/Sources/Firebase.php +++ b/src/Transfer/Sources/Firebase.php @@ -16,6 +16,9 @@ use Utopia\Transfer\Resources\Database\Collection; use Utopia\Transfer\Resources\Database\Database; use Utopia\Transfer\Resources\Database\Document; +use Utopia\Transfer\Resources\Storage\Bucket; +use Utopia\Transfer\Resources\Storage\File; +use Utopia\Transfer\Resources\Storage\FileData; class Firebase extends Source { @@ -112,6 +115,11 @@ public function getSupportedResources(): array Resource::TYPE_COLLECTION, Resource::TYPE_ATTRIBUTE, Resource::TYPE_DOCUMENT, + + // Storage + Resource::TYPE_BUCKET, + Resource::TYPE_FILE, + Resource::TYPE_FILEDATA ]; } @@ -210,20 +218,48 @@ public function exportDatabasesGroup(int $batchSize, array $resources) } if (in_array(Resource::TYPE_COLLECTION, $resources)) { - $this->traceDBResource($database, 'projects/' . $this->projectID . '/databases/' . $database->getId() . '/documents/', $batchSize); - } - - if (in_array(Resource::TYPE_DOCUMENT, $resources)) { - $this->exportDocuments($batchSize); + $this->handleDBData($batchSize, in_array(Resource::TYPE_DOCUMENT, $resources), $database); } } - function exportDocuments(int $batchSize) + function handleDBData(int $batchSize, bool $pushDocuments, Database $database) { - $collections = $this->resourceCache->get(Collection::getName()); + $baseURL = "https://firestore.googleapis.com/v1/{$this->projectID}/databases/(default)"; + + $nextPageToken = null; + $allCollections = []; + while (true) { + $collections = []; + + $result = $this->call('POST', $baseURL . ':listCollectionIds', [ + 'Content-Type' => 'application/json', + ], [ + 'pageSize' => $batchSize, + 'pageToken' => $nextPageToken + ]); + + // Transfer Collections + foreach ($result['collectionIds'] as $collection) { + $collections[] = new Collection($database, $collection, $collection); + } + + if (count($collections) !== 0) { + $allCollections = array_merge($allCollections, $collections); + $this->callback($collections); + } else { + return; + } - foreach ($collections as $collection) { - + // Transfer Documents and Calculate Schema + foreach ($collections as $collection) { + $this->handleCollection($collection, $batchSize, $pushDocuments); + } + + if (count($result['collectionIds']) < $batchSize) { + break; + } + + $nextPageToken = $result['nextPageToken'] ?? null; } } @@ -278,7 +314,7 @@ function calculateArrayType(Collection $collection, string $key, array $data): A } } - function handleCollection(Collection $collection, int $batchSize) + function handleCollection(Collection $collection, int $batchSize, bool $transferDocuments) { $resourceURL = 'https://firestore.googleapis.com/v1/projects/' . $this->projectID . '/databases/' . $collection->getDatabase()->getId() . '/documents/' . $collection->getId(); @@ -314,12 +350,13 @@ function handleCollection(Collection $collection, int $batchSize) } } - $this->traceDBResource($collection->getDatabase(), $document['name'], $batchSize); $documents[] = $this->convertDocument($collection, $document); } - // Transfer Documents - $this->callback($documents); + // Transfer Documents + if ($transferDocuments) { + $this->callback($documents); + } if (count($result['documents']) < $batchSize) { break; @@ -329,7 +366,8 @@ function handleCollection(Collection $collection, int $batchSize) } } - function calculateValue(array $field) { + function calculateValue(array $field) + { if (array_key_exists("booleanValue", $field)) { return $field['booleanValue']; } elseif (array_key_exists("bytesValue", $field)) { @@ -351,7 +389,7 @@ function calculateValue(array $field) { } elseif (array_key_exists("geoPointValue", $field)) { return $field['geoPointValue']; } elseif (array_key_exists("arrayValue", $field)) { - //TODO: + //TODO: } else { throw new \Exception('Unknown field type'); } @@ -367,40 +405,38 @@ function convertDocument(Collection $collection, array $document): Document return new Document($document['name'], $collection->getDatabase(), $collection, $data, []); } - function traceDBResource(Database $database, string $resource, int $batchSize) + public function exportStorageGroup(int $batchSize, array $resources) + { + if (in_array(Resource::TYPE_BUCKET, $resources)) + $this->exportBuckets($batchSize); + + if (in_array(Resource::TYPE_FILE, $resources)) + $this->exportFiles($batchSize); + } + + public function exportBuckets(int $batchsize) { - $baseURL = 'https://firestore.googleapis.com/v1/' . $resource; + $endpoint = 'https://storage.googleapis.com/storage/v1/b'; $nextPageToken = null; - $allCollections = []; - while (true) { - $collections = []; - $result = $this->call('POST', $baseURL . ':listCollectionIds', [ - 'Content-Type' => 'application/json', - ], [ - 'pageSize' => $batchSize, - 'pageToken' => $nextPageToken + while (true) { + $result = $this->call('GET', $endpoint, [], [ + 'project' => $this->projectID, + 'maxResults' => $batchsize, + 'pageToken' => $nextPageToken, + 'alt' => 'json' ]); - // Transfer Collections - foreach ($result['collectionIds'] as $collection) { - $collections[] = new Collection($database, $collection, $collection); - } - - if (count($collections) !== 0) { - $allCollections = array_merge($allCollections, $collections); - $this->callback($collections); - } else { - return; + if (empty($result)) { + break; } - // Transfer Documents and Calculate Schema - foreach ($collections as $collection) { - $this->handleCollection($collection, $batchSize); + foreach ($result['items'] as $bucket) { + $this->callback([new Bucket($bucket['id'], [], false, $bucket['name'])]); } - if (count($result['collectionIds']) < $batchSize) { + if (!isset($result['nextPageToken'])) { break; } @@ -408,12 +444,76 @@ function traceDBResource(Database $database, string $resource, int $batchSize) } } - public function exportFunctionsGroup(int $batchSize, array $resources) + public function exportFiles(int $batchsize) { - throw new \Exception('Not implemented'); + $buckets = $this->resourceCache->get(Bucket::getName()); + + foreach ($buckets as $bucket) { + $endpoint = 'https://storage.googleapis.com/storage/v1/b/' . $bucket->getId() . '/o'; + + $nextPageToken = null; + + while (true) { + $result = $this->call('GET', $endpoint, [ + 'Content-Type' => 'application/json', + ], [ + 'pageSize' => $batchsize, + 'pageToken' => $nextPageToken + ]); + + if (empty($result)) { + break; + } + + if (!isset($result['items'])) { + break; + } + + foreach ($result['items'] as $item) { + $this->handleDataTransfer(new File($item['name'], $bucket, $item['name'])); + } + + if (count($result['items']) < $batchsize) { + break; + } + + $nextPageToken = $result['nextPageToken'] ?? null; + } + } } - public function exportStorageGroup(int $batchSize, array $resources) + public function handleDataTransfer(File $file) + { + $endpoint = 'https://storage.googleapis.com/storage/v1/b/' . $file->getBucket()->getId() . '/o/' . $file->getId() . '?alt=media'; + $start = 0; + $end = Transfer::STORAGE_MAX_CHUNK_SIZE - 1; + + while (true) { + $result = $this->call('GET', $endpoint, [ + 'Range' => 'bytes=' . $start . '-' . $end + ]); + + if (empty($result)) { + break; + } + + $this->callback([new FileData( + $result, + $start, + $end, + $file + )]); + + if (strlen($result) < Transfer::STORAGE_MAX_CHUNK_SIZE) { + break; + } + + $start += Transfer::STORAGE_MAX_CHUNK_SIZE; + $end += Transfer::STORAGE_MAX_CHUNK_SIZE; + } + } + + public function exportFunctionsGroup(int $batchSize, array $resources) { throw new \Exception('Not implemented'); } diff --git a/src/Transfer/Target.php b/src/Transfer/Target.php index 6aabee0..0045a79 100644 --- a/src/Transfer/Target.php +++ b/src/Transfer/Target.php @@ -91,7 +91,7 @@ abstract public function report(array $resources = []): array; public function call(string $method, string $path = '', array $headers = array(), array $params = array()): array|string { $headers = array_merge($this->headers, $headers); - $ch = curl_init((str_contains($path, 'http') ? $path : $this->endpoint . $path . (($method == 'GET' && !empty($params)) ? '?' . http_build_query($params) : ''))); + $ch = curl_init((str_contains($path, 'http') ? $path . (($method == 'GET' && !empty($params)) ? '?' . http_build_query($params) : '') : $this->endpoint . $path . (($method == 'GET' && !empty($params)) ? '?' . http_build_query($params) : ''))); $responseHeaders = []; $responseStatus = -1; $responseType = ''; diff --git a/src/Transfer/Transfer.php b/src/Transfer/Transfer.php index 5f45f9f..a805842 100644 --- a/src/Transfer/Transfer.php +++ b/src/Transfer/Transfer.php @@ -21,12 +21,12 @@ class Transfer public const GROUP_DATABASES_RESOURCES = [Resource::TYPE_DATABASE, Resource::TYPE_COLLECTION, Resource::TYPE_INDEX, Resource::TYPE_ATTRIBUTE, Resource::TYPE_DOCUMENT]; public const GROUP_SETTINGS_RESOURCES = []; public const ALL_PUBLIC_RESOURCES = [ - Resource::TYPE_USER, Resource::TYPE_TEAM, - Resource::TYPE_TEAM_MEMBERSHIP,Resource::TYPE_FILE, - Resource::TYPE_BUCKET, Resource::TYPE_FUNCTION, + Resource::TYPE_USER, Resource::TYPE_TEAM, + Resource::TYPE_TEAM_MEMBERSHIP, Resource::TYPE_FILE, + Resource::TYPE_BUCKET, Resource::TYPE_FUNCTION, Resource::TYPE_ENVVAR, Resource::TYPE_DEPLOYMENT, - Resource::TYPE_DATABASE, Resource::TYPE_COLLECTION, - Resource::TYPE_INDEX, Resource::TYPE_ATTRIBUTE, + Resource::TYPE_DATABASE, Resource::TYPE_COLLECTION, + Resource::TYPE_INDEX, Resource::TYPE_ATTRIBUTE, Resource::TYPE_DOCUMENT ]; @@ -92,11 +92,11 @@ public function getStatusCounters() { $status = []; - foreach ($this->resourceCache as $resources) { + foreach ($this->resourceCache->getAll() as $resources) { foreach ($resources as $resource) { /** @var Resource $resource */ - if (!array_key_exists($resource->getGroup(), $status)) { - $status[$resource->getGroup()] = [ + if (!array_key_exists($resource->getName(), $status)) { + $status[$resource->getName()] = [ Resource::STATUS_PENDING => 0, Resource::STATUS_SUCCESS => 0, Resource::STATUS_ERROR => 0, @@ -106,7 +106,7 @@ public function getStatusCounters() ]; } - $status[$resource->getGroup()][$resource->getStatus()]++; + $status[$resource->getName()][$resource->getStatus()]++; } } @@ -169,17 +169,19 @@ public function getReport(string $statusLevel = ''): array $resourceCache = $this->resourceCache->getAll(); - foreach ($resourceCache as $resource) { - if ($statusLevel && $resource->getStatus() !== $statusLevel) { - continue; - } + foreach ($resourceCache as $type => $resources) { + foreach ($resources as $resource) { + if ($statusLevel && $resource->getStatus() !== $statusLevel) { + continue; + } - $report[] = [ - 'resource' => $resource->getType(), - 'id' => $resource->getId(), - 'status' => $resource->getStatus(), - 'message' => $resource->getMessage(), - ]; + $report[] = [ + 'resource' => $type, + 'id' => $resource->getId(), + 'status' => $resource->getStatus(), + 'message' => $resource->getReason(), + ]; + } } return $report; From ee8dd2079499c9aceed8c6854455b2a76f996a97 Mon Sep 17 00:00:00 2001 From: Bradley Schofield Date: Wed, 17 May 2023 16:36:21 +0900 Subject: [PATCH 44/70] Run phpcbf and fix the linter issues --- README.md | 2 +- src/Transfer/Destinations/Appwrite.php | 6 +- src/Transfer/Destinations/Local.php | 55 +++++++++---------- src/Transfer/Resource.php | 54 +++++++++--------- src/Transfer/Resources/Auth/Hash.php | 2 +- src/Transfer/Resources/Auth/Team.php | 24 ++++---- .../Resources/Auth/TeamMembership.php | 24 ++++---- src/Transfer/Resources/Auth/User.php | 2 +- src/Transfer/Resources/Database/Attribute.php | 2 +- .../Resources/Database/Collection.php | 2 +- src/Transfer/Resources/Database/Database.php | 2 +- src/Transfer/Resources/Database/Document.php | 2 +- src/Transfer/Resources/Database/Index.php | 2 +- .../Resources/Functions/Deployment.php | 4 +- src/Transfer/Resources/Functions/EnvVar.php | 2 +- src/Transfer/Resources/Functions/Func.php | 2 +- src/Transfer/Resources/Storage/Bucket.php | 2 +- src/Transfer/Resources/Storage/File.php | 2 +- src/Transfer/Resources/Storage/FileData.php | 2 +- src/Transfer/Sources/Appwrite.php | 49 ++++++++++------- src/Transfer/Sources/Firebase.php | 38 +++++++------ src/Transfer/Sources/NHost.php | 31 ++++++----- src/Transfer/Sources/Supabase.php | 17 +++--- src/Transfer/Target.php | 8 +-- src/Transfer/Transfer.php | 4 +- tests/Unit/ConcreteResource.php | 24 ++++++++ tests/e2e/Sources/Appwrite.php | 5 +- tests/e2e/Sources/SourceTest.php | 13 +++-- tests/e2e/adapters/MockDestination.php | 17 +++--- tests/e2e/adapters/MockSource.php | 18 +++--- tests/unit/ResourceCacheTest.php | 20 +------ 31 files changed, 230 insertions(+), 207 deletions(-) create mode 100644 tests/Unit/ConcreteResource.php diff --git a/README.md b/README.md index 6055062..b5e8c17 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,7 @@ Sources: | Appwrite | ✅ | ✅ | ✅ | ✅ | | | Supabase | ✅ | ✅ | ✅ | | | | NHost | ✅ | ✅ | ✅ | | | -| Firebase | ✅ | ✅ | | | | +| Firebase | ✅ | ✅ | ✅ | | | Destinations: | | Auth | Databases | Storage | Functions | Settings | diff --git a/src/Transfer/Destinations/Appwrite.php b/src/Transfer/Destinations/Appwrite.php index 7dd9fdf..6acd96f 100644 --- a/src/Transfer/Destinations/Appwrite.php +++ b/src/Transfer/Destinations/Appwrite.php @@ -60,7 +60,7 @@ public function __construct(string $project, string $endpoint, string $key) * * @return string */ - static function getName(): string + public static function getName(): string { return 'Appwrite'; } @@ -218,7 +218,7 @@ public function report(array $resources = []): array } } - function importResources(array $resources, callable $callback): void + public function importResources(array $resources, callable $callback): void { foreach ($resources as $resource) { /** @var Resource $resource */ @@ -663,7 +663,7 @@ public function importFunctionResource(Resource $resource): Resource } } - function importDeployment(Deployment $deployment): Resource + private function importDeployment(Deployment $deployment): Resource { $functionId = $deployment->getFunction()->getId(); diff --git a/src/Transfer/Destinations/Local.php b/src/Transfer/Destinations/Local.php index 45250e8..52d31bd 100644 --- a/src/Transfer/Destinations/Local.php +++ b/src/Transfer/Destinations/Local.php @@ -37,7 +37,7 @@ public function __construct(string $path) * * @return string */ - static function getName(): string + public static function getName(): string { return 'Local'; } @@ -101,42 +101,39 @@ public function importResources(array $resources, callable $callback): void foreach ($resources as $resource) { /** @var Resource $resource */ switch ($resource->getName()) { - case "Deployment": { - /** @var Deployment $resource */ - if ($resource->getStart() === 0) { - $this->data[$resource->getGroup()][$resource->getName()][$resource->getInternalId()] = $resource->asArray(); - } - - file_put_contents($this->path . 'deployments/'.$resource->getId().'.tar.gz', $resource->getData(), FILE_APPEND); + case "Deployment": + /** @var Deployment $resource */ + if ($resource->getStart() === 0) { + $this->data[$resource->getGroup()][$resource->getName()][$resource->getInternalId()] = $resource->asArray(); } + + file_put_contents($this->path . 'deployments/' . $resource->getId() . '.tar.gz', $resource->getData(), FILE_APPEND); break; - case "FileData": { - /** @var FileData $resource */ + case "FileData": + /** @var FileData $resource */ - // Handle folders - if (str_contains($resource->getFile()->getFileName(), '/')) { - $folders = explode('/', $resource->getFile()->getFileName()); - $folderPath = $this->path . '/files'; + // Handle folders + if (str_contains($resource->getFile()->getFileName(), '/')) { + $folders = explode('/', $resource->getFile()->getFileName()); + $folderPath = $this->path . '/files'; - foreach ($folders as $folder) { - $folderPath .= '/' . $folder; + foreach ($folders as $folder) { + $folderPath .= '/' . $folder; - if (!\file_exists($folderPath) && str_contains($folder, '.') === false) { - mkdir($folderPath, 0777, true); - } + if (!\file_exists($folderPath) && str_contains($folder, '.') === false) { + mkdir($folderPath, 0777, true); } } - - file_put_contents($this->path . '/files/' . $resource->getFile()->getFileName(), $resource->getData(), FILE_APPEND); - break; - } - case "File": { - /** @var File $resource */ - if (\file_exists($this->path . '/files/' . $resource->getFileName())) { - \unlink($this->path . '/files/' . $resource->getFileName()); - } - break; } + + file_put_contents($this->path . '/files/' . $resource->getFile()->getFileName(), $resource->getData(), FILE_APPEND); + break; + case "File": + /** @var File $resource */ + if (\file_exists($this->path . '/files/' . $resource->getFileName())) { + \unlink($this->path . '/files/' . $resource->getFileName()); + }; + break; } if ($resource->getName() !== Resource::TYPE_FILEDATA && $resource->getName() !== Resource::TYPE_DEPLOYMENT) { diff --git a/src/Transfer/Resource.php b/src/Transfer/Resource.php index 07bb2c4..f3bbc6f 100644 --- a/src/Transfer/Resource.php +++ b/src/Transfer/Resource.php @@ -4,37 +4,37 @@ abstract class Resource { - const STATUS_PENDING = 'PENDING'; - const STATUS_SUCCESS = 'SUCCESS'; - const STATUS_ERROR = 'ERROR'; - const STATUS_SKIPPED = 'SKIP'; - const STATUS_PROCESSING = 'PROCESSING'; - const STATUS_WARNING = 'WARNING'; + public const STATUS_PENDING = 'PENDING'; + public const STATUS_SUCCESS = 'SUCCESS'; + public const STATUS_ERROR = 'ERROR'; + public const STATUS_SKIPPED = 'SKIP'; + public const STATUS_PROCESSING = 'PROCESSING'; + public const STATUS_WARNING = 'WARNING'; /** * For some transfers (namely Firebase) we have to keep resources in cache that do not necessarily need to be Transferred * This status is used to mark resources that are not going to be transferred but are still needed for the transfer to work * e.g Documents are required for Database transfers because of schema tracing in firebase */ - const STATUS_DISREGARDED = 'DISREGARDED'; - - - const TYPE_ATTRIBUTE = 'Attribute'; - const TYPE_BUCKET = 'Bucket'; - const TYPE_COLLECTION = 'Collection'; - const TYPE_DATABASE = 'Database'; - const TYPE_DOCUMENT = 'Document'; - const TYPE_FILE = 'File'; - const TYPE_FILEDATA = 'FileData'; - const TYPE_FUNCTION = 'Function'; - const TYPE_DEPLOYMENT = 'Deployment'; - const TYPE_HASH = 'Hash'; - const TYPE_INDEX = 'Index'; - const TYPE_USER = 'User'; - const TYPE_ENVVAR = 'EnvVar'; - const TYPE_TEAM = 'Team'; - const TYPE_TEAM_MEMBERSHIP = 'TeamMembership'; - - const ALL_RESOURCES = [ + public const STATUS_DISREGARDED = 'DISREGARDED'; + + + public const TYPE_ATTRIBUTE = 'Attribute'; + public const TYPE_BUCKET = 'Bucket'; + public const TYPE_COLLECTION = 'Collection'; + public const TYPE_DATABASE = 'Database'; + public const TYPE_DOCUMENT = 'Document'; + public const TYPE_FILE = 'File'; + public const TYPE_FILEDATA = 'FileData'; + public const TYPE_FUNCTION = 'Function'; + public const TYPE_DEPLOYMENT = 'Deployment'; + public const TYPE_HASH = 'Hash'; + public const TYPE_INDEX = 'Index'; + public const TYPE_USER = 'User'; + public const TYPE_ENVVAR = 'EnvVar'; + public const TYPE_TEAM = 'Team'; + public const TYPE_TEAM_MEMBERSHIP = 'TeamMembership'; + + public const ALL_RESOURCES = [ self::TYPE_ATTRIBUTE, self::TYPE_BUCKET, self::TYPE_COLLECTION, @@ -77,7 +77,7 @@ abstract class Resource * * @return string */ - abstract static function getName(): string; + abstract public static function getName(): string; /** * Get Parent Group diff --git a/src/Transfer/Resources/Auth/Hash.php b/src/Transfer/Resources/Auth/Hash.php index b2934a1..643de2b 100644 --- a/src/Transfer/Resources/Auth/Hash.php +++ b/src/Transfer/Resources/Auth/Hash.php @@ -43,7 +43,7 @@ public function __construct(string $hash, string $salt = '', string $algorithm = $this->passwordLength = $passwordLength; } - static function getName(): string + public static function getName(): string { return Resource::TYPE_HASH; } diff --git a/src/Transfer/Resources/Auth/Team.php b/src/Transfer/Resources/Auth/Team.php index 0cd97d4..1d3f5e2 100644 --- a/src/Transfer/Resources/Auth/Team.php +++ b/src/Transfer/Resources/Auth/Team.php @@ -13,7 +13,7 @@ class Team extends Resource protected array $preferences = []; protected array $members = []; - function __construct(string $id, string $name, array $preferences = [], array $members = []) + public function __construct(string $id, string $name, array $preferences = [], array $members = []) { $this->id = $id; $this->name = $name; @@ -21,50 +21,50 @@ function __construct(string $id, string $name, array $preferences = [], array $m $this->members = $members; } - static function getName(): string + public static function getName(): string { return Resource::TYPE_TEAM; } - function getGroup(): string + public function getGroup(): string { return Transfer::GROUP_AUTH; } - function getTeamName(): string + public function getTeamName(): string { return $this->name; } - function setTeamName(string $name): self + public function setTeamName(string $name): self { $this->name = $name; return $this; } - function getId(): string + public function getId(): string { return $this->id; } - function setId(string $id): self + public function setId(string $id): self { $this->id = $id; return $this; } - function getPreferences(): array + public function getPreferences(): array { return $this->preferences; } - function setPreferences(array $preferences): self + public function setPreferences(array $preferences): self { $this->preferences = $preferences; return $this; } - function getMembers(): array + public function getMembers(): array { return $this->members; } @@ -72,13 +72,13 @@ function getMembers(): array /** * @param User[] $members */ - function setMembers(array $members): self + public function setMembers(array $members): self { $this->members = $members; return $this; } - function asArray(): array + public function asArray(): array { return [ 'id' => $this->id, diff --git a/src/Transfer/Resources/Auth/TeamMembership.php b/src/Transfer/Resources/Auth/TeamMembership.php index 5d23dfa..5e82c01 100644 --- a/src/Transfer/Resources/Auth/TeamMembership.php +++ b/src/Transfer/Resources/Auth/TeamMembership.php @@ -12,7 +12,7 @@ class TeamMembership extends Resource protected array $roles; protected bool $active = true; - function __construct(Team $team, string $userId, array $roles = [], bool $active = true) + public function __construct(Team $team, string $userId, array $roles = [], bool $active = true) { $this->team = $team; $this->userId = $userId; @@ -20,61 +20,61 @@ function __construct(Team $team, string $userId, array $roles = [], bool $active $this->active = $active; } - static function getName(): string + public static function getName(): string { return Resource::TYPE_TEAM_MEMBERSHIP; } - function getGroup(): string + public function getGroup(): string { return Transfer::GROUP_AUTH; } - function getTeam(): Team + public function getTeam(): Team { return $this->team; } - function setTeam(Team $team): self + public function setTeam(Team $team): self { $this->team = $team; return $this; } - function getUserId(): string + public function getUserId(): string { return $this->userId; } - function setUserId(string $userId): self + public function setUserId(string $userId): self { $this->userId = $userId; return $this; } - function getRoles(): array + public function getRoles(): array { return $this->roles; } - function setRoles(array $roles): self + public function setRoles(array $roles): self { $this->roles = $roles; return $this; } - function getActive(): bool + public function getActive(): bool { return $this->active; } - function setActive(bool $active): self + public function setActive(bool $active): self { $this->active = $active; return $this; } - function asArray(): array + public function asArray(): array { return [ 'userId' => $this->userId, diff --git a/src/Transfer/Resources/Auth/User.php b/src/Transfer/Resources/Auth/User.php index d8c778b..238c251 100644 --- a/src/Transfer/Resources/Auth/User.php +++ b/src/Transfer/Resources/Auth/User.php @@ -57,7 +57,7 @@ public function __construct( * * @return string */ - static function getName(): string + public static function getName(): string { return Resource::TYPE_USER; } diff --git a/src/Transfer/Resources/Database/Attribute.php b/src/Transfer/Resources/Database/Attribute.php index df48742..59874f8 100644 --- a/src/Transfer/Resources/Database/Attribute.php +++ b/src/Transfer/Resources/Database/Attribute.php @@ -37,7 +37,7 @@ public function __construct(string $key, Collection $collection, bool $required $this->collection = $collection; } - static function getName(): string + public static function getName(): string { return Resource::TYPE_ATTRIBUTE; } diff --git a/src/Transfer/Resources/Database/Collection.php b/src/Transfer/Resources/Database/Collection.php index b085241..7874bf5 100644 --- a/src/Transfer/Resources/Database/Collection.php +++ b/src/Transfer/Resources/Database/Collection.php @@ -49,7 +49,7 @@ public function __construct(Database $database, string $name, string $id, bool $ $this->permissions = $permissions; } - static function getName(): string + public static function getName(): string { return Resource::TYPE_COLLECTION; } diff --git a/src/Transfer/Resources/Database/Database.php b/src/Transfer/Resources/Database/Database.php index 7e78171..d0f3d52 100644 --- a/src/Transfer/Resources/Database/Database.php +++ b/src/Transfer/Resources/Database/Database.php @@ -29,7 +29,7 @@ public function __construct(string $name = '', string $id = '') $this->id = $id; } - static function getName(): string + public static function getName(): string { return Resource::TYPE_DATABASE; } diff --git a/src/Transfer/Resources/Database/Document.php b/src/Transfer/Resources/Database/Document.php index 31af490..b195e03 100644 --- a/src/Transfer/Resources/Database/Document.php +++ b/src/Transfer/Resources/Database/Document.php @@ -22,7 +22,7 @@ public function __construct(string $id, Database $database, Collection $collecti $this->permissions = $permissions; } - static function getName(): string + public static function getName(): string { return Resource::TYPE_DOCUMENT; } diff --git a/src/Transfer/Resources/Database/Index.php b/src/Transfer/Resources/Database/Index.php index 2859daa..229141c 100644 --- a/src/Transfer/Resources/Database/Index.php +++ b/src/Transfer/Resources/Database/Index.php @@ -35,7 +35,7 @@ public function __construct(string $id, string $key, Collection $collection, str $this->collection = $collection; } - static function getName(): string + public static function getName(): string { return Resource::TYPE_INDEX; } diff --git a/src/Transfer/Resources/Functions/Deployment.php b/src/Transfer/Resources/Functions/Deployment.php index 89000bd..6d8710b 100644 --- a/src/Transfer/Resources/Functions/Deployment.php +++ b/src/Transfer/Resources/Functions/Deployment.php @@ -29,7 +29,7 @@ public function __construct(string $id, Func $func, int $size, string $entrypoin $this->activated = $activated; } - static function getName(): string + public static function getName(): string { return Resource::TYPE_DEPLOYMENT; } @@ -93,7 +93,7 @@ public function getStart(): int { return $this->start; } - + public function setEnd(int $end): self { $this->end = $end; diff --git a/src/Transfer/Resources/Functions/EnvVar.php b/src/Transfer/Resources/Functions/EnvVar.php index e08395d..57f40c0 100644 --- a/src/Transfer/Resources/Functions/EnvVar.php +++ b/src/Transfer/Resources/Functions/EnvVar.php @@ -18,7 +18,7 @@ public function __construct(Func $func, string $key, string $value) $this->value = $value; } - static function getName(): string + public static function getName(): string { return Resource::TYPE_ENVVAR; } diff --git a/src/Transfer/Resources/Functions/Func.php b/src/Transfer/Resources/Functions/Func.php index dbeff41..7121955 100644 --- a/src/Transfer/Resources/Functions/Func.php +++ b/src/Transfer/Resources/Functions/Func.php @@ -28,7 +28,7 @@ public function __construct(string $name, string $id, string $runtime, array $ex $this->timeout = $timeout; } - static function getName(): string + public static function getName(): string { return Resource::TYPE_FUNCTION; } diff --git a/src/Transfer/Resources/Storage/Bucket.php b/src/Transfer/Resources/Storage/Bucket.php index 7ea786c..3c54119 100644 --- a/src/Transfer/Resources/Storage/Bucket.php +++ b/src/Transfer/Resources/Storage/Bucket.php @@ -32,7 +32,7 @@ public function __construct(string $id = '', array $permissions = [], bool $file $this->antiVirus = $antiVirus; } - static function getName(): string + public static function getName(): string { return Resource::TYPE_BUCKET; } diff --git a/src/Transfer/Resources/Storage/File.php b/src/Transfer/Resources/Storage/File.php index 03af00f..050f56d 100644 --- a/src/Transfer/Resources/Storage/File.php +++ b/src/Transfer/Resources/Storage/File.php @@ -26,7 +26,7 @@ public function __construct(string $id = '', Bucket $bucket = null, string $name $this->size = $size; } - static function getName(): string + public static function getName(): string { return Resource::TYPE_FILE; } diff --git a/src/Transfer/Resources/Storage/FileData.php b/src/Transfer/Resources/Storage/FileData.php index 3d81523..11f6fd4 100644 --- a/src/Transfer/Resources/Storage/FileData.php +++ b/src/Transfer/Resources/Storage/FileData.php @@ -20,7 +20,7 @@ public function __construct(string $data, int $start, int $end, File $file) $this->file = $file; } - static function getName(): string + public static function getName(): string { return Resource::TYPE_FILEDATA; } diff --git a/src/Transfer/Sources/Appwrite.php b/src/Transfer/Sources/Appwrite.php index b92c8d6..c055b42 100644 --- a/src/Transfer/Sources/Appwrite.php +++ b/src/Transfer/Sources/Appwrite.php @@ -83,7 +83,7 @@ public function __construct(string $project, string $endpoint, string $key) * * @return string */ - static function getName(): string + public static function getName(): string { return "Appwrite"; } @@ -140,12 +140,14 @@ public function report(array $resources = []): array // Auth try { $currentPermission = 'users.read'; - if (in_array(Resource::TYPE_USER, $resources)) + if (in_array(Resource::TYPE_USER, $resources)) { $report[Resource::TYPE_USER] = $usersClient->list()['total']; + } $currentPermission = 'teams.read'; - if (in_array(Resource::TYPE_TEAM, $resources)) + if (in_array(Resource::TYPE_TEAM, $resources)) { $report[Resource::TYPE_TEAM] = $teamsClient->list()['total']; + } if (in_array(Resource::TYPE_TEAM_MEMBERSHIP, $resources)) { $report[Resource::TYPE_TEAM_MEMBERSHIP] = 0; @@ -157,8 +159,9 @@ public function report(array $resources = []): array // Databases $currentPermission = 'databases.read'; - if (in_array(Resource::TYPE_DATABASE, $resources)) + if (in_array(Resource::TYPE_DATABASE, $resources)) { $report[Resource::TYPE_DATABASE] = $databaseClient->list()['total']; + } $currentPermission = 'collections.read'; if (in_array(Resource::TYPE_COLLECTION, $resources)) { @@ -207,8 +210,9 @@ public function report(array $resources = []): array // Storage $currentPermission = 'buckets.read'; - if (in_array(Resource::TYPE_BUCKET, $resources)) + if (in_array(Resource::TYPE_BUCKET, $resources)) { $report[Resource::TYPE_BUCKET] = $storageClient->listBuckets()['total']; + } $currentPermission = 'files.read'; if (in_array(Resource::TYPE_FILE, $resources)) { @@ -221,8 +225,9 @@ public function report(array $resources = []): array // Functions $currentPermission = 'functions.read'; - if (in_array(Resource::TYPE_FUNCTION, $resources)) + if (in_array(Resource::TYPE_FUNCTION, $resources)) { $report[Resource::TYPE_FUNCTION] = $functionsClient->list()['total']; + } if (in_array(Resource::TYPE_DEPLOYMENT, $resources)) { $report[Resource::TYPE_DEPLOYMENT] = 0; @@ -272,7 +277,7 @@ public function exportAuthGroup(int $batchSize, array $resources) } } - function exportUsers(int $batchSize) + private function exportUsers(int $batchSize) { $usersClient = new Users($this->client); $lastDocument = null; @@ -319,7 +324,7 @@ function exportUsers(int $batchSize) } } - function exportTeams(int $batchSize) + private function exportTeams(int $batchSize) { $teamsClient = new Teams($this->client); $lastDocument = null; @@ -358,7 +363,7 @@ function exportTeams(int $batchSize) } } - function exportTeamMemberships(int $batchSize) + private function exportTeamMemberships(int $batchSize) { $teamsClient = new Teams($this->client); @@ -427,7 +432,7 @@ public function exportDatabasesGroup(int $batchSize, array $resources) } - function exportDocuments(int $batchSize) + private function exportDocuments(int $batchSize) { $databaseClient = new Databases($this->client); $collections = $this->resourceCache->get(Collection::getName()); @@ -480,7 +485,7 @@ function exportDocuments(int $batchSize) } } - function convertAttribute(array $value, Collection $collection): Attribute + private function convertAttribute(array $value, Collection $collection): Attribute { switch ($value["type"]) { case "string": @@ -593,7 +598,7 @@ function convertAttribute(array $value, Collection $collection): Attribute throw new \Exception("Unknown attribute type: " . $value["type"]); } - function exportDatabases(int $batchSize) + private function exportDatabases(int $batchSize) { $databaseClient = new Databases($this->client); @@ -627,7 +632,7 @@ function exportDatabases(int $batchSize) } } - function exportCollections(int $batchSize) + private function exportCollections(int $batchSize) { $databaseClient = new Databases($this->client); @@ -670,7 +675,7 @@ function exportCollections(int $batchSize) } } - function exportAttributes(int $batchSize) + private function exportAttributes(int $batchSize) { $databaseClient = new Databases($this->client); @@ -707,7 +712,7 @@ function exportAttributes(int $batchSize) } } - function exportIndexes(int $batchSize) + private function exportIndexes(int $batchSize) { $databaseClient = new Databases($this->client); @@ -751,7 +756,7 @@ function exportIndexes(int $batchSize) } } - function calculateTypes(array $user): array + private function calculateTypes(array $user): array { if (empty($user["email"]) && empty($user["phone"])) { return [User::TYPE_ANONYMOUS]; @@ -781,7 +786,7 @@ public function exportStorageGroup(int $batchSize, array $resources) } } - function exportBuckets(int $batchSize) + private function exportBuckets(int $batchSize) { //TODO: Impl batching $storageClient = new Storage($this->client); @@ -812,7 +817,7 @@ function exportBuckets(int $batchSize) $this->callback($convertedBuckets); } - function exportFiles(int $batchSize) + private function exportFiles(int $batchSize) { $storageClient = new Storage($this->client); @@ -853,7 +858,7 @@ function exportFiles(int $batchSize) } } - function handleDataTransfer(File $file) + private function handleDataTransfer(File $file) { // Set the chunk size (5MB) $start = 0; @@ -894,11 +899,13 @@ function handleDataTransfer(File $file) public function exportFunctionsGroup(int $batchSize, array $resources) { - if (in_array(Resource::TYPE_FUNCTION, $resources)) + if (in_array(Resource::TYPE_FUNCTION, $resources)) { $this->exportFunctions($batchSize); + } - if (in_array(Resource::TYPE_DEPLOYMENT, $resources)) + if (in_array(Resource::TYPE_DEPLOYMENT, $resources)) { $this->exportDeployments($batchSize); + } } public function exportFunctions(int $batchSize) diff --git a/src/Transfer/Sources/Firebase.php b/src/Transfer/Sources/Firebase.php index 380c047..8155c6d 100644 --- a/src/Transfer/Sources/Firebase.php +++ b/src/Transfer/Sources/Firebase.php @@ -33,17 +33,17 @@ public function __construct(array $serviceAccount) $this->projectID = $serviceAccount['project_id']; } - static function getName(): string + public static function getName(): string { return 'Firebase'; } - function base64url_encode($data) + private function base64UrlEncode($data) { return str_replace(['+', '/', '='], ['-', '_', ''], base64_encode($data)); } - function calculateJWT(): string + private function calculateJWT(): string { $jwtClaim = [ 'iss' => $this->serviceAccount['client_email'], @@ -58,11 +58,11 @@ function calculateJWT(): string 'typ' => 'JWT' ]; - $jwtPayload = $this->base64url_encode(json_encode($jwtHeader)) . '.' . $this->base64url_encode(json_encode($jwtClaim)); + $jwtPayload = $this->base64UrlEncode(json_encode($jwtHeader)) . '.' . $this->base64UrlEncode(json_encode($jwtClaim)); $jwtSignature = ''; openssl_sign($jwtPayload, $jwtSignature, $this->serviceAccount['private_key'], 'sha256'); - $jwtSignature = $this->base64url_encode($jwtSignature); + $jwtSignature = $this->base64UrlEncode($jwtSignature); return $jwtPayload . '.' . $jwtSignature; } @@ -70,7 +70,7 @@ function calculateJWT(): string /** * Computes the JWT then fetches an auth token from the Google OAuth2 API which is valid for an hour */ - function authenticate() + private function authenticate() { if (time() < $this->tokenExpires) { return; @@ -135,7 +135,7 @@ public function exportAuthGroup(int $batchSize, array $resources) } } - function exportUsers(int $batchSize) + private function exportUsers(int $batchSize) { // Fetch our Hash Config $hashConfig = ($this->call('GET', 'https://identitytoolkit.googleapis.com/admin/v2/projects/' . $this->projectID . '/config'))["signIn"]["hashConfig"]; @@ -185,7 +185,7 @@ function exportUsers(int $batchSize) } } - function calculateUserType(array $providerData): array + private function calculateUserType(array $providerData): array { if (count($providerData) === 0) { return [User::TYPE_ANONYMOUS]; @@ -222,7 +222,7 @@ public function exportDatabasesGroup(int $batchSize, array $resources) } } - function handleDBData(int $batchSize, bool $pushDocuments, Database $database) + private function handleDBData(int $batchSize, bool $pushDocuments, Database $database) { $baseURL = "https://firestore.googleapis.com/v1/{$this->projectID}/databases/(default)"; @@ -263,7 +263,7 @@ function handleDBData(int $batchSize, bool $pushDocuments, Database $database) } } - function convertAttribute(Collection $collection, string $key, array $field): Attribute + private function convertAttribute(Collection $collection, string $key, array $field): Attribute { if (array_key_exists("booleanValue", $field)) { return new BoolAttribute($key, $collection, false, false, null); @@ -292,7 +292,7 @@ function convertAttribute(Collection $collection, string $key, array $field): At } } - function calculateArrayType(Collection $collection, string $key, array $data): Attribute + private function calculateArrayType(Collection $collection, string $key, array $data): Attribute { $isSameType = true; $previousType = null; @@ -314,7 +314,7 @@ function calculateArrayType(Collection $collection, string $key, array $data): A } } - function handleCollection(Collection $collection, int $batchSize, bool $transferDocuments) + private function handleCollection(Collection $collection, int $batchSize, bool $transferDocuments) { $resourceURL = 'https://firestore.googleapis.com/v1/projects/' . $this->projectID . '/databases/' . $collection->getDatabase()->getId() . '/documents/' . $collection->getId(); @@ -353,7 +353,7 @@ function handleCollection(Collection $collection, int $batchSize, bool $transfer $documents[] = $this->convertDocument($collection, $document); } - // Transfer Documents + // Transfer Documents if ($transferDocuments) { $this->callback($documents); } @@ -366,7 +366,7 @@ function handleCollection(Collection $collection, int $batchSize, bool $transfer } } - function calculateValue(array $field) + private function calculateValue(array $field) { if (array_key_exists("booleanValue", $field)) { return $field['booleanValue']; @@ -389,13 +389,13 @@ function calculateValue(array $field) } elseif (array_key_exists("geoPointValue", $field)) { return $field['geoPointValue']; } elseif (array_key_exists("arrayValue", $field)) { - //TODO: + //TODO: } else { throw new \Exception('Unknown field type'); } } - function convertDocument(Collection $collection, array $document): Document + private function convertDocument(Collection $collection, array $document): Document { $data = []; foreach ($document['fields'] as $key => $field) { @@ -407,11 +407,13 @@ function convertDocument(Collection $collection, array $document): Document public function exportStorageGroup(int $batchSize, array $resources) { - if (in_array(Resource::TYPE_BUCKET, $resources)) + if (in_array(Resource::TYPE_BUCKET, $resources)) { $this->exportBuckets($batchSize); + } - if (in_array(Resource::TYPE_FILE, $resources)) + if (in_array(Resource::TYPE_FILE, $resources)) { $this->exportFiles($batchSize); + } } public function exportBuckets(int $batchsize) diff --git a/src/Transfer/Sources/NHost.php b/src/Transfer/Sources/NHost.php index 2876311..e572920 100644 --- a/src/Transfer/Sources/NHost.php +++ b/src/Transfer/Sources/NHost.php @@ -53,7 +53,7 @@ public function __construct(string $subdomain, string $region, string $adminSecr } } - static function getName(): string + public static function getName(): string { return 'NHost'; } @@ -114,8 +114,9 @@ public function report(array $resources = []): array } // Databases - if (in_array(Resource::TYPE_DATABASE, $resources)) + if (in_array(Resource::TYPE_DATABASE, $resources)) { $report[Resource::TYPE_DATABASE] = 1; + } if (in_array(Resource::TYPE_COLLECTION, $resources)) { $statement = $this->pdo->prepare('SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = \'public\''); @@ -194,7 +195,7 @@ public function exportAuthGroup(int $batchSize, array $resources) } } - function exportUsers(int $batchSize) + private function exportUsers(int $batchSize) { $total = $this->pdo->query('SELECT COUNT(*) FROM auth.users')->fetchColumn(); @@ -255,7 +256,7 @@ public function exportDatabasesGroup(int $batchSize, array $resources) } } - function exportDatabases(int $batchSize): void + private function exportDatabases(int $batchSize): void { // We'll only transfer the public database for now, since it's the only one that exists by default. //TODO: Handle edge cases where there are user created databases and data. @@ -263,7 +264,7 @@ function exportDatabases(int $batchSize): void $this->callback([$transferDatabase]); } - function exportCollections(int $batchSize) + private function exportCollections(int $batchSize) { $databases = $this->resourceCache->get(Database::getName()); @@ -293,7 +294,7 @@ function exportCollections(int $batchSize) } } - function exportAttributes(int $batchSize) + private function exportAttributes(int $batchSize) { $collections = $this->resourceCache->get(Collection::getName()); @@ -314,7 +315,7 @@ function exportAttributes(int $batchSize) } } - function exportIndexes(int $batchSize) + private function exportIndexes(int $batchSize) { $collections = $this->resourceCache->get(Collection::getName()); @@ -337,7 +338,7 @@ function exportIndexes(int $batchSize) } } - function exportDocuments(int $batchSize) + private function exportDocuments(int $batchSize) { $databases = $this->resourceCache->get(Database::getName()); @@ -389,7 +390,7 @@ function exportDocuments(int $batchSize) } } - function convertAttribute(array $column, Collection $collection): Attribute + private function convertAttribute(array $column, Collection $collection): Attribute { $isArray = $column['data_type'] === 'ARRAY'; @@ -459,7 +460,7 @@ function convertAttribute(array $column, Collection $collection): Attribute } } - function convertIndex(array $index, Collection $collection): Index|false + private function convertIndex(array $index, Collection $collection): Index|false { $pattern = "/CREATE (?\w+)? INDEX (?\w+) ON (?
\w+\.\w+) USING (?\w+) \((?\w+)\)/"; @@ -506,7 +507,7 @@ function convertIndex(array $index, Collection $collection): Index|false } } - function calculateUserTypes(array $user): array + private function calculateUserTypes(array $user): array { if (empty($user['password_hash']) && empty($user['phone_number'])) { return [User::TYPE_ANONYMOUS]; @@ -527,11 +528,13 @@ function calculateUserTypes(array $user): array public function exportStorageGroup(int $batchSize, array $resources) { - if (in_array(Resource::TYPE_BUCKET, $resources)) + if (in_array(Resource::TYPE_BUCKET, $resources)) { $this->exportBuckets($batchSize); + } - if (in_array(Resource::TYPE_FILE, $resources)) + if (in_array(Resource::TYPE_FILE, $resources)) { $this->exportFiles($batchSize); + } } public function exportBuckets(int $batchSize) @@ -614,7 +617,7 @@ public function handleDataTransfer(File $file) $end = Transfer::STORAGE_MAX_CHUNK_SIZE - 1; $fileSize = $file->getSize(); - $response = $this->call("GET", $url."/v1/files/{$file->getId()}/presignedurl", [ + $response = $this->call("GET", $url . "/v1/files/{$file->getId()}/presignedurl", [ 'X-Hasura-Admin-Secret' => $this->adminSecret, ]); diff --git a/src/Transfer/Sources/Supabase.php b/src/Transfer/Sources/Supabase.php index eb6c124..eb324ca 100644 --- a/src/Transfer/Sources/Supabase.php +++ b/src/Transfer/Sources/Supabase.php @@ -14,7 +14,7 @@ class Supabase extends NHost { - static function getName(): string + public static function getName(): string { return 'Supabase'; } @@ -85,8 +85,9 @@ public function report(array $resources = []): array } // Databases - if (in_array(Resource::TYPE_DATABASE, $resources)) + if (in_array(Resource::TYPE_DATABASE, $resources)) { $report[Resource::TYPE_DATABASE] = 1; + } if (in_array(Resource::TYPE_COLLECTION, $resources)) { $statement = $this->pdo->prepare('SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = \'public\''); @@ -165,7 +166,7 @@ public function exportAuthGroup(int $batchSize, array $resources) } } - function exportUsers(int $batchSize) + private function exportUsers(int $batchSize) { $total = $this->pdo->query('SELECT COUNT(*) FROM auth.users')->fetchColumn(); @@ -203,7 +204,7 @@ function exportUsers(int $batchSize) } } - function convertMimes(array $mimes): array + private function convertMimes(array $mimes): array { $extensions = []; @@ -214,7 +215,7 @@ function convertMimes(array $mimes): array return $extensions; } - function calculateAuthTypes(array $user): array + private function calculateAuthTypes(array $user): array { if (empty($user['encrypted_password']) && empty($user['phone'])) { return [User::TYPE_ANONYMOUS]; @@ -244,7 +245,7 @@ public function exportStorageGroup(int $batchSize, array $resources) } } - function exportBuckets(int $batchSize) + private function exportBuckets(int $batchSize) { $statement = $this->pdo->prepare('SELECT * FROM storage.buckets order by created_at'); $statement->execute(); @@ -268,7 +269,7 @@ function exportBuckets(int $batchSize) $this->callback($transferBuckets); } - function exportFiles(int $batchSize) + private function exportFiles(int $batchSize) { /** * TODO: Supabase has folders, with enough folders within folders this could cause us to hit the max name length @@ -313,7 +314,7 @@ function exportFiles(int $batchSize) } } - function handleDataTransfer(File $file) + private function handleDataTransfer(File $file) { $start = 0; $end = Transfer::STORAGE_MAX_CHUNK_SIZE - 1; diff --git a/src/Transfer/Target.php b/src/Transfer/Target.php index 0045a79..1867768 100644 --- a/src/Transfer/Target.php +++ b/src/Transfer/Target.php @@ -34,7 +34,7 @@ abstract class Target * * @return string */ - abstract static function getName(): string; + abstract public static function getName(): string; /** * Get Supported Resources @@ -65,13 +65,13 @@ abstract public function run(array $resources, callable $callback): void; /** * Report Resources - * + * * This function performs a count of all resources that are available for transfer. * It also serves a secondary purpose of checking if the API is available for the given adapter. - * + * * On Destinations, this function should just return nothing but still check if the API is available. * If any issues are found then an exception should be thrown with an error message. - * + * * @param array $resources */ abstract public function report(array $resources = []): array; diff --git a/src/Transfer/Transfer.php b/src/Transfer/Transfer.php index a805842..7e0937b 100644 --- a/src/Transfer/Transfer.php +++ b/src/Transfer/Transfer.php @@ -157,10 +157,10 @@ public function getCurrentResource(): string /** * Get Transfer Report - * + * * @param string $statusLevel * If no status level is provided, all status types will be returned. - * + * * @return array */ public function getReport(string $statusLevel = ''): array diff --git a/tests/Unit/ConcreteResource.php b/tests/Unit/ConcreteResource.php new file mode 100644 index 0000000..4ac245d --- /dev/null +++ b/tests/Unit/ConcreteResource.php @@ -0,0 +1,24 @@ +assertIsArray($report); $this->validateReport($report); } -} \ No newline at end of file +} diff --git a/tests/e2e/Sources/SourceTest.php b/tests/e2e/Sources/SourceTest.php index 5dc9d95..f65ecc8 100644 --- a/tests/e2e/Sources/SourceTest.php +++ b/tests/e2e/Sources/SourceTest.php @@ -6,7 +6,7 @@ use Utopia\Transfer\Resource; use Utopia\Transfer\Source; -abstract class SourceTest extends TestCase +abstract class SourceTest extends TestCase { protected ?Source $source = null; @@ -20,7 +20,7 @@ public function testGetSupportedResources(): void $this->assertNotEmpty($this->source->getSupportedResources()); foreach ($this->source->getSupportedResources() as $resource) { - $this->assertContains($resource, Resource::ALL_RESOURCES); + $this->assertContains($resource, Resource::ALL_RESOURCES); } } @@ -31,14 +31,15 @@ public function testTransferCache(): void $this->assertNotNull($this->source->resourceCache); } - public abstract function testReport(): void; + abstract public function testReport(): void; - public function validateReport(array $report) { + public function validateReport(array $report) + { foreach ($report as $resource => $amount) { $this->assertContains($resource, Resource::ALL_RESOURCES); $this->assertIsInt($amount); } } - public abstract function testExportResources(): void; -} \ No newline at end of file + abstract public function testExportResources(): void; +} diff --git a/tests/e2e/adapters/MockDestination.php b/tests/e2e/adapters/MockDestination.php index 4e98215..01b052a 100644 --- a/tests/e2e/adapters/MockDestination.php +++ b/tests/e2e/adapters/MockDestination.php @@ -1,16 +1,19 @@ Date: Mon, 5 Jun 2023 12:35:10 +0100 Subject: [PATCH 45/70] Merge File and FileData and remove psalm for pint --- Dockerfile | 18 + README.md | 6 - composer.json | 2 +- docker-compose.yml | 38 + playground.php | 45 +- psalm.xml | 15 - src/Transfer/Destination.php | 16 +- src/Transfer/Destinations/Appwrite.php | 98 +- src/Transfer/Destinations/Local.php | 46 +- src/Transfer/Resource.php | 53 +- src/Transfer/ResourceCache.php | 13 +- src/Transfer/Resources/Auth/Hash.php | 72 +- .../{TeamMembership.php => Membership.php} | 11 +- src/Transfer/Resources/Auth/Team.php | 11 +- src/Transfer/Resources/Auth/User.php | 87 +- src/Transfer/Resources/Database/Attribute.php | 21 +- .../Database/Attributes/BoolAttribute.php | 9 +- .../Database/Attributes/DateTimeAttribute.php | 6 +- .../Database/Attributes/EmailAttribute.php | 6 +- .../Database/Attributes/EnumAttribute.php | 10 +- .../Database/Attributes/FloatAttribute.php | 15 +- .../Database/Attributes/IPAttribute.php | 6 +- .../Database/Attributes/IntAttribute.php | 15 +- .../Attributes/RelationshipAttribute.php | 17 +- .../Database/Attributes/StringAttribute.php | 9 +- .../Database/Attributes/URLAttribute.php | 6 +- .../Resources/Database/Collection.php | 22 +- src/Transfer/Resources/Database/Database.php | 9 +- src/Transfer/Resources/Database/Document.php | 17 +- src/Transfer/Resources/Database/Index.php | 21 +- .../Resources/Functions/Deployment.php | 16 +- src/Transfer/Resources/Functions/EnvVar.php | 5 + src/Transfer/Resources/Functions/Func.php | 14 + src/Transfer/Resources/Storage/Bucket.php | 19 + src/Transfer/Resources/Storage/File.php | 84 +- src/Transfer/Resources/Storage/FileData.php | 61 - src/Transfer/Source.php | 30 +- src/Transfer/Sources/Appwrite.php | 287 +- src/Transfer/Sources/Firebase.php | 143 +- src/Transfer/Sources/NHost.php | 80 +- src/Transfer/Sources/Supabase.php | 69 +- src/Transfer/Target.php | 52 +- src/Transfer/Transfer.php | 60 +- tests/e2e/Sources/Appwrite.php | 5 - tests/e2e/adapters/MockDestination.php | 4 +- tests/e2e/adapters/MockSource.php | 2 +- tests/resources/nhost/backup.tar | 2361 +++++++++ tests/resources/restore.sh | 3 + tests/resources/supabase/backup.tar | 4405 +++++++++++++++++ tests/resources/updateBackups.sh | 15 + tests/unit/ResourceCacheTest.php | 2 - 51 files changed, 7559 insertions(+), 878 deletions(-) create mode 100644 Dockerfile create mode 100644 docker-compose.yml delete mode 100644 psalm.xml rename src/Transfer/Resources/Auth/{TeamMembership.php => Membership.php} (94%) delete mode 100644 src/Transfer/Resources/Storage/FileData.php create mode 100644 tests/resources/nhost/backup.tar create mode 100644 tests/resources/restore.sh create mode 100644 tests/resources/supabase/backup.tar create mode 100755 tests/resources/updateBackups.sh diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..ddd53d1 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,18 @@ +FROM postgres:alpine3.18 as supabase-db +COPY ./tests/resources/supabase/backup.tar /docker-entrypoint-initdb.d/backup.tar +COPY ./tests/resources/restore.sh /docker-entrypoint-initdb.d/restore.sh + +FROM postgres:alpine3.18 as nhost-db +COPY ./tests/resources/nhost/backup.tar /docker-entrypoint-initdb.d/backup.tar +COPY ./tests/resources/restore.sh /docker-entrypoint-initdb.d/restore.sh + +# Use my fork of mockoon while waiting for range headers to be merged +FROM node:14-alpine3.14 as mock-api +WORKDIR /app +RUN git clone https://github.com/PineappleIOnic/mockoon.git . +RUN npm run bootstrap +RUN npm run build:libs +RUN npm run build:cli +RUN mv ./packages/cli/dist/run /usr/local/bin/mockoon + +FROM php:8.0-fpm-alpine3.14 as tests \ No newline at end of file diff --git a/README.md b/README.md index b5e8c17..0874903 100644 --- a/README.md +++ b/README.md @@ -69,12 +69,6 @@ Destinations: Utopia Transfer requires PHP 8.0 or later. We recommend using the latest PHP version whenever possible. -## Authors - -**Bradley Schofield** - -+ [https://github.com/PineappleIOnic](https://github.com/PineappleIOnic) - ## Copyright and license The MIT License (MIT) [http://www.opensource.org/licenses/mit-license.php](http://www.opensource.org/licenses/mit-license.php) diff --git a/composer.json b/composer.json index aa87a2c..710bce6 100644 --- a/composer.json +++ b/composer.json @@ -28,6 +28,6 @@ "phpunit/phpunit": "^9.3", "vimeo/psalm": "^5.6", "vlucas/phpdotenv": "^5.5", - "squizlabs/php_codesniffer": "3.*" + "laravel/pint": "^1.10" } } diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..45f865f --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,38 @@ +version: '3' + +services: + supabase-db: + build: + context: . + target: supabase-db + ports: + - "5432:5432" + environment: + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres + POSTGRES_DB: postgres + + nhost-db: + build: + context: . + target: nhost-db + ports: + - "5433:5432" + environment: + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres + POSTGRES_DB: postgres + + tests: + build: + context: . + target: tests + volumes: + - ./:/app + working_dir: /app + depends_on: + - supabase-db + - nhost-db + environment: + - NHOST_DB_URL=postgres://postgres:postgres@nhost-db:5432/postgres + - SUPABASE_DB_URL=postgres://postgres:postgres@supabase-db:5432/postgres \ No newline at end of file diff --git a/playground.php b/playground.php index 6b8eb3a..d99978a 100644 --- a/playground.php +++ b/playground.php @@ -2,27 +2,25 @@ /** * Playground for Transfer Library Tests - * + * * A place to test and debug the Transfer Library stuff */ +require_once __DIR__.'/vendor/autoload.php'; -require_once __DIR__ . '/vendor/autoload.php'; - -use Utopia\Transfer\Transfer; -use Utopia\Transfer\Sources\Appwrite; +use Dotenv\Dotenv; use Utopia\Transfer\Destinations\Appwrite as AppwriteDestination; use Utopia\Transfer\Destinations\Local; +use Utopia\Transfer\Sources\Appwrite; use Utopia\Transfer\Sources\Firebase; use Utopia\Transfer\Sources\NHost; use Utopia\Transfer\Sources\Supabase; -use Dotenv\Dotenv; -use Utopia\Transfer\Resource; +use Utopia\Transfer\Transfer; $dotenv = Dotenv::createImmutable(__DIR__); $dotenv->load(); /** - * Initialise All Source Adapters + * Initialise All Source Adapters */ $sourceAppwrite = new Appwrite( $_ENV['SOURCE_APPWRITE_TEST_PROJECT'], @@ -30,7 +28,7 @@ $_ENV['SOURCE_APPWRITE_TEST_KEY'] ); -$firebase = json_decode($_ENV["FIREBASE_TEST_ACCOUNT"], true); +$firebase = json_decode($_ENV['FIREBASE_TEST_ACCOUNT'], true); $sourceFirebase = new Firebase( $firebase, @@ -39,24 +37,24 @@ $sourceNHost = new NHost( $_ENV['NHOST_TEST_SUBDOMAIN'] ?? '', - $_ENV["NHOST_TEST_REGION"] ?? '', + $_ENV['NHOST_TEST_REGION'] ?? '', $_ENV['NHOST_TEST_SECRET'] ?? '', - $_ENV["NHOST_TEST_DATABASE"] ?? '', - $_ENV["NHOST_TEST_USERNAME"] ?? '', - $_ENV["NHOST_TEST_PASSWORD"] ?? '', + $_ENV['NHOST_TEST_DATABASE'] ?? '', + $_ENV['NHOST_TEST_USERNAME'] ?? '', + $_ENV['NHOST_TEST_PASSWORD'] ?? '', ); $sourceSupabase = new Supabase( $_ENV['SUPABASE_TEST_ENDPOINT'] ?? '', $_ENV['SUPABASE_TEST_KEY'] ?? '', - $_ENV["SUPABASE_TEST_HOST"] ?? '', - $_ENV["SUPABASE_TEST_DATABASE"] ?? '', - $_ENV["SUPABASE_TEST_USERNAME"] ?? '', - $_ENV["SUPABASE_TEST_PASSWORD"] ?? '', + $_ENV['SUPABASE_TEST_HOST'] ?? '', + $_ENV['SUPABASE_TEST_DATABASE'] ?? '', + $_ENV['SUPABASE_TEST_USERNAME'] ?? '', + $_ENV['SUPABASE_TEST_PASSWORD'] ?? '', ); /** - * Initialise All Destination Adapters + * Initialise All Destination Adapters */ $destinationAppwrite = new AppwriteDestination( $_ENV['DESTINATION_APPWRITE_TEST_PROJECT'], @@ -64,22 +62,21 @@ $_ENV['DESTINATION_APPWRITE_TEST_KEY'] ); -$destinationLocal = new Local(__DIR__ . '/localBackup/'); +$destinationLocal = new Local(__DIR__.'/localBackup/'); /** - * Initialise Transfer Class + * Initialise Transfer Class */ - $transfer = new Transfer( $sourceFirebase, $destinationLocal ); /** - * Run Transfer + * Run Transfer */ $transfer->run( - [Transfer::GROUP_STORAGE_RESOURCES], - function (array $resources) use ($transfer) { + [Transfer::GROUP_STORAGE_RESOURCES, Transfer::GROUP_DATABASES_RESOURCES], + function (array $resources) { } ); diff --git a/psalm.xml b/psalm.xml deleted file mode 100644 index b5d5891..0000000 --- a/psalm.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/src/Transfer/Destination.php b/src/Transfer/Destination.php index 245f7f3..466b400 100644 --- a/src/Transfer/Destination.php +++ b/src/Transfer/Destination.php @@ -6,15 +6,11 @@ abstract class Destination extends Target { /** * Source - * - * @var Source $source */ protected Source $source; /** * Get Source - * - * @return Source */ public function getSource(): Source { @@ -23,22 +19,16 @@ public function getSource(): Source /** * Set Soruce - * - * @param Source $source - * - * @return self */ public function setSource(Source $source): self { $this->source = $source; + return $this; } /** * Transfer Resources to Destination from Source callback - * - * @param array $resources - * @param callable $callback */ public function run(array $resources, callable $callback): void { @@ -50,9 +40,7 @@ public function run(array $resources, callable $callback): void /** * Import Resources * - * @param array $resources - * @param callable $callback (array $resources) - * + * @param callable $callback (array $resources) */ abstract public function importResources(array $resources, callable $callback): void; } diff --git a/src/Transfer/Destinations/Appwrite.php b/src/Transfer/Destinations/Appwrite.php index 6acd96f..94430f7 100644 --- a/src/Transfer/Destinations/Appwrite.php +++ b/src/Transfer/Destinations/Appwrite.php @@ -4,23 +4,16 @@ use Appwrite\Client; use Appwrite\InputFile; -use Appwrite\Services\Users; use Appwrite\Services\Databases; use Appwrite\Services\Functions; use Appwrite\Services\Storage; use Appwrite\Services\Teams; +use Appwrite\Services\Users; use Utopia\Transfer\Destination; -use Utopia\Transfer\Transfer; -use Utopia\Transfer\Resources\Auth\User; +use Utopia\Transfer\Resource; use Utopia\Transfer\Resources\Auth\Hash; -use Utopia\Transfer\Resources\Auth\TeamMembership; -use Utopia\Transfer\Resources\Storage\Bucket; -use Utopia\Transfer\Resources\Storage\FileData; -use Utopia\Transfer\Resources\Storage\Index; -use Utopia\Transfer\Resources\Functions\Func; -use Utopia\Transfer\Resources\Functions\EnvVar; -use Utopia\Transfer\Resources\Database\Database; -use Utopia\Transfer\Resources\Database\Collection; +use Utopia\Transfer\Resources\Auth\Membership; +use Utopia\Transfer\Resources\Auth\User; use Utopia\Transfer\Resources\Database\Attribute; use Utopia\Transfer\Resources\Database\Attributes\BoolAttribute; use Utopia\Transfer\Resources\Database\Attributes\DateTimeAttribute; @@ -29,18 +22,26 @@ use Utopia\Transfer\Resources\Database\Attributes\FloatAttribute; use Utopia\Transfer\Resources\Database\Attributes\IntAttribute; use Utopia\Transfer\Resources\Database\Attributes\IPAttribute; +use Utopia\Transfer\Resources\Database\Attributes\RelationshipAttribute; use Utopia\Transfer\Resources\Database\Attributes\StringAttribute; use Utopia\Transfer\Resources\Database\Attributes\URLAttribute; -use Utopia\Transfer\Resources\Database\Attributes\RelationshipAttribute; +use Utopia\Transfer\Resources\Database\Collection; +use Utopia\Transfer\Resources\Database\Database; use Utopia\Transfer\Resources\Database\Document; -use Utopia\Transfer\Resource; use Utopia\Transfer\Resources\Functions\Deployment; +use Utopia\Transfer\Resources\Functions\EnvVar; +use Utopia\Transfer\Resources\Functions\Func; +use Utopia\Transfer\Resources\Storage\Bucket; +use Utopia\Transfer\Resources\Storage\File; +use Utopia\Transfer\Resources\Storage\Index; +use Utopia\Transfer\Transfer; class Appwrite extends Destination { protected Client $client; protected string $project; + protected string $key; public function __construct(string $project, string $endpoint, string $key) @@ -57,8 +58,6 @@ public function __construct(string $project, string $endpoint, string $key) /** * Get Name - * - * @return string */ public static function getName(): string { @@ -67,8 +66,6 @@ public static function getName(): string /** * Get Supported Resources - * - * @return array */ public function getSupportedResources(): array { @@ -76,7 +73,7 @@ public function getSupportedResources(): array // Auth Resource::TYPE_USER, Resource::TYPE_TEAM, - Resource::TYPE_TEAM_MEMBERSHIP, + Resource::TYPE_MEMBERSHIP, // Database Resource::TYPE_DATABASE, @@ -88,7 +85,6 @@ public function getSupportedResources(): array // Storage Resource::TYPE_BUCKET, Resource::TYPE_FILE, - Resource::TYPE_FILEDATA, // Functions Resource::TYPE_FUNCTION, @@ -133,7 +129,7 @@ public function report(array $resources = []): array $teams->create('', ''); } - if (in_array(Resource::TYPE_TEAM_MEMBERSHIP, $resources)) { + if (in_array(Resource::TYPE_MEMBERSHIP, $resources)) { $currentPermission = 'memberships.read'; $teams->listMemberships(''); @@ -211,7 +207,7 @@ public function report(array $resources = []): array return []; } catch (\Exception $exception) { if ($exception->getCode() === 403) { - throw new \Exception('Missing permission: ' . $currentPermission); + throw new \Exception('Missing permission: '.$currentPermission); } else { throw $exception; } @@ -221,7 +217,7 @@ public function report(array $resources = []): array public function importResources(array $resources, callable $callback): void { foreach ($resources as $resource) { - /** @var Resource $resource */ + /** @var resource $resource */ switch ($resource->getGroup()) { case Transfer::GROUP_DATABASES: $responseResource = $this->importDatabaseResource($resource); @@ -357,11 +353,6 @@ public function createAttribute(Attribute $attribute): void /** * Await Attribute Creation - * - * @param Attribute $attribute - * @param int $timeout - * - * @return bool */ public function awaitAttributeCreation(Attribute $attribute, int $timeout): bool { @@ -392,15 +383,7 @@ public function importFileResource(Resource $resource): Resource switch ($resource->getName()) { case Resource::TYPE_FILE: /** @var File $resource */ - $response = $storageService->createFile( - $resource->getBucket()->getId(), - $resource->getId(), - $resource->getFileName(), - $resource->getPermissions() - ); - break; - case Resource::TYPE_FILEDATA: - return $this->importFileData($resource); + return $this->importFile($resource); break; case Resource::TYPE_BUCKET: /** @var Bucket $resource */ @@ -430,12 +413,12 @@ public function importFileResource(Resource $resource): Resource /** * Import File Data * - * @param FileData $filePart - * @returns FileData + * @param File $file + * + * @returns File */ - public function importFileData(FileData $resource): FileData + public function importFile(File $file): File { - $file = $resource->getFile(); $bucketId = $file->getBucket()->getId(); $response = null; @@ -450,13 +433,14 @@ public function importFileData(FileData $resource): FileData [ 'bucketId' => $bucketId, 'fileId' => $file->getId(), - 'file' => new \CurlFile('data://' . $file->getMimeType() . ';base64,' . base64_encode($resource->getData()), $file->getMimeType(), $file->getFileName()), + 'file' => new \CurlFile('data://'.$file->getMimeType().';base64,'.base64_encode($file->getData()), $file->getMimeType(), $file->getFileName()), 'permissions' => $file->getPermissions(), ] ); - $resource->setStatus(Resource::STATUS_SUCCESS); - return $resource; + $file->setStatus(Resource::STATUS_SUCCESS); + + return $file; } $response = $this->client->call( @@ -464,26 +448,26 @@ public function importFileData(FileData $resource): FileData "/v1/storage/buckets/{$bucketId}/files", [ 'content-type' => 'multipart/form-data', - 'content-range' => 'bytes ' . ($resource->getStart()) . '-' . ($resource->getEnd() == ($file->getSize() - 1) ? $file->getSize() : $resource->getEnd()) . '/' . $file->getSize(), + 'content-range' => 'bytes '.($file->getStart()).'-'.($file->getEnd() == ($file->getSize() - 1) ? $file->getSize() : $file->getEnd()).'/'.$file->getSize(), ], [ 'bucketId' => $bucketId, 'fileId' => $file->getId(), - 'file' => new \CurlFile('data://' . $file->getMimeType() . ';base64,' . base64_encode($resource->getData()), $file->getMimeType(), $file->getFileName()), + 'file' => new \CurlFile('data://'.$file->getMimeType().';base64,'.base64_encode($file->getData()), $file->getMimeType(), $file->getFileName()), 'permissions' => $file->getPermissions(), ] ); - if ($resource->getEnd() == ($file->getSize() - 1)) { + if ($file->getEnd() == ($file->getSize() - 1)) { // Signatures for Encrypted files are invalid, so we skip the check if ($file->getBucket()->getEncryption() == false || $file->getSize() > (20 * 1024 * 1024)) { if ($response['signature'] !== $file->getSignature()) { - $resource->setStatus(Resource::STATUS_WARNING, 'File signature mismatch, Possibly corrupted.'); + $file->setStatus(Resource::STATUS_WARNING, 'File signature mismatch, Possibly corrupted.'); } } } - return $resource; + return $file; } public function importAuthResource(Resource $resource): Resource @@ -518,7 +502,7 @@ public function importAuthResource(Resource $resource): Resource } if ($resource->getDisabled()) { - $userService->updateStatus($resource->getId(), !$resource->getDisabled()); + $userService->updateStatus($resource->getId(), ! $resource->getDisabled()); } break; @@ -527,8 +511,8 @@ public function importAuthResource(Resource $resource): Resource $teamService->create($resource->getId(), $resource->getName()); $teamService->updatePrefs($resource->getId(), $resource->getPrefs()); break; - case Resource::TYPE_TEAM_MEMBERSHIP: - /** @var TeamMembership $resource */ + case Resource::TYPE_MEMBERSHIP: + /** @var Membership $resource */ //TODO: Discuss in meeting. // $teamService->createMembership($resource->getTeam()->getId(), $resource->getRoles(), ) // break; @@ -655,6 +639,7 @@ public function importFunctionResource(Resource $resource): Resource } $resource->setStatus(Resource::STATUS_SUCCESS); + return $resource; } catch (\Exception $e) { $resource->setStatus(Resource::STATUS_ERROR, $e->getMessage()); @@ -678,13 +663,14 @@ private function importDeployment(Deployment $deployment): Resource ], [ 'functionId' => $functionId, - 'code' => new \CurlFile('data://application/gzip;base64,' . base64_encode($deployment->getData()), 'application/gzip', 'deployment.tar.gz'), + 'code' => new \CurlFile('data://application/gzip;base64,'.base64_encode($deployment->getData()), 'application/gzip', 'deployment.tar.gz'), 'activate' => $deployment->getActivated() ? 'true' : 'false', - 'entrypoint' => $deployment->getEntrypoint() + 'entrypoint' => $deployment->getEntrypoint(), ] ); $deployment->setStatus(Resource::STATUS_SUCCESS); + return $deployment; } @@ -693,14 +679,14 @@ private function importDeployment(Deployment $deployment): Resource "/v1/functions/{$functionId}/deployments", [ 'content-type' => 'multipart/form-data', - 'content-range' => 'bytes ' . ($deployment->getStart()) . '-' . ($deployment->getEnd() == ($deployment->getSize() - 1) ? $deployment->getSize() : $deployment->getEnd()) . '/' . $deployment->getSize(), + 'content-range' => 'bytes '.($deployment->getStart()).'-'.($deployment->getEnd() == ($deployment->getSize() - 1) ? $deployment->getSize() : $deployment->getEnd()).'/'.$deployment->getSize(), 'x-appwrite-id' => $deployment->getId(), ], [ 'functionId' => $functionId, - 'code' => new \CurlFile('data://application/gzip;base64,' . base64_encode($deployment->getData()), 'application/gzip', 'deployment.tar.gz'), + 'code' => new \CurlFile('data://application/gzip;base64,'.base64_encode($deployment->getData()), 'application/gzip', 'deployment.tar.gz'), 'activate' => $deployment->getActivated(), - 'entrypoint' => $deployment->getEntrypoint() + 'entrypoint' => $deployment->getEntrypoint(), ] ); diff --git a/src/Transfer/Destinations/Local.php b/src/Transfer/Destinations/Local.php index 52d31bd..800ecd1 100644 --- a/src/Transfer/Destinations/Local.php +++ b/src/Transfer/Destinations/Local.php @@ -3,10 +3,9 @@ namespace Utopia\Transfer\Destinations; use Utopia\Transfer\Destination; -use Utopia\Transfer\Resources\Storage\File; -use Utopia\Transfer\Resources\Storage\FileData; -use Utopia\Transfer\Resources\Functions\Deployment; use Utopia\Transfer\Resource; +use Utopia\Transfer\Resources\Functions\Deployment; +use Utopia\Transfer\Resources\Storage\File; use Utopia\Transfer\Transfer; /** @@ -34,8 +33,6 @@ public function __construct(string $path) /** * Get Name - * - * @return string */ public static function getName(): string { @@ -44,8 +41,6 @@ public static function getName(): string /** * Get Supported Resources - * - * @return array */ public function getSupportedResources(): array { @@ -54,17 +49,16 @@ public function getSupportedResources(): array Resource::TYPE_BUCKET, Resource::TYPE_COLLECTION, Resource::TYPE_DATABASE, + Resource::TYPE_DEPLOYMENT, Resource::TYPE_DOCUMENT, + Resource::TYPE_ENVVAR, Resource::TYPE_FILE, - Resource::TYPE_FILEDATA, Resource::TYPE_FUNCTION, - Resource::TYPE_DEPLOYMENT, Resource::TYPE_HASH, Resource::TYPE_INDEX, - Resource::TYPE_USER, - Resource::TYPE_ENVVAR, Resource::TYPE_TEAM, - Resource::TYPE_TEAM_MEMBERSHIP, + Resource::TYPE_MEMBERSHIP, + Resource::TYPE_USER ]; } @@ -99,9 +93,9 @@ public function syncFile(): void public function importResources(array $resources, callable $callback): void { foreach ($resources as $resource) { - /** @var Resource $resource */ + /** @var resource $resource */ switch ($resource->getName()) { - case "Deployment": + case 'Deployment': /** @var Deployment $resource */ if ($resource->getStart() === 0) { $this->data[$resource->getGroup()][$resource->getName()][$resource->getInternalId()] = $resource->asArray(); @@ -109,12 +103,12 @@ public function importResources(array $resources, callable $callback): void file_put_contents($this->path . 'deployments/' . $resource->getId() . '.tar.gz', $resource->getData(), FILE_APPEND); break; - case "FileData": - /** @var FileData $resource */ + case 'File': + /** @var File $resource */ // Handle folders - if (str_contains($resource->getFile()->getFileName(), '/')) { - $folders = explode('/', $resource->getFile()->getFileName()); + if (str_contains($resource->getFileName(), '/')) { + $folders = explode('/', $resource->getFileName()); $folderPath = $this->path . '/files'; foreach ($folders as $folder) { @@ -126,18 +120,12 @@ public function importResources(array $resources, callable $callback): void } } - file_put_contents($this->path . '/files/' . $resource->getFile()->getFileName(), $resource->getData(), FILE_APPEND); - break; - case "File": - /** @var File $resource */ - if (\file_exists($this->path . '/files/' . $resource->getFileName())) { - \unlink($this->path . '/files/' . $resource->getFileName()); - }; - break; - } + if ($resource->getStart() === 0 && \file_exists($this->path . '/files/' . $resource->getFileName())) { + unlink($this->path . '/files/' . $resource->getFileName()); + } - if ($resource->getName() !== Resource::TYPE_FILEDATA && $resource->getName() !== Resource::TYPE_DEPLOYMENT) { - $this->data[$resource->getGroup()][$resource->getName()][$resource->getInternalId()] = $resource->asArray(); + file_put_contents($this->path . '/files/' . $resource->getFileName(), $resource->getData(), FILE_APPEND); + break; } $resource->setStatus(Resource::STATUS_SUCCESS); diff --git a/src/Transfer/Resource.php b/src/Transfer/Resource.php index f3bbc6f..971360a 100644 --- a/src/Transfer/Resource.php +++ b/src/Transfer/Resource.php @@ -5,11 +5,17 @@ abstract class Resource { public const STATUS_PENDING = 'PENDING'; + public const STATUS_SUCCESS = 'SUCCESS'; + public const STATUS_ERROR = 'ERROR'; + public const STATUS_SKIPPED = 'SKIP'; + public const STATUS_PROCESSING = 'PROCESSING'; + public const STATUS_WARNING = 'WARNING'; + /** * For some transfers (namely Firebase) we have to keep resources in cache that do not necessarily need to be Transferred * This status is used to mark resources that are not going to be transferred but are still needed for the transfer to work @@ -17,22 +23,33 @@ abstract class Resource */ public const STATUS_DISREGARDED = 'DISREGARDED'; - public const TYPE_ATTRIBUTE = 'Attribute'; + public const TYPE_BUCKET = 'Bucket'; + public const TYPE_COLLECTION = 'Collection'; + public const TYPE_DATABASE = 'Database'; + public const TYPE_DOCUMENT = 'Document'; + public const TYPE_FILE = 'File'; - public const TYPE_FILEDATA = 'FileData'; + public const TYPE_FUNCTION = 'Function'; + public const TYPE_DEPLOYMENT = 'Deployment'; + public const TYPE_HASH = 'Hash'; + public const TYPE_INDEX = 'Index'; + public const TYPE_USER = 'User'; + public const TYPE_ENVVAR = 'EnvVar'; + public const TYPE_TEAM = 'Team'; - public const TYPE_TEAM_MEMBERSHIP = 'TeamMembership'; + + public const TYPE_MEMBERSHIP = 'Membership'; public const ALL_RESOURCES = [ self::TYPE_ATTRIBUTE, @@ -41,7 +58,6 @@ abstract class Resource self::TYPE_DATABASE, self::TYPE_DOCUMENT, self::TYPE_FILE, - self::TYPE_FILEDATA, self::TYPE_FUNCTION, self::TYPE_DEPLOYMENT, self::TYPE_HASH, @@ -49,7 +65,7 @@ abstract class Resource self::TYPE_USER, self::TYPE_ENVVAR, self::TYPE_TEAM, - self::TYPE_TEAM_MEMBERSHIP + self::TYPE_MEMBERSHIP, ]; /** @@ -74,22 +90,16 @@ abstract class Resource /** * Gets the name of the adapter. - * - * @return string */ abstract public static function getName(): string; /** * Get Parent Group - * - * @return */ abstract public function getGroup(): string; /** * Get ID - * - * @return string */ public function getId(): string { @@ -98,20 +108,16 @@ public function getId(): string /** * Set ID - * - * @param string $id - * @return self */ public function setId(string $id): self { $this->id = $id; + return $this; } /** * Get Internal ID - * - * @return string */ public function getInternalId(): string { @@ -120,20 +126,16 @@ public function getInternalId(): string /** * Set Internal ID - * - * @param string $internalId - * @return self */ public function setInternalId(string $internalId): self { $this->internalId = $internalId; + return $this; } /** * Get Status - * - * @return string */ public function getStatus(): string { @@ -142,22 +144,17 @@ public function getStatus(): string /** * Set Status - * - * @param string $status - * @param string $reason - * @return self */ public function setStatus(string $status, string $reason = ''): self { $this->status = $status; $this->reason = $reason; + return $this; } /** * Get Reason - * - * @return string */ public function getReason(): string { @@ -166,8 +163,6 @@ public function getReason(): string /** * As Array - * - * @return array */ abstract public function asArray(): array; } diff --git a/src/Transfer/ResourceCache.php b/src/Transfer/ResourceCache.php index a606e77..b611ef1 100644 --- a/src/Transfer/ResourceCache.php +++ b/src/Transfer/ResourceCache.php @@ -7,7 +7,7 @@ class ResourceCache /** * Resource Cache * - * @var array $resourceCache + * @var array */ protected $resourceCache = []; @@ -18,7 +18,7 @@ public function __construct() public function add($resource) { - if (!$resource->getInternalId()) { + if (! $resource->getInternalId()) { $resourceId = uniqid(); if (isset($this->resourceCache[$resource->getName()][$resourceId])) { $resourceId = uniqid(); @@ -37,7 +37,7 @@ public function addAll(array $resources) public function update($resource) { - if (!in_array($resource, $this->resourceCache[$resource->getName()])) { + if (! in_array($resource, $this->resourceCache[$resource->getName()])) { throw new \Exception('Resource does not exist in cache'); } @@ -53,7 +53,7 @@ public function updateAll($resources) public function remove($resource) { - if (!in_array($resource, $this->resourceCache[$resource->getName()])) { + if (! in_array($resource, $this->resourceCache[$resource->getName()])) { throw new \Exception('Resource does not exist in cache'); } @@ -63,9 +63,8 @@ public function remove($resource) /** * Get Resources * - * @param string|Resource $resourceType - * - * @return Resource[] + * @param string|resource $resourceType + * @return resource[] */ public function get($resource) { diff --git a/src/Transfer/Resources/Auth/Hash.php b/src/Transfer/Resources/Auth/Hash.php index 643de2b..0c76b0d 100644 --- a/src/Transfer/Resources/Auth/Hash.php +++ b/src/Transfer/Resources/Auth/Hash.php @@ -8,26 +8,40 @@ /** * Helper class for hashing. */ - class Hash extends Resource { public const SCRYPT_MODIFIED = 'ScryptModified'; + public const BCRYPT = 'Bcrypt'; + public const MD5 = 'MD5'; + public const ARGON2 = 'Argon2'; + public const SHA256 = 'SHA256'; + public const PHPASS = 'PHPass'; + public const SCRYPT = 'Scrypt'; + public const PLAINTEXT = 'PlainText'; private string $hash; + private string $salt = ''; + private string $algorithm = self::SHA256; + private string $separator = ''; + private string $signingKey = ''; + private int $passwordCpu = 0; + private int $passwordMemory = 0; + private int $passwordParallel = 0; + private int $passwordLength = 0; public function __construct(string $hash, string $salt = '', string $algorithm = self::SHA256, string $separator = '', string $signingKey = '', int $passwordCpu = 0, int $passwordMemory = 0, int $passwordParallel = 0, int $passwordLength = 0) @@ -55,8 +69,6 @@ public function getGroup(): string /** * Get Hash - * - * @return string */ public function getHash(): string { @@ -65,20 +77,16 @@ public function getHash(): string /** * Set Hash - * - * @param string $hash - * @return self */ public function setHash(string $hash): self { $this->hash = $hash; + return $this; } /** * Get Salt - * - * @return string */ public function getSalt(): string { @@ -87,20 +95,16 @@ public function getSalt(): string /** * Set Salt - * - * @param string $salt - * @return self */ public function setSalt(string $salt): self { $this->salt = $salt; + return $this; } /** * Get Algorithm - * - * @return string */ public function getAlgorithm(): string { @@ -109,20 +113,16 @@ public function getAlgorithm(): string /** * Set Algorithm - * - * @param string $algorithm - * @return self */ public function setAlgorithm(string $algorithm): self { $this->algorithm = $algorithm; + return $this; } /** * Get Separator - * - * @return string */ public function getSeparator(): string { @@ -131,20 +131,16 @@ public function getSeparator(): string /** * Set Separator - * - * @param string $separator - * @return self */ public function setSeparator(string $separator): self { $this->separator = $separator; + return $this; } /** * Get Signing Key - * - * @return string */ public function getSigningKey(): string { @@ -153,20 +149,16 @@ public function getSigningKey(): string /** * Set Signing Key - * - * @param string $signingKey - * @return self */ public function setSigningKey(string $signingKey): self { $this->signingKey = $signingKey; + return $this; } /** * Get Password CPU - * - * @return int */ public function getPasswordCpu(): int { @@ -175,20 +167,16 @@ public function getPasswordCpu(): int /** * Set Password CPU - * - * @param int $passwordCpu - * @return self */ public function setPasswordCpu(int $passwordCpu): self { $this->passwordCpu = $passwordCpu; + return $this; } /** * Get Password Memory - * - * @return int */ public function getPasswordMemory(): int { @@ -197,20 +185,16 @@ public function getPasswordMemory(): int /** * Set Password Memory - * - * @param int $passwordMemory - * @return self */ public function setPasswordMemory(int $passwordMemory): self { $this->passwordMemory = $passwordMemory; + return $this; } /** * Get Password Parallel - * - * @return int */ public function getPasswordParallel(): int { @@ -219,20 +203,16 @@ public function getPasswordParallel(): int /** * Set Password Parallel - * - * @param int $passwordParallel - * @return self */ public function setPasswordParallel(int $passwordParallel): self { $this->passwordParallel = $passwordParallel; + return $this; } /** * Get Password Length - * - * @return int */ public function getPasswordLength(): int { @@ -241,20 +221,16 @@ public function getPasswordLength(): int /** * Set Password Length - * - * @param int $passwordLength - * @return self */ public function setPasswordLength(int $passwordLength): self { $this->passwordLength = $passwordLength; + return $this; } /** * As Array - * - * @return array */ public function asArray(): array { diff --git a/src/Transfer/Resources/Auth/TeamMembership.php b/src/Transfer/Resources/Auth/Membership.php similarity index 94% rename from src/Transfer/Resources/Auth/TeamMembership.php rename to src/Transfer/Resources/Auth/Membership.php index 5e82c01..f582e51 100644 --- a/src/Transfer/Resources/Auth/TeamMembership.php +++ b/src/Transfer/Resources/Auth/Membership.php @@ -5,11 +5,14 @@ use Utopia\Transfer\Resource; use Utopia\Transfer\Transfer; -class TeamMembership extends Resource +class Membership extends Resource { protected Team $team; + protected string $userId; + protected array $roles; + protected bool $active = true; public function __construct(Team $team, string $userId, array $roles = [], bool $active = true) @@ -22,7 +25,7 @@ public function __construct(Team $team, string $userId, array $roles = [], bool public static function getName(): string { - return Resource::TYPE_TEAM_MEMBERSHIP; + return Resource::TYPE_MEMBERSHIP; } public function getGroup(): string @@ -38,6 +41,7 @@ public function getTeam(): Team public function setTeam(Team $team): self { $this->team = $team; + return $this; } @@ -49,6 +53,7 @@ public function getUserId(): string public function setUserId(string $userId): self { $this->userId = $userId; + return $this; } @@ -60,6 +65,7 @@ public function getRoles(): array public function setRoles(array $roles): self { $this->roles = $roles; + return $this; } @@ -71,6 +77,7 @@ public function getActive(): bool public function setActive(bool $active): self { $this->active = $active; + return $this; } diff --git a/src/Transfer/Resources/Auth/Team.php b/src/Transfer/Resources/Auth/Team.php index 1d3f5e2..78afcb6 100644 --- a/src/Transfer/Resources/Auth/Team.php +++ b/src/Transfer/Resources/Auth/Team.php @@ -3,14 +3,17 @@ namespace Utopia\Transfer\Resources\Auth; use Utopia\Transfer\Resource; -use Utopia\Transfer\Transfer; use Utopia\Transfer\Resources\User; +use Utopia\Transfer\Transfer; class Team extends Resource { protected string $id; + protected string $name; + protected array $preferences = []; + protected array $members = []; public function __construct(string $id, string $name, array $preferences = [], array $members = []) @@ -39,6 +42,7 @@ public function getTeamName(): string public function setTeamName(string $name): self { $this->name = $name; + return $this; } @@ -50,6 +54,7 @@ public function getId(): string public function setId(string $id): self { $this->id = $id; + return $this; } @@ -61,6 +66,7 @@ public function getPreferences(): array public function setPreferences(array $preferences): self { $this->preferences = $preferences; + return $this; } @@ -70,11 +76,12 @@ public function getMembers(): array } /** - * @param User[] $members + * @param User[] $members */ public function setMembers(array $members): self { $this->members = $members; + return $this; } diff --git a/src/Transfer/Resources/Auth/User.php b/src/Transfer/Resources/Auth/User.php index 238c251..782af79 100644 --- a/src/Transfer/Resources/Auth/User.php +++ b/src/Transfer/Resources/Auth/User.php @@ -3,27 +3,40 @@ namespace Utopia\Transfer\Resources\Auth; use Utopia\Transfer\Resource; -use Utopia\Transfer\Resources\Auth\Hash; use Utopia\Transfer\Transfer; class User extends Resource { public const TYPE_EMAIL = 'email'; + public const TYPE_PHONE = 'phone'; + public const TYPE_ANONYMOUS = 'anonymous'; + public const TYPE_MAGIC = 'magic'; + public const TYPE_OAUTH = 'oauth'; protected string $id = ''; + protected string $email = ''; + protected string $username = ''; + protected ?Hash $passwordHash = null; + protected string $phone = ''; + protected array $types = [self::TYPE_ANONYMOUS]; + protected string $oauthProvider = ''; + protected bool $emailVerified = false; + protected bool $phoneVerified = false; + protected bool $disabled = false; + protected array $preferences = []; public function __construct( @@ -54,8 +67,6 @@ public function __construct( /** * Get Name - * - * @return string */ public static function getName(): string { @@ -64,9 +75,7 @@ public static function getName(): string /** * Get ID - * - * @return string - */ + */ public function getId(): string { return $this->id; @@ -74,20 +83,16 @@ public function getId(): string /** * Set ID - * - * @param string $id - * @return self */ public function setId(string $id): self { $this->id = $id; + return $this; } /** * Get Email - * - * @return string */ public function getEmail(): string { @@ -96,20 +101,16 @@ public function getEmail(): string /** * Set Email - * - * @param string $email - * @return self */ public function setEmail(string $email): self { $this->email = $email; + return $this; } /** * Get Username - * - * @return string */ public function getUsername(): string { @@ -118,20 +119,16 @@ public function getUsername(): string /** * Set Username - * - * @param string $username - * @return self */ public function setUsername(string $username): self { $this->username = $username; + return $this; } /** * Get Password Hash - * - * @return Hash */ public function getPasswordHash(): Hash { @@ -140,20 +137,16 @@ public function getPasswordHash(): Hash /** * Set Password Hash - * - * @param Hash $passwordHash - * @return self */ public function setPasswordHash(Hash $passwordHash): self { $this->passwordHash = $passwordHash; + return $this; } /** * Get Phone - * - * @return string */ public function getPhone(): string { @@ -162,20 +155,16 @@ public function getPhone(): string /** * Set Phone - * - * @param string $phone - * @return self */ public function setPhone(string $phone): self { $this->phone = $phone; + return $this; } /** * Get Type - * - * @return array */ public function getTypes(): array { @@ -184,20 +173,16 @@ public function getTypes(): array /** * Set Types - * - * @param string $types - * @return self */ public function setTypes(string $types): self { $this->types = $types; + return $this; } /** * Get OAuth Provider - * - * @return string */ public function getOAuthProvider(): string { @@ -206,20 +191,16 @@ public function getOAuthProvider(): string /** * Set OAuth Provider - * - * @param string $oauthProvider - * @return self */ public function setOAuthProvider(string $oauthProvider): self { $this->oauthProvider = $oauthProvider; + return $this; } /** * Get Email Verified - * - * @return bool */ public function getEmailVerified(): bool { @@ -228,20 +209,16 @@ public function getEmailVerified(): bool /** * Set Email Verified - * - * @param bool $verified - * @return self */ public function setEmailVerified(bool $verified): self { $this->emailVerified = $verified; + return $this; } /** * Get Email Verified - * - * @return bool */ public function getPhoneVerified(): bool { @@ -250,13 +227,11 @@ public function getPhoneVerified(): bool /** * Set Phone Verified - * - * @param bool $verified - * @return self */ public function setPhoneVerified(bool $verified): self { $this->phoneVerified = $verified; + return $this; } @@ -267,8 +242,6 @@ public function getGroup(): string /** * Get Disabled - * - * @return bool */ public function getDisabled(): bool { @@ -277,20 +250,16 @@ public function getDisabled(): bool /** * Set Disabled - * - * @param bool $disabled - * @return self */ public function setDisabled(bool $disabled): self { $this->disabled = $disabled; + return $this; } /** * Get Preferences - * - * @return array */ public function getPreferences(): array { @@ -299,20 +268,16 @@ public function getPreferences(): array /** * Set Preferences - * - * @param array $preferences - * @return self */ public function setPreferences(array $preferences): self { $this->preferences = $preferences; + return $this; } /** * As Array - * - * @return array */ public function asArray(): array { diff --git a/src/Transfer/Resources/Database/Attribute.php b/src/Transfer/Resources/Database/Attribute.php index 59874f8..4081669 100644 --- a/src/Transfer/Resources/Database/Attribute.php +++ b/src/Transfer/Resources/Database/Attribute.php @@ -8,26 +8,35 @@ abstract class Attribute extends Resource { public const TYPE_STRING = 'stringAttribute'; + public const TYPE_INTEGER = 'intAttribute'; + public const TYPE_FLOAT = 'floatAttribute'; + public const TYPE_BOOLEAN = 'boolAttribute'; + public const TYPE_DATETIME = 'dateTimeAttribute'; + public const TYPE_EMAIL = 'emailAttribute'; + public const TYPE_ENUM = 'enumAttribute'; + public const TYPE_IP = 'IPAttribute'; + public const TYPE_URL = 'URLAttribute'; + public const TYPE_RELATIONSHIP = 'relationshipAttribute'; protected string $key; + protected bool $required; + protected bool $array; + protected Collection $collection; /** - * @param string $key - * @param bool $required - * @param bool $array - * @param int $size + * @param int $size */ public function __construct(string $key, Collection $collection, bool $required = false, bool $array = false) { @@ -57,6 +66,7 @@ public function getKey(): string public function setKey(string $key): self { $this->key = $key; + return $this; } @@ -68,6 +78,7 @@ public function getCollection(): Collection public function setCollection(Collection $collection) { $this->collection = $collection; + return $this; } @@ -79,6 +90,7 @@ public function getRequired(): bool public function setRequired(bool $required): self { $this->required = $required; + return $this; } @@ -90,6 +102,7 @@ public function getArray(): bool public function setArray(bool $array): self { $this->array = $array; + return $this; } diff --git a/src/Transfer/Resources/Database/Attributes/BoolAttribute.php b/src/Transfer/Resources/Database/Attributes/BoolAttribute.php index 1547f0f..c5db96f 100644 --- a/src/Transfer/Resources/Database/Attributes/BoolAttribute.php +++ b/src/Transfer/Resources/Database/Attributes/BoolAttribute.php @@ -8,16 +8,15 @@ class BoolAttribute extends Attribute { protected string $key; + protected bool $required; + protected bool $array; + protected ?bool $default; /** - * @param string $key - * @param Collection $collection - * @param bool $required - * @param bool $array - * @param ?bool $default + * @param ?bool $default */ public function __construct(string $key, Collection $collection, bool $required = false, bool $array = false, ?bool $default = null) { diff --git a/src/Transfer/Resources/Database/Attributes/DateTimeAttribute.php b/src/Transfer/Resources/Database/Attributes/DateTimeAttribute.php index b94d4f9..e2e5680 100644 --- a/src/Transfer/Resources/Database/Attributes/DateTimeAttribute.php +++ b/src/Transfer/Resources/Database/Attributes/DateTimeAttribute.php @@ -10,11 +10,7 @@ class DateTimeAttribute extends Attribute protected ?string $default; /** - * @param string $key - * @param Collection $collection - * @param bool $required - * @param bool $array - * @param ?string $default + * @param ?string $default */ public function __construct(string $key, Collection $collection, bool $required = false, bool $array = false, ?string $default = null) { diff --git a/src/Transfer/Resources/Database/Attributes/EmailAttribute.php b/src/Transfer/Resources/Database/Attributes/EmailAttribute.php index bd1ae66..3203735 100644 --- a/src/Transfer/Resources/Database/Attributes/EmailAttribute.php +++ b/src/Transfer/Resources/Database/Attributes/EmailAttribute.php @@ -10,11 +10,7 @@ class EmailAttribute extends Attribute protected ?string $default; /** - * @param string $key - * @param Collection $collection - * @param bool $required - * @param bool $array - * @param ?string $default + * @param ?string $default */ public function __construct(string $key, Collection $collection, bool $required = false, bool $array = false, ?string $default = null) { diff --git a/src/Transfer/Resources/Database/Attributes/EnumAttribute.php b/src/Transfer/Resources/Database/Attributes/EnumAttribute.php index ad527f5..5c8a85f 100644 --- a/src/Transfer/Resources/Database/Attributes/EnumAttribute.php +++ b/src/Transfer/Resources/Database/Attributes/EnumAttribute.php @@ -8,15 +8,12 @@ class EnumAttribute extends Attribute { protected ?string $default; + protected array $elements; /** - * @param string $key - * @param Collection $collection - * @param string[] $elements - * @param bool $required - * @param bool $array - * @param ?string $default + * @param string[] $elements + * @param ?string $default */ public function __construct(string $key, Collection $collection, array $elements, bool $required, bool $array, ?string $default) { @@ -38,6 +35,7 @@ public function getElements(): array public function setElements(array $elements): self { $this->elements = $elements; + return $this; } diff --git a/src/Transfer/Resources/Database/Attributes/FloatAttribute.php b/src/Transfer/Resources/Database/Attributes/FloatAttribute.php index f4d62a0..f33b275 100644 --- a/src/Transfer/Resources/Database/Attributes/FloatAttribute.php +++ b/src/Transfer/Resources/Database/Attributes/FloatAttribute.php @@ -8,17 +8,15 @@ class FloatAttribute extends Attribute { protected ?float $default; + protected ?float $min; + protected ?float $max; /** - * @param string $key - * @param Collection $collection - * @param bool $required - * @param bool $array - * @param ?float $default - * @param ?float $min - * @param ?float $max + * @param ?float $default + * @param ?float $min + * @param ?float $max */ public function __construct(string $key, Collection $collection, bool $required = false, bool $array = false, ?float $default = null, float $min = null, float $max = null) { @@ -46,12 +44,14 @@ public function getMax(): float|null public function setMin(float $min): self { $this->min = $min; + return $this; } public function setMax(float $max): self { $this->max = $max; + return $this; } @@ -63,6 +63,7 @@ public function getDefault(): ?float public function setDefault(float $default): self { $this->default = $default; + return $this; } diff --git a/src/Transfer/Resources/Database/Attributes/IPAttribute.php b/src/Transfer/Resources/Database/Attributes/IPAttribute.php index b4eb26e..ae46b0e 100644 --- a/src/Transfer/Resources/Database/Attributes/IPAttribute.php +++ b/src/Transfer/Resources/Database/Attributes/IPAttribute.php @@ -10,11 +10,7 @@ class IPAttribute extends Attribute protected ?string $default; /** - * @param string $key - * @param Collection $collection - * @param bool $required - * @param bool $array - * @param string $default + * @param string $default */ public function __construct(string $key, Collection $collection, bool $required = false, bool $array = false, ?string $default = null) { diff --git a/src/Transfer/Resources/Database/Attributes/IntAttribute.php b/src/Transfer/Resources/Database/Attributes/IntAttribute.php index ec5175d..5d46c1c 100644 --- a/src/Transfer/Resources/Database/Attributes/IntAttribute.php +++ b/src/Transfer/Resources/Database/Attributes/IntAttribute.php @@ -8,17 +8,15 @@ class IntAttribute extends Attribute { protected ?int $default; + protected ?int $min; + protected ?int $max; /** - * @param string $key - * @param Collection $collection - * @param bool $required - * @param bool $array - * @param ?int $default - * @param ?int $min - * @param ?int $max + * @param ?int $default + * @param ?int $min + * @param ?int $max */ public function __construct(string $key, Collection $collection, bool $required = false, bool $array = false, ?int $default = null, int $min = null, int $max = null) { @@ -46,12 +44,14 @@ public function getMax(): int|null public function setMin(int|null $min): self { $this->min = $min; + return $this; } public function setMax(int|null $max): self { $this->max = $max; + return $this; } @@ -63,6 +63,7 @@ public function getDefault(): ?int public function setDefault(int $default): self { $this->default = $default; + return $this; } diff --git a/src/Transfer/Resources/Database/Attributes/RelationshipAttribute.php b/src/Transfer/Resources/Database/Attributes/RelationshipAttribute.php index 34b99c0..24e9c1d 100644 --- a/src/Transfer/Resources/Database/Attributes/RelationshipAttribute.php +++ b/src/Transfer/Resources/Database/Attributes/RelationshipAttribute.php @@ -8,24 +8,17 @@ class RelationshipAttribute extends Attribute { protected string $relatedCollection; + protected string $relationType; + protected bool $twoWay; + protected string $twoWayKey; + protected string $onDelete; + protected string $side; - /** - * @param string $key - * @param Collection $collection - * @param bool $required - * @param bool $array - * @param string $relatedCollection - * @param string $relationType - * @param bool $twoWay - * @param string $twoWayKey - * @param string $onDelete - * @param string $side - */ public function __construct(string $key, Collection $collection, bool $required = false, bool $array = false, string $relatedCollection = '', string $relationType = '', bool $twoWay = false, string $twoWayKey = '', string $onDelete = '', string $side = '') { parent::__construct($key, $collection, $required, $array); diff --git a/src/Transfer/Resources/Database/Attributes/StringAttribute.php b/src/Transfer/Resources/Database/Attributes/StringAttribute.php index 210c68d..46abde5 100644 --- a/src/Transfer/Resources/Database/Attributes/StringAttribute.php +++ b/src/Transfer/Resources/Database/Attributes/StringAttribute.php @@ -8,15 +8,11 @@ class StringAttribute extends Attribute { protected ?string $default; + protected int $size = 256; /** - * @param string $key - * @param Collection $collection - * @param bool $required - * @param bool $array - * @param ?string $default - * @param int $size + * @param ?string $default */ public function __construct(string $key, Collection $collection, bool $required = false, bool $array = false, ?string $default = null, int $size = 256) { @@ -38,6 +34,7 @@ public function getSize(): int public function setSize(int $size): self { $this->size = $size; + return $this; } diff --git a/src/Transfer/Resources/Database/Attributes/URLAttribute.php b/src/Transfer/Resources/Database/Attributes/URLAttribute.php index f1bf6c2..3834c74 100644 --- a/src/Transfer/Resources/Database/Attributes/URLAttribute.php +++ b/src/Transfer/Resources/Database/Attributes/URLAttribute.php @@ -10,11 +10,7 @@ class URLAttribute extends Attribute protected ?string $default; /** - * @param string $key - * @param Collection $collection - * @param bool $required - * @param bool $array - * @param ?string $default + * @param ?string $default */ public function __construct(string $key, Collection $collection, bool $required = false, bool $array = false, ?string $default = null) { diff --git a/src/Transfer/Resources/Database/Collection.php b/src/Transfer/Resources/Database/Collection.php index 7874bf5..2e09a74 100644 --- a/src/Transfer/Resources/Database/Collection.php +++ b/src/Transfer/Resources/Database/Collection.php @@ -8,36 +8,23 @@ class Collection extends Resource { /** - * @var list $columns + * @var list */ - private array $columns = []; /** - * @var list $indexes + * @var list */ private array $indexes = []; private Database $database; - /** - * @var array $permissions - */ protected array $permissions = []; - /** - * @var bool $documentSecurity - */ protected bool $documentSecurity = false; - /** - * @var string $name - */ protected string $name; - /** - * @var string $id - */ protected string $id; public function __construct(Database $database, string $name, string $id, bool $documentSecurity = false, array $permissions = []) @@ -67,6 +54,7 @@ public function getDatabase(): Database public function setDatabase(Database $database): self { $this->database = $database; + return $this; } @@ -78,6 +66,7 @@ public function getCollectionName(): string public function setCollectionName(string $name): self { $this->name = $name; + return $this; } @@ -89,6 +78,7 @@ public function getId(): string public function setId(string $id): self { $this->id = $id; + return $this; } @@ -100,6 +90,7 @@ public function getDocumentSecurity(): bool public function setDocumentSecurity(bool $documentSecurity): self { $this->documentSecurity = $documentSecurity; + return $this; } @@ -111,6 +102,7 @@ public function getPermissions(): array public function setPermissions(array $permissions): self { $this->permissions = $permissions; + return $this; } diff --git a/src/Transfer/Resources/Database/Database.php b/src/Transfer/Resources/Database/Database.php index d0f3d52..33bc69f 100644 --- a/src/Transfer/Resources/Database/Database.php +++ b/src/Transfer/Resources/Database/Database.php @@ -16,11 +16,12 @@ class Database extends Resource { /** - * @var list $collections + * @var list */ private array $collections = []; protected string $name; + protected string $id; public function __construct(string $name = '', string $id = '') @@ -52,6 +53,7 @@ public function getId(): string public function setId(string $id): self { $this->id = $id; + return $this; } @@ -64,8 +66,7 @@ public function getCollections(): array } /** - * @param list $collections - * @return self + * @param list $collections */ public function setCollections(array $collections): self { @@ -81,7 +82,7 @@ public function asArray(): array 'id' => $this->id, 'collections' => array_map(function ($collection) { return $collection->asArray(); - }, $this->collections) + }, $this->collections), ]; } } diff --git a/src/Transfer/Resources/Database/Document.php b/src/Transfer/Resources/Database/Document.php index b195e03..676eb2b 100644 --- a/src/Transfer/Resources/Database/Document.php +++ b/src/Transfer/Resources/Database/Document.php @@ -8,9 +8,13 @@ class Document extends Resource { protected string $id; + protected Database $database; + protected Collection $collection; + protected array $data; + protected array $permissions; public function __construct(string $id, Database $database, Collection $collection, array $data = [], array $permissions = []) @@ -40,6 +44,7 @@ public function getId(): string public function setId(string $id): self { $this->id = $id; + return $this; } @@ -51,6 +56,7 @@ public function getDatabase(): Database public function setDatabase(Database $database): self { $this->database = $database; + return $this; } @@ -62,6 +68,7 @@ public function getCollection(): Collection public function setCollection(Collection $collection): self { $this->collection = $collection; + return $this; } @@ -73,13 +80,12 @@ public function getData(): array /** * Set Data * - * @param array $data - * - * @return self + * @param array $data */ public function setData(array $data): self { $this->data = $data; + return $this; } @@ -91,13 +97,12 @@ public function getPermissions(): array /** * Set Permissions * - * @param array $permissions - * - * @return self + * @param array $permissions */ public function setPermissions(array $permissions): self { $this->permissions = $permissions; + return $this; } diff --git a/src/Transfer/Resources/Database/Index.php b/src/Transfer/Resources/Database/Index.php index 229141c..0faf205 100644 --- a/src/Transfer/Resources/Database/Index.php +++ b/src/Transfer/Resources/Database/Index.php @@ -8,23 +8,24 @@ class Index extends Resource { protected string $key; + protected string $type; + protected array $attributes; + protected array $orders; + protected Collection $collection; public const TYPE_UNIQUE = 'unique'; + public const TYPE_FULLTEXT = 'fulltext'; + public const TYPE_KEY = 'key'; /** - * @param string $key - * @param string $type - * @param Collection $collection - * @param list $attributes - * @param array $orders + * @param list $attributes */ - public function __construct(string $id, string $key, Collection $collection, string $type = '', array $attributes = [], array $orders = []) { $this->id = $id; @@ -53,6 +54,7 @@ public function getKey(): string public function setKey(string $key): self { $this->key = $key; + return $this; } @@ -64,6 +66,7 @@ public function getCollection(): Collection public function setCollection(Collection $collection): self { $this->collection = $collection; + return $this; } @@ -75,6 +78,7 @@ public function getType(): string public function setType(string $type): self { $this->type = $type; + return $this; } @@ -84,12 +88,12 @@ public function getAttributes(): array } /** - * @param list $attributes - * @return self + * @param list $attributes */ public function setAttributes(array $attributes): self { $this->attributes = $attributes; + return $this; } @@ -101,6 +105,7 @@ public function getOrders(): array public function setOrders(array $orders): self { $this->orders = $orders; + return $this; } diff --git a/src/Transfer/Resources/Functions/Deployment.php b/src/Transfer/Resources/Functions/Deployment.php index 6d8710b..2f6fe40 100644 --- a/src/Transfer/Resources/Functions/Deployment.php +++ b/src/Transfer/Resources/Functions/Deployment.php @@ -3,18 +3,24 @@ namespace Utopia\Transfer\Resources\Functions; use Utopia\Transfer\Resource; -use Utopia\Transfer\Resources\Functions\Func; use Utopia\Transfer\Transfer; class Deployment extends Resource { protected string $id; + protected Func $func; + protected string $entrypoint; + protected int $size; + protected int $start; + protected int $end; + protected string $data; + protected bool $activated; public function __construct(string $id, Func $func, int $size, string $entrypoint, int $start = 0, int $end = 0, string $data = '', bool $activated = false) @@ -47,6 +53,7 @@ public function getId(): string public function setId(string $id): self { $this->id = $id; + return $this; } @@ -58,12 +65,14 @@ public function getFunction(): Func public function setFunction(Func $func): self { $this->func = $func; + return $this; } public function setSize(int $size): self { $this->size = $size; + return $this; } @@ -75,6 +84,7 @@ public function getSize(): int public function setEntrypoint(string $entrypoint): self { $this->entrypoint = $entrypoint; + return $this; } @@ -86,6 +96,7 @@ public function getEntrypoint(): string public function setStart(int $start): self { $this->start = $start; + return $this; } @@ -97,6 +108,7 @@ public function getStart(): int public function setEnd(int $end): self { $this->end = $end; + return $this; } @@ -108,6 +120,7 @@ public function getEnd(): int public function setData(string $data): self { $this->data = $data; + return $this; } @@ -119,6 +132,7 @@ public function getData(): string public function setActivated(bool $activated): self { $this->activated = $activated; + return $this; } diff --git a/src/Transfer/Resources/Functions/EnvVar.php b/src/Transfer/Resources/Functions/EnvVar.php index 57f40c0..d11f3b0 100644 --- a/src/Transfer/Resources/Functions/EnvVar.php +++ b/src/Transfer/Resources/Functions/EnvVar.php @@ -8,7 +8,9 @@ class EnvVar extends Resource { protected Func $func; + protected string $key; + protected string $value; public function __construct(Func $func, string $key, string $value) @@ -36,6 +38,7 @@ public function getFunc(): Func public function setFunc(Func $func): self { $this->func = $func; + return $this; } @@ -47,6 +50,7 @@ public function getKey(): string public function setKey(string $key): self { $this->key = $key; + return $this; } @@ -58,6 +62,7 @@ public function getValue(): string public function setValue(string $value): self { $this->value = $value; + return $this; } diff --git a/src/Transfer/Resources/Functions/Func.php b/src/Transfer/Resources/Functions/Func.php index 7121955..ef65d3d 100644 --- a/src/Transfer/Resources/Functions/Func.php +++ b/src/Transfer/Resources/Functions/Func.php @@ -8,12 +8,19 @@ class Func extends Resource { protected string $name; + protected string $id; + protected array $execute; + protected bool $enabled; + protected string $runtime; + protected array $events; + protected string $schedule; + protected int $timeout; public function __construct(string $name, string $id, string $runtime, array $execute = [], bool $enabled = true, array $events = [], string $schedule = '', int $timeout = 0) @@ -51,6 +58,7 @@ public function getId(): string public function setId(string $id): self { $this->id = $id; + return $this; } @@ -62,6 +70,7 @@ public function getExecute(): array public function setExecute(array $execute): self { $this->execute = $execute; + return $this; } @@ -73,6 +82,7 @@ public function getEnabled(): bool public function setEnabled(bool $enabled): self { $this->enabled = $enabled; + return $this; } @@ -84,6 +94,7 @@ public function getRuntime(): string public function setRuntime(string $runtime): self { $this->runtime = $runtime; + return $this; } @@ -95,6 +106,7 @@ public function getEvents(): array public function setEvents(array $events): self { $this->events = $events; + return $this; } @@ -106,6 +118,7 @@ public function getSchedule(): string public function setSchedule(string $schedule): self { $this->schedule = $schedule; + return $this; } @@ -117,6 +130,7 @@ public function getTimeout(): int public function setTimeout(int $timeout): self { $this->timeout = $timeout; + return $this; } diff --git a/src/Transfer/Resources/Storage/Bucket.php b/src/Transfer/Resources/Storage/Bucket.php index 3c54119..6dfc003 100644 --- a/src/Transfer/Resources/Storage/Bucket.php +++ b/src/Transfer/Resources/Storage/Bucket.php @@ -8,14 +8,23 @@ class Bucket extends Resource { protected string $id; + protected array $permissions; + protected bool $fileSecurity; + protected string $name; + protected bool $enabled; + protected int $maxFileSize; + protected array $allowedFileExtensions; + protected string $compression; + protected bool $encryption; + protected bool $antiVirus; public function __construct(string $id = '', array $permissions = [], bool $fileSecurity = false, string $name = '', bool $enabled = false, int $maxFileSize = 0, array $allowedFileExtensions = [], string $compression = '', bool $encryption = false, bool $antiVirus = false) @@ -50,6 +59,7 @@ public function getId(): string public function setId(string $id): self { $this->id = $id; + return $this; } @@ -61,6 +71,7 @@ public function getPermissions(): array public function setPermissions(array $permissions): self { $this->permissions = $permissions; + return $this; } @@ -72,6 +83,7 @@ public function getFileSecurity(): bool public function setFileSecurity(bool $fileSecurity): self { $this->fileSecurity = $fileSecurity; + return $this; } @@ -83,6 +95,7 @@ public function getBucketName(): string public function setBucketName(string $name): self { $this->name = $name; + return $this; } @@ -94,6 +107,7 @@ public function getEnabled(): bool public function setEnabled(bool $enabled): self { $this->enabled = $enabled; + return $this; } @@ -105,6 +119,7 @@ public function getMaxFileSize(): int public function setMaxFileSize(int $maxFileSize): self { $this->maxFileSize = $maxFileSize; + return $this; } @@ -116,6 +131,7 @@ public function getAllowedFileExtensions(): array public function setAllowedFileExtensions(array $allowedFileExtensions): self { $this->allowedFileExtensions = $allowedFileExtensions; + return $this; } @@ -127,6 +143,7 @@ public function getCompression(): string public function setCompression(string $compression): self { $this->compression = $compression; + return $this; } @@ -138,6 +155,7 @@ public function getEncryption(): bool public function setEncryption(bool $encryption): self { $this->encryption = $encryption; + return $this; } @@ -149,6 +167,7 @@ public function getAntiVirus(): bool public function setAntiVirus(bool $antiVirus): self { $this->antiVirus = $antiVirus; + return $this; } diff --git a/src/Transfer/Resources/Storage/File.php b/src/Transfer/Resources/Storage/File.php index 050f56d..4c09743 100644 --- a/src/Transfer/Resources/Storage/File.php +++ b/src/Transfer/Resources/Storage/File.php @@ -8,14 +8,26 @@ class File extends Resource { protected string $id; + protected Bucket $bucket; + protected string $name; + protected string $signature; + protected string $mimeType; + protected array $permissions; + protected int $size; - public function __construct(string $id = '', Bucket $bucket = null, string $name = '', string $signature = '', string $mimeType = '', array $permissions = [], int $size = 0) + protected string $data; + + protected int $start; + + protected int $end; + + public function __construct(string $id = '', Bucket $bucket = null, string $name = '', string $signature = '', string $mimeType = '', array $permissions = [], int $size = 0, string $data = '', int $start = 0, int $end = 0) { $this->id = $id; $this->bucket = $bucket; @@ -24,6 +36,9 @@ public function __construct(string $id = '', Bucket $bucket = null, string $name $this->mimeType = $mimeType; $this->permissions = $permissions; $this->size = $size; + $this->data = $data; + $this->start = $start; + $this->end = $end; } public static function getName(): string @@ -44,6 +59,7 @@ public function getId(): string public function setId(string $id): self { $this->id = $id; + return $this; } @@ -55,6 +71,7 @@ public function getBucket(): Bucket public function setBucket(Bucket $bucket): self { $this->bucket = $bucket; + return $this; } @@ -63,12 +80,18 @@ public function getFileName(): string return $this->name; } - public function setFileName(string $name): self + public function setName(string $name): self { $this->name = $name; + return $this; } + public function getSize(): int + { + return $this->size; + } + public function getSignature(): string { return $this->signature; @@ -77,6 +100,7 @@ public function getSignature(): string public function setSignature(string $signature): self { $this->signature = $signature; + return $this; } @@ -88,6 +112,7 @@ public function getMimeType(): string public function setMimeType(string $mimeType): self { $this->mimeType = $mimeType; + return $this; } @@ -96,26 +121,71 @@ public function getPermissions(): array return $this->permissions; } - public function getSize(): int + public function setPermissions(array $permissions): self { - return $this->size; + $this->permissions = $permissions; + + return $this; } - public function setSize(int $size): self + public function getData(): string { - $this->size = $size; + return $this->data; + } + + public function setData(string $data): self + { + $this->data = $data; + return $this; } + public function getStart(): int + { + return $this->start; + } + + public function setStart(int $start): self + { + $this->start = $start; + + return $this; + } + + public function getEnd(): int + { + return $this->end; + } + + public function setEnd(int $end): self + { + $this->end = $end; + + return $this; + } + + public function getSizeInBytes(): int + { + return strlen($this->data); + } + + public function getChunkSize(): int + { + return $this->end - $this->start; + } + public function asArray(): array { return [ 'id' => $this->id, - 'bucket' => $this->bucket->asArray(), + 'bucket' => $this->bucket->getId(), 'name' => $this->name, 'signature' => $this->signature, 'mimeType' => $this->mimeType, + 'permissions' => $this->permissions, 'size' => $this->size, + 'start' => $this->start, + 'end' => $this->end, ]; } } diff --git a/src/Transfer/Resources/Storage/FileData.php b/src/Transfer/Resources/Storage/FileData.php deleted file mode 100644 index 11f6fd4..0000000 --- a/src/Transfer/Resources/Storage/FileData.php +++ /dev/null @@ -1,61 +0,0 @@ -data = $data; - $this->start = $start; - $this->end = $end; - $this->file = $file; - } - - public static function getName(): string - { - return Resource::TYPE_FILEDATA; - } - - public function getGroup(): string - { - return Transfer::GROUP_STORAGE; - } - - public function getData(): string - { - return $this->data; - } - - public function getStart(): int - { - return $this->start; - } - - public function getEnd(): int - { - return $this->end; - } - - public function getFile(): File - { - return $this->file; - } - - public function asArray(): array - { - return [ - 'start' => $this->start, - 'end' => $this->end, - 'file' => $this->file->asArray(), - ]; - } -} diff --git a/src/Transfer/Source.php b/src/Transfer/Source.php index ac4d22b..b250ad4 100644 --- a/src/Transfer/Source.php +++ b/src/Transfer/Source.php @@ -13,17 +13,14 @@ public function callback(array $resources): void /** * Transfer Resources into destination - * - * @param array $resources - * @param callable $callback */ public function run(array $resources, callable $callback): void { $this->transferCallback = function (array $returnedResources) use ($callback, $resources) { $prunedResurces = []; foreach ($returnedResources as $resource) { - /** @var Resource $resource */ - if (!in_array($resource->getName(), $resources)) { + /** @var resource $resource */ + if (! in_array($resource->getName(), $resources)) { $resource->setStatus(Resource::STATUS_SKIPPED); } else { $prunedResurces[] = $resource; @@ -40,9 +37,7 @@ public function run(array $resources, callable $callback): void /** * Export Resources * - * @param string[] $resources - * @param int $batchSize - * + * @param string[] $resources * @return void */ public function exportResources(array $resources, int $batchSize) @@ -87,9 +82,7 @@ public function exportResources(array $resources, int $batchSize) /** * Export Auth Group * - * @param int $batchSize - * @param array $resources Resources to export - * + * @param array $resources Resources to export * @return void */ abstract public function exportAuthGroup(int $batchSize, array $resources); @@ -97,9 +90,8 @@ abstract public function exportAuthGroup(int $batchSize, array $resources); /** * Export Databases Group * - * @param int $batchSize Max 100 - * @param array $resources Resources to export - * + * @param int $batchSize Max 100 + * @param array $resources Resources to export * @return void */ abstract public function exportDatabasesGroup(int $batchSize, array $resources); @@ -107,9 +99,8 @@ abstract public function exportDatabasesGroup(int $batchSize, array $resources); /** * Export Storage Group * - * @param int $batchSize Max 5 - * @param array $resources Resources to export - * + * @param int $batchSize Max 5 + * @param array $resources Resources to export * @return void */ abstract public function exportStorageGroup(int $batchSize, array $resources); @@ -117,9 +108,8 @@ abstract public function exportStorageGroup(int $batchSize, array $resources); /** * Export Functions Group * - * @param int $batchSize Max 100 - * @param array $resources Resources to export - * + * @param int $batchSize Max 100 + * @param array $resources Resources to export * @return void */ abstract public function exportFunctionsGroup(int $batchSize, array $resources); diff --git a/src/Transfer/Sources/Appwrite.php b/src/Transfer/Sources/Appwrite.php index c055b42..ac8d5c3 100644 --- a/src/Transfer/Sources/Appwrite.php +++ b/src/Transfer/Sources/Appwrite.php @@ -2,7 +2,6 @@ namespace Utopia\Transfer\Sources; -use Utopia\Transfer\Source; use Appwrite\Client; use Appwrite\Query; use Appwrite\Services\Databases; @@ -11,8 +10,11 @@ use Appwrite\Services\Teams; use Appwrite\Services\Users; use Utopia\Transfer\Resource; +use Utopia\Transfer\Resources\Auth\Hash; +use Utopia\Transfer\Resources\Auth\Team; +use Utopia\Transfer\Resources\Auth\Membership; +use Utopia\Transfer\Resources\Auth\User; use Utopia\Transfer\Resources\Database\Attribute; -use Utopia\Transfer\Transfer; use Utopia\Transfer\Resources\Database\Attributes\BoolAttribute; use Utopia\Transfer\Resources\Database\Attributes\DateTimeAttribute; use Utopia\Transfer\Resources\Database\Attributes\EmailAttribute; @@ -20,23 +22,20 @@ use Utopia\Transfer\Resources\Database\Attributes\FloatAttribute; use Utopia\Transfer\Resources\Database\Attributes\IntAttribute; use Utopia\Transfer\Resources\Database\Attributes\IPAttribute; +use Utopia\Transfer\Resources\Database\Attributes\RelationshipAttribute; use Utopia\Transfer\Resources\Database\Attributes\StringAttribute; use Utopia\Transfer\Resources\Database\Attributes\URLAttribute; -use Utopia\Transfer\Resources\Database\Attributes\RelationshipAttribute; use Utopia\Transfer\Resources\Database\Collection; use Utopia\Transfer\Resources\Database\Database; use Utopia\Transfer\Resources\Database\Document; -use Utopia\Transfer\Resources\Auth\User; -use Utopia\Transfer\Resources\Auth\Hash; use Utopia\Transfer\Resources\Database\Index; -use Utopia\Transfer\Resources\Storage\Bucket; +use Utopia\Transfer\Resources\Functions\Deployment; use Utopia\Transfer\Resources\Functions\EnvVar; -use Utopia\Transfer\Resources\Storage\File; -use Utopia\Transfer\Resources\Storage\FileData; use Utopia\Transfer\Resources\Functions\Func; -use Utopia\Transfer\Resources\Auth\Team; -use Utopia\Transfer\Resources\Auth\TeamMembership; -use Utopia\Transfer\Resources\Functions\Deployment; +use Utopia\Transfer\Resources\Storage\Bucket; +use Utopia\Transfer\Resources\Storage\File; +use Utopia\Transfer\Source; +use Utopia\Transfer\Transfer; class Appwrite extends Source { @@ -45,22 +44,13 @@ class Appwrite extends Source */ protected $client = null; - /** - * @var string - */ protected string $project = ''; - /** - * @var string - */ protected string $key = ''; /** * Constructor * - * @param string $project - * @param string $endpoint - * @param string $key * * @return self */ @@ -80,18 +70,14 @@ public function __construct(string $project, string $endpoint, string $key) /** * Get Name - * - * @return string */ public static function getName(): string { - return "Appwrite"; + return 'Appwrite'; } /** * Get Supported Resources - * - * @return array */ public function getSupportedResources(): array { @@ -99,7 +85,7 @@ public function getSupportedResources(): array // Auth Resource::TYPE_USER, Resource::TYPE_TEAM, - Resource::TYPE_TEAM_MEMBERSHIP, + Resource::TYPE_MEMBERSHIP, // Database Resource::TYPE_DATABASE, @@ -111,7 +97,6 @@ public function getSupportedResources(): array // Storage Resource::TYPE_BUCKET, Resource::TYPE_FILE, - Resource::TYPE_FILEDATA, // Functions Resource::TYPE_FUNCTION, @@ -149,11 +134,11 @@ public function report(array $resources = []): array $report[Resource::TYPE_TEAM] = $teamsClient->list()['total']; } - if (in_array(Resource::TYPE_TEAM_MEMBERSHIP, $resources)) { - $report[Resource::TYPE_TEAM_MEMBERSHIP] = 0; + if (in_array(Resource::TYPE_MEMBERSHIP, $resources)) { + $report[Resource::TYPE_MEMBERSHIP] = 0; $teams = $teamsClient->list()['teams']; foreach ($teams as $team) { - $report[Resource::TYPE_TEAM_MEMBERSHIP] += $teamsClient->listMemberships($team['$id'])['total']; + $report[Resource::TYPE_MEMBERSHIP] += $teamsClient->listMemberships($team['$id'])['total']; } } @@ -258,8 +243,7 @@ public function report(array $resources = []): array /** * Export Auth Resources * - * @param int $batchSize Max 100 - * + * @param int $batchSize Max 100 * @return void */ public function exportAuthGroup(int $batchSize, array $resources) @@ -272,8 +256,8 @@ public function exportAuthGroup(int $batchSize, array $resources) $this->exportTeams($batchSize); } - if (in_array(Resource::TYPE_TEAM_MEMBERSHIP, $resources)) { - $this->exportTeamMemberships($batchSize); + if (in_array(Resource::TYPE_MEMBERSHIP, $resources)) { + $this->exportMemberships($batchSize); } } @@ -294,23 +278,23 @@ private function exportUsers(int $batchSize) $response = $usersClient->list($queries); - if ($response["total"] == 0) { + if ($response['total'] == 0) { break; } - foreach ($response["users"] as $user) { + foreach ($response['users'] as $user) { $users[] = new User( $user['$id'], - $user["email"], - $user["name"], - $user["password"] ? new Hash($user["password"], $user["hash"]) : null, - $user["phone"], + $user['email'], + $user['name'], + $user['password'] ? new Hash($user['password'], $user['hash']) : null, + $user['phone'], $this->calculateTypes($user), - "", - $user["emailVerification"], - $user["phoneVerification"], - !$user["status"], - $user["prefs"] + '', + $user['emailVerification'], + $user['phoneVerification'], + ! $user['status'], + $user['prefs'] ); $lastDocument = $user['$id']; @@ -341,15 +325,15 @@ private function exportTeams(int $batchSize) $response = $teamsClient->list($queries); - if ($response["total"] == 0) { + if ($response['total'] == 0) { break; } - foreach ($response["teams"] as $team) { + foreach ($response['teams'] as $team) { $teams[] = new Team( $team['$id'], - $team["name"], - $team["prefs"], + $team['name'], + $team['prefs'], ); $lastDocument = $team['$id']; @@ -363,7 +347,7 @@ private function exportTeams(int $batchSize) } } - private function exportTeamMemberships(int $batchSize) + private function exportMemberships(int $batchSize) { $teamsClient = new Teams($this->client); @@ -373,6 +357,7 @@ private function exportTeamMemberships(int $batchSize) $cacheTeams = $this->resourceCache->get(Team::getName()); foreach ($cacheTeams as $team) { + /** @param Team $team */ while (true) { $memberships = []; @@ -384,12 +369,12 @@ private function exportTeamMemberships(int $batchSize) $response = $teamsClient->listMemberships($team->getId(), $queries); - if ($response["total"] == 0) { + if ($response['total'] == 0) { break; } - foreach ($response["memberships"] as $membership) { - $memberships[] = new TeamMembership( + foreach ($response['memberships'] as $membership) { + $memberships[] = new Membership( $team, $membership['userId'], $membership['roles'], @@ -431,7 +416,6 @@ public function exportDatabasesGroup(int $batchSize, array $resources) } } - private function exportDocuments(int $batchSize) { $databaseClient = new Databases($this->client); @@ -456,7 +440,7 @@ private function exportDocuments(int $batchSize) $queries ); - foreach ($response["documents"] as $document) { + foreach ($response['documents'] as $document) { $id = $document['$id']; $permissions = $document['$permissions']; unset($document['$id']); @@ -478,7 +462,7 @@ private function exportDocuments(int $batchSize) $this->callback($documents); - if (count($response["documents"]) < $batchSize) { + if (count($response['documents']) < $batchSize) { break; } } @@ -487,115 +471,115 @@ private function exportDocuments(int $batchSize) private function convertAttribute(array $value, Collection $collection): Attribute { - switch ($value["type"]) { - case "string": - if (!isset($value["format"])) { + switch ($value['type']) { + case 'string': + if (! isset($value['format'])) { return new StringAttribute( - $value["key"], + $value['key'], $collection, - $value["required"], - $value["array"], - $value["default"], - $value["size"] ?? 0 + $value['required'], + $value['array'], + $value['default'], + $value['size'] ?? 0 ); } - switch ($value["format"]) { - case "email": + switch ($value['format']) { + case 'email': return new EmailAttribute( - $value["key"], + $value['key'], $collection, - $value["required"], - $value["array"], - $value["default"] + $value['required'], + $value['array'], + $value['default'] ); - case "enum": + case 'enum': return new EnumAttribute( - $value["key"], + $value['key'], $collection, - $value["elements"], - $value["required"], - $value["array"], - $value["default"] + $value['elements'], + $value['required'], + $value['array'], + $value['default'] ); - case "url": + case 'url': return new URLAttribute( - $value["key"], + $value['key'], $collection, - $value["required"], - $value["array"], - $value["default"] + $value['required'], + $value['array'], + $value['default'] ); - case "ip": + case 'ip': return new IPAttribute( - $value["key"], + $value['key'], $collection, - $value["required"], - $value["array"], - $value["default"] + $value['required'], + $value['array'], + $value['default'] ); - case "datetime": + case 'datetime': return new DateTimeAttribute( - $value["key"], + $value['key'], $collection, - $value["required"], - $value["array"], - $value["default"] + $value['required'], + $value['array'], + $value['default'] ); default: return new StringAttribute( - $value["key"], + $value['key'], $collection, - $value["required"], - $value["array"], - $value["default"], - $value["size"] ?? 0 + $value['required'], + $value['array'], + $value['default'], + $value['size'] ?? 0 ); } - case "boolean": + case 'boolean': return new BoolAttribute( - $value["key"], + $value['key'], $collection, - $value["required"], - $value["array"], - $value["default"] + $value['required'], + $value['array'], + $value['default'] ); - case "integer": + case 'integer': return new IntAttribute( - $value["key"], + $value['key'], $collection, - $value["required"], - $value["array"], - $value["default"], - $value["min"] ?? 0, - $value["max"] ?? 0 + $value['required'], + $value['array'], + $value['default'], + $value['min'] ?? 0, + $value['max'] ?? 0 ); - case "double": + case 'double': return new FloatAttribute( - $value["key"], + $value['key'], $collection, - $value["required"], - $value["array"], - $value["default"], - $value["min"] ?? 0, - $value["max"] ?? 0 + $value['required'], + $value['array'], + $value['default'], + $value['min'] ?? 0, + $value['max'] ?? 0 ); - case "relationship": + case 'relationship': return new RelationshipAttribute( - $value["key"], + $value['key'], $collection, - $value["required"], - $value["array"], - $value["relatedCollection"], - $value["relationType"], - $value["twoWay"], - $value["twoWayKey"], - $value["onDelete"], - $value["side"] + $value['required'], + $value['array'], + $value['relatedCollection'], + $value['relationType'], + $value['twoWay'], + $value['twoWayKey'], + $value['onDelete'], + $value['side'] ); } - throw new \Exception("Unknown attribute type: " . $value["type"]); + throw new \Exception('Unknown attribute type: '.$value['type']); } private function exportDatabases(int $batchSize) @@ -615,9 +599,9 @@ private function exportDatabases(int $batchSize) $response = $databaseClient->list($queries); - foreach ($response["databases"] as $database) { + foreach ($response['databases'] as $database) { $newDatabase = new Database( - $database["name"], + $database['name'], $database['$id'] ); @@ -654,12 +638,12 @@ private function exportCollections(int $batchSize) $queries ); - foreach ($response["collections"] as $collection) { + foreach ($response['collections'] as $collection) { $newCollection = new Collection( $database, - $collection["name"], + $collection['name'], $collection['$id'], - $collection["documentSecurity"], + $collection['documentSecurity'], $collection['$permissions'] ); @@ -683,7 +667,6 @@ private function exportAttributes(int $batchSize) $lastDocument = null; $collections = $this->resourceCache->get(Collection::getName()); /** @var Collection[] $collections */ - foreach ($collections as $collection) { while (true) { $queries = [Query::limit($batchSize)]; @@ -699,7 +682,7 @@ private function exportAttributes(int $batchSize) $queries ); - foreach ($response["attributes"] as $attribute) { + foreach ($response['attributes'] as $attribute) { $attributes[] = $this->convertAttribute($attribute, $collection); } @@ -736,14 +719,14 @@ private function exportIndexes(int $batchSize) $queries ); - foreach ($response["indexes"] as $index) { + foreach ($response['indexes'] as $index) { $indexes[] = new Index( - "unique()", - $index["key"], + 'unique()', + $index['key'], $collection, - $index["type"], - $index["attributes"], - $index["orders"] + $index['type'], + $index['attributes'], + $index['orders'] ); } @@ -758,17 +741,17 @@ private function exportIndexes(int $batchSize) private function calculateTypes(array $user): array { - if (empty($user["email"]) && empty($user["phone"])) { + if (empty($user['email']) && empty($user['phone'])) { return [User::TYPE_ANONYMOUS]; } $types = []; - if (!empty($user["email"])) { + if (! empty($user['email'])) { $types[] = User::TYPE_EMAIL; } - if (!empty($user["phone"])) { + if (! empty($user['phone'])) { $types[] = User::TYPE_PHONE; } @@ -837,7 +820,7 @@ private function exportFiles(int $batchSize) $queries ); - foreach ($response["files"] as $file) { + foreach ($response['files'] as $file) { $this->handleDataTransfer(new File( $file['$id'], $bucket, @@ -851,7 +834,7 @@ private function exportFiles(int $batchSize) $lastDocument = $file['$id']; } - if (count($response["files"]) < $batchSize) { + if (count($response['files']) < $batchSize) { break; } } @@ -880,12 +863,11 @@ private function handleDataTransfer(File $file) ); // Send the chunk to the callback function - $this->callback([new FileData( - $chunkData, - $start, - $end, - $file - )]); + $file->setData($chunkData) + ->setStart($start) + ->setEnd($end); + + $this->callback([$file]); // Update the range $start += Transfer::STORAGE_MAX_CHUNK_SIZE; @@ -955,7 +937,6 @@ public function exportDeployments(int $batchSize) foreach ($functions as $func) { /** @var Func $func */ - $lastDocument = null; while (true) { $queries = [Query::limit($batchSize)]; @@ -969,13 +950,13 @@ public function exportDeployments(int $batchSize) $queries ); - foreach ($response["deployments"] as $deployment) { + foreach ($response['deployments'] as $deployment) { $this->handleDeploymentData($func, $deployment); $lastDocument = $deployment['$id']; } - if (count($response["deployments"]) < $batchSize) { + if (count($response['deployments']) < $batchSize) { break; } } diff --git a/src/Transfer/Sources/Firebase.php b/src/Transfer/Sources/Firebase.php index 8155c6d..96f5341 100644 --- a/src/Transfer/Sources/Firebase.php +++ b/src/Transfer/Sources/Firebase.php @@ -3,8 +3,6 @@ namespace Utopia\Transfer\Sources; use Utopia\Transfer\Resource; -use Utopia\Transfer\Source; -use Utopia\Transfer\Transfer; use Utopia\Transfer\Resources\Auth\Hash; use Utopia\Transfer\Resources\Auth\User; use Utopia\Transfer\Resources\Database\Attribute; @@ -18,13 +16,17 @@ use Utopia\Transfer\Resources\Database\Document; use Utopia\Transfer\Resources\Storage\Bucket; use Utopia\Transfer\Resources\Storage\File; -use Utopia\Transfer\Resources\Storage\FileData; +use Utopia\Transfer\Source; +use Utopia\Transfer\Transfer; class Firebase extends Source { private array $serviceAccount; + private string $projectID; + private string $currentToken = ''; + private int $tokenExpires = 0; public function __construct(array $serviceAccount) @@ -50,21 +52,21 @@ private function calculateJWT(): string 'scope' => 'https://www.googleapis.com/auth/firebase https://www.googleapis.com/auth/cloud-platform https://www.googleapis.com/auth/datastore', 'exp' => time() + 3600, 'iat' => time(), - 'aud' => 'https://oauth2.googleapis.com/token' + 'aud' => 'https://oauth2.googleapis.com/token', ]; $jwtHeader = [ 'alg' => 'RS256', - 'typ' => 'JWT' + 'typ' => 'JWT', ]; - $jwtPayload = $this->base64UrlEncode(json_encode($jwtHeader)) . '.' . $this->base64UrlEncode(json_encode($jwtClaim)); + $jwtPayload = $this->base64UrlEncode(json_encode($jwtHeader)).'.'.$this->base64UrlEncode(json_encode($jwtClaim)); $jwtSignature = ''; openssl_sign($jwtPayload, $jwtSignature, $this->serviceAccount['private_key'], 'sha256'); $jwtSignature = $this->base64UrlEncode($jwtSignature); - return $jwtPayload . '.' . $jwtSignature; + return $jwtPayload.'.'.$jwtSignature; } /** @@ -81,18 +83,18 @@ private function authenticate() 'Content-Type' => 'application/x-www-form-urlencoded', ], [ 'grant_type' => 'urn:ietf:params:oauth:grant-type:jwt-bearer', - 'assertion' => $this->calculateJWT() + 'assertion' => $this->calculateJWT(), ]); $this->currentToken = $response['access_token']; $this->tokenExpires = time() + $response['expires_in']; - $this->headers['Authorization'] = 'Bearer ' . $this->currentToken; + $this->headers['Authorization'] = 'Bearer '.$this->currentToken; } catch (\Exception $e) { - throw new \Exception('Failed to authenticate with Firebase: ' . $e->getMessage()); + throw new \Exception('Failed to authenticate with Firebase: '.$e->getMessage()); } } - public function call(string $method, string $path = '', array $headers = array(), array $params = array()): array|string + public function call(string $method, string $path = '', array $headers = [], array $params = []): array|string { $this->authenticate(); @@ -101,8 +103,6 @@ public function call(string $method, string $path = '', array $headers = array() /** * Get Supported Resources - * - * @return array */ public function getSupportedResources(): array { @@ -119,7 +119,6 @@ public function getSupportedResources(): array // Storage Resource::TYPE_BUCKET, Resource::TYPE_FILE, - Resource::TYPE_FILEDATA ]; } @@ -138,7 +137,7 @@ public function exportAuthGroup(int $batchSize, array $resources) private function exportUsers(int $batchSize) { // Fetch our Hash Config - $hashConfig = ($this->call('GET', 'https://identitytoolkit.googleapis.com/admin/v2/projects/' . $this->projectID . '/config'))["signIn"]["hashConfig"]; + $hashConfig = ($this->call('GET', 'https://identitytoolkit.googleapis.com/admin/v2/projects/'.$this->projectID.'/config'))['signIn']['hashConfig']; $nextPageToken = null; @@ -147,33 +146,33 @@ private function exportUsers(int $batchSize) $users = []; $request = [ - "targetProjectId" => $this->projectID, - "maxResults" => $batchSize, + 'targetProjectId' => $this->projectID, + 'maxResults' => $batchSize, ]; if ($nextPageToken) { - $request["nextPageToken"] = $nextPageToken; + $request['nextPageToken'] = $nextPageToken; } $response = $this->call('POST', 'https://identitytoolkit.googleapis.com/identitytoolkit/v3/relyingparty/downloadAccount', [ 'Content-Type' => 'application/json', ], $request); - $result = $response["users"]; - $nextPageToken = $response["nextPageToken"] ?? null; + $result = $response['users']; + $nextPageToken = $response['nextPageToken'] ?? null; foreach ($result as $user) { $users[] = new User( - $user["localId"] ?? '', - $user["email"] ?? '', - $user["displayName"] ?? $user["email"] ?? '', - new Hash($user["passwordHash"] ?? '', $user["salt"] ?? '', Hash::SCRYPT_MODIFIED, $hashConfig["saltSeparator"], $hashConfig["signerKey"]), - $user["phoneNumber"] ?? '', + $user['localId'] ?? '', + $user['email'] ?? '', + $user['displayName'] ?? $user['email'] ?? '', + new Hash($user['passwordHash'] ?? '', $user['salt'] ?? '', Hash::SCRYPT_MODIFIED, $hashConfig['saltSeparator'], $hashConfig['signerKey']), + $user['phoneNumber'] ?? '', $this->calculateUserType($user['providerUserInfo'] ?? []), '', - $user["emailVerified"], + $user['emailVerified'], false, // Can't get phone number status on firebase :/ - $user["disabled"] + $user['disabled'] ); } @@ -194,7 +193,7 @@ private function calculateUserType(array $providerData): array $types = []; foreach ($providerData as $provider) { - switch ($provider["providerId"]) { + switch ($provider['providerId']) { case 'password': $types[] = User::TYPE_EMAIL; break; @@ -231,11 +230,11 @@ private function handleDBData(int $batchSize, bool $pushDocuments, Database $dat while (true) { $collections = []; - $result = $this->call('POST', $baseURL . ':listCollectionIds', [ + $result = $this->call('POST', $baseURL.':listCollectionIds', [ 'Content-Type' => 'application/json', ], [ 'pageSize' => $batchSize, - 'pageToken' => $nextPageToken + 'pageToken' => $nextPageToken, ]); // Transfer Collections @@ -265,28 +264,28 @@ private function handleDBData(int $batchSize, bool $pushDocuments, Database $dat private function convertAttribute(Collection $collection, string $key, array $field): Attribute { - if (array_key_exists("booleanValue", $field)) { + if (array_key_exists('booleanValue', $field)) { return new BoolAttribute($key, $collection, false, false, null); - } elseif (array_key_exists("bytesValue", $field)) { + } elseif (array_key_exists('bytesValue', $field)) { return new StringAttribute($key, $collection, false, false, null, 1000000); - } elseif (array_key_exists("doubleValue", $field)) { + } elseif (array_key_exists('doubleValue', $field)) { return new FloatAttribute($key, $collection, false, false, null); - } elseif (array_key_exists("integerValue", $field)) { + } elseif (array_key_exists('integerValue', $field)) { return new IntAttribute($key, $collection, false, false, null); - } elseif (array_key_exists("mapValue", $field)) { + } elseif (array_key_exists('mapValue', $field)) { return new StringAttribute($key, $collection, false, false, null, 1000000); - } elseif (array_key_exists("nullValue", $field)) { + } elseif (array_key_exists('nullValue', $field)) { return new StringAttribute($key, $collection, false, false, null, 1000000); - } elseif (array_key_exists("referenceValue", $field)) { + } elseif (array_key_exists('referenceValue', $field)) { return new StringAttribute($key, $collection, false, false, null, 1000000); //TODO: This should be a reference attribute - } elseif (array_key_exists("stringValue", $field)) { + } elseif (array_key_exists('stringValue', $field)) { return new StringAttribute($key, $collection, false, false, null, 1000000); - } elseif (array_key_exists("timestampValue", $field)) { + } elseif (array_key_exists('timestampValue', $field)) { return new DateTimeAttribute($key, $collection, false, false, null); - } elseif (array_key_exists("geoPointValue", $field)) { + } elseif (array_key_exists('geoPointValue', $field)) { return new StringAttribute($key, $collection, false, false, null, 1000000); - } elseif (array_key_exists("arrayValue", $field)) { - return $this->calculateArrayType($collection, $key, $field["arrayValue"]); + } elseif (array_key_exists('arrayValue', $field)) { + return $this->calculateArrayType($collection, $key, $field['arrayValue']); } else { throw new \Exception('Unknown field type'); } @@ -297,8 +296,8 @@ private function calculateArrayType(Collection $collection, string $key, array $ $isSameType = true; $previousType = null; - foreach ($data["values"] as $field) { - if (!$previousType) { + foreach ($data['values'] as $field) { + if (! $previousType) { $previousType = $this->convertAttribute($collection, $key, $field); } elseif ($previousType->getName() != ($this->convertAttribute($collection, $key, $field))->getName()) { $isSameType = false; @@ -308,6 +307,7 @@ private function calculateArrayType(Collection $collection, string $key, array $ if ($isSameType) { $previousType->setArray(true); + return $previousType; } else { return new StringAttribute($key, $collection, false, true, null, 1000000); @@ -316,7 +316,7 @@ private function calculateArrayType(Collection $collection, string $key, array $ private function handleCollection(Collection $collection, int $batchSize, bool $transferDocuments) { - $resourceURL = 'https://firestore.googleapis.com/v1/projects/' . $this->projectID . '/databases/' . $collection->getDatabase()->getId() . '/documents/' . $collection->getId(); + $resourceURL = 'https://firestore.googleapis.com/v1/projects/'.$this->projectID.'/databases/'.$collection->getDatabase()->getId().'/documents/'.$collection->getId(); $nextPageToken = null; @@ -330,7 +330,7 @@ private function handleCollection(Collection $collection, int $batchSize, bool $ 'Content-Type' => 'application/json', ], [ 'pageSize' => $batchSize, - 'pageToken' => $nextPageToken + 'pageToken' => $nextPageToken, ]); if (empty($result)) { @@ -340,12 +340,12 @@ private function handleCollection(Collection $collection, int $batchSize, bool $ // Calculate Schema and handle subcollections $documentSchema = []; foreach ($result['documents'] as $document) { - if (!isset($document['fields'])) { + if (! isset($document['fields'])) { continue; //TODO: Transfer Empty Documents } foreach ($document['fields'] as $key => $field) { - if (!isset($documentSchema[$key])) { + if (! isset($documentSchema[$key])) { $documentSchema[$key] = $this->convertAttribute($collection, $key, $field); } } @@ -368,27 +368,27 @@ private function handleCollection(Collection $collection, int $batchSize, bool $ private function calculateValue(array $field) { - if (array_key_exists("booleanValue", $field)) { + if (array_key_exists('booleanValue', $field)) { return $field['booleanValue']; - } elseif (array_key_exists("bytesValue", $field)) { + } elseif (array_key_exists('bytesValue', $field)) { return $field['bytesValue']; - } elseif (array_key_exists("doubleValue", $field)) { + } elseif (array_key_exists('doubleValue', $field)) { return $field['doubleValue']; - } elseif (array_key_exists("integerValue", $field)) { + } elseif (array_key_exists('integerValue', $field)) { return $field['integerValue']; - } elseif (array_key_exists("mapValue", $field)) { + } elseif (array_key_exists('mapValue', $field)) { return $field['mapValue']; - } elseif (array_key_exists("nullValue", $field)) { + } elseif (array_key_exists('nullValue', $field)) { return $field['nullValue']; - } elseif (array_key_exists("referenceValue", $field)) { + } elseif (array_key_exists('referenceValue', $field)) { return $field['referenceValue']; //TODO: This should be a reference attribute - } elseif (array_key_exists("stringValue", $field)) { + } elseif (array_key_exists('stringValue', $field)) { return $field['stringValue']; - } elseif (array_key_exists("timestampValue", $field)) { + } elseif (array_key_exists('timestampValue', $field)) { return $field['timestampValue']; - } elseif (array_key_exists("geoPointValue", $field)) { + } elseif (array_key_exists('geoPointValue', $field)) { return $field['geoPointValue']; - } elseif (array_key_exists("arrayValue", $field)) { + } elseif (array_key_exists('arrayValue', $field)) { //TODO: } else { throw new \Exception('Unknown field type'); @@ -427,7 +427,7 @@ public function exportBuckets(int $batchsize) 'project' => $this->projectID, 'maxResults' => $batchsize, 'pageToken' => $nextPageToken, - 'alt' => 'json' + 'alt' => 'json', ]); if (empty($result)) { @@ -438,7 +438,7 @@ public function exportBuckets(int $batchsize) $this->callback([new Bucket($bucket['id'], [], false, $bucket['name'])]); } - if (!isset($result['nextPageToken'])) { + if (! isset($result['nextPageToken'])) { break; } @@ -451,7 +451,7 @@ public function exportFiles(int $batchsize) $buckets = $this->resourceCache->get(Bucket::getName()); foreach ($buckets as $bucket) { - $endpoint = 'https://storage.googleapis.com/storage/v1/b/' . $bucket->getId() . '/o'; + $endpoint = 'https://storage.googleapis.com/storage/v1/b/'.$bucket->getId().'/o'; $nextPageToken = null; @@ -460,14 +460,14 @@ public function exportFiles(int $batchsize) 'Content-Type' => 'application/json', ], [ 'pageSize' => $batchsize, - 'pageToken' => $nextPageToken + 'pageToken' => $nextPageToken, ]); if (empty($result)) { break; } - if (!isset($result['items'])) { + if (! isset($result['items'])) { break; } @@ -486,25 +486,24 @@ public function exportFiles(int $batchsize) public function handleDataTransfer(File $file) { - $endpoint = 'https://storage.googleapis.com/storage/v1/b/' . $file->getBucket()->getId() . '/o/' . $file->getId() . '?alt=media'; + $endpoint = 'https://storage.googleapis.com/storage/v1/b/'.$file->getBucket()->getId().'/o/'.$file->getId().'?alt=media'; $start = 0; $end = Transfer::STORAGE_MAX_CHUNK_SIZE - 1; while (true) { $result = $this->call('GET', $endpoint, [ - 'Range' => 'bytes=' . $start . '-' . $end + 'Range' => 'bytes='.$start.'-'.$end, ]); if (empty($result)) { break; } - $this->callback([new FileData( - $result, - $start, - $end, - $file - )]); + $file->setData($result) + ->setStart($start) + ->setEnd($end); + + $this->callback([$file]); if (strlen($result) < Transfer::STORAGE_MAX_CHUNK_SIZE) { break; diff --git a/src/Transfer/Sources/NHost.php b/src/Transfer/Sources/NHost.php index e572920..042de99 100644 --- a/src/Transfer/Sources/NHost.php +++ b/src/Transfer/Sources/NHost.php @@ -3,23 +3,22 @@ namespace Utopia\Transfer\Sources; use Utopia\Transfer\Resource; -use Utopia\Transfer\Source; +use Utopia\Transfer\Resources\Auth\Hash; use Utopia\Transfer\Resources\Auth\User; -use Utopia\Transfer\Transfer; use Utopia\Transfer\Resources\Database\Attribute; -use Utopia\Transfer\Resources\Database\Database; -use Utopia\Transfer\Resources\Auth\Hash; use Utopia\Transfer\Resources\Database\Attributes\BoolAttribute; use Utopia\Transfer\Resources\Database\Attributes\DateTimeAttribute; use Utopia\Transfer\Resources\Database\Attributes\FloatAttribute; use Utopia\Transfer\Resources\Database\Attributes\IntAttribute; use Utopia\Transfer\Resources\Database\Attributes\StringAttribute; use Utopia\Transfer\Resources\Database\Collection; +use Utopia\Transfer\Resources\Database\Database; use Utopia\Transfer\Resources\Database\Document; use Utopia\Transfer\Resources\Database\Index; use Utopia\Transfer\Resources\Storage\Bucket; use Utopia\Transfer\Resources\Storage\File; -use Utopia\Transfer\Resources\Storage\FileData; +use Utopia\Transfer\Source; +use Utopia\Transfer\Transfer; class NHost extends Source { @@ -29,11 +28,17 @@ class NHost extends Source public $pdo; public string $subdomain; + public string $region; + public string $databaseName; + public string $username; + public string $password; + public string $port; + public string $adminSecret; public function __construct(string $subdomain, string $region, string $adminSecret, string $databaseName, string $username, string $password, string $port = '5432') @@ -47,9 +52,9 @@ public function __construct(string $subdomain, string $region, string $adminSecr $this->port = $port; try { - $this->pdo = new \PDO("pgsql:host=" . $this->subdomain . '.db.' . $this->region . '.nhost.run' . ";port=" . $this->port . ";dbname=" . $this->databaseName, $this->username, $this->password); + $this->pdo = new \PDO('pgsql:host='.$this->subdomain.'.db.'.$this->region.'.nhost.run'.';port='.$this->port.';dbname='.$this->databaseName, $this->username, $this->password); } catch (\PDOException $e) { - throw new \Exception('Failed to connect to database: ' . $e->getMessage()); + throw new \Exception('Failed to connect to database: '.$e->getMessage()); } } @@ -60,8 +65,6 @@ public static function getName(): string /** * Get Supported Resources - * - * @return array */ public function getSupportedResources(): array { @@ -79,7 +82,6 @@ public function getSupportedResources(): array // Storage Resource::TYPE_BUCKET, Resource::TYPE_FILE, - Resource::TYPE_FILEDATA, ]; } @@ -92,13 +94,13 @@ public function report(array $resources = []): array } try { - $this->pdo = new \PDO("pgsql:host=" . $this->subdomain . '.db.' . $this->region . '.nhost.run' . ";port=" . $this->port . ";dbname=" . $this->databaseName, $this->username, $this->password); + $this->pdo = new \PDO('pgsql:host='.$this->subdomain.'.db.'.$this->region.'.nhost.run'.';port='.$this->port.';dbname='.$this->databaseName, $this->username, $this->password); } catch (\PDOException $e) { - throw new \Exception('Failed to connect to database. PDO Code: ' . $e->getCode() . ' Error: ' . $e->getMessage()); + throw new \Exception('Failed to connect to database. PDO Code: '.$e->getCode().' Error: '.$e->getMessage()); } - if (!empty($this->pdo->errorCode())) { - throw new \Exception('Failed to connect to database. PDO Code: ' . $this->pdo->errorCode() . (empty($this->pdo->errorInfo()[2]) ? '' : ' Error: ' . $this->pdo->errorInfo()[2])); + if (! empty($this->pdo->errorCode())) { + throw new \Exception('Failed to connect to database. PDO Code: '.$this->pdo->errorCode().(empty($this->pdo->errorInfo()[2]) ? '' : ' Error: '.$this->pdo->errorInfo()[2])); } // Auth @@ -107,7 +109,7 @@ public function report(array $resources = []): array $statement->execute(); if ($statement->errorCode() !== '00000') { - throw new \Exception('Failed to access users table. Error: ' . $statement->errorInfo()[2]); + throw new \Exception('Failed to access users table. Error: '.$statement->errorInfo()[2]); } $report[Resource::TYPE_USER] = $statement->fetchColumn(); @@ -123,7 +125,7 @@ public function report(array $resources = []): array $statement->execute(); if ($statement->errorCode() !== '00000') { - throw new \Exception('Failed to access tables table. Error: ' . $statement->errorInfo()[2]); + throw new \Exception('Failed to access tables table. Error: '.$statement->errorInfo()[2]); } $report[Resource::TYPE_COLLECTION] = $statement->fetchColumn(); @@ -134,7 +136,7 @@ public function report(array $resources = []): array $statement->execute(); if ($statement->errorCode() !== '00000') { - throw new \Exception('Failed to access columns table. Error: ' . $statement->errorInfo()[2]); + throw new \Exception('Failed to access columns table. Error: '.$statement->errorInfo()[2]); } $report[Resource::TYPE_ATTRIBUTE] = $statement->fetchColumn(); @@ -145,7 +147,7 @@ public function report(array $resources = []): array $statement->execute(); if ($statement->errorCode() !== '00000') { - throw new \Exception('Failed to access indexes table. Error: ' . $statement->errorInfo()[2]); + throw new \Exception('Failed to access indexes table. Error: '.$statement->errorInfo()[2]); } $report[Resource::TYPE_INDEX] = $statement->fetchColumn(); @@ -156,7 +158,7 @@ public function report(array $resources = []): array $statement->execute(); if ($statement->errorCode() !== '00000') { - throw new \Exception('Failed to access tables table. Error: ' . $statement->errorInfo()[2]); + throw new \Exception('Failed to access tables table. Error: '.$statement->errorInfo()[2]); } $report[Resource::TYPE_DOCUMENT] = $statement->fetchColumn(); @@ -168,7 +170,7 @@ public function report(array $resources = []): array $statement->execute(); if ($statement->errorCode() !== '00000') { - throw new \Exception('Failed to access buckets table. Error: ' . $statement->errorInfo()[2]); + throw new \Exception('Failed to access buckets table. Error: '.$statement->errorInfo()[2]); } $report[Resource::TYPE_BUCKET] = $statement->fetchColumn(); @@ -179,7 +181,7 @@ public function report(array $resources = []): array $statement->execute(); if ($statement->errorCode() !== '00000') { - throw new \Exception('Failed to access files table. Error: ' . $statement->errorInfo()[2]); + throw new \Exception('Failed to access files table. Error: '.$statement->errorInfo()[2]); } $report[Resource::TYPE_FILE] = $statement->fetchColumn(); @@ -299,7 +301,7 @@ private function exportAttributes(int $batchSize) $collections = $this->resourceCache->get(Collection::getName()); foreach ($collections as $collection) { - /** @var Collection $collection */ + /** @var Collection $collection */ $statement = $this->pdo->prepare('SELECT * FROM information_schema."columns" where "table_name" = :tableName'); $statement->bindValue(':tableName', $collection->getCollectionName(), \PDO::PARAM_STR); $statement->execute(); @@ -321,7 +323,6 @@ private function exportIndexes(int $batchSize) foreach ($collections as $collection) { /** @var Collection $collection */ - $indexStatement = $this->pdo->prepare('SELECT indexname, indexdef FROM pg_indexes WHERE tablename = :tableName'); $indexStatement->bindValue(':tableName', $collection->getCollectionName(), \PDO::PARAM_STR); $indexStatement->execute(); @@ -347,12 +348,12 @@ private function exportDocuments(int $batchSize) $collections = $database->getCollections(); foreach ($collections as $collection) { - $total = $this->pdo->query('SELECT COUNT(*) FROM ' . $collection->getCollectionName())->fetchColumn(); + $total = $this->pdo->query('SELECT COUNT(*) FROM '.$collection->getCollectionName())->fetchColumn(); $offset = 0; while ($offset < $total) { - $statement = $this->pdo->prepare('SELECT row_to_json(t) FROM (SELECT * FROM ' . $collection->getCollectionName() . ' LIMIT :limit OFFSET :offset) t;'); + $statement = $this->pdo->prepare('SELECT row_to_json(t) FROM (SELECT * FROM '.$collection->getCollectionName().' LIMIT :limit OFFSET :offset) t;'); $statement->bindValue(':limit', $batchSize, \PDO::PARAM_INT); $statement->bindValue(':offset', $offset, \PDO::PARAM_INT); $statement->execute(); @@ -374,7 +375,7 @@ private function exportDocuments(int $batchSize) $processedData = []; foreach ($collectionAttributes as $attribute) { /** @var Attribute $attribute */ - if (!$attribute->getArray() && \is_array($data[$attribute->getKey()])) { + if (! $attribute->getArray() && \is_array($data[$attribute->getKey()])) { $processedData[$attribute->getKey()] = json_encode($data[$attribute->getKey()]); } else { $processedData[$attribute->getKey()] = $data[$attribute->getKey()]; @@ -395,7 +396,7 @@ private function convertAttribute(array $column, Collection $collection): Attrib $isArray = $column['data_type'] === 'ARRAY'; switch ($isArray ? str_replace('_', '', $column['udt_name']) : $column['data_type']) { - // Numbers + // Numbers case 'boolean': case 'bool': return new BoolAttribute($column['column_name'], $collection, $column['is_nullable'] === 'NO', $isArray, $column['column_default']); @@ -485,7 +486,7 @@ private function convertIndex(array $index, Collection $collection): Index|false $attributes = []; $order = []; - $targets = explode(",", $matches['columns']); + $targets = explode(',', $matches['columns']); foreach ($targets as $target) { if (\strpos($target, ' ') !== false) { @@ -494,7 +495,7 @@ private function convertIndex(array $index, Collection $collection): Index|false $order[] = $target[1]; } else { $attributes[] = $target; - $order[] = "ASC"; + $order[] = 'ASC'; } } @@ -515,11 +516,11 @@ private function calculateUserTypes(array $user): array $types = []; - if (!empty($user['password_hash'])) { + if (! empty($user['password_hash'])) { $types[] = User::TYPE_EMAIL; } - if (!empty($user['phone_number'])) { + if (! empty($user['phone_number'])) { $types[] = User::TYPE_PHONE; } @@ -617,7 +618,7 @@ public function handleDataTransfer(File $file) $end = Transfer::STORAGE_MAX_CHUNK_SIZE - 1; $fileSize = $file->getSize(); - $response = $this->call("GET", $url . "/v1/files/{$file->getId()}/presignedurl", [ + $response = $this->call('GET', $url."/v1/files/{$file->getId()}/presignedurl", [ 'X-Hasura-Admin-Secret' => $this->adminSecret, ]); @@ -630,7 +631,7 @@ public function handleDataTransfer(File $file) while ($start < $fileSize) { if (\time() > $refreshTime) { - $response = $this->call("GET", "/v1/files/{$file->getId()}/presignedurl", [ + $response = $this->call('GET', "/v1/files/{$file->getId()}/presignedurl", [ 'X-Hasura-Admin-Secret' => $this->adminSecret, ]); @@ -644,12 +645,11 @@ public function handleDataTransfer(File $file) ['range' => "bytes=$start-$end"] ); - $this->callback([new FileData( - $chunkData, - $start, - $end, - $file - )]); + $file->setData($chunkData) + ->setStart($start) + ->setEnd($end); + + $this->callback([$file]); $start += Transfer::STORAGE_MAX_CHUNK_SIZE; $end += Transfer::STORAGE_MAX_CHUNK_SIZE; @@ -657,7 +657,7 @@ public function handleDataTransfer(File $file) if ($end > $fileSize) { $end = $fileSize - 1; } - }; + } } public function exportFunctionsGroup(int $batchSize, array $resources) diff --git a/src/Transfer/Sources/Supabase.php b/src/Transfer/Sources/Supabase.php index eb324ca..f02a850 100644 --- a/src/Transfer/Sources/Supabase.php +++ b/src/Transfer/Sources/Supabase.php @@ -3,14 +3,13 @@ namespace Utopia\Transfer\Sources; use Utopia\Transfer\Resource; -use Utopia\Transfer\Resources\Auth\User; use Utopia\Transfer\Resources\Auth\Hash; +use Utopia\Transfer\Resources\Auth\User; use Utopia\Transfer\Resources\Storage\Bucket; use Utopia\Transfer\Resources\Storage\File; -use Utopia\Transfer\Resources\Storage\FileData; use Utopia\Transfer\Transfer; -const MIME_MAP = ['video/3gpp2' => '3g2', 'video/3gp' => '3gp', 'video/3gpp' => '3gp', 'application/x-compressed' => '7zip', 'audio/x-acc' => 'aac', 'audio/ac3' => 'ac3', 'application/postscript' => 'ai', 'audio/x-aiff' => 'aif', 'audio/aiff' => 'aif', 'audio/x-au' => 'au', 'video/x-msvideo' => 'avi', 'video/msvideo' => 'avi', 'video/avi' => 'avi', 'application/x-troff-msvideo' => 'avi', 'application/macbinary' => 'bin', 'application/mac-binary' => 'bin', 'application/x-binary' => 'bin', 'application/x-macbinary' => 'bin', 'image/bmp' => 'bmp', 'image/x-bmp' => 'bmp', 'image/x-bitmap' => 'bmp', 'image/x-xbitmap' => 'bmp', 'image/x-win-bitmap' => 'bmp', 'image/x-windows-bmp' => 'bmp', 'image/ms-bmp' => 'bmp', 'image/x-ms-bmp' => 'bmp', 'application/bmp' => 'bmp', 'application/x-bmp' => 'bmp', 'application/x-win-bitmap' => 'bmp', 'application/cdr' => 'cdr', 'application/coreldraw' => 'cdr', 'application/x-cdr' => 'cdr', 'application/x-coreldraw' => 'cdr', 'image/cdr' => 'cdr', 'image/x-cdr' => 'cdr', 'zz-application/zz-winassoc-cdr' => 'cdr', 'application/mac-compactpro' => 'cpt', 'application/pkix-crl' => 'crl', 'application/pkcs-crl' => 'crl', 'application/x-x509-ca-cert' => 'crt', 'application/pkix-cert' => 'crt', 'text/css' => 'css', 'text/x-comma-separated-values' => 'csv', 'text/comma-separated-values' => 'csv', 'application/vnd.msexcel' => 'csv', 'application/x-director' => 'dcr', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' => 'docx', 'application/x-dvi' => 'dvi', 'message/rfc822' => 'eml', 'application/x-msdownload' => 'exe', 'video/x-f4v' => 'f4v', 'audio/x-flac' => 'flac', 'video/x-flv' => 'flv', 'image/gif' => 'gif', 'application/gpg-keys' => 'gpg', 'application/x-gtar' => 'gtar', 'application/x-gzip' => 'gzip', 'application/mac-binhex40' => 'hqx', 'application/mac-binhex' => 'hqx', 'application/x-binhex40' => 'hqx', 'application/x-mac-binhex40' => 'hqx', 'text/html' => 'html', 'image/x-icon' => 'ico', 'image/x-ico' => 'ico', 'image/vnd.microsoft.icon' => 'ico', 'text/calendar' => 'ics', 'application/java-archive' => 'jar', 'application/x-java-application' => 'jar', 'application/x-jar' => 'jar', 'image/jp2' => 'jp2', 'video/mj2' => 'jp2', 'image/jpx' => 'jp2', 'image/jpm' => 'jp2', 'image/jpeg' => 'jpeg', 'image/jpg' => 'jpeg', 'image/pjpeg' => 'jpeg', 'application/x-javascript' => 'js', 'application/json' => 'json', 'text/json' => 'json', 'application/vnd.google-earth.kml+xml' => 'kml', 'application/vnd.google-earth.kmz' => 'kmz', 'text/x-log' => 'log', 'audio/x-m4a' => 'm4a', 'audio/mp4' => 'm4a', 'application/vnd.mpegurl' => 'm4u', 'audio/midi' => 'mid', 'application/vnd.mif' => 'mif', 'video/quicktime' => 'mov', 'video/x-sgi-movie' => 'movie', 'audio/mpeg' => 'mp3', 'audio/mpg' => 'mp3', 'audio/mpeg3' => 'mp3', 'audio/mp3' => 'mp3', 'video/mp4' => 'mp4', 'video/mpeg' => 'mpeg', 'application/oda' => 'oda', 'audio/ogg' => 'ogg', 'video/ogg' => 'ogg', 'application/ogg' => 'ogg', 'font/otf' => 'otf', 'application/x-pkcs10' => 'p10', 'application/pkcs10' => 'p10', 'application/x-pkcs12' => 'p12', 'application/x-pkcs7-signature' => 'p7a', 'application/pkcs7-mime' => 'p7c', 'application/x-pkcs7-mime' => 'p7c', 'application/x-pkcs7-certreqresp' => 'p7r', 'application/pkcs7-signature' => 'p7s', 'application/pdf' => 'pdf', 'application/octet-stream' => 'pdf', 'application/x-x509-user-cert' => 'pem', 'application/x-pem-file' => 'pem', 'application/pgp' => 'pgp', 'application/x-httpd-php' => 'php', 'application/php' => 'php', 'application/x-php' => 'php', 'text/php' => 'php', 'text/x-php' => 'php', 'application/x-httpd-php-source' => 'php', 'image/png' => 'png', 'image/x-png' => 'png', 'application/powerpoint' => 'ppt', 'application/vnd.ms-powerpoint' => 'ppt', 'application/vnd.ms-office' => 'ppt', 'application/msword' => 'doc', 'application/vnd.openxmlformats-officedocument.presentationml.presentation' => 'pptx', 'application/x-photoshop' => 'psd', 'image/vnd.adobe.photoshop' => 'psd', 'audio/x-realaudio' => 'ra', 'audio/x-pn-realaudio' => 'ram', 'application/x-rar' => 'rar', 'application/rar' => 'rar', 'application/x-rar-compressed' => 'rar', 'audio/x-pn-realaudio-plugin' => 'rpm', 'application/x-pkcs7' => 'rsa', 'text/rtf' => 'rtf', 'text/richtext' => 'rtx', 'video/vnd.rn-realvideo' => 'rv', 'application/x-stuffit' => 'sit', 'application/smil' => 'smil', 'text/srt' => 'srt', 'image/svg+xml' => 'svg', 'application/x-shockwave-flash' => 'swf', 'application/x-tar' => 'tar', 'application/x-gzip-compressed' => 'tgz', 'image/tiff' => 'tiff', 'font/ttf' => 'ttf', 'text/plain' => 'txt', 'text/x-vcard' => 'vcf', 'application/videolan' => 'vlc', 'text/vtt' => 'vtt', 'audio/x-wav' => 'wav', 'audio/wave' => 'wav', 'audio/wav' => 'wav', 'application/wbxml' => 'wbxml', 'video/webm' => 'webm', 'image/webp' => 'webp', 'audio/x-ms-wma' => 'wma', 'application/wmlc' => 'wmlc', 'video/x-ms-wmv' => 'wmv', 'video/x-ms-asf' => 'wmv', 'font/woff' => 'woff', 'font/woff2' => 'woff2', 'application/xhtml+xml' => 'xhtml', 'application/excel' => 'xl', 'application/msexcel' => 'xls', 'application/x-msexcel' => 'xls', 'application/x-ms-excel' => 'xls', 'application/x-excel' => 'xls', 'application/x-dos_ms_excel' => 'xls', 'application/xls' => 'xls', 'application/x-xls' => 'xls', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' => 'xlsx', 'application/vnd.ms-excel' => 'xlsx', 'application/xml' => 'xml', 'text/xml' => 'xml', 'text/xsl' => 'xsl', 'application/xspf+xml' => 'xspf', 'application/x-compress' => 'z', 'application/x-zip' => 'zip', 'application/zip' => 'zip', 'application/x-zip-compressed' => 'zip', 'application/s-compressed' => 'zip', 'multipart/x-zip' => 'zip', 'text/x-scriptzsh' => 'zsh',]; +const MIME_MAP = ['video/3gpp2' => '3g2', 'video/3gp' => '3gp', 'video/3gpp' => '3gp', 'application/x-compressed' => '7zip', 'audio/x-acc' => 'aac', 'audio/ac3' => 'ac3', 'application/postscript' => 'ai', 'audio/x-aiff' => 'aif', 'audio/aiff' => 'aif', 'audio/x-au' => 'au', 'video/x-msvideo' => 'avi', 'video/msvideo' => 'avi', 'video/avi' => 'avi', 'application/x-troff-msvideo' => 'avi', 'application/macbinary' => 'bin', 'application/mac-binary' => 'bin', 'application/x-binary' => 'bin', 'application/x-macbinary' => 'bin', 'image/bmp' => 'bmp', 'image/x-bmp' => 'bmp', 'image/x-bitmap' => 'bmp', 'image/x-xbitmap' => 'bmp', 'image/x-win-bitmap' => 'bmp', 'image/x-windows-bmp' => 'bmp', 'image/ms-bmp' => 'bmp', 'image/x-ms-bmp' => 'bmp', 'application/bmp' => 'bmp', 'application/x-bmp' => 'bmp', 'application/x-win-bitmap' => 'bmp', 'application/cdr' => 'cdr', 'application/coreldraw' => 'cdr', 'application/x-cdr' => 'cdr', 'application/x-coreldraw' => 'cdr', 'image/cdr' => 'cdr', 'image/x-cdr' => 'cdr', 'zz-application/zz-winassoc-cdr' => 'cdr', 'application/mac-compactpro' => 'cpt', 'application/pkix-crl' => 'crl', 'application/pkcs-crl' => 'crl', 'application/x-x509-ca-cert' => 'crt', 'application/pkix-cert' => 'crt', 'text/css' => 'css', 'text/x-comma-separated-values' => 'csv', 'text/comma-separated-values' => 'csv', 'application/vnd.msexcel' => 'csv', 'application/x-director' => 'dcr', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' => 'docx', 'application/x-dvi' => 'dvi', 'message/rfc822' => 'eml', 'application/x-msdownload' => 'exe', 'video/x-f4v' => 'f4v', 'audio/x-flac' => 'flac', 'video/x-flv' => 'flv', 'image/gif' => 'gif', 'application/gpg-keys' => 'gpg', 'application/x-gtar' => 'gtar', 'application/x-gzip' => 'gzip', 'application/mac-binhex40' => 'hqx', 'application/mac-binhex' => 'hqx', 'application/x-binhex40' => 'hqx', 'application/x-mac-binhex40' => 'hqx', 'text/html' => 'html', 'image/x-icon' => 'ico', 'image/x-ico' => 'ico', 'image/vnd.microsoft.icon' => 'ico', 'text/calendar' => 'ics', 'application/java-archive' => 'jar', 'application/x-java-application' => 'jar', 'application/x-jar' => 'jar', 'image/jp2' => 'jp2', 'video/mj2' => 'jp2', 'image/jpx' => 'jp2', 'image/jpm' => 'jp2', 'image/jpeg' => 'jpeg', 'image/jpg' => 'jpeg', 'image/pjpeg' => 'jpeg', 'application/x-javascript' => 'js', 'application/json' => 'json', 'text/json' => 'json', 'application/vnd.google-earth.kml+xml' => 'kml', 'application/vnd.google-earth.kmz' => 'kmz', 'text/x-log' => 'log', 'audio/x-m4a' => 'm4a', 'audio/mp4' => 'm4a', 'application/vnd.mpegurl' => 'm4u', 'audio/midi' => 'mid', 'application/vnd.mif' => 'mif', 'video/quicktime' => 'mov', 'video/x-sgi-movie' => 'movie', 'audio/mpeg' => 'mp3', 'audio/mpg' => 'mp3', 'audio/mpeg3' => 'mp3', 'audio/mp3' => 'mp3', 'video/mp4' => 'mp4', 'video/mpeg' => 'mpeg', 'application/oda' => 'oda', 'audio/ogg' => 'ogg', 'video/ogg' => 'ogg', 'application/ogg' => 'ogg', 'font/otf' => 'otf', 'application/x-pkcs10' => 'p10', 'application/pkcs10' => 'p10', 'application/x-pkcs12' => 'p12', 'application/x-pkcs7-signature' => 'p7a', 'application/pkcs7-mime' => 'p7c', 'application/x-pkcs7-mime' => 'p7c', 'application/x-pkcs7-certreqresp' => 'p7r', 'application/pkcs7-signature' => 'p7s', 'application/pdf' => 'pdf', 'application/octet-stream' => 'pdf', 'application/x-x509-user-cert' => 'pem', 'application/x-pem-file' => 'pem', 'application/pgp' => 'pgp', 'application/x-httpd-php' => 'php', 'application/php' => 'php', 'application/x-php' => 'php', 'text/php' => 'php', 'text/x-php' => 'php', 'application/x-httpd-php-source' => 'php', 'image/png' => 'png', 'image/x-png' => 'png', 'application/powerpoint' => 'ppt', 'application/vnd.ms-powerpoint' => 'ppt', 'application/vnd.ms-office' => 'ppt', 'application/msword' => 'doc', 'application/vnd.openxmlformats-officedocument.presentationml.presentation' => 'pptx', 'application/x-photoshop' => 'psd', 'image/vnd.adobe.photoshop' => 'psd', 'audio/x-realaudio' => 'ra', 'audio/x-pn-realaudio' => 'ram', 'application/x-rar' => 'rar', 'application/rar' => 'rar', 'application/x-rar-compressed' => 'rar', 'audio/x-pn-realaudio-plugin' => 'rpm', 'application/x-pkcs7' => 'rsa', 'text/rtf' => 'rtf', 'text/richtext' => 'rtx', 'video/vnd.rn-realvideo' => 'rv', 'application/x-stuffit' => 'sit', 'application/smil' => 'smil', 'text/srt' => 'srt', 'image/svg+xml' => 'svg', 'application/x-shockwave-flash' => 'swf', 'application/x-tar' => 'tar', 'application/x-gzip-compressed' => 'tgz', 'image/tiff' => 'tiff', 'font/ttf' => 'ttf', 'text/plain' => 'txt', 'text/x-vcard' => 'vcf', 'application/videolan' => 'vlc', 'text/vtt' => 'vtt', 'audio/x-wav' => 'wav', 'audio/wave' => 'wav', 'audio/wav' => 'wav', 'application/wbxml' => 'wbxml', 'video/webm' => 'webm', 'image/webp' => 'webp', 'audio/x-ms-wma' => 'wma', 'application/wmlc' => 'wmlc', 'video/x-ms-wmv' => 'wmv', 'video/x-ms-asf' => 'wmv', 'font/woff' => 'woff', 'font/woff2' => 'woff2', 'application/xhtml+xml' => 'xhtml', 'application/excel' => 'xl', 'application/msexcel' => 'xls', 'application/x-msexcel' => 'xls', 'application/x-ms-excel' => 'xls', 'application/x-excel' => 'xls', 'application/x-dos_ms_excel' => 'xls', 'application/xls' => 'xls', 'application/x-xls' => 'xls', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' => 'xlsx', 'application/vnd.ms-excel' => 'xlsx', 'application/xml' => 'xml', 'text/xml' => 'xml', 'text/xsl' => 'xsl', 'application/xspf+xml' => 'xspf', 'application/x-compress' => 'z', 'application/x-zip' => 'zip', 'application/zip' => 'zip', 'application/x-zip-compressed' => 'zip', 'application/s-compressed' => 'zip', 'multipart/x-zip' => 'zip', 'text/x-scriptzsh' => 'zsh']; class Supabase extends NHost { @@ -20,18 +19,12 @@ public static function getName(): string } protected string $key; + protected string $host; /** * Constructor * - * @param string $endpoint - * @param string $key - * @param string $host - * @param string $databaseName - * @param string $username - * @param string $password - * @param string $port * * @return self */ @@ -45,12 +38,12 @@ public function __construct(string $endpoint, string $key, string $host, string $this->password = $password; $this->port = $port; - $this->headers['Authorization'] = 'Bearer ' . $this->key; + $this->headers['Authorization'] = 'Bearer '.$this->key; try { - $this->pdo = new \PDO("pgsql:host=" . $this->host . ";port=" . $this->port . ";dbname=" . $this->databaseName, $this->username, $this->password); + $this->pdo = new \PDO('pgsql:host='.$this->host.';port='.$this->port.';dbname='.$this->databaseName, $this->username, $this->password); } catch (\PDOException $e) { - throw new \Exception('Failed to connect to database: ' . $e->getMessage()); + throw new \Exception('Failed to connect to database: '.$e->getMessage()); } } @@ -63,13 +56,13 @@ public function report(array $resources = []): array } try { - $this->pdo = new \PDO("pgsql:host=" . $this->host . ";port=" . $this->port . ";dbname=" . $this->databaseName, $this->username, $this->password); + $this->pdo = new \PDO('pgsql:host='.$this->host.';port='.$this->port.';dbname='.$this->databaseName, $this->username, $this->password); } catch (\PDOException $e) { - throw new \Exception('Failed to connect to database. PDO Code: ' . $e->getCode() . ' Error: ' . $e->getMessage()); + throw new \Exception('Failed to connect to database. PDO Code: '.$e->getCode().' Error: '.$e->getMessage()); } - if (!empty($this->pdo->errorCode())) { - throw new \Exception('Failed to connect to database. PDO Code: ' . $this->pdo->errorCode() . (empty($this->pdo->errorInfo()[2]) ? '' : ' Error: ' . $this->pdo->errorInfo()[2])); + if (! empty($this->pdo->errorCode())) { + throw new \Exception('Failed to connect to database. PDO Code: '.$this->pdo->errorCode().(empty($this->pdo->errorInfo()[2]) ? '' : ' Error: '.$this->pdo->errorInfo()[2])); } // Auth @@ -78,7 +71,7 @@ public function report(array $resources = []): array $statement->execute(); if ($statement->errorCode() !== '00000') { - throw new \Exception('Failed to access users table. Error: ' . $statement->errorInfo()[2]); + throw new \Exception('Failed to access users table. Error: '.$statement->errorInfo()[2]); } $report[Resource::TYPE_USER] = $statement->fetchColumn(); @@ -94,7 +87,7 @@ public function report(array $resources = []): array $statement->execute(); if ($statement->errorCode() !== '00000') { - throw new \Exception('Failed to access tables table. Error: ' . $statement->errorInfo()[2]); + throw new \Exception('Failed to access tables table. Error: '.$statement->errorInfo()[2]); } $report[Resource::TYPE_COLLECTION] = $statement->fetchColumn(); @@ -105,7 +98,7 @@ public function report(array $resources = []): array $statement->execute(); if ($statement->errorCode() !== '00000') { - throw new \Exception('Failed to access columns table. Error: ' . $statement->errorInfo()[2]); + throw new \Exception('Failed to access columns table. Error: '.$statement->errorInfo()[2]); } $report[Resource::TYPE_ATTRIBUTE] = $statement->fetchColumn(); @@ -116,7 +109,7 @@ public function report(array $resources = []): array $statement->execute(); if ($statement->errorCode() !== '00000') { - throw new \Exception('Failed to access indexes table. Error: ' . $statement->errorInfo()[2]); + throw new \Exception('Failed to access indexes table. Error: '.$statement->errorInfo()[2]); } $report[Resource::TYPE_INDEX] = $statement->fetchColumn(); @@ -127,7 +120,7 @@ public function report(array $resources = []): array $statement->execute(); if ($statement->errorCode() !== '00000') { - throw new \Exception('Failed to access tables table. Error: ' . $statement->errorInfo()[2]); + throw new \Exception('Failed to access tables table. Error: '.$statement->errorInfo()[2]); } $report[Resource::TYPE_DOCUMENT] = $statement->fetchColumn(); @@ -139,7 +132,7 @@ public function report(array $resources = []): array $statement->execute(); if ($statement->errorCode() !== '00000') { - throw new \Exception('Failed to access buckets table. Error: ' . $statement->errorInfo()[2]); + throw new \Exception('Failed to access buckets table. Error: '.$statement->errorInfo()[2]); } $report[Resource::TYPE_BUCKET] = $statement->fetchColumn(); @@ -150,7 +143,7 @@ public function report(array $resources = []): array $statement->execute(); if ($statement->errorCode() !== '00000') { - throw new \Exception('Failed to access files table. Error: ' . $statement->errorInfo()[2]); + throw new \Exception('Failed to access files table. Error: '.$statement->errorInfo()[2]); } $report[Resource::TYPE_FILE] = $statement->fetchColumn(); @@ -193,8 +186,8 @@ private function exportUsers(int $batchSize) $user['phone'] ?? '', $this->calculateAuthTypes($user), '', - !empty($user['email_confirmed_at']), - !empty($user['phone_confirmed_at']), + ! empty($user['email_confirmed_at']), + ! empty($user['phone_confirmed_at']), false, [] ); @@ -223,11 +216,11 @@ private function calculateAuthTypes(array $user): array $types = []; - if (!empty($user['encrypted_password'])) { + if (! empty($user['encrypted_password'])) { $types[] = User::TYPE_EMAIL; } - if (!empty($user['phone'])) { + if (! empty($user['phone'])) { $types[] = User::TYPE_PHONE; } @@ -274,8 +267,7 @@ private function exportFiles(int $batchSize) /** * TODO: Supabase has folders, with enough folders within folders this could cause us to hit the max name length * Need to figure out a solution to this. - */ - + */ $buckets = $this->resourceCache->get(Bucket::getName()); foreach ($buckets as $bucket) { @@ -290,7 +282,7 @@ private function exportFiles(int $batchSize) $statement->execute([ ':bucketId' => $bucket->getId(), ':limit' => $batchSize, - ':offset' => $offset + ':offset' => $offset, ]); $files = $statement->fetchAll(\PDO::FETCH_ASSOC); @@ -329,18 +321,17 @@ private function handleDataTransfer(File $file) while ($start < $fileSize) { $chunkData = $this->call( 'GET', - '/storage/v1/object/' . - rawurlencode($file->getBucket()->getId()) . '/' . rawurlencode($file->getFileName()), + '/storage/v1/object/'. + rawurlencode($file->getBucket()->getId()).'/'.rawurlencode($file->getFileName()), ['range' => "bytes=$start-$end"] ); // Send the chunk to the callback function - $this->callback([new FileData( - $chunkData, - $start, - $end, - $file - )]); + $file->setData($chunkData) + ->setStart($start) + ->setEnd($end); + + $this->callback([$file]); // Update the range $start += Transfer::STORAGE_MAX_CHUNK_SIZE; diff --git a/src/Transfer/Target.php b/src/Transfer/Target.php index 1867768..7fe5155 100644 --- a/src/Transfer/Target.php +++ b/src/Transfer/Target.php @@ -2,8 +2,6 @@ namespace Utopia\Transfer; -use Utopia\Transfer\ResourceCache; - abstract class Target { /** @@ -18,37 +16,29 @@ abstract class Target /** * Resource Cache * - * @var ResourceCache $resourceCache + * @var ResourceCache */ public $resourceCache; /** * Endpoint * - * @var string $endpoint + * @var string */ protected $endpoint = ''; /** * Gets the name of the adapter. - * - * @return string */ abstract public static function getName(): string; /** * Get Supported Resources - * - * @return array */ abstract public function getSupportedResources(): array; /** * Register Transfer Cache - * - * @param ResourceCache &$cache - * - * @return void */ public function registerTransferCache(ResourceCache &$cache): void { @@ -57,9 +47,6 @@ public function registerTransferCache(ResourceCache &$cache): void /** * Run Transfer - * - * @param array $resources - * @param callable $callback */ abstract public function run(array $resources, callable $callback): void; @@ -71,8 +58,6 @@ abstract public function run(array $resources, callable $callback): void; * * On Destinations, this function should just return nothing but still check if the API is available. * If any issues are found then an exception should be thrown with an error message. - * - * @param array $resources */ abstract public function report(array $resources = []): array; @@ -81,21 +66,16 @@ abstract public function report(array $resources = []): array; * * Make an API call * - * @param string $method - * @param string $path - * @param array $params - * @param array $headers - * @return array|string * @throws \Exception */ - public function call(string $method, string $path = '', array $headers = array(), array $params = array()): array|string + public function call(string $method, string $path = '', array $headers = [], array $params = []): array|string { - $headers = array_merge($this->headers, $headers); - $ch = curl_init((str_contains($path, 'http') ? $path . (($method == 'GET' && !empty($params)) ? '?' . http_build_query($params) : '') : $this->endpoint . $path . (($method == 'GET' && !empty($params)) ? '?' . http_build_query($params) : ''))); - $responseHeaders = []; - $responseStatus = -1; - $responseType = ''; - $responseBody = ''; + $headers = array_merge($this->headers, $headers); + $ch = curl_init((str_contains($path, 'http') ? $path.(($method == 'GET' && ! empty($params)) ? '?'.http_build_query($params) : '') : $this->endpoint.$path.(($method == 'GET' && ! empty($params)) ? '?'.http_build_query($params) : ''))); + $responseHeaders = []; + $responseStatus = -1; + $responseType = ''; + $responseBody = ''; switch ($headers['Content-Type']) { case 'application/json': @@ -112,13 +92,13 @@ public function call(string $method, string $path = '', array $headers = array() } foreach ($headers as $i => $header) { - $headers[] = $i . ':' . $header; + $headers[] = $i.':'.$header; unset($headers[$i]); } curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); - curl_setopt($ch, CURLOPT_USERAGENT, php_uname('s') . '-' . php_uname('r') . ':php-' . phpversion()); + curl_setopt($ch, CURLOPT_USERAGENT, php_uname('s').'-'.php_uname('r').':php-'.phpversion()); curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); curl_setopt($ch, CURLOPT_HEADERFUNCTION, function ($curl, $header) use (&$responseHeaders) { @@ -138,9 +118,9 @@ public function call(string $method, string $path = '', array $headers = array() curl_setopt($ch, CURLOPT_POSTFIELDS, $query); } - $responseBody = curl_exec($ch); + $responseBody = curl_exec($ch); - $responseType = $responseHeaders['Content-Type'] ?? $responseHeaders['content-type'] ?? ''; + $responseType = $responseHeaders['Content-Type'] ?? $responseHeaders['content-type'] ?? ''; $responseStatus = curl_getinfo($ch, CURLINFO_HTTP_CODE); switch (substr($responseType, 0, strpos($responseType, ';'))) { @@ -159,7 +139,7 @@ public function call(string $method, string $path = '', array $headers = array() if (is_array($responseBody)) { throw new \Exception(json_encode($responseBody)); } else { - throw new \Exception($responseStatus . ': ' . $responseBody); + throw new \Exception($responseStatus.': '.$responseBody); } } @@ -168,10 +148,6 @@ public function call(string $method, string $path = '', array $headers = array() /** * Flatten params array to PHP multiple format - * - * @param array $data - * @param string $prefix - * @return array */ protected function flatten(array $data, string $prefix = ''): array { diff --git a/src/Transfer/Transfer.php b/src/Transfer/Transfer.php index 7e0937b..da0a61a 100644 --- a/src/Transfer/Transfer.php +++ b/src/Transfer/Transfer.php @@ -2,40 +2,43 @@ namespace Utopia\Transfer; -use Utopia\Transfer\ResourceCache; -use Utopia\Transfer\Destination; -use Utopia\Transfer\Source; - class Transfer { public const GROUP_GENERAL = 'General'; + public const GROUP_AUTH = 'Auth'; + public const GROUP_STORAGE = 'Storage'; + public const GROUP_FUNCTIONS = 'Functions'; + public const GROUP_DATABASES = 'Databases'; + public const GROUP_SETTINGS = 'Settings'; - public const GROUP_AUTH_RESOURCES = [Resource::TYPE_USER, Resource::TYPE_TEAM, Resource::TYPE_TEAM_MEMBERSHIP, Resource::TYPE_HASH]; - public const GROUP_STORAGE_RESOURCES = [Resource::TYPE_FILE, Resource::TYPE_BUCKET, Resource::TYPE_FILEDATA]; + public const GROUP_AUTH_RESOURCES = [Resource::TYPE_USER, Resource::TYPE_TEAM, Resource::TYPE_MEMBERSHIP, Resource::TYPE_HASH]; + + public const GROUP_STORAGE_RESOURCES = [Resource::TYPE_FILE, Resource::TYPE_BUCKET]; + public const GROUP_FUNCTIONS_RESOURCES = [Resource::TYPE_FUNCTION, Resource::TYPE_ENVVAR, Resource::TYPE_DEPLOYMENT]; + public const GROUP_DATABASES_RESOURCES = [Resource::TYPE_DATABASE, Resource::TYPE_COLLECTION, Resource::TYPE_INDEX, Resource::TYPE_ATTRIBUTE, Resource::TYPE_DOCUMENT]; + public const GROUP_SETTINGS_RESOURCES = []; + public const ALL_PUBLIC_RESOURCES = [ Resource::TYPE_USER, Resource::TYPE_TEAM, - Resource::TYPE_TEAM_MEMBERSHIP, Resource::TYPE_FILE, + Resource::TYPE_MEMBERSHIP, Resource::TYPE_FILE, Resource::TYPE_BUCKET, Resource::TYPE_FUNCTION, Resource::TYPE_ENVVAR, Resource::TYPE_DEPLOYMENT, Resource::TYPE_DATABASE, Resource::TYPE_COLLECTION, Resource::TYPE_INDEX, Resource::TYPE_ATTRIBUTE, - Resource::TYPE_DOCUMENT + Resource::TYPE_DOCUMENT, ]; public const STORAGE_MAX_CHUNK_SIZE = 1024 * 1024 * 5; // 5MB /** - * @param Source $source - * @param Destination $destination - * * @return Transfer */ public function __construct(Source $source, Destination $destination) @@ -51,41 +54,21 @@ public function __construct(Source $source, Destination $destination) return $this; } - /** - * @var Source - */ protected Source $source; - /** - * @var Destination - */ protected Destination $destination; - /** - * @var string - */ protected string $currentResource; /** * A local cache of resources that were transferred. - * - * @var ResourceCache */ protected ResourceCache $resourceCache; - /** - * @var array - */ protected array $options = []; - /** - * @var array - */ protected array $callbacks = []; - /** - * @var array - */ protected array $events = []; public function getStatusCounters() @@ -94,8 +77,8 @@ public function getStatusCounters() foreach ($this->resourceCache->getAll() as $resources) { foreach ($resources as $resource) { - /** @var Resource $resource */ - if (!array_key_exists($resource->getName(), $status)) { + /** @var resource $resource */ + if (! array_key_exists($resource->getName(), $status)) { $status[$resource->getName()] = [ Resource::STATUS_PENDING => 0, Resource::STATUS_SUCCESS => 0, @@ -116,8 +99,7 @@ public function getStatusCounters() /** * Transfer Resources between adapters * - * @param array $resources - * @param callable $callback (array $resources) + * @param callable $callback (array $resources) */ public function run(array $resources, callable $callback): void { @@ -136,8 +118,6 @@ public function run(array $resources, callable $callback): void /** * Get Resource Cache - * - * @return ResourceCache */ public function getResourceCache(): ResourceCache { @@ -147,9 +127,7 @@ public function getResourceCache(): ResourceCache /** * Get Current Resource * - * @return string **/ - public function getCurrentResource(): string { return $this->currentResource; @@ -158,10 +136,8 @@ public function getCurrentResource(): string /** * Get Transfer Report * - * @param string $statusLevel + * @param string $statusLevel * If no status level is provided, all status types will be returned. - * - * @return array */ public function getReport(string $statusLevel = ''): array { diff --git a/tests/e2e/Sources/Appwrite.php b/tests/e2e/Sources/Appwrite.php index 654e489..c58e8d8 100644 --- a/tests/e2e/Sources/Appwrite.php +++ b/tests/e2e/Sources/Appwrite.php @@ -2,11 +2,6 @@ namespace Tests\E2E\Sources; -use PHPUnit\Framework\TestCase; -use Utopia\Transfer\Resources\Auth\Hash; -use Utopia\Transfer\Resources\Auth\Team; -use Utopia\Transfer\Resources\Auth\User; - class Appwrite extends SourceTest { public function testExportResources(): void diff --git a/tests/e2e/adapters/MockDestination.php b/tests/e2e/adapters/MockDestination.php index 01b052a..80bab3b 100644 --- a/tests/e2e/adapters/MockDestination.php +++ b/tests/e2e/adapters/MockDestination.php @@ -4,7 +4,6 @@ use Utopia\Transfer\Destination; use Utopia\Transfer\Resource; -use Utopia\Transfer\Transfer; class MockDestination extends Destination { @@ -22,7 +21,6 @@ public function getSupportedResources(): array Resource::TYPE_DATABASE, Resource::TYPE_DOCUMENT, Resource::TYPE_FILE, - Resource::TYPE_FILEDATA, Resource::TYPE_FUNCTION, Resource::TYPE_DEPLOYMENT, Resource::TYPE_HASH, @@ -30,7 +28,7 @@ public function getSupportedResources(): array Resource::TYPE_USER, Resource::TYPE_ENVVAR, Resource::TYPE_TEAM, - Resource::TYPE_TEAM_MEMBERSHIP, + Resource::TYPE_MEMBERSHIP, ]; } diff --git a/tests/e2e/adapters/MockSource.php b/tests/e2e/adapters/MockSource.php index c01b545..5609c8c 100644 --- a/tests/e2e/adapters/MockSource.php +++ b/tests/e2e/adapters/MockSource.php @@ -19,7 +19,7 @@ public function getSupportedResources(): array Transfer::GROUP_DATABASES, Transfer::GROUP_FUNCTIONS, Transfer::GROUP_SETTINGS, - Transfer::GROUP_STORAGE + Transfer::GROUP_STORAGE, ]; } diff --git a/tests/resources/nhost/backup.tar b/tests/resources/nhost/backup.tar new file mode 100644 index 0000000..6657eca --- /dev/null +++ b/tests/resources/nhost/backup.tar @@ -0,0 +1,2361 @@ +-- +-- PostgreSQL database dump +-- + +-- Dumped from database version 14.5 (Debian 14.5-2.pgdg110+2) +-- Dumped by pg_dump version 15.2 + +SET statement_timeout = 0; +SET lock_timeout = 0; +SET idle_in_transaction_session_timeout = 0; +SET client_encoding = 'UTF8'; +SET standard_conforming_strings = on; +SELECT pg_catalog.set_config('search_path', '', false); +SET check_function_bodies = false; +SET xmloption = content; +SET client_min_messages = warning; +SET row_security = off; + +-- +-- Name: auth; Type: SCHEMA; Schema: -; Owner: nhost_admin +-- + +CREATE SCHEMA auth; + + +ALTER SCHEMA auth OWNER TO nhost_admin; + +-- +-- Name: hdb_catalog; Type: SCHEMA; Schema: -; Owner: nhost_hasura +-- + +CREATE SCHEMA hdb_catalog; + + +ALTER SCHEMA hdb_catalog OWNER TO nhost_hasura; + +-- +-- Name: pgbouncer; Type: SCHEMA; Schema: -; Owner: nhost_admin +-- + +CREATE SCHEMA pgbouncer; + + +ALTER SCHEMA pgbouncer OWNER TO nhost_admin; + +-- +-- Name: public; Type: SCHEMA; Schema: -; Owner: postgres +-- + +-- *not* creating schema, since initdb creates it + + +ALTER SCHEMA public OWNER TO postgres; + +-- +-- Name: storage; Type: SCHEMA; Schema: -; Owner: nhost_admin +-- + +CREATE SCHEMA storage; + + +ALTER SCHEMA storage OWNER TO nhost_admin; + +-- +-- Name: citext; Type: EXTENSION; Schema: -; Owner: - +-- + +CREATE EXTENSION IF NOT EXISTS citext WITH SCHEMA public; + + +-- +-- Name: EXTENSION citext; Type: COMMENT; Schema: -; Owner: +-- + +COMMENT ON EXTENSION citext IS 'data type for case-insensitive character strings'; + + +-- +-- Name: pgcrypto; Type: EXTENSION; Schema: -; Owner: - +-- + +CREATE EXTENSION IF NOT EXISTS pgcrypto WITH SCHEMA public; + + +-- +-- Name: EXTENSION pgcrypto; Type: COMMENT; Schema: -; Owner: +-- + +COMMENT ON EXTENSION pgcrypto IS 'cryptographic functions'; + + +-- +-- Name: email; Type: DOMAIN; Schema: auth; Owner: nhost_auth_admin +-- + +CREATE DOMAIN auth.email AS public.citext + CONSTRAINT email_check CHECK (((VALUE)::text ~ '^[a-zA-Z0-9.!#$%&''*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$'::text)); + + +ALTER DOMAIN auth.email OWNER TO nhost_auth_admin; + +-- +-- Name: set_current_timestamp_updated_at(); Type: FUNCTION; Schema: auth; Owner: nhost_auth_admin +-- + +CREATE FUNCTION auth.set_current_timestamp_updated_at() RETURNS trigger + LANGUAGE plpgsql + AS $$ +DECLARE + _new record; +BEGIN + _new := new; + _new. "updated_at" = now(); + RETURN _new; +END; +$$; + + +ALTER FUNCTION auth.set_current_timestamp_updated_at() OWNER TO nhost_auth_admin; + +-- +-- Name: gen_hasura_uuid(); Type: FUNCTION; Schema: hdb_catalog; Owner: nhost_hasura +-- + +CREATE FUNCTION hdb_catalog.gen_hasura_uuid() RETURNS uuid + LANGUAGE sql + AS $$select gen_random_uuid()$$; + + +ALTER FUNCTION hdb_catalog.gen_hasura_uuid() OWNER TO nhost_hasura; + +-- +-- Name: user_lookup(text); Type: FUNCTION; Schema: pgbouncer; Owner: postgres +-- + +CREATE FUNCTION pgbouncer.user_lookup(i_username text, OUT uname text, OUT phash text) RETURNS record + LANGUAGE plpgsql SECURITY DEFINER + AS $$ +BEGIN + SELECT usename, passwd FROM pg_catalog.pg_shadow + WHERE usename = i_username INTO uname, phash; + RETURN; +END; +$$; + + +ALTER FUNCTION pgbouncer.user_lookup(i_username text, OUT uname text, OUT phash text) OWNER TO postgres; + +-- +-- Name: protect_default_bucket_delete(); Type: FUNCTION; Schema: storage; Owner: nhost_storage_admin +-- + +CREATE FUNCTION storage.protect_default_bucket_delete() RETURNS trigger + LANGUAGE plpgsql + AS $$ +BEGIN + IF OLD.ID = 'default' THEN + RAISE EXCEPTION 'Can not delete default bucket'; + END IF; + RETURN OLD; +END; +$$; + + +ALTER FUNCTION storage.protect_default_bucket_delete() OWNER TO nhost_storage_admin; + +-- +-- Name: protect_default_bucket_update(); Type: FUNCTION; Schema: storage; Owner: nhost_storage_admin +-- + +CREATE FUNCTION storage.protect_default_bucket_update() RETURNS trigger + LANGUAGE plpgsql + AS $$ +BEGIN + IF OLD.ID = 'default' AND NEW.ID <> 'default' THEN + RAISE EXCEPTION 'Can not rename default bucket'; + END IF; + RETURN NEW; +END; +$$; + + +ALTER FUNCTION storage.protect_default_bucket_update() OWNER TO nhost_storage_admin; + +-- +-- Name: set_current_timestamp_updated_at(); Type: FUNCTION; Schema: storage; Owner: nhost_storage_admin +-- + +CREATE FUNCTION storage.set_current_timestamp_updated_at() RETURNS trigger + LANGUAGE plpgsql + AS $$ +DECLARE + _new record; +BEGIN + _new := new; + _new. "updated_at" = now(); + RETURN _new; +END; +$$; + + +ALTER FUNCTION storage.set_current_timestamp_updated_at() OWNER TO nhost_storage_admin; + +SET default_tablespace = ''; + +SET default_table_access_method = heap; + +-- +-- Name: migrations; Type: TABLE; Schema: auth; Owner: nhost_auth_admin +-- + +CREATE TABLE auth.migrations ( + id integer NOT NULL, + name character varying(100) NOT NULL, + hash character varying(40) NOT NULL, + executed_at timestamp without time zone DEFAULT CURRENT_TIMESTAMP +); + + +ALTER TABLE auth.migrations OWNER TO nhost_auth_admin; + +-- +-- Name: TABLE migrations; Type: COMMENT; Schema: auth; Owner: nhost_auth_admin +-- + +COMMENT ON TABLE auth.migrations IS 'Internal table for tracking migrations. Don''t modify its structure as Hasura Auth relies on it to function properly.'; + + +-- +-- Name: provider_requests; Type: TABLE; Schema: auth; Owner: nhost_auth_admin +-- + +CREATE TABLE auth.provider_requests ( + id uuid NOT NULL, + options jsonb +); + + +ALTER TABLE auth.provider_requests OWNER TO nhost_auth_admin; + +-- +-- Name: TABLE provider_requests; Type: COMMENT; Schema: auth; Owner: nhost_auth_admin +-- + +COMMENT ON TABLE auth.provider_requests IS 'Oauth requests, inserted before redirecting to the provider''s site. Don''t modify its structure as Hasura Auth relies on it to function properly.'; + + +-- +-- Name: providers; Type: TABLE; Schema: auth; Owner: nhost_auth_admin +-- + +CREATE TABLE auth.providers ( + id text NOT NULL +); + + +ALTER TABLE auth.providers OWNER TO nhost_auth_admin; + +-- +-- Name: TABLE providers; Type: COMMENT; Schema: auth; Owner: nhost_auth_admin +-- + +COMMENT ON TABLE auth.providers IS 'List of available Oauth providers. Don''t modify its structure as Hasura Auth relies on it to function properly.'; + + +-- +-- Name: refresh_tokens; Type: TABLE; Schema: auth; Owner: nhost_auth_admin +-- + +CREATE TABLE auth.refresh_tokens ( + refresh_token uuid NOT NULL, + created_at timestamp with time zone DEFAULT now() NOT NULL, + expires_at timestamp with time zone NOT NULL, + user_id uuid NOT NULL, + refresh_token_hash character varying(255) GENERATED ALWAYS AS (sha256(((refresh_token)::text)::bytea)) STORED +); + + +ALTER TABLE auth.refresh_tokens OWNER TO nhost_auth_admin; + +-- +-- Name: TABLE refresh_tokens; Type: COMMENT; Schema: auth; Owner: nhost_auth_admin +-- + +COMMENT ON TABLE auth.refresh_tokens IS 'User refresh tokens. Hasura auth uses them to rotate new access tokens as long as the refresh token is not expired. Don''t modify its structure as Hasura Auth relies on it to function properly.'; + + +-- +-- Name: COLUMN refresh_tokens.refresh_token; Type: COMMENT; Schema: auth; Owner: nhost_auth_admin +-- + +COMMENT ON COLUMN auth.refresh_tokens.refresh_token IS 'DEPRECATED: auto-generated refresh token id. Will be replaced by a genereric id column that will be used as a primary key, not the refresh token itself. Use refresh_token_hash instead.'; + + +-- +-- Name: roles; Type: TABLE; Schema: auth; Owner: nhost_auth_admin +-- + +CREATE TABLE auth.roles ( + role text NOT NULL +); + + +ALTER TABLE auth.roles OWNER TO nhost_auth_admin; + +-- +-- Name: TABLE roles; Type: COMMENT; Schema: auth; Owner: nhost_auth_admin +-- + +COMMENT ON TABLE auth.roles IS 'Persistent Hasura roles for users. Don''t modify its structure as Hasura Auth relies on it to function properly.'; + + +-- +-- Name: user_providers; Type: TABLE; Schema: auth; Owner: nhost_auth_admin +-- + +CREATE TABLE auth.user_providers ( + id uuid DEFAULT public.gen_random_uuid() NOT NULL, + created_at timestamp with time zone DEFAULT now() NOT NULL, + updated_at timestamp with time zone DEFAULT now() NOT NULL, + user_id uuid NOT NULL, + access_token text NOT NULL, + refresh_token text, + provider_id text NOT NULL, + provider_user_id text NOT NULL +); + + +ALTER TABLE auth.user_providers OWNER TO nhost_auth_admin; + +-- +-- Name: TABLE user_providers; Type: COMMENT; Schema: auth; Owner: nhost_auth_admin +-- + +COMMENT ON TABLE auth.user_providers IS 'Active providers for a given user. Don''t modify its structure as Hasura Auth relies on it to function properly.'; + + +-- +-- Name: user_roles; Type: TABLE; Schema: auth; Owner: nhost_auth_admin +-- + +CREATE TABLE auth.user_roles ( + id uuid DEFAULT public.gen_random_uuid() NOT NULL, + created_at timestamp with time zone DEFAULT now() NOT NULL, + user_id uuid NOT NULL, + role text NOT NULL +); + + +ALTER TABLE auth.user_roles OWNER TO nhost_auth_admin; + +-- +-- Name: TABLE user_roles; Type: COMMENT; Schema: auth; Owner: nhost_auth_admin +-- + +COMMENT ON TABLE auth.user_roles IS 'Roles of users. Don''t modify its structure as Hasura Auth relies on it to function properly.'; + + +-- +-- Name: user_security_keys; Type: TABLE; Schema: auth; Owner: nhost_auth_admin +-- + +CREATE TABLE auth.user_security_keys ( + id uuid DEFAULT public.gen_random_uuid() NOT NULL, + user_id uuid NOT NULL, + credential_id text NOT NULL, + credential_public_key bytea, + counter bigint DEFAULT 0 NOT NULL, + transports character varying(255) DEFAULT ''::character varying NOT NULL, + nickname text +); + + +ALTER TABLE auth.user_security_keys OWNER TO nhost_auth_admin; + +-- +-- Name: TABLE user_security_keys; Type: COMMENT; Schema: auth; Owner: nhost_auth_admin +-- + +COMMENT ON TABLE auth.user_security_keys IS 'User webauthn security keys. Don''t modify its structure as Hasura Auth relies on it to function properly.'; + + +-- +-- Name: users; Type: TABLE; Schema: auth; Owner: nhost_auth_admin +-- + +CREATE TABLE auth.users ( + id uuid DEFAULT public.gen_random_uuid() NOT NULL, + created_at timestamp with time zone DEFAULT now() NOT NULL, + updated_at timestamp with time zone DEFAULT now() NOT NULL, + last_seen timestamp with time zone, + disabled boolean DEFAULT false NOT NULL, + display_name text DEFAULT ''::text NOT NULL, + avatar_url text DEFAULT ''::text NOT NULL, + locale character varying(2) NOT NULL, + email auth.email, + phone_number text, + password_hash text, + email_verified boolean DEFAULT false NOT NULL, + phone_number_verified boolean DEFAULT false NOT NULL, + new_email auth.email, + otp_method_last_used text, + otp_hash text, + otp_hash_expires_at timestamp with time zone DEFAULT now() NOT NULL, + default_role text DEFAULT 'user'::text NOT NULL, + is_anonymous boolean DEFAULT false NOT NULL, + totp_secret text, + active_mfa_type text, + ticket text, + ticket_expires_at timestamp with time zone DEFAULT now() NOT NULL, + metadata jsonb, + webauthn_current_challenge text, + CONSTRAINT active_mfa_types_check CHECK (((active_mfa_type = 'totp'::text) OR (active_mfa_type = 'sms'::text))) +); + + +ALTER TABLE auth.users OWNER TO nhost_auth_admin; + +-- +-- Name: TABLE users; Type: COMMENT; Schema: auth; Owner: nhost_auth_admin +-- + +COMMENT ON TABLE auth.users IS 'User account information. Don''t modify its structure as Hasura Auth relies on it to function properly.'; + + +-- +-- Name: hdb_action_log; Type: TABLE; Schema: hdb_catalog; Owner: nhost_hasura +-- + +CREATE TABLE hdb_catalog.hdb_action_log ( + id uuid DEFAULT hdb_catalog.gen_hasura_uuid() NOT NULL, + action_name text, + input_payload jsonb NOT NULL, + request_headers jsonb NOT NULL, + session_variables jsonb NOT NULL, + response_payload jsonb, + errors jsonb, + created_at timestamp with time zone DEFAULT now() NOT NULL, + response_received_at timestamp with time zone, + status text NOT NULL, + CONSTRAINT hdb_action_log_status_check CHECK ((status = ANY (ARRAY['created'::text, 'processing'::text, 'completed'::text, 'error'::text]))) +); + + +ALTER TABLE hdb_catalog.hdb_action_log OWNER TO nhost_hasura; + +-- +-- Name: hdb_cron_event_invocation_logs; Type: TABLE; Schema: hdb_catalog; Owner: nhost_hasura +-- + +CREATE TABLE hdb_catalog.hdb_cron_event_invocation_logs ( + id text DEFAULT hdb_catalog.gen_hasura_uuid() NOT NULL, + event_id text, + status integer, + request json, + response json, + created_at timestamp with time zone DEFAULT now() +); + + +ALTER TABLE hdb_catalog.hdb_cron_event_invocation_logs OWNER TO nhost_hasura; + +-- +-- Name: hdb_cron_events; Type: TABLE; Schema: hdb_catalog; Owner: nhost_hasura +-- + +CREATE TABLE hdb_catalog.hdb_cron_events ( + id text DEFAULT hdb_catalog.gen_hasura_uuid() NOT NULL, + trigger_name text NOT NULL, + scheduled_time timestamp with time zone NOT NULL, + status text DEFAULT 'scheduled'::text NOT NULL, + tries integer DEFAULT 0 NOT NULL, + created_at timestamp with time zone DEFAULT now(), + next_retry_at timestamp with time zone, + CONSTRAINT valid_status CHECK ((status = ANY (ARRAY['scheduled'::text, 'locked'::text, 'delivered'::text, 'error'::text, 'dead'::text]))) +); + + +ALTER TABLE hdb_catalog.hdb_cron_events OWNER TO nhost_hasura; + +-- +-- Name: hdb_metadata; Type: TABLE; Schema: hdb_catalog; Owner: nhost_hasura +-- + +CREATE TABLE hdb_catalog.hdb_metadata ( + id integer NOT NULL, + metadata json NOT NULL, + resource_version integer DEFAULT 1 NOT NULL +); + + +ALTER TABLE hdb_catalog.hdb_metadata OWNER TO nhost_hasura; + +-- +-- Name: hdb_scheduled_event_invocation_logs; Type: TABLE; Schema: hdb_catalog; Owner: nhost_hasura +-- + +CREATE TABLE hdb_catalog.hdb_scheduled_event_invocation_logs ( + id text DEFAULT hdb_catalog.gen_hasura_uuid() NOT NULL, + event_id text, + status integer, + request json, + response json, + created_at timestamp with time zone DEFAULT now() +); + + +ALTER TABLE hdb_catalog.hdb_scheduled_event_invocation_logs OWNER TO nhost_hasura; + +-- +-- Name: hdb_scheduled_events; Type: TABLE; Schema: hdb_catalog; Owner: nhost_hasura +-- + +CREATE TABLE hdb_catalog.hdb_scheduled_events ( + id text DEFAULT hdb_catalog.gen_hasura_uuid() NOT NULL, + webhook_conf json NOT NULL, + scheduled_time timestamp with time zone NOT NULL, + retry_conf json, + payload json, + header_conf json, + status text DEFAULT 'scheduled'::text NOT NULL, + tries integer DEFAULT 0 NOT NULL, + created_at timestamp with time zone DEFAULT now(), + next_retry_at timestamp with time zone, + comment text, + CONSTRAINT valid_status CHECK ((status = ANY (ARRAY['scheduled'::text, 'locked'::text, 'delivered'::text, 'error'::text, 'dead'::text]))) +); + + +ALTER TABLE hdb_catalog.hdb_scheduled_events OWNER TO nhost_hasura; + +-- +-- Name: hdb_schema_notifications; Type: TABLE; Schema: hdb_catalog; Owner: nhost_hasura +-- + +CREATE TABLE hdb_catalog.hdb_schema_notifications ( + id integer NOT NULL, + notification json NOT NULL, + resource_version integer DEFAULT 1 NOT NULL, + instance_id uuid NOT NULL, + updated_at timestamp with time zone DEFAULT now(), + CONSTRAINT hdb_schema_notifications_id_check CHECK ((id = 1)) +); + + +ALTER TABLE hdb_catalog.hdb_schema_notifications OWNER TO nhost_hasura; + +-- +-- Name: hdb_version; Type: TABLE; Schema: hdb_catalog; Owner: nhost_hasura +-- + +CREATE TABLE hdb_catalog.hdb_version ( + hasura_uuid uuid DEFAULT hdb_catalog.gen_hasura_uuid() NOT NULL, + version text NOT NULL, + upgraded_on timestamp with time zone NOT NULL, + cli_state jsonb DEFAULT '{}'::jsonb NOT NULL, + console_state jsonb DEFAULT '{}'::jsonb NOT NULL +); + + +ALTER TABLE hdb_catalog.hdb_version OWNER TO nhost_hasura; + +-- +-- Name: data_test; Type: TABLE; Schema: public; Owner: nhost_hasura +-- + +CREATE TABLE public.data_test ( + text text NOT NULL, + charvar character varying NOT NULL, + "char" bpchar NOT NULL, + uuid uuid NOT NULL, + json json NOT NULL, + jsonb jsonb NOT NULL, + "smallint" smallint NOT NULL, + "integer" integer NOT NULL, + "bigint" bigint NOT NULL, + "decimal" numeric NOT NULL, + "numeric" numeric NOT NULL, + "real" real NOT NULL, + double_precision double precision NOT NULL, + bool boolean NOT NULL, + date date NOT NULL, + "timestamp" timestamp without time zone NOT NULL, + timestamptz timestamp with time zone NOT NULL, + "time" time without time zone NOT NULL, + timetz timestamp with time zone NOT NULL, + "interval" interval NOT NULL, + bytea bytea NOT NULL, + money money NOT NULL +); + + +ALTER TABLE public.data_test OWNER TO nhost_hasura; + +-- +-- Name: test_table_1; Type: TABLE; Schema: public; Owner: nhost_hasura +-- + +CREATE TABLE public.test_table_1 ( + name text NOT NULL, + uuid uuid, + user_data json NOT NULL, + json_binary jsonb +); + + +ALTER TABLE public.test_table_1 OWNER TO nhost_hasura; + +-- +-- Name: buckets; Type: TABLE; Schema: storage; Owner: nhost_storage_admin +-- + +CREATE TABLE storage.buckets ( + id text NOT NULL, + created_at timestamp with time zone DEFAULT now() NOT NULL, + updated_at timestamp with time zone DEFAULT now() NOT NULL, + download_expiration integer DEFAULT 30 NOT NULL, + min_upload_file_size integer DEFAULT 1 NOT NULL, + max_upload_file_size integer DEFAULT 50000000 NOT NULL, + cache_control text DEFAULT 'max-age=3600'::text, + presigned_urls_enabled boolean DEFAULT true NOT NULL, + CONSTRAINT download_expiration_valid_range CHECK (((download_expiration >= 1) AND (download_expiration <= 604800))) +); + + +ALTER TABLE storage.buckets OWNER TO nhost_storage_admin; + +-- +-- Name: files; Type: TABLE; Schema: storage; Owner: nhost_storage_admin +-- + +CREATE TABLE storage.files ( + id uuid DEFAULT public.gen_random_uuid() NOT NULL, + created_at timestamp with time zone DEFAULT now() NOT NULL, + updated_at timestamp with time zone DEFAULT now() NOT NULL, + bucket_id text DEFAULT 'default'::text NOT NULL, + name text, + size integer, + mime_type text, + etag text, + is_uploaded boolean DEFAULT false, + uploaded_by_user_id uuid +); + + +ALTER TABLE storage.files OWNER TO nhost_storage_admin; + +-- +-- Name: schema_migrations; Type: TABLE; Schema: storage; Owner: nhost_storage_admin +-- + +CREATE TABLE storage.schema_migrations ( + version bigint NOT NULL, + dirty boolean NOT NULL +); + + +ALTER TABLE storage.schema_migrations OWNER TO nhost_storage_admin; + +-- +-- Data for Name: migrations; Type: TABLE DATA; Schema: auth; Owner: nhost_auth_admin +-- + +COPY auth.migrations (id, name, hash, executed_at) FROM stdin; +0 create-migrations-table 9c0c864e0ccb0f8d1c77ab0576ef9f2841ec1b68 2023-04-18 03:08:07.102223 +1 create-initial-tables c16083c88329c867581a9c73c3f140783a1a5df4 2023-04-18 03:08:07.190394 +2 custom-user-fields 78236c9c2b50da88786bcf50099dd290f820e000 2023-04-18 03:08:07.194918 +3 discord-twitch-providers 857db1e92c7a8034e61a3d88ea672aec9b424036 2023-04-18 03:08:07.198837 +4 provider-request-options 42428265112b904903d9ad7833d8acf2812a00ed 2023-04-18 03:08:07.202855 +5 table-comments 78f76f88eff3b11ebab9be4f2469020dae017110 2023-04-18 03:08:07.204858 +6 setup-webauthn 87ba279363f8ecf8b450a681938a74b788cf536c 2023-04-18 03:08:07.221819 +7 add_authenticator_nickname d32fd62bb7a441eea48c5434f5f3744f2e334288 2023-04-18 03:08:07.22606 +8 workos-provider 0727238a633ff119bedcbebfec6a9ea83b2bd01d 2023-04-18 03:08:07.230566 +9 rename-authenticator-to-security-key fd7e00bef4d141a6193cf9642afd88fb6fe2b283 2023-04-18 03:08:07.235551 +10 azuread-provider f492ff4780f8210016e1c12fa0ed83eb4278a780 2023-04-18 03:08:07.243968 +11 add_refresh_token_hash_column 62a2cd295f63153dd9f16f3159d1ab2a49b01c2f 2023-04-18 03:08:07.262305 +\. + + +-- +-- Data for Name: provider_requests; Type: TABLE DATA; Schema: auth; Owner: nhost_auth_admin +-- + +COPY auth.provider_requests (id, options) FROM stdin; +\. + + +-- +-- Data for Name: providers; Type: TABLE DATA; Schema: auth; Owner: nhost_auth_admin +-- + +COPY auth.providers (id) FROM stdin; +github +facebook +twitter +google +apple +linkedin +windowslive +spotify +strava +gitlab +bitbucket +discord +twitch +workos +azuread +\. + + +-- +-- Data for Name: refresh_tokens; Type: TABLE DATA; Schema: auth; Owner: nhost_auth_admin +-- + +COPY auth.refresh_tokens (refresh_token, created_at, expires_at, user_id) FROM stdin; +\. + + +-- +-- Data for Name: roles; Type: TABLE DATA; Schema: auth; Owner: nhost_auth_admin +-- + +COPY auth.roles (role) FROM stdin; +user +anonymous +me +\. + + +-- +-- Data for Name: user_providers; Type: TABLE DATA; Schema: auth; Owner: nhost_auth_admin +-- + +COPY auth.user_providers (id, created_at, updated_at, user_id, access_token, refresh_token, provider_id, provider_user_id) FROM stdin; +\. + + +-- +-- Data for Name: user_roles; Type: TABLE DATA; Schema: auth; Owner: nhost_auth_admin +-- + +COPY auth.user_roles (id, created_at, user_id, role) FROM stdin; +31f489e2-18a2-446f-989d-5c51aa490d38 2023-05-11 05:01:30.97538+00 d148130e-b92f-4aed-ba91-7885a988dbad me +fefa3f20-6d80-4593-8753-09c29cf5446a 2023-05-11 05:01:30.97538+00 d148130e-b92f-4aed-ba91-7885a988dbad user +\. + + +-- +-- Data for Name: user_security_keys; Type: TABLE DATA; Schema: auth; Owner: nhost_auth_admin +-- + +COPY auth.user_security_keys (id, user_id, credential_id, credential_public_key, counter, transports, nickname) FROM stdin; +\. + + +-- +-- Data for Name: users; Type: TABLE DATA; Schema: auth; Owner: nhost_auth_admin +-- + +COPY auth.users (id, created_at, updated_at, last_seen, disabled, display_name, avatar_url, locale, email, phone_number, password_hash, email_verified, phone_number_verified, new_email, otp_method_last_used, otp_hash, otp_hash_expires_at, default_role, is_anonymous, totp_secret, active_mfa_type, ticket, ticket_expires_at, metadata, webauthn_current_challenge) FROM stdin; +d148130e-b92f-4aed-ba91-7885a988dbad 2023-05-11 05:01:30.97538+00 2023-05-11 05:01:30.97538+00 \N f test@test.com https://s.gravatar.com/avatar/b642b4217b34b1e8d3bd915fc65c4452?r=g&default=blank en test@test.com \N $2a$10$J77xU.nuyH2f/6k1jfsn..Mty1i9dMeuKQdMcuBAqLcrDW7f9vKoK f f \N \N \N 2023-05-11 05:01:30.97538+00 user f \N \N verifyEmail:872dc13f-c73d-43a7-ad7b-bd88d0a8e43c 2023-06-10 05:01:30.967+00 {} \N +\. + + +-- +-- Data for Name: hdb_action_log; Type: TABLE DATA; Schema: hdb_catalog; Owner: nhost_hasura +-- + +COPY hdb_catalog.hdb_action_log (id, action_name, input_payload, request_headers, session_variables, response_payload, errors, created_at, response_received_at, status) FROM stdin; +\. + + +-- +-- Data for Name: hdb_cron_event_invocation_logs; Type: TABLE DATA; Schema: hdb_catalog; Owner: nhost_hasura +-- + +COPY hdb_catalog.hdb_cron_event_invocation_logs (id, event_id, status, request, response, created_at) FROM stdin; +\. + + +-- +-- Data for Name: hdb_cron_events; Type: TABLE DATA; Schema: hdb_catalog; Owner: nhost_hasura +-- + +COPY hdb_catalog.hdb_cron_events (id, trigger_name, scheduled_time, status, tries, created_at, next_retry_at) FROM stdin; +\. + + +-- +-- Data for Name: hdb_metadata; Type: TABLE DATA; Schema: hdb_catalog; Owner: nhost_hasura +-- + +COPY hdb_catalog.hdb_metadata (id, metadata, resource_version) FROM stdin; +1 {"sources":[{"configuration":{"connection_info":{"database_url":{"from_env":"HASURA_GRAPHQL_DATABASE_URL"},"isolation_level":"read-committed","pool_settings":{"connection_lifetime":600,"idle_timeout":180,"max_connections":50,"retries":1},"use_prepared_statements":true}},"kind":"postgres","name":"default","tables":[{"configuration":{"column_config":{"id":{"custom_name":"id"},"options":{"custom_name":"options"}},"custom_column_names":{"id":"id","options":"options"},"custom_name":"authProviderRequests","custom_root_fields":{"delete":"deleteAuthProviderRequests","delete_by_pk":"deleteAuthProviderRequest","insert":"insertAuthProviderRequests","insert_one":"insertAuthProviderRequest","select":"authProviderRequests","select_aggregate":"authProviderRequestsAggregate","select_by_pk":"authProviderRequest","update":"updateAuthProviderRequests","update_by_pk":"updateAuthProviderRequest"}},"table":{"name":"provider_requests","schema":"auth"}},{"array_relationships":[{"name":"userProviders","using":{"foreign_key_constraint_on":{"column":"provider_id","table":{"name":"user_providers","schema":"auth"}}}}],"configuration":{"column_config":{"id":{"custom_name":"id"}},"custom_column_names":{"id":"id"},"custom_name":"authProviders","custom_root_fields":{"delete":"deleteAuthProviders","delete_by_pk":"deleteAuthProvider","insert":"insertAuthProviders","insert_one":"insertAuthProvider","select":"authProviders","select_aggregate":"authProvidersAggregate","select_by_pk":"authProvider","update":"updateAuthProviders","update_by_pk":"updateAuthProvider"}},"table":{"name":"providers","schema":"auth"}},{"configuration":{"column_config":{"created_at":{"custom_name":"createdAt"},"expires_at":{"custom_name":"expiresAt"},"refresh_token":{"custom_name":"refreshToken"},"refresh_token_hash":{"custom_name":"refreshTokenHash"},"user_id":{"custom_name":"userId"}},"custom_column_names":{"created_at":"createdAt","expires_at":"expiresAt","refresh_token":"refreshToken","refresh_token_hash":"refreshTokenHash","user_id":"userId"},"custom_name":"authRefreshTokens","custom_root_fields":{"delete":"deleteAuthRefreshTokens","delete_by_pk":"deleteAuthRefreshToken","insert":"insertAuthRefreshTokens","insert_one":"insertAuthRefreshToken","select":"authRefreshTokens","select_aggregate":"authRefreshTokensAggregate","select_by_pk":"authRefreshToken","update":"updateAuthRefreshTokens","update_by_pk":"updateAuthRefreshToken"}},"object_relationships":[{"name":"user","using":{"foreign_key_constraint_on":"user_id"}}],"table":{"name":"refresh_tokens","schema":"auth"}},{"array_relationships":[{"name":"userRoles","using":{"foreign_key_constraint_on":{"column":"role","table":{"name":"user_roles","schema":"auth"}}}},{"name":"usersByDefaultRole","using":{"foreign_key_constraint_on":{"column":"default_role","table":{"name":"users","schema":"auth"}}}}],"configuration":{"column_config":{"role":{"custom_name":"role"}},"custom_column_names":{"role":"role"},"custom_name":"authRoles","custom_root_fields":{"delete":"deleteAuthRoles","delete_by_pk":"deleteAuthRole","insert":"insertAuthRoles","insert_one":"insertAuthRole","select":"authRoles","select_aggregate":"authRolesAggregate","select_by_pk":"authRole","update":"updateAuthRoles","update_by_pk":"updateAuthRole"}},"table":{"name":"roles","schema":"auth"}},{"configuration":{"column_config":{"access_token":{"custom_name":"accessToken"},"created_at":{"custom_name":"createdAt"},"id":{"custom_name":"id"},"provider_id":{"custom_name":"providerId"},"provider_user_id":{"custom_name":"providerUserId"},"refresh_token":{"custom_name":"refreshToken"},"updated_at":{"custom_name":"updatedAt"},"user_id":{"custom_name":"userId"}},"custom_column_names":{"access_token":"accessToken","created_at":"createdAt","id":"id","provider_id":"providerId","provider_user_id":"providerUserId","refresh_token":"refreshToken","updated_at":"updatedAt","user_id":"userId"},"custom_name":"authUserProviders","custom_root_fields":{"delete":"deleteAuthUserProviders","delete_by_pk":"deleteAuthUserProvider","insert":"insertAuthUserProviders","insert_one":"insertAuthUserProvider","select":"authUserProviders","select_aggregate":"authUserProvidersAggregate","select_by_pk":"authUserProvider","update":"updateAuthUserProviders","update_by_pk":"updateAuthUserProvider"}},"object_relationships":[{"name":"provider","using":{"foreign_key_constraint_on":"provider_id"}},{"name":"user","using":{"foreign_key_constraint_on":"user_id"}}],"table":{"name":"user_providers","schema":"auth"}},{"configuration":{"column_config":{"created_at":{"custom_name":"createdAt"},"id":{"custom_name":"id"},"role":{"custom_name":"role"},"user_id":{"custom_name":"userId"}},"custom_column_names":{"created_at":"createdAt","id":"id","role":"role","user_id":"userId"},"custom_name":"authUserRoles","custom_root_fields":{"delete":"deleteAuthUserRoles","delete_by_pk":"deleteAuthUserRole","insert":"insertAuthUserRoles","insert_one":"insertAuthUserRole","select":"authUserRoles","select_aggregate":"authUserRolesAggregate","select_by_pk":"authUserRole","update":"updateAuthUserRoles","update_by_pk":"updateAuthUserRole"}},"object_relationships":[{"name":"roleByRole","using":{"foreign_key_constraint_on":"role"}},{"name":"user","using":{"foreign_key_constraint_on":"user_id"}}],"table":{"name":"user_roles","schema":"auth"}},{"configuration":{"column_config":{"credential_id":{"custom_name":"credentialId"},"credential_public_key":{"custom_name":"credentialPublicKey"},"id":{"custom_name":"id"},"user_id":{"custom_name":"userId"}},"custom_column_names":{"credential_id":"credentialId","credential_public_key":"credentialPublicKey","id":"id","user_id":"userId"},"custom_name":"authUserSecurityKeys","custom_root_fields":{"delete":"deleteAuthUserSecurityKeys","delete_by_pk":"deleteAuthUserSecurityKey","insert":"insertAuthUserSecurityKeys","insert_one":"insertAuthUserSecurityKey","select":"authUserSecurityKeys","select_aggregate":"authUserSecurityKeysAggregate","select_by_pk":"authUserSecurityKey","update":"updateAuthUserSecurityKeys","update_by_pk":"updateAuthUserSecurityKey"}},"object_relationships":[{"name":"user","using":{"foreign_key_constraint_on":"user_id"}}],"table":{"name":"user_security_keys","schema":"auth"}},{"array_relationships":[{"name":"refreshTokens","using":{"foreign_key_constraint_on":{"column":"user_id","table":{"name":"refresh_tokens","schema":"auth"}}}},{"name":"roles","using":{"foreign_key_constraint_on":{"column":"user_id","table":{"name":"user_roles","schema":"auth"}}}},{"name":"securityKeys","using":{"foreign_key_constraint_on":{"column":"user_id","table":{"name":"user_security_keys","schema":"auth"}}}},{"name":"userProviders","using":{"foreign_key_constraint_on":{"column":"user_id","table":{"name":"user_providers","schema":"auth"}}}}],"configuration":{"column_config":{"active_mfa_type":{"custom_name":"activeMfaType"},"avatar_url":{"custom_name":"avatarUrl"},"created_at":{"custom_name":"createdAt"},"default_role":{"custom_name":"defaultRole"},"disabled":{"custom_name":"disabled"},"display_name":{"custom_name":"displayName"},"email":{"custom_name":"email"},"email_verified":{"custom_name":"emailVerified"},"id":{"custom_name":"id"},"is_anonymous":{"custom_name":"isAnonymous"},"last_seen":{"custom_name":"lastSeen"},"locale":{"custom_name":"locale"},"new_email":{"custom_name":"newEmail"},"otp_hash":{"custom_name":"otpHash"},"otp_hash_expires_at":{"custom_name":"otpHashExpiresAt"},"otp_method_last_used":{"custom_name":"otpMethodLastUsed"},"password_hash":{"custom_name":"passwordHash"},"phone_number":{"custom_name":"phoneNumber"},"phone_number_verified":{"custom_name":"phoneNumberVerified"},"ticket":{"custom_name":"ticket"},"ticket_expires_at":{"custom_name":"ticketExpiresAt"},"totp_secret":{"custom_name":"totpSecret"},"updated_at":{"custom_name":"updatedAt"},"webauthn_current_challenge":{"custom_name":"currentChallenge"}},"custom_column_names":{"active_mfa_type":"activeMfaType","avatar_url":"avatarUrl","created_at":"createdAt","default_role":"defaultRole","disabled":"disabled","display_name":"displayName","email":"email","email_verified":"emailVerified","id":"id","is_anonymous":"isAnonymous","last_seen":"lastSeen","locale":"locale","new_email":"newEmail","otp_hash":"otpHash","otp_hash_expires_at":"otpHashExpiresAt","otp_method_last_used":"otpMethodLastUsed","password_hash":"passwordHash","phone_number":"phoneNumber","phone_number_verified":"phoneNumberVerified","ticket":"ticket","ticket_expires_at":"ticketExpiresAt","totp_secret":"totpSecret","updated_at":"updatedAt","webauthn_current_challenge":"currentChallenge"},"custom_name":"users","custom_root_fields":{"delete":"deleteUsers","delete_by_pk":"deleteUser","insert":"insertUsers","insert_one":"insertUser","select":"users","select_aggregate":"usersAggregate","select_by_pk":"user","update":"updateUsers","update_by_pk":"updateUser"}},"object_relationships":[{"name":"defaultRoleByRole","using":{"foreign_key_constraint_on":"default_role"}}],"table":{"name":"users","schema":"auth"}},{"table":{"name":"data_test","schema":"public"}},{"table":{"name":"test_table_1","schema":"public"}},{"array_relationships":[{"name":"files","using":{"foreign_key_constraint_on":{"column":"bucket_id","table":{"name":"files","schema":"storage"}}}}],"configuration":{"column_config":{"cache_control":{"custom_name":"cacheControl"},"created_at":{"custom_name":"createdAt"},"download_expiration":{"custom_name":"downloadExpiration"},"id":{"custom_name":"id"},"max_upload_file_size":{"custom_name":"maxUploadFileSize"},"min_upload_file_size":{"custom_name":"minUploadFileSize"},"presigned_urls_enabled":{"custom_name":"presignedUrlsEnabled"},"updated_at":{"custom_name":"updatedAt"}},"custom_column_names":{"cache_control":"cacheControl","created_at":"createdAt","download_expiration":"downloadExpiration","id":"id","max_upload_file_size":"maxUploadFileSize","min_upload_file_size":"minUploadFileSize","presigned_urls_enabled":"presignedUrlsEnabled","updated_at":"updatedAt"},"custom_name":"buckets","custom_root_fields":{"delete":"deleteBuckets","delete_by_pk":"deleteBucket","insert":"insertBuckets","insert_one":"insertBucket","select":"buckets","select_aggregate":"bucketsAggregate","select_by_pk":"bucket","update":"updateBuckets","update_by_pk":"updateBucket"}},"table":{"name":"buckets","schema":"storage"}},{"configuration":{"column_config":{"bucket_id":{"custom_name":"bucketId"},"created_at":{"custom_name":"createdAt"},"etag":{"custom_name":"etag"},"id":{"custom_name":"id"},"is_uploaded":{"custom_name":"isUploaded"},"mime_type":{"custom_name":"mimeType"},"name":{"custom_name":"name"},"size":{"custom_name":"size"},"updated_at":{"custom_name":"updatedAt"},"uploaded_by_user_id":{"custom_name":"uploadedByUserId"}},"custom_column_names":{"bucket_id":"bucketId","created_at":"createdAt","etag":"etag","id":"id","is_uploaded":"isUploaded","mime_type":"mimeType","name":"name","size":"size","updated_at":"updatedAt","uploaded_by_user_id":"uploadedByUserId"},"custom_name":"files","custom_root_fields":{"delete":"deleteFiles","delete_by_pk":"deleteFile","insert":"insertFiles","insert_one":"insertFile","select":"files","select_aggregate":"filesAggregate","select_by_pk":"file","update":"updateFiles","update_by_pk":"updateFile"}},"object_relationships":[{"name":"bucket","using":{"foreign_key_constraint_on":"bucket_id"}}],"table":{"name":"files","schema":"storage"}}]}],"version":3} 21 +\. + + +-- +-- Data for Name: hdb_scheduled_event_invocation_logs; Type: TABLE DATA; Schema: hdb_catalog; Owner: nhost_hasura +-- + +COPY hdb_catalog.hdb_scheduled_event_invocation_logs (id, event_id, status, request, response, created_at) FROM stdin; +\. + + +-- +-- Data for Name: hdb_scheduled_events; Type: TABLE DATA; Schema: hdb_catalog; Owner: nhost_hasura +-- + +COPY hdb_catalog.hdb_scheduled_events (id, webhook_conf, scheduled_time, retry_conf, payload, header_conf, status, tries, created_at, next_retry_at, comment) FROM stdin; +\. + + +-- +-- Data for Name: hdb_schema_notifications; Type: TABLE DATA; Schema: hdb_catalog; Owner: nhost_hasura +-- + +COPY hdb_catalog.hdb_schema_notifications (id, notification, resource_version, instance_id, updated_at) FROM stdin; +1 {"metadata":false,"remote_schemas":[],"sources":[],"data_connectors":[]} 21 99337c5e-5108-4930-aaf3-66e079655667 2023-04-18 03:08:07.470393+00 +\. + + +-- +-- Data for Name: hdb_version; Type: TABLE DATA; Schema: hdb_catalog; Owner: nhost_hasura +-- + +COPY hdb_catalog.hdb_version (hasura_uuid, version, upgraded_on, cli_state, console_state) FROM stdin; +1e3c7077-3b31-4c5d-b58b-dc7f7c1d84f9 47 2023-04-18 03:07:45.413334+00 {} {"console_notifications": {"admin": {"date": "2023-05-10T12:50:38.771Z", "read": [], "showBadge": false}}, "telemetryNotificationShown": true} +\. + + +-- +-- Data for Name: data_test; Type: TABLE DATA; Schema: public; Owner: nhost_hasura +-- + +COPY public.data_test (text, charvar, "char", uuid, json, jsonb, "smallint", "integer", "bigint", "decimal", "numeric", "real", double_precision, bool, date, "timestamp", timestamptz, "time", timetz, "interval", bytea, money) FROM stdin; +text charvar bpchar c7654b8a-4e90-4da3-aff1-d58d804d1ac2 {"hello": "world!"} {"hello": "world!"} 32767 2147483647 9223372036854775807 131072 1000 100 100 t 2023-05-26 2023-05-26 10:54:23 2023-05-26 10:54:23+00 10:54:23 2023-05-26 10:54:23+00 34293:33:09 \\x68656c6c6f21 $20.23 +\. + + +-- +-- Data for Name: test_table_1; Type: TABLE DATA; Schema: public; Owner: nhost_hasura +-- + +COPY public.test_table_1 (name, uuid, user_data, json_binary) FROM stdin; +Sarah 22598e7c-3f7d-4d94-9690-67ad925e17ce { "name": "Sarah", "data": { "test": "Test" } } {"text": "hello world!"} +\. + + +-- +-- Data for Name: buckets; Type: TABLE DATA; Schema: storage; Owner: nhost_storage_admin +-- + +COPY storage.buckets (id, created_at, updated_at, download_expiration, min_upload_file_size, max_upload_file_size, cache_control, presigned_urls_enabled) FROM stdin; +default 2023-04-18 03:07:31.49138+00 2023-04-18 03:07:31.49138+00 30 1 50000000 max-age=3600 t +\. + + +-- +-- Data for Name: files; Type: TABLE DATA; Schema: storage; Owner: nhost_storage_admin +-- + +COPY storage.files (id, created_at, updated_at, bucket_id, name, size, mime_type, etag, is_uploaded, uploaded_by_user_id) FROM stdin; +fda0ec19-8d12-418d-be2f-c534653f7510 2023-05-10 11:27:09.487996+00 2023-05-10 11:27:09.919043+00 default 1 15728640 application/octet-stream "bf787d648d170d6a601792ce759705e1" t \N +\. + + +-- +-- Data for Name: schema_migrations; Type: TABLE DATA; Schema: storage; Owner: nhost_storage_admin +-- + +COPY storage.schema_migrations (version, dirty) FROM stdin; +3 f +\. + + +-- +-- Name: migrations migrations_name_key; Type: CONSTRAINT; Schema: auth; Owner: nhost_auth_admin +-- + +ALTER TABLE ONLY auth.migrations + ADD CONSTRAINT migrations_name_key UNIQUE (name); + + +-- +-- Name: migrations migrations_pkey; Type: CONSTRAINT; Schema: auth; Owner: nhost_auth_admin +-- + +ALTER TABLE ONLY auth.migrations + ADD CONSTRAINT migrations_pkey PRIMARY KEY (id); + + +-- +-- Name: provider_requests provider_requests_pkey; Type: CONSTRAINT; Schema: auth; Owner: nhost_auth_admin +-- + +ALTER TABLE ONLY auth.provider_requests + ADD CONSTRAINT provider_requests_pkey PRIMARY KEY (id); + + +-- +-- Name: providers providers_pkey; Type: CONSTRAINT; Schema: auth; Owner: nhost_auth_admin +-- + +ALTER TABLE ONLY auth.providers + ADD CONSTRAINT providers_pkey PRIMARY KEY (id); + + +-- +-- Name: refresh_tokens refresh_tokens_pkey; Type: CONSTRAINT; Schema: auth; Owner: nhost_auth_admin +-- + +ALTER TABLE ONLY auth.refresh_tokens + ADD CONSTRAINT refresh_tokens_pkey PRIMARY KEY (refresh_token); + + +-- +-- Name: roles roles_pkey; Type: CONSTRAINT; Schema: auth; Owner: nhost_auth_admin +-- + +ALTER TABLE ONLY auth.roles + ADD CONSTRAINT roles_pkey PRIMARY KEY (role); + + +-- +-- Name: user_providers user_providers_pkey; Type: CONSTRAINT; Schema: auth; Owner: nhost_auth_admin +-- + +ALTER TABLE ONLY auth.user_providers + ADD CONSTRAINT user_providers_pkey PRIMARY KEY (id); + + +-- +-- Name: user_providers user_providers_provider_id_provider_user_id_key; Type: CONSTRAINT; Schema: auth; Owner: nhost_auth_admin +-- + +ALTER TABLE ONLY auth.user_providers + ADD CONSTRAINT user_providers_provider_id_provider_user_id_key UNIQUE (provider_id, provider_user_id); + + +-- +-- Name: user_providers user_providers_user_id_provider_id_key; Type: CONSTRAINT; Schema: auth; Owner: nhost_auth_admin +-- + +ALTER TABLE ONLY auth.user_providers + ADD CONSTRAINT user_providers_user_id_provider_id_key UNIQUE (user_id, provider_id); + + +-- +-- Name: user_roles user_roles_pkey; Type: CONSTRAINT; Schema: auth; Owner: nhost_auth_admin +-- + +ALTER TABLE ONLY auth.user_roles + ADD CONSTRAINT user_roles_pkey PRIMARY KEY (id); + + +-- +-- Name: user_roles user_roles_user_id_role_key; Type: CONSTRAINT; Schema: auth; Owner: nhost_auth_admin +-- + +ALTER TABLE ONLY auth.user_roles + ADD CONSTRAINT user_roles_user_id_role_key UNIQUE (user_id, role); + + +-- +-- Name: user_security_keys user_security_key_credential_id_key; Type: CONSTRAINT; Schema: auth; Owner: nhost_auth_admin +-- + +ALTER TABLE ONLY auth.user_security_keys + ADD CONSTRAINT user_security_key_credential_id_key UNIQUE (credential_id); + + +-- +-- Name: user_security_keys user_security_keys_pkey; Type: CONSTRAINT; Schema: auth; Owner: nhost_auth_admin +-- + +ALTER TABLE ONLY auth.user_security_keys + ADD CONSTRAINT user_security_keys_pkey PRIMARY KEY (id); + + +-- +-- Name: users users_email_key; Type: CONSTRAINT; Schema: auth; Owner: nhost_auth_admin +-- + +ALTER TABLE ONLY auth.users + ADD CONSTRAINT users_email_key UNIQUE (email); + + +-- +-- Name: users users_phone_number_key; Type: CONSTRAINT; Schema: auth; Owner: nhost_auth_admin +-- + +ALTER TABLE ONLY auth.users + ADD CONSTRAINT users_phone_number_key UNIQUE (phone_number); + + +-- +-- Name: users users_pkey; Type: CONSTRAINT; Schema: auth; Owner: nhost_auth_admin +-- + +ALTER TABLE ONLY auth.users + ADD CONSTRAINT users_pkey PRIMARY KEY (id); + + +-- +-- Name: hdb_action_log hdb_action_log_pkey; Type: CONSTRAINT; Schema: hdb_catalog; Owner: nhost_hasura +-- + +ALTER TABLE ONLY hdb_catalog.hdb_action_log + ADD CONSTRAINT hdb_action_log_pkey PRIMARY KEY (id); + + +-- +-- Name: hdb_cron_event_invocation_logs hdb_cron_event_invocation_logs_pkey; Type: CONSTRAINT; Schema: hdb_catalog; Owner: nhost_hasura +-- + +ALTER TABLE ONLY hdb_catalog.hdb_cron_event_invocation_logs + ADD CONSTRAINT hdb_cron_event_invocation_logs_pkey PRIMARY KEY (id); + + +-- +-- Name: hdb_cron_events hdb_cron_events_pkey; Type: CONSTRAINT; Schema: hdb_catalog; Owner: nhost_hasura +-- + +ALTER TABLE ONLY hdb_catalog.hdb_cron_events + ADD CONSTRAINT hdb_cron_events_pkey PRIMARY KEY (id); + + +-- +-- Name: hdb_metadata hdb_metadata_pkey; Type: CONSTRAINT; Schema: hdb_catalog; Owner: nhost_hasura +-- + +ALTER TABLE ONLY hdb_catalog.hdb_metadata + ADD CONSTRAINT hdb_metadata_pkey PRIMARY KEY (id); + + +-- +-- Name: hdb_metadata hdb_metadata_resource_version_key; Type: CONSTRAINT; Schema: hdb_catalog; Owner: nhost_hasura +-- + +ALTER TABLE ONLY hdb_catalog.hdb_metadata + ADD CONSTRAINT hdb_metadata_resource_version_key UNIQUE (resource_version); + + +-- +-- Name: hdb_scheduled_event_invocation_logs hdb_scheduled_event_invocation_logs_pkey; Type: CONSTRAINT; Schema: hdb_catalog; Owner: nhost_hasura +-- + +ALTER TABLE ONLY hdb_catalog.hdb_scheduled_event_invocation_logs + ADD CONSTRAINT hdb_scheduled_event_invocation_logs_pkey PRIMARY KEY (id); + + +-- +-- Name: hdb_scheduled_events hdb_scheduled_events_pkey; Type: CONSTRAINT; Schema: hdb_catalog; Owner: nhost_hasura +-- + +ALTER TABLE ONLY hdb_catalog.hdb_scheduled_events + ADD CONSTRAINT hdb_scheduled_events_pkey PRIMARY KEY (id); + + +-- +-- Name: hdb_schema_notifications hdb_schema_notifications_pkey; Type: CONSTRAINT; Schema: hdb_catalog; Owner: nhost_hasura +-- + +ALTER TABLE ONLY hdb_catalog.hdb_schema_notifications + ADD CONSTRAINT hdb_schema_notifications_pkey PRIMARY KEY (id); + + +-- +-- Name: hdb_version hdb_version_pkey; Type: CONSTRAINT; Schema: hdb_catalog; Owner: nhost_hasura +-- + +ALTER TABLE ONLY hdb_catalog.hdb_version + ADD CONSTRAINT hdb_version_pkey PRIMARY KEY (hasura_uuid); + + +-- +-- Name: data_test data_test_pkey; Type: CONSTRAINT; Schema: public; Owner: nhost_hasura +-- + +ALTER TABLE ONLY public.data_test + ADD CONSTRAINT data_test_pkey PRIMARY KEY (text); + + +-- +-- Name: test_table_1 test_table_1_pkey; Type: CONSTRAINT; Schema: public; Owner: nhost_hasura +-- + +ALTER TABLE ONLY public.test_table_1 + ADD CONSTRAINT test_table_1_pkey PRIMARY KEY (name); + + +-- +-- Name: test_table_1 test_table_1_uuid_key; Type: CONSTRAINT; Schema: public; Owner: nhost_hasura +-- + +ALTER TABLE ONLY public.test_table_1 + ADD CONSTRAINT test_table_1_uuid_key UNIQUE (uuid); + + +-- +-- Name: buckets buckets_pkey; Type: CONSTRAINT; Schema: storage; Owner: nhost_storage_admin +-- + +ALTER TABLE ONLY storage.buckets + ADD CONSTRAINT buckets_pkey PRIMARY KEY (id); + + +-- +-- Name: files files_pkey; Type: CONSTRAINT; Schema: storage; Owner: nhost_storage_admin +-- + +ALTER TABLE ONLY storage.files + ADD CONSTRAINT files_pkey PRIMARY KEY (id); + + +-- +-- Name: schema_migrations schema_migrations_pkey; Type: CONSTRAINT; Schema: storage; Owner: nhost_storage_admin +-- + +ALTER TABLE ONLY storage.schema_migrations + ADD CONSTRAINT schema_migrations_pkey PRIMARY KEY (version); + + +-- +-- Name: hdb_cron_event_invocation_event_id; Type: INDEX; Schema: hdb_catalog; Owner: nhost_hasura +-- + +CREATE INDEX hdb_cron_event_invocation_event_id ON hdb_catalog.hdb_cron_event_invocation_logs USING btree (event_id); + + +-- +-- Name: hdb_cron_event_status; Type: INDEX; Schema: hdb_catalog; Owner: nhost_hasura +-- + +CREATE INDEX hdb_cron_event_status ON hdb_catalog.hdb_cron_events USING btree (status); + + +-- +-- Name: hdb_cron_events_unique_scheduled; Type: INDEX; Schema: hdb_catalog; Owner: nhost_hasura +-- + +CREATE UNIQUE INDEX hdb_cron_events_unique_scheduled ON hdb_catalog.hdb_cron_events USING btree (trigger_name, scheduled_time) WHERE (status = 'scheduled'::text); + + +-- +-- Name: hdb_scheduled_event_status; Type: INDEX; Schema: hdb_catalog; Owner: nhost_hasura +-- + +CREATE INDEX hdb_scheduled_event_status ON hdb_catalog.hdb_scheduled_events USING btree (status); + + +-- +-- Name: hdb_version_one_row; Type: INDEX; Schema: hdb_catalog; Owner: nhost_hasura +-- + +CREATE UNIQUE INDEX hdb_version_one_row ON hdb_catalog.hdb_version USING btree (((version IS NOT NULL))); + + +-- +-- Name: user_providers set_auth_user_providers_updated_at; Type: TRIGGER; Schema: auth; Owner: nhost_auth_admin +-- + +CREATE TRIGGER set_auth_user_providers_updated_at BEFORE UPDATE ON auth.user_providers FOR EACH ROW EXECUTE FUNCTION auth.set_current_timestamp_updated_at(); + + +-- +-- Name: users set_auth_users_updated_at; Type: TRIGGER; Schema: auth; Owner: nhost_auth_admin +-- + +CREATE TRIGGER set_auth_users_updated_at BEFORE UPDATE ON auth.users FOR EACH ROW EXECUTE FUNCTION auth.set_current_timestamp_updated_at(); + + +-- +-- Name: buckets check_default_bucket_delete; Type: TRIGGER; Schema: storage; Owner: nhost_storage_admin +-- + +CREATE TRIGGER check_default_bucket_delete BEFORE DELETE ON storage.buckets FOR EACH ROW EXECUTE FUNCTION storage.protect_default_bucket_delete(); + + +-- +-- Name: buckets check_default_bucket_update; Type: TRIGGER; Schema: storage; Owner: nhost_storage_admin +-- + +CREATE TRIGGER check_default_bucket_update BEFORE UPDATE ON storage.buckets FOR EACH ROW EXECUTE FUNCTION storage.protect_default_bucket_update(); + + +-- +-- Name: buckets set_storage_buckets_updated_at; Type: TRIGGER; Schema: storage; Owner: nhost_storage_admin +-- + +CREATE TRIGGER set_storage_buckets_updated_at BEFORE UPDATE ON storage.buckets FOR EACH ROW EXECUTE FUNCTION storage.set_current_timestamp_updated_at(); + + +-- +-- Name: files set_storage_files_updated_at; Type: TRIGGER; Schema: storage; Owner: nhost_storage_admin +-- + +CREATE TRIGGER set_storage_files_updated_at BEFORE UPDATE ON storage.files FOR EACH ROW EXECUTE FUNCTION storage.set_current_timestamp_updated_at(); + + +-- +-- Name: users fk_default_role; Type: FK CONSTRAINT; Schema: auth; Owner: nhost_auth_admin +-- + +ALTER TABLE ONLY auth.users + ADD CONSTRAINT fk_default_role FOREIGN KEY (default_role) REFERENCES auth.roles(role) ON UPDATE CASCADE ON DELETE RESTRICT; + + +-- +-- Name: user_providers fk_provider; Type: FK CONSTRAINT; Schema: auth; Owner: nhost_auth_admin +-- + +ALTER TABLE ONLY auth.user_providers + ADD CONSTRAINT fk_provider FOREIGN KEY (provider_id) REFERENCES auth.providers(id) ON UPDATE CASCADE ON DELETE RESTRICT; + + +-- +-- Name: user_roles fk_role; Type: FK CONSTRAINT; Schema: auth; Owner: nhost_auth_admin +-- + +ALTER TABLE ONLY auth.user_roles + ADD CONSTRAINT fk_role FOREIGN KEY (role) REFERENCES auth.roles(role) ON UPDATE CASCADE ON DELETE RESTRICT; + + +-- +-- Name: user_providers fk_user; Type: FK CONSTRAINT; Schema: auth; Owner: nhost_auth_admin +-- + +ALTER TABLE ONLY auth.user_providers + ADD CONSTRAINT fk_user FOREIGN KEY (user_id) REFERENCES auth.users(id) ON UPDATE CASCADE ON DELETE CASCADE; + + +-- +-- Name: user_roles fk_user; Type: FK CONSTRAINT; Schema: auth; Owner: nhost_auth_admin +-- + +ALTER TABLE ONLY auth.user_roles + ADD CONSTRAINT fk_user FOREIGN KEY (user_id) REFERENCES auth.users(id) ON UPDATE CASCADE ON DELETE CASCADE; + + +-- +-- Name: refresh_tokens fk_user; Type: FK CONSTRAINT; Schema: auth; Owner: nhost_auth_admin +-- + +ALTER TABLE ONLY auth.refresh_tokens + ADD CONSTRAINT fk_user FOREIGN KEY (user_id) REFERENCES auth.users(id) ON UPDATE CASCADE ON DELETE CASCADE; + + +-- +-- Name: user_security_keys fk_user; Type: FK CONSTRAINT; Schema: auth; Owner: nhost_auth_admin +-- + +ALTER TABLE ONLY auth.user_security_keys + ADD CONSTRAINT fk_user FOREIGN KEY (user_id) REFERENCES auth.users(id) ON UPDATE CASCADE ON DELETE CASCADE; + + +-- +-- Name: hdb_cron_event_invocation_logs hdb_cron_event_invocation_logs_event_id_fkey; Type: FK CONSTRAINT; Schema: hdb_catalog; Owner: nhost_hasura +-- + +ALTER TABLE ONLY hdb_catalog.hdb_cron_event_invocation_logs + ADD CONSTRAINT hdb_cron_event_invocation_logs_event_id_fkey FOREIGN KEY (event_id) REFERENCES hdb_catalog.hdb_cron_events(id) ON UPDATE CASCADE ON DELETE CASCADE; + + +-- +-- Name: hdb_scheduled_event_invocation_logs hdb_scheduled_event_invocation_logs_event_id_fkey; Type: FK CONSTRAINT; Schema: hdb_catalog; Owner: nhost_hasura +-- + +ALTER TABLE ONLY hdb_catalog.hdb_scheduled_event_invocation_logs + ADD CONSTRAINT hdb_scheduled_event_invocation_logs_event_id_fkey FOREIGN KEY (event_id) REFERENCES hdb_catalog.hdb_scheduled_events(id) ON UPDATE CASCADE ON DELETE CASCADE; + + +-- +-- Name: files fk_bucket; Type: FK CONSTRAINT; Schema: storage; Owner: nhost_storage_admin +-- + +ALTER TABLE ONLY storage.files + ADD CONSTRAINT fk_bucket FOREIGN KEY (bucket_id) REFERENCES storage.buckets(id) ON UPDATE CASCADE ON DELETE CASCADE; + + +-- +-- Name: SCHEMA auth; Type: ACL; Schema: -; Owner: nhost_admin +-- + +GRANT ALL ON SCHEMA auth TO nhost_auth_admin; +GRANT USAGE ON SCHEMA auth TO nhost_hasura; + + +-- +-- Name: SCHEMA pgbouncer; Type: ACL; Schema: -; Owner: nhost_admin +-- + +GRANT USAGE ON SCHEMA pgbouncer TO pgbouncer; + + +-- +-- Name: SCHEMA public; Type: ACL; Schema: -; Owner: postgres +-- + +REVOKE USAGE ON SCHEMA public FROM PUBLIC; +GRANT ALL ON SCHEMA public TO PUBLIC; +GRANT USAGE ON SCHEMA public TO nhost_hasura; + + +-- +-- Name: SCHEMA storage; Type: ACL; Schema: -; Owner: nhost_admin +-- + +GRANT ALL ON SCHEMA storage TO nhost_storage_admin; +GRANT USAGE ON SCHEMA storage TO nhost_hasura; + + +-- +-- Name: FUNCTION user_lookup(i_username text, OUT uname text, OUT phash text); Type: ACL; Schema: pgbouncer; Owner: postgres +-- + +REVOKE ALL ON FUNCTION pgbouncer.user_lookup(i_username text, OUT uname text, OUT phash text) FROM PUBLIC; +GRANT ALL ON FUNCTION pgbouncer.user_lookup(i_username text, OUT uname text, OUT phash text) TO pgbouncer; + + +-- +-- Name: TABLE migrations; Type: ACL; Schema: auth; Owner: nhost_auth_admin +-- + +GRANT ALL ON TABLE auth.migrations TO nhost_hasura; + + +-- +-- Name: TABLE provider_requests; Type: ACL; Schema: auth; Owner: nhost_auth_admin +-- + +GRANT ALL ON TABLE auth.provider_requests TO nhost_hasura; + + +-- +-- Name: TABLE providers; Type: ACL; Schema: auth; Owner: nhost_auth_admin +-- + +GRANT ALL ON TABLE auth.providers TO nhost_hasura; + + +-- +-- Name: TABLE refresh_tokens; Type: ACL; Schema: auth; Owner: nhost_auth_admin +-- + +GRANT ALL ON TABLE auth.refresh_tokens TO nhost_hasura; + + +-- +-- Name: TABLE roles; Type: ACL; Schema: auth; Owner: nhost_auth_admin +-- + +GRANT ALL ON TABLE auth.roles TO nhost_hasura; + + +-- +-- Name: TABLE user_providers; Type: ACL; Schema: auth; Owner: nhost_auth_admin +-- + +GRANT ALL ON TABLE auth.user_providers TO nhost_hasura; + + +-- +-- Name: TABLE user_roles; Type: ACL; Schema: auth; Owner: nhost_auth_admin +-- + +GRANT ALL ON TABLE auth.user_roles TO nhost_hasura; + + +-- +-- Name: TABLE user_security_keys; Type: ACL; Schema: auth; Owner: nhost_auth_admin +-- + +GRANT ALL ON TABLE auth.user_security_keys TO nhost_hasura; + + +-- +-- Name: TABLE users; Type: ACL; Schema: auth; Owner: nhost_auth_admin +-- + +GRANT ALL ON TABLE auth.users TO nhost_hasura; + + +-- +-- Name: TABLE pg_aggregate; Type: ACL; Schema: pg_catalog; Owner: postgres +-- + +GRANT SELECT ON TABLE pg_catalog.pg_aggregate TO nhost_hasura; + + +-- +-- Name: TABLE pg_am; Type: ACL; Schema: pg_catalog; Owner: postgres +-- + +GRANT SELECT ON TABLE pg_catalog.pg_am TO nhost_hasura; + + +-- +-- Name: TABLE pg_amop; Type: ACL; Schema: pg_catalog; Owner: postgres +-- + +GRANT SELECT ON TABLE pg_catalog.pg_amop TO nhost_hasura; + + +-- +-- Name: TABLE pg_amproc; Type: ACL; Schema: pg_catalog; Owner: postgres +-- + +GRANT SELECT ON TABLE pg_catalog.pg_amproc TO nhost_hasura; + + +-- +-- Name: TABLE pg_attrdef; Type: ACL; Schema: pg_catalog; Owner: postgres +-- + +GRANT SELECT ON TABLE pg_catalog.pg_attrdef TO nhost_hasura; + + +-- +-- Name: TABLE pg_attribute; Type: ACL; Schema: pg_catalog; Owner: postgres +-- + +GRANT SELECT ON TABLE pg_catalog.pg_attribute TO nhost_hasura; + + +-- +-- Name: TABLE pg_auth_members; Type: ACL; Schema: pg_catalog; Owner: postgres +-- + +GRANT SELECT ON TABLE pg_catalog.pg_auth_members TO nhost_hasura; + + +-- +-- Name: TABLE pg_authid; Type: ACL; Schema: pg_catalog; Owner: postgres +-- + +GRANT SELECT ON TABLE pg_catalog.pg_authid TO nhost_hasura; + + +-- +-- Name: TABLE pg_available_extension_versions; Type: ACL; Schema: pg_catalog; Owner: postgres +-- + +GRANT SELECT ON TABLE pg_catalog.pg_available_extension_versions TO nhost_hasura; + + +-- +-- Name: TABLE pg_available_extensions; Type: ACL; Schema: pg_catalog; Owner: postgres +-- + +GRANT SELECT ON TABLE pg_catalog.pg_available_extensions TO nhost_hasura; + + +-- +-- Name: TABLE pg_backend_memory_contexts; Type: ACL; Schema: pg_catalog; Owner: postgres +-- + +GRANT SELECT ON TABLE pg_catalog.pg_backend_memory_contexts TO nhost_hasura; + + +-- +-- Name: TABLE pg_cast; Type: ACL; Schema: pg_catalog; Owner: postgres +-- + +GRANT SELECT ON TABLE pg_catalog.pg_cast TO nhost_hasura; + + +-- +-- Name: TABLE pg_class; Type: ACL; Schema: pg_catalog; Owner: postgres +-- + +GRANT SELECT ON TABLE pg_catalog.pg_class TO nhost_hasura; + + +-- +-- Name: TABLE pg_collation; Type: ACL; Schema: pg_catalog; Owner: postgres +-- + +GRANT SELECT ON TABLE pg_catalog.pg_collation TO nhost_hasura; + + +-- +-- Name: TABLE pg_config; Type: ACL; Schema: pg_catalog; Owner: postgres +-- + +GRANT SELECT ON TABLE pg_catalog.pg_config TO nhost_hasura; + + +-- +-- Name: TABLE pg_constraint; Type: ACL; Schema: pg_catalog; Owner: postgres +-- + +GRANT SELECT ON TABLE pg_catalog.pg_constraint TO nhost_hasura; + + +-- +-- Name: TABLE pg_conversion; Type: ACL; Schema: pg_catalog; Owner: postgres +-- + +GRANT SELECT ON TABLE pg_catalog.pg_conversion TO nhost_hasura; + + +-- +-- Name: TABLE pg_cursors; Type: ACL; Schema: pg_catalog; Owner: postgres +-- + +GRANT SELECT ON TABLE pg_catalog.pg_cursors TO nhost_hasura; + + +-- +-- Name: TABLE pg_database; Type: ACL; Schema: pg_catalog; Owner: postgres +-- + +GRANT SELECT ON TABLE pg_catalog.pg_database TO nhost_hasura; + + +-- +-- Name: TABLE pg_db_role_setting; Type: ACL; Schema: pg_catalog; Owner: postgres +-- + +GRANT SELECT ON TABLE pg_catalog.pg_db_role_setting TO nhost_hasura; + + +-- +-- Name: TABLE pg_default_acl; Type: ACL; Schema: pg_catalog; Owner: postgres +-- + +GRANT SELECT ON TABLE pg_catalog.pg_default_acl TO nhost_hasura; + + +-- +-- Name: TABLE pg_depend; Type: ACL; Schema: pg_catalog; Owner: postgres +-- + +GRANT SELECT ON TABLE pg_catalog.pg_depend TO nhost_hasura; + + +-- +-- Name: TABLE pg_description; Type: ACL; Schema: pg_catalog; Owner: postgres +-- + +GRANT SELECT ON TABLE pg_catalog.pg_description TO nhost_hasura; + + +-- +-- Name: TABLE pg_enum; Type: ACL; Schema: pg_catalog; Owner: postgres +-- + +GRANT SELECT ON TABLE pg_catalog.pg_enum TO nhost_hasura; + + +-- +-- Name: TABLE pg_event_trigger; Type: ACL; Schema: pg_catalog; Owner: postgres +-- + +GRANT SELECT ON TABLE pg_catalog.pg_event_trigger TO nhost_hasura; + + +-- +-- Name: TABLE pg_extension; Type: ACL; Schema: pg_catalog; Owner: postgres +-- + +GRANT SELECT ON TABLE pg_catalog.pg_extension TO nhost_hasura; + + +-- +-- Name: TABLE pg_file_settings; Type: ACL; Schema: pg_catalog; Owner: postgres +-- + +GRANT SELECT ON TABLE pg_catalog.pg_file_settings TO nhost_hasura; + + +-- +-- Name: TABLE pg_foreign_data_wrapper; Type: ACL; Schema: pg_catalog; Owner: postgres +-- + +GRANT SELECT ON TABLE pg_catalog.pg_foreign_data_wrapper TO nhost_hasura; + + +-- +-- Name: TABLE pg_foreign_server; Type: ACL; Schema: pg_catalog; Owner: postgres +-- + +GRANT SELECT ON TABLE pg_catalog.pg_foreign_server TO nhost_hasura; + + +-- +-- Name: TABLE pg_foreign_table; Type: ACL; Schema: pg_catalog; Owner: postgres +-- + +GRANT SELECT ON TABLE pg_catalog.pg_foreign_table TO nhost_hasura; + + +-- +-- Name: TABLE pg_group; Type: ACL; Schema: pg_catalog; Owner: postgres +-- + +GRANT SELECT ON TABLE pg_catalog.pg_group TO nhost_hasura; + + +-- +-- Name: TABLE pg_hba_file_rules; Type: ACL; Schema: pg_catalog; Owner: postgres +-- + +GRANT SELECT ON TABLE pg_catalog.pg_hba_file_rules TO nhost_hasura; + + +-- +-- Name: TABLE pg_index; Type: ACL; Schema: pg_catalog; Owner: postgres +-- + +GRANT SELECT ON TABLE pg_catalog.pg_index TO nhost_hasura; + + +-- +-- Name: TABLE pg_indexes; Type: ACL; Schema: pg_catalog; Owner: postgres +-- + +GRANT SELECT ON TABLE pg_catalog.pg_indexes TO nhost_hasura; + + +-- +-- Name: TABLE pg_inherits; Type: ACL; Schema: pg_catalog; Owner: postgres +-- + +GRANT SELECT ON TABLE pg_catalog.pg_inherits TO nhost_hasura; + + +-- +-- Name: TABLE pg_init_privs; Type: ACL; Schema: pg_catalog; Owner: postgres +-- + +GRANT SELECT ON TABLE pg_catalog.pg_init_privs TO nhost_hasura; + + +-- +-- Name: TABLE pg_language; Type: ACL; Schema: pg_catalog; Owner: postgres +-- + +GRANT SELECT ON TABLE pg_catalog.pg_language TO nhost_hasura; + + +-- +-- Name: TABLE pg_largeobject; Type: ACL; Schema: pg_catalog; Owner: postgres +-- + +GRANT SELECT ON TABLE pg_catalog.pg_largeobject TO nhost_hasura; + + +-- +-- Name: TABLE pg_largeobject_metadata; Type: ACL; Schema: pg_catalog; Owner: postgres +-- + +GRANT SELECT ON TABLE pg_catalog.pg_largeobject_metadata TO nhost_hasura; + + +-- +-- Name: TABLE pg_locks; Type: ACL; Schema: pg_catalog; Owner: postgres +-- + +GRANT SELECT ON TABLE pg_catalog.pg_locks TO nhost_hasura; + + +-- +-- Name: TABLE pg_matviews; Type: ACL; Schema: pg_catalog; Owner: postgres +-- + +GRANT SELECT ON TABLE pg_catalog.pg_matviews TO nhost_hasura; + + +-- +-- Name: TABLE pg_namespace; Type: ACL; Schema: pg_catalog; Owner: postgres +-- + +GRANT SELECT ON TABLE pg_catalog.pg_namespace TO nhost_hasura; + + +-- +-- Name: TABLE pg_opclass; Type: ACL; Schema: pg_catalog; Owner: postgres +-- + +GRANT SELECT ON TABLE pg_catalog.pg_opclass TO nhost_hasura; + + +-- +-- Name: TABLE pg_operator; Type: ACL; Schema: pg_catalog; Owner: postgres +-- + +GRANT SELECT ON TABLE pg_catalog.pg_operator TO nhost_hasura; + + +-- +-- Name: TABLE pg_opfamily; Type: ACL; Schema: pg_catalog; Owner: postgres +-- + +GRANT SELECT ON TABLE pg_catalog.pg_opfamily TO nhost_hasura; + + +-- +-- Name: TABLE pg_partitioned_table; Type: ACL; Schema: pg_catalog; Owner: postgres +-- + +GRANT SELECT ON TABLE pg_catalog.pg_partitioned_table TO nhost_hasura; + + +-- +-- Name: TABLE pg_policies; Type: ACL; Schema: pg_catalog; Owner: postgres +-- + +GRANT SELECT ON TABLE pg_catalog.pg_policies TO nhost_hasura; + + +-- +-- Name: TABLE pg_policy; Type: ACL; Schema: pg_catalog; Owner: postgres +-- + +GRANT SELECT ON TABLE pg_catalog.pg_policy TO nhost_hasura; + + +-- +-- Name: TABLE pg_prepared_statements; Type: ACL; Schema: pg_catalog; Owner: postgres +-- + +GRANT SELECT ON TABLE pg_catalog.pg_prepared_statements TO nhost_hasura; + + +-- +-- Name: TABLE pg_prepared_xacts; Type: ACL; Schema: pg_catalog; Owner: postgres +-- + +GRANT SELECT ON TABLE pg_catalog.pg_prepared_xacts TO nhost_hasura; + + +-- +-- Name: TABLE pg_proc; Type: ACL; Schema: pg_catalog; Owner: postgres +-- + +GRANT SELECT ON TABLE pg_catalog.pg_proc TO nhost_hasura; + + +-- +-- Name: TABLE pg_publication; Type: ACL; Schema: pg_catalog; Owner: postgres +-- + +GRANT SELECT ON TABLE pg_catalog.pg_publication TO nhost_hasura; + + +-- +-- Name: TABLE pg_publication_rel; Type: ACL; Schema: pg_catalog; Owner: postgres +-- + +GRANT SELECT ON TABLE pg_catalog.pg_publication_rel TO nhost_hasura; + + +-- +-- Name: TABLE pg_publication_tables; Type: ACL; Schema: pg_catalog; Owner: postgres +-- + +GRANT SELECT ON TABLE pg_catalog.pg_publication_tables TO nhost_hasura; + + +-- +-- Name: TABLE pg_range; Type: ACL; Schema: pg_catalog; Owner: postgres +-- + +GRANT SELECT ON TABLE pg_catalog.pg_range TO nhost_hasura; + + +-- +-- Name: TABLE pg_replication_origin; Type: ACL; Schema: pg_catalog; Owner: postgres +-- + +GRANT SELECT ON TABLE pg_catalog.pg_replication_origin TO nhost_hasura; + + +-- +-- Name: TABLE pg_replication_origin_status; Type: ACL; Schema: pg_catalog; Owner: postgres +-- + +GRANT SELECT ON TABLE pg_catalog.pg_replication_origin_status TO nhost_hasura; + + +-- +-- Name: TABLE pg_replication_slots; Type: ACL; Schema: pg_catalog; Owner: postgres +-- + +GRANT SELECT ON TABLE pg_catalog.pg_replication_slots TO nhost_hasura; + + +-- +-- Name: TABLE pg_rewrite; Type: ACL; Schema: pg_catalog; Owner: postgres +-- + +GRANT SELECT ON TABLE pg_catalog.pg_rewrite TO nhost_hasura; + + +-- +-- Name: TABLE pg_roles; Type: ACL; Schema: pg_catalog; Owner: postgres +-- + +GRANT SELECT ON TABLE pg_catalog.pg_roles TO nhost_hasura; + + +-- +-- Name: TABLE pg_rules; Type: ACL; Schema: pg_catalog; Owner: postgres +-- + +GRANT SELECT ON TABLE pg_catalog.pg_rules TO nhost_hasura; + + +-- +-- Name: TABLE pg_seclabel; Type: ACL; Schema: pg_catalog; Owner: postgres +-- + +GRANT SELECT ON TABLE pg_catalog.pg_seclabel TO nhost_hasura; + + +-- +-- Name: TABLE pg_seclabels; Type: ACL; Schema: pg_catalog; Owner: postgres +-- + +GRANT SELECT ON TABLE pg_catalog.pg_seclabels TO nhost_hasura; + + +-- +-- Name: TABLE pg_sequence; Type: ACL; Schema: pg_catalog; Owner: postgres +-- + +GRANT SELECT ON TABLE pg_catalog.pg_sequence TO nhost_hasura; + + +-- +-- Name: TABLE pg_sequences; Type: ACL; Schema: pg_catalog; Owner: postgres +-- + +GRANT SELECT ON TABLE pg_catalog.pg_sequences TO nhost_hasura; + + +-- +-- Name: TABLE pg_settings; Type: ACL; Schema: pg_catalog; Owner: postgres +-- + +GRANT SELECT ON TABLE pg_catalog.pg_settings TO nhost_hasura; + + +-- +-- Name: TABLE pg_shadow; Type: ACL; Schema: pg_catalog; Owner: postgres +-- + +GRANT SELECT ON TABLE pg_catalog.pg_shadow TO nhost_hasura; + + +-- +-- Name: TABLE pg_shdepend; Type: ACL; Schema: pg_catalog; Owner: postgres +-- + +GRANT SELECT ON TABLE pg_catalog.pg_shdepend TO nhost_hasura; + + +-- +-- Name: TABLE pg_shdescription; Type: ACL; Schema: pg_catalog; Owner: postgres +-- + +GRANT SELECT ON TABLE pg_catalog.pg_shdescription TO nhost_hasura; + + +-- +-- Name: TABLE pg_shmem_allocations; Type: ACL; Schema: pg_catalog; Owner: postgres +-- + +GRANT SELECT ON TABLE pg_catalog.pg_shmem_allocations TO nhost_hasura; + + +-- +-- Name: TABLE pg_shseclabel; Type: ACL; Schema: pg_catalog; Owner: postgres +-- + +GRANT SELECT ON TABLE pg_catalog.pg_shseclabel TO nhost_hasura; + + +-- +-- Name: TABLE pg_stat_activity; Type: ACL; Schema: pg_catalog; Owner: postgres +-- + +GRANT SELECT ON TABLE pg_catalog.pg_stat_activity TO nhost_hasura; + + +-- +-- Name: TABLE pg_stat_all_indexes; Type: ACL; Schema: pg_catalog; Owner: postgres +-- + +GRANT SELECT ON TABLE pg_catalog.pg_stat_all_indexes TO nhost_hasura; + + +-- +-- Name: TABLE pg_stat_all_tables; Type: ACL; Schema: pg_catalog; Owner: postgres +-- + +GRANT SELECT ON TABLE pg_catalog.pg_stat_all_tables TO nhost_hasura; + + +-- +-- Name: TABLE pg_stat_archiver; Type: ACL; Schema: pg_catalog; Owner: postgres +-- + +GRANT SELECT ON TABLE pg_catalog.pg_stat_archiver TO nhost_hasura; + + +-- +-- Name: TABLE pg_stat_bgwriter; Type: ACL; Schema: pg_catalog; Owner: postgres +-- + +GRANT SELECT ON TABLE pg_catalog.pg_stat_bgwriter TO nhost_hasura; + + +-- +-- Name: TABLE pg_stat_database; Type: ACL; Schema: pg_catalog; Owner: postgres +-- + +GRANT SELECT ON TABLE pg_catalog.pg_stat_database TO nhost_hasura; + + +-- +-- Name: TABLE pg_stat_database_conflicts; Type: ACL; Schema: pg_catalog; Owner: postgres +-- + +GRANT SELECT ON TABLE pg_catalog.pg_stat_database_conflicts TO nhost_hasura; + + +-- +-- Name: TABLE pg_stat_gssapi; Type: ACL; Schema: pg_catalog; Owner: postgres +-- + +GRANT SELECT ON TABLE pg_catalog.pg_stat_gssapi TO nhost_hasura; + + +-- +-- Name: TABLE pg_stat_progress_analyze; Type: ACL; Schema: pg_catalog; Owner: postgres +-- + +GRANT SELECT ON TABLE pg_catalog.pg_stat_progress_analyze TO nhost_hasura; + + +-- +-- Name: TABLE pg_stat_progress_basebackup; Type: ACL; Schema: pg_catalog; Owner: postgres +-- + +GRANT SELECT ON TABLE pg_catalog.pg_stat_progress_basebackup TO nhost_hasura; + + +-- +-- Name: TABLE pg_stat_progress_cluster; Type: ACL; Schema: pg_catalog; Owner: postgres +-- + +GRANT SELECT ON TABLE pg_catalog.pg_stat_progress_cluster TO nhost_hasura; + + +-- +-- Name: TABLE pg_stat_progress_copy; Type: ACL; Schema: pg_catalog; Owner: postgres +-- + +GRANT SELECT ON TABLE pg_catalog.pg_stat_progress_copy TO nhost_hasura; + + +-- +-- Name: TABLE pg_stat_progress_create_index; Type: ACL; Schema: pg_catalog; Owner: postgres +-- + +GRANT SELECT ON TABLE pg_catalog.pg_stat_progress_create_index TO nhost_hasura; + + +-- +-- Name: TABLE pg_stat_progress_vacuum; Type: ACL; Schema: pg_catalog; Owner: postgres +-- + +GRANT SELECT ON TABLE pg_catalog.pg_stat_progress_vacuum TO nhost_hasura; + + +-- +-- Name: TABLE pg_stat_replication; Type: ACL; Schema: pg_catalog; Owner: postgres +-- + +GRANT SELECT ON TABLE pg_catalog.pg_stat_replication TO nhost_hasura; + + +-- +-- Name: TABLE pg_stat_replication_slots; Type: ACL; Schema: pg_catalog; Owner: postgres +-- + +GRANT SELECT ON TABLE pg_catalog.pg_stat_replication_slots TO nhost_hasura; + + +-- +-- Name: TABLE pg_stat_slru; Type: ACL; Schema: pg_catalog; Owner: postgres +-- + +GRANT SELECT ON TABLE pg_catalog.pg_stat_slru TO nhost_hasura; + + +-- +-- Name: TABLE pg_stat_ssl; Type: ACL; Schema: pg_catalog; Owner: postgres +-- + +GRANT SELECT ON TABLE pg_catalog.pg_stat_ssl TO nhost_hasura; + + +-- +-- Name: TABLE pg_stat_subscription; Type: ACL; Schema: pg_catalog; Owner: postgres +-- + +GRANT SELECT ON TABLE pg_catalog.pg_stat_subscription TO nhost_hasura; + + +-- +-- Name: TABLE pg_stat_sys_indexes; Type: ACL; Schema: pg_catalog; Owner: postgres +-- + +GRANT SELECT ON TABLE pg_catalog.pg_stat_sys_indexes TO nhost_hasura; + + +-- +-- Name: TABLE pg_stat_sys_tables; Type: ACL; Schema: pg_catalog; Owner: postgres +-- + +GRANT SELECT ON TABLE pg_catalog.pg_stat_sys_tables TO nhost_hasura; + + +-- +-- Name: TABLE pg_stat_user_functions; Type: ACL; Schema: pg_catalog; Owner: postgres +-- + +GRANT SELECT ON TABLE pg_catalog.pg_stat_user_functions TO nhost_hasura; + + +-- +-- Name: TABLE pg_stat_user_indexes; Type: ACL; Schema: pg_catalog; Owner: postgres +-- + +GRANT SELECT ON TABLE pg_catalog.pg_stat_user_indexes TO nhost_hasura; + + +-- +-- Name: TABLE pg_stat_user_tables; Type: ACL; Schema: pg_catalog; Owner: postgres +-- + +GRANT SELECT ON TABLE pg_catalog.pg_stat_user_tables TO nhost_hasura; + + +-- +-- Name: TABLE pg_stat_wal; Type: ACL; Schema: pg_catalog; Owner: postgres +-- + +GRANT SELECT ON TABLE pg_catalog.pg_stat_wal TO nhost_hasura; + + +-- +-- Name: TABLE pg_stat_wal_receiver; Type: ACL; Schema: pg_catalog; Owner: postgres +-- + +GRANT SELECT ON TABLE pg_catalog.pg_stat_wal_receiver TO nhost_hasura; + + +-- +-- Name: TABLE pg_stat_xact_all_tables; Type: ACL; Schema: pg_catalog; Owner: postgres +-- + +GRANT SELECT ON TABLE pg_catalog.pg_stat_xact_all_tables TO nhost_hasura; + + +-- +-- Name: TABLE pg_stat_xact_sys_tables; Type: ACL; Schema: pg_catalog; Owner: postgres +-- + +GRANT SELECT ON TABLE pg_catalog.pg_stat_xact_sys_tables TO nhost_hasura; + + +-- +-- Name: TABLE pg_stat_xact_user_functions; Type: ACL; Schema: pg_catalog; Owner: postgres +-- + +GRANT SELECT ON TABLE pg_catalog.pg_stat_xact_user_functions TO nhost_hasura; + + +-- +-- Name: TABLE pg_stat_xact_user_tables; Type: ACL; Schema: pg_catalog; Owner: postgres +-- + +GRANT SELECT ON TABLE pg_catalog.pg_stat_xact_user_tables TO nhost_hasura; + + +-- +-- Name: TABLE pg_statio_all_indexes; Type: ACL; Schema: pg_catalog; Owner: postgres +-- + +GRANT SELECT ON TABLE pg_catalog.pg_statio_all_indexes TO nhost_hasura; + + +-- +-- Name: TABLE pg_statio_all_sequences; Type: ACL; Schema: pg_catalog; Owner: postgres +-- + +GRANT SELECT ON TABLE pg_catalog.pg_statio_all_sequences TO nhost_hasura; + + +-- +-- Name: TABLE pg_statio_all_tables; Type: ACL; Schema: pg_catalog; Owner: postgres +-- + +GRANT SELECT ON TABLE pg_catalog.pg_statio_all_tables TO nhost_hasura; + + +-- +-- Name: TABLE pg_statio_sys_indexes; Type: ACL; Schema: pg_catalog; Owner: postgres +-- + +GRANT SELECT ON TABLE pg_catalog.pg_statio_sys_indexes TO nhost_hasura; + + +-- +-- Name: TABLE pg_statio_sys_sequences; Type: ACL; Schema: pg_catalog; Owner: postgres +-- + +GRANT SELECT ON TABLE pg_catalog.pg_statio_sys_sequences TO nhost_hasura; + + +-- +-- Name: TABLE pg_statio_sys_tables; Type: ACL; Schema: pg_catalog; Owner: postgres +-- + +GRANT SELECT ON TABLE pg_catalog.pg_statio_sys_tables TO nhost_hasura; + + +-- +-- Name: TABLE pg_statio_user_indexes; Type: ACL; Schema: pg_catalog; Owner: postgres +-- + +GRANT SELECT ON TABLE pg_catalog.pg_statio_user_indexes TO nhost_hasura; + + +-- +-- Name: TABLE pg_statio_user_sequences; Type: ACL; Schema: pg_catalog; Owner: postgres +-- + +GRANT SELECT ON TABLE pg_catalog.pg_statio_user_sequences TO nhost_hasura; + + +-- +-- Name: TABLE pg_statio_user_tables; Type: ACL; Schema: pg_catalog; Owner: postgres +-- + +GRANT SELECT ON TABLE pg_catalog.pg_statio_user_tables TO nhost_hasura; + + +-- +-- Name: TABLE pg_statistic; Type: ACL; Schema: pg_catalog; Owner: postgres +-- + +GRANT SELECT ON TABLE pg_catalog.pg_statistic TO nhost_hasura; + + +-- +-- Name: TABLE pg_statistic_ext; Type: ACL; Schema: pg_catalog; Owner: postgres +-- + +GRANT SELECT ON TABLE pg_catalog.pg_statistic_ext TO nhost_hasura; + + +-- +-- Name: TABLE pg_statistic_ext_data; Type: ACL; Schema: pg_catalog; Owner: postgres +-- + +GRANT SELECT ON TABLE pg_catalog.pg_statistic_ext_data TO nhost_hasura; + + +-- +-- Name: TABLE pg_stats; Type: ACL; Schema: pg_catalog; Owner: postgres +-- + +GRANT SELECT ON TABLE pg_catalog.pg_stats TO nhost_hasura; + + +-- +-- Name: TABLE pg_stats_ext; Type: ACL; Schema: pg_catalog; Owner: postgres +-- + +GRANT SELECT ON TABLE pg_catalog.pg_stats_ext TO nhost_hasura; + + +-- +-- Name: TABLE pg_stats_ext_exprs; Type: ACL; Schema: pg_catalog; Owner: postgres +-- + +GRANT SELECT ON TABLE pg_catalog.pg_stats_ext_exprs TO nhost_hasura; + + +-- +-- Name: TABLE pg_subscription; Type: ACL; Schema: pg_catalog; Owner: postgres +-- + +GRANT SELECT ON TABLE pg_catalog.pg_subscription TO nhost_hasura; + + +-- +-- Name: TABLE pg_subscription_rel; Type: ACL; Schema: pg_catalog; Owner: postgres +-- + +GRANT SELECT ON TABLE pg_catalog.pg_subscription_rel TO nhost_hasura; + + +-- +-- Name: TABLE pg_tables; Type: ACL; Schema: pg_catalog; Owner: postgres +-- + +GRANT SELECT ON TABLE pg_catalog.pg_tables TO nhost_hasura; + + +-- +-- Name: TABLE pg_tablespace; Type: ACL; Schema: pg_catalog; Owner: postgres +-- + +GRANT SELECT ON TABLE pg_catalog.pg_tablespace TO nhost_hasura; + + +-- +-- Name: TABLE pg_timezone_abbrevs; Type: ACL; Schema: pg_catalog; Owner: postgres +-- + +GRANT SELECT ON TABLE pg_catalog.pg_timezone_abbrevs TO nhost_hasura; + + +-- +-- Name: TABLE pg_timezone_names; Type: ACL; Schema: pg_catalog; Owner: postgres +-- + +GRANT SELECT ON TABLE pg_catalog.pg_timezone_names TO nhost_hasura; + + +-- +-- Name: TABLE pg_transform; Type: ACL; Schema: pg_catalog; Owner: postgres +-- + +GRANT SELECT ON TABLE pg_catalog.pg_transform TO nhost_hasura; + + +-- +-- Name: TABLE pg_trigger; Type: ACL; Schema: pg_catalog; Owner: postgres +-- + +GRANT SELECT ON TABLE pg_catalog.pg_trigger TO nhost_hasura; + + +-- +-- Name: TABLE pg_ts_config; Type: ACL; Schema: pg_catalog; Owner: postgres +-- + +GRANT SELECT ON TABLE pg_catalog.pg_ts_config TO nhost_hasura; + + +-- +-- Name: TABLE pg_ts_config_map; Type: ACL; Schema: pg_catalog; Owner: postgres +-- + +GRANT SELECT ON TABLE pg_catalog.pg_ts_config_map TO nhost_hasura; + + +-- +-- Name: TABLE pg_ts_dict; Type: ACL; Schema: pg_catalog; Owner: postgres +-- + +GRANT SELECT ON TABLE pg_catalog.pg_ts_dict TO nhost_hasura; + + +-- +-- Name: TABLE pg_ts_parser; Type: ACL; Schema: pg_catalog; Owner: postgres +-- + +GRANT SELECT ON TABLE pg_catalog.pg_ts_parser TO nhost_hasura; + + +-- +-- Name: TABLE pg_ts_template; Type: ACL; Schema: pg_catalog; Owner: postgres +-- + +GRANT SELECT ON TABLE pg_catalog.pg_ts_template TO nhost_hasura; + + +-- +-- Name: TABLE pg_type; Type: ACL; Schema: pg_catalog; Owner: postgres +-- + +GRANT SELECT ON TABLE pg_catalog.pg_type TO nhost_hasura; + + +-- +-- Name: TABLE pg_user; Type: ACL; Schema: pg_catalog; Owner: postgres +-- + +GRANT SELECT ON TABLE pg_catalog.pg_user TO nhost_hasura; + + +-- +-- Name: TABLE pg_user_mapping; Type: ACL; Schema: pg_catalog; Owner: postgres +-- + +GRANT SELECT ON TABLE pg_catalog.pg_user_mapping TO nhost_hasura; + + +-- +-- Name: TABLE pg_user_mappings; Type: ACL; Schema: pg_catalog; Owner: postgres +-- + +GRANT SELECT ON TABLE pg_catalog.pg_user_mappings TO nhost_hasura; + + +-- +-- Name: TABLE pg_views; Type: ACL; Schema: pg_catalog; Owner: postgres +-- + +GRANT SELECT ON TABLE pg_catalog.pg_views TO nhost_hasura; + + +-- +-- Name: TABLE buckets; Type: ACL; Schema: storage; Owner: nhost_storage_admin +-- + +GRANT ALL ON TABLE storage.buckets TO nhost_hasura; + + +-- +-- Name: TABLE files; Type: ACL; Schema: storage; Owner: nhost_storage_admin +-- + +GRANT ALL ON TABLE storage.files TO nhost_hasura; + + +-- +-- Name: TABLE schema_migrations; Type: ACL; Schema: storage; Owner: nhost_storage_admin +-- + +GRANT ALL ON TABLE storage.schema_migrations TO nhost_hasura; + + +-- +-- Name: DEFAULT PRIVILEGES FOR TABLES; Type: DEFAULT ACL; Schema: auth; Owner: nhost_auth_admin +-- + +ALTER DEFAULT PRIVILEGES FOR ROLE nhost_auth_admin IN SCHEMA auth GRANT ALL ON TABLES TO nhost_hasura; + + +-- +-- Name: DEFAULT PRIVILEGES FOR TABLES; Type: DEFAULT ACL; Schema: storage; Owner: nhost_storage_admin +-- + +ALTER DEFAULT PRIVILEGES FOR ROLE nhost_storage_admin IN SCHEMA storage GRANT ALL ON TABLES TO nhost_hasura; + + +-- +-- PostgreSQL database dump complete +-- + diff --git a/tests/resources/restore.sh b/tests/resources/restore.sh new file mode 100644 index 0000000..45c8b7f --- /dev/null +++ b/tests/resources/restore.sh @@ -0,0 +1,3 @@ +#!/bin/bash +set -e +pg_restore -U "$POSTGRES_USER" -d "$POSTGRES_DB" -F t /docker-entrypoint-initdb.d/backup.tar \ No newline at end of file diff --git a/tests/resources/supabase/backup.tar b/tests/resources/supabase/backup.tar new file mode 100644 index 0000000..ac30a06 --- /dev/null +++ b/tests/resources/supabase/backup.tar @@ -0,0 +1,4405 @@ +-- +-- PostgreSQL database dump +-- + +-- Dumped from database version 15.1 +-- Dumped by pg_dump version 15.2 + +SET statement_timeout = 0; +SET lock_timeout = 0; +SET idle_in_transaction_session_timeout = 0; +SET client_encoding = 'UTF8'; +SET standard_conforming_strings = on; +SELECT pg_catalog.set_config('search_path', '', false); +SET check_function_bodies = false; +SET xmloption = content; +SET client_min_messages = warning; +SET row_security = off; + +-- +-- Name: auth; Type: SCHEMA; Schema: -; Owner: supabase_admin +-- + +CREATE SCHEMA auth; + + +ALTER SCHEMA auth OWNER TO supabase_admin; + +-- +-- Name: extensions; Type: SCHEMA; Schema: -; Owner: postgres +-- + +CREATE SCHEMA extensions; + + +ALTER SCHEMA extensions OWNER TO postgres; + +-- +-- Name: graphql; Type: SCHEMA; Schema: -; Owner: supabase_admin +-- + +CREATE SCHEMA graphql; + + +ALTER SCHEMA graphql OWNER TO supabase_admin; + +-- +-- Name: graphql_public; Type: SCHEMA; Schema: -; Owner: supabase_admin +-- + +CREATE SCHEMA graphql_public; + + +ALTER SCHEMA graphql_public OWNER TO supabase_admin; + +-- +-- Name: pgbouncer; Type: SCHEMA; Schema: -; Owner: pgbouncer +-- + +CREATE SCHEMA pgbouncer; + + +ALTER SCHEMA pgbouncer OWNER TO pgbouncer; + +-- +-- Name: pgsodium; Type: SCHEMA; Schema: -; Owner: postgres +-- + +CREATE SCHEMA pgsodium; + + +ALTER SCHEMA pgsodium OWNER TO postgres; + +-- +-- Name: pgsodium; Type: EXTENSION; Schema: -; Owner: - +-- + +CREATE EXTENSION IF NOT EXISTS pgsodium WITH SCHEMA pgsodium; + + +-- +-- Name: EXTENSION pgsodium; Type: COMMENT; Schema: -; Owner: +-- + +COMMENT ON EXTENSION pgsodium IS 'Pgsodium is a modern cryptography library for Postgres.'; + + +-- +-- Name: pgtle; Type: SCHEMA; Schema: -; Owner: supabase_admin +-- + +CREATE SCHEMA pgtle; + + +ALTER SCHEMA pgtle OWNER TO supabase_admin; + +-- +-- Name: public; Type: SCHEMA; Schema: -; Owner: postgres +-- + +-- *not* creating schema, since initdb creates it + + +ALTER SCHEMA public OWNER TO postgres; + +-- +-- Name: SCHEMA public; Type: COMMENT; Schema: -; Owner: postgres +-- + +COMMENT ON SCHEMA public IS ''; + + +-- +-- Name: realtime; Type: SCHEMA; Schema: -; Owner: supabase_admin +-- + +CREATE SCHEMA realtime; + + +ALTER SCHEMA realtime OWNER TO supabase_admin; + +-- +-- Name: storage; Type: SCHEMA; Schema: -; Owner: supabase_admin +-- + +CREATE SCHEMA storage; + + +ALTER SCHEMA storage OWNER TO supabase_admin; + +-- +-- Name: vault; Type: SCHEMA; Schema: -; Owner: supabase_admin +-- + +CREATE SCHEMA vault; + + +ALTER SCHEMA vault OWNER TO supabase_admin; + +-- +-- Name: pg_graphql; Type: EXTENSION; Schema: -; Owner: - +-- + +CREATE EXTENSION IF NOT EXISTS pg_graphql WITH SCHEMA graphql; + + +-- +-- Name: EXTENSION pg_graphql; Type: COMMENT; Schema: -; Owner: +-- + +COMMENT ON EXTENSION pg_graphql IS 'pg_graphql: GraphQL support'; + + +-- +-- Name: pg_stat_statements; Type: EXTENSION; Schema: -; Owner: - +-- + +CREATE EXTENSION IF NOT EXISTS pg_stat_statements WITH SCHEMA extensions; + + +-- +-- Name: EXTENSION pg_stat_statements; Type: COMMENT; Schema: -; Owner: +-- + +COMMENT ON EXTENSION pg_stat_statements IS 'track planning and execution statistics of all SQL statements executed'; + + +-- +-- Name: pgcrypto; Type: EXTENSION; Schema: -; Owner: - +-- + +CREATE EXTENSION IF NOT EXISTS pgcrypto WITH SCHEMA extensions; + + +-- +-- Name: EXTENSION pgcrypto; Type: COMMENT; Schema: -; Owner: +-- + +COMMENT ON EXTENSION pgcrypto IS 'cryptographic functions'; + + +-- +-- Name: pgjwt; Type: EXTENSION; Schema: -; Owner: - +-- + +CREATE EXTENSION IF NOT EXISTS pgjwt WITH SCHEMA extensions; + + +-- +-- Name: EXTENSION pgjwt; Type: COMMENT; Schema: -; Owner: +-- + +COMMENT ON EXTENSION pgjwt IS 'JSON Web Token API for Postgresql'; + + +-- +-- Name: supabase_vault; Type: EXTENSION; Schema: -; Owner: - +-- + +CREATE EXTENSION IF NOT EXISTS supabase_vault WITH SCHEMA vault; + + +-- +-- Name: EXTENSION supabase_vault; Type: COMMENT; Schema: -; Owner: +-- + +COMMENT ON EXTENSION supabase_vault IS 'Supabase Vault Extension'; + + +-- +-- Name: uuid-ossp; Type: EXTENSION; Schema: -; Owner: - +-- + +CREATE EXTENSION IF NOT EXISTS "uuid-ossp" WITH SCHEMA extensions; + + +-- +-- Name: EXTENSION "uuid-ossp"; Type: COMMENT; Schema: -; Owner: +-- + +COMMENT ON EXTENSION "uuid-ossp" IS 'generate universally unique identifiers (UUIDs)'; + + +-- +-- Name: aal_level; Type: TYPE; Schema: auth; Owner: supabase_auth_admin +-- + +CREATE TYPE auth.aal_level AS ENUM ( + 'aal1', + 'aal2', + 'aal3' +); + + +ALTER TYPE auth.aal_level OWNER TO supabase_auth_admin; + +-- +-- Name: code_challenge_method; Type: TYPE; Schema: auth; Owner: supabase_auth_admin +-- + +CREATE TYPE auth.code_challenge_method AS ENUM ( + 's256', + 'plain' +); + + +ALTER TYPE auth.code_challenge_method OWNER TO supabase_auth_admin; + +-- +-- Name: factor_status; Type: TYPE; Schema: auth; Owner: supabase_auth_admin +-- + +CREATE TYPE auth.factor_status AS ENUM ( + 'unverified', + 'verified' +); + + +ALTER TYPE auth.factor_status OWNER TO supabase_auth_admin; + +-- +-- Name: factor_type; Type: TYPE; Schema: auth; Owner: supabase_auth_admin +-- + +CREATE TYPE auth.factor_type AS ENUM ( + 'totp', + 'webauthn' +); + + +ALTER TYPE auth.factor_type OWNER TO supabase_auth_admin; + +-- +-- Name: email(); Type: FUNCTION; Schema: auth; Owner: supabase_auth_admin +-- + +CREATE FUNCTION auth.email() RETURNS text + LANGUAGE sql STABLE + AS $$ + select + coalesce( + nullif(current_setting('request.jwt.claim.email', true), ''), + (nullif(current_setting('request.jwt.claims', true), '')::jsonb ->> 'email') + )::text +$$; + + +ALTER FUNCTION auth.email() OWNER TO supabase_auth_admin; + +-- +-- Name: FUNCTION email(); Type: COMMENT; Schema: auth; Owner: supabase_auth_admin +-- + +COMMENT ON FUNCTION auth.email() IS 'Deprecated. Use auth.jwt() -> ''email'' instead.'; + + +-- +-- Name: jwt(); Type: FUNCTION; Schema: auth; Owner: supabase_auth_admin +-- + +CREATE FUNCTION auth.jwt() RETURNS jsonb + LANGUAGE sql STABLE + AS $$ + select + coalesce( + nullif(current_setting('request.jwt.claim', true), ''), + nullif(current_setting('request.jwt.claims', true), '') + )::jsonb +$$; + + +ALTER FUNCTION auth.jwt() OWNER TO supabase_auth_admin; + +-- +-- Name: role(); Type: FUNCTION; Schema: auth; Owner: supabase_auth_admin +-- + +CREATE FUNCTION auth.role() RETURNS text + LANGUAGE sql STABLE + AS $$ + select + coalesce( + nullif(current_setting('request.jwt.claim.role', true), ''), + (nullif(current_setting('request.jwt.claims', true), '')::jsonb ->> 'role') + )::text +$$; + + +ALTER FUNCTION auth.role() OWNER TO supabase_auth_admin; + +-- +-- Name: FUNCTION role(); Type: COMMENT; Schema: auth; Owner: supabase_auth_admin +-- + +COMMENT ON FUNCTION auth.role() IS 'Deprecated. Use auth.jwt() -> ''role'' instead.'; + + +-- +-- Name: uid(); Type: FUNCTION; Schema: auth; Owner: supabase_auth_admin +-- + +CREATE FUNCTION auth.uid() RETURNS uuid + LANGUAGE sql STABLE + AS $$ + select + coalesce( + nullif(current_setting('request.jwt.claim.sub', true), ''), + (nullif(current_setting('request.jwt.claims', true), '')::jsonb ->> 'sub') + )::uuid +$$; + + +ALTER FUNCTION auth.uid() OWNER TO supabase_auth_admin; + +-- +-- Name: FUNCTION uid(); Type: COMMENT; Schema: auth; Owner: supabase_auth_admin +-- + +COMMENT ON FUNCTION auth.uid() IS 'Deprecated. Use auth.jwt() -> ''sub'' instead.'; + + +-- +-- Name: grant_pg_cron_access(); Type: FUNCTION; Schema: extensions; Owner: postgres +-- + +CREATE FUNCTION extensions.grant_pg_cron_access() RETURNS event_trigger + LANGUAGE plpgsql + AS $$ +DECLARE + schema_is_cron bool; +BEGIN + schema_is_cron = ( + SELECT n.nspname = 'cron' + FROM pg_event_trigger_ddl_commands() AS ev + LEFT JOIN pg_catalog.pg_namespace AS n + ON ev.objid = n.oid + ); + + IF schema_is_cron + THEN + grant usage on schema cron to postgres with grant option; + + alter default privileges in schema cron grant all on tables to postgres with grant option; + alter default privileges in schema cron grant all on functions to postgres with grant option; + alter default privileges in schema cron grant all on sequences to postgres with grant option; + + alter default privileges for user supabase_admin in schema cron grant all + on sequences to postgres with grant option; + alter default privileges for user supabase_admin in schema cron grant all + on tables to postgres with grant option; + alter default privileges for user supabase_admin in schema cron grant all + on functions to postgres with grant option; + + grant all privileges on all tables in schema cron to postgres with grant option; + + END IF; + +END; +$$; + + +ALTER FUNCTION extensions.grant_pg_cron_access() OWNER TO postgres; + +-- +-- Name: FUNCTION grant_pg_cron_access(); Type: COMMENT; Schema: extensions; Owner: postgres +-- + +COMMENT ON FUNCTION extensions.grant_pg_cron_access() IS 'Grants access to pg_cron'; + + +-- +-- Name: grant_pg_graphql_access(); Type: FUNCTION; Schema: extensions; Owner: supabase_admin +-- + +CREATE FUNCTION extensions.grant_pg_graphql_access() RETURNS event_trigger + LANGUAGE plpgsql + AS $_$ +DECLARE + func_is_graphql_resolve bool; +BEGIN + func_is_graphql_resolve = ( + SELECT n.proname = 'resolve' + FROM pg_event_trigger_ddl_commands() AS ev + LEFT JOIN pg_catalog.pg_proc AS n + ON ev.objid = n.oid + ); + + IF func_is_graphql_resolve + THEN + -- Update public wrapper to pass all arguments through to the pg_graphql resolve func + DROP FUNCTION IF EXISTS graphql_public.graphql; + create or replace function graphql_public.graphql( + "operationName" text default null, + query text default null, + variables jsonb default null, + extensions jsonb default null + ) + returns jsonb + language sql + as $$ + select graphql.resolve( + query := query, + variables := coalesce(variables, '{}'), + "operationName" := "operationName", + extensions := extensions + ); + $$; + + -- This hook executes when `graphql.resolve` is created. That is not necessarily the last + -- function in the extension so we need to grant permissions on existing entities AND + -- update default permissions to any others that are created after `graphql.resolve` + grant usage on schema graphql to postgres, anon, authenticated, service_role; + grant select on all tables in schema graphql to postgres, anon, authenticated, service_role; + grant execute on all functions in schema graphql to postgres, anon, authenticated, service_role; + grant all on all sequences in schema graphql to postgres, anon, authenticated, service_role; + alter default privileges in schema graphql grant all on tables to postgres, anon, authenticated, service_role; + alter default privileges in schema graphql grant all on functions to postgres, anon, authenticated, service_role; + alter default privileges in schema graphql grant all on sequences to postgres, anon, authenticated, service_role; + END IF; + +END; +$_$; + + +ALTER FUNCTION extensions.grant_pg_graphql_access() OWNER TO supabase_admin; + +-- +-- Name: FUNCTION grant_pg_graphql_access(); Type: COMMENT; Schema: extensions; Owner: supabase_admin +-- + +COMMENT ON FUNCTION extensions.grant_pg_graphql_access() IS 'Grants access to pg_graphql'; + + +-- +-- Name: grant_pg_net_access(); Type: FUNCTION; Schema: extensions; Owner: postgres +-- + +CREATE FUNCTION extensions.grant_pg_net_access() RETURNS event_trigger + LANGUAGE plpgsql + AS $$ +BEGIN + IF EXISTS ( + SELECT 1 + FROM pg_event_trigger_ddl_commands() AS ev + JOIN pg_extension AS ext + ON ev.objid = ext.oid + WHERE ext.extname = 'pg_net' + ) + THEN + IF NOT EXISTS ( + SELECT 1 + FROM pg_roles + WHERE rolname = 'supabase_functions_admin' + ) + THEN + CREATE USER supabase_functions_admin NOINHERIT CREATEROLE LOGIN NOREPLICATION; + END IF; + + GRANT USAGE ON SCHEMA net TO supabase_functions_admin, postgres, anon, authenticated, service_role; + + ALTER function net.http_get(url text, params jsonb, headers jsonb, timeout_milliseconds integer) SECURITY DEFINER; + ALTER function net.http_post(url text, body jsonb, params jsonb, headers jsonb, timeout_milliseconds integer) SECURITY DEFINER; + ALTER function net.http_collect_response(request_id bigint, async boolean) SECURITY DEFINER; + + ALTER function net.http_get(url text, params jsonb, headers jsonb, timeout_milliseconds integer) SET search_path = net; + ALTER function net.http_post(url text, body jsonb, params jsonb, headers jsonb, timeout_milliseconds integer) SET search_path = net; + ALTER function net.http_collect_response(request_id bigint, async boolean) SET search_path = net; + + REVOKE ALL ON FUNCTION net.http_get(url text, params jsonb, headers jsonb, timeout_milliseconds integer) FROM PUBLIC; + REVOKE ALL ON FUNCTION net.http_post(url text, body jsonb, params jsonb, headers jsonb, timeout_milliseconds integer) FROM PUBLIC; + REVOKE ALL ON FUNCTION net.http_collect_response(request_id bigint, async boolean) FROM PUBLIC; + + GRANT EXECUTE ON FUNCTION net.http_get(url text, params jsonb, headers jsonb, timeout_milliseconds integer) TO supabase_functions_admin, postgres, anon, authenticated, service_role; + GRANT EXECUTE ON FUNCTION net.http_post(url text, body jsonb, params jsonb, headers jsonb, timeout_milliseconds integer) TO supabase_functions_admin, postgres, anon, authenticated, service_role; + GRANT EXECUTE ON FUNCTION net.http_collect_response(request_id bigint, async boolean) TO supabase_functions_admin, postgres, anon, authenticated, service_role; + END IF; +END; +$$; + + +ALTER FUNCTION extensions.grant_pg_net_access() OWNER TO postgres; + +-- +-- Name: FUNCTION grant_pg_net_access(); Type: COMMENT; Schema: extensions; Owner: postgres +-- + +COMMENT ON FUNCTION extensions.grant_pg_net_access() IS 'Grants access to pg_net'; + + +-- +-- Name: pgrst_ddl_watch(); Type: FUNCTION; Schema: extensions; Owner: supabase_admin +-- + +CREATE FUNCTION extensions.pgrst_ddl_watch() RETURNS event_trigger + LANGUAGE plpgsql + AS $$ +DECLARE + cmd record; +BEGIN + FOR cmd IN SELECT * FROM pg_event_trigger_ddl_commands() + LOOP + IF cmd.command_tag IN ( + 'CREATE SCHEMA', 'ALTER SCHEMA' + , 'CREATE TABLE', 'CREATE TABLE AS', 'SELECT INTO', 'ALTER TABLE' + , 'CREATE FOREIGN TABLE', 'ALTER FOREIGN TABLE' + , 'CREATE VIEW', 'ALTER VIEW' + , 'CREATE MATERIALIZED VIEW', 'ALTER MATERIALIZED VIEW' + , 'CREATE FUNCTION', 'ALTER FUNCTION' + , 'CREATE TRIGGER' + , 'CREATE TYPE', 'ALTER TYPE' + , 'CREATE RULE' + , 'COMMENT' + ) + -- don't notify in case of CREATE TEMP table or other objects created on pg_temp + AND cmd.schema_name is distinct from 'pg_temp' + THEN + NOTIFY pgrst, 'reload schema'; + END IF; + END LOOP; +END; $$; + + +ALTER FUNCTION extensions.pgrst_ddl_watch() OWNER TO supabase_admin; + +-- +-- Name: pgrst_drop_watch(); Type: FUNCTION; Schema: extensions; Owner: supabase_admin +-- + +CREATE FUNCTION extensions.pgrst_drop_watch() RETURNS event_trigger + LANGUAGE plpgsql + AS $$ +DECLARE + obj record; +BEGIN + FOR obj IN SELECT * FROM pg_event_trigger_dropped_objects() + LOOP + IF obj.object_type IN ( + 'schema' + , 'table' + , 'foreign table' + , 'view' + , 'materialized view' + , 'function' + , 'trigger' + , 'type' + , 'rule' + ) + AND obj.is_temporary IS false -- no pg_temp objects + THEN + NOTIFY pgrst, 'reload schema'; + END IF; + END LOOP; +END; $$; + + +ALTER FUNCTION extensions.pgrst_drop_watch() OWNER TO supabase_admin; + +-- +-- Name: set_graphql_placeholder(); Type: FUNCTION; Schema: extensions; Owner: supabase_admin +-- + +CREATE FUNCTION extensions.set_graphql_placeholder() RETURNS event_trigger + LANGUAGE plpgsql + AS $_$ + DECLARE + graphql_is_dropped bool; + BEGIN + graphql_is_dropped = ( + SELECT ev.schema_name = 'graphql_public' + FROM pg_event_trigger_dropped_objects() AS ev + WHERE ev.schema_name = 'graphql_public' + ); + + IF graphql_is_dropped + THEN + create or replace function graphql_public.graphql( + "operationName" text default null, + query text default null, + variables jsonb default null, + extensions jsonb default null + ) + returns jsonb + language plpgsql + as $$ + DECLARE + server_version float; + BEGIN + server_version = (SELECT (SPLIT_PART((select version()), ' ', 2))::float); + + IF server_version >= 14 THEN + RETURN jsonb_build_object( + 'errors', jsonb_build_array( + jsonb_build_object( + 'message', 'pg_graphql extension is not enabled.' + ) + ) + ); + ELSE + RETURN jsonb_build_object( + 'errors', jsonb_build_array( + jsonb_build_object( + 'message', 'pg_graphql is only available on projects running Postgres 14 onwards.' + ) + ) + ); + END IF; + END; + $$; + END IF; + + END; +$_$; + + +ALTER FUNCTION extensions.set_graphql_placeholder() OWNER TO supabase_admin; + +-- +-- Name: FUNCTION set_graphql_placeholder(); Type: COMMENT; Schema: extensions; Owner: supabase_admin +-- + +COMMENT ON FUNCTION extensions.set_graphql_placeholder() IS 'Reintroduces placeholder function for graphql_public.graphql'; + + +-- +-- Name: get_auth(text); Type: FUNCTION; Schema: pgbouncer; Owner: postgres +-- + +CREATE FUNCTION pgbouncer.get_auth(p_usename text) RETURNS TABLE(username text, password text) + LANGUAGE plpgsql SECURITY DEFINER + AS $$ +BEGIN + RAISE WARNING 'PgBouncer auth request: %', p_usename; + + RETURN QUERY + SELECT usename::TEXT, passwd::TEXT FROM pg_catalog.pg_shadow + WHERE usename = p_usename; +END; +$$; + + +ALTER FUNCTION pgbouncer.get_auth(p_usename text) OWNER TO postgres; + +-- +-- Name: can_insert_object(text, text, uuid, jsonb); Type: FUNCTION; Schema: storage; Owner: supabase_storage_admin +-- + +CREATE FUNCTION storage.can_insert_object(bucketid text, name text, owner uuid, metadata jsonb) RETURNS void + LANGUAGE plpgsql + AS $$ +BEGIN + INSERT INTO "storage"."objects" ("bucket_id", "name", "owner", "metadata") VALUES (bucketid, name, owner, metadata); + -- hack to rollback the successful insert + RAISE sqlstate 'PT200' using + message = 'ROLLBACK', + detail = 'rollback successful insert'; +END +$$; + + +ALTER FUNCTION storage.can_insert_object(bucketid text, name text, owner uuid, metadata jsonb) OWNER TO supabase_storage_admin; + +-- +-- Name: extension(text); Type: FUNCTION; Schema: storage; Owner: supabase_storage_admin +-- + +CREATE FUNCTION storage.extension(name text) RETURNS text + LANGUAGE plpgsql + AS $$ +DECLARE +_parts text[]; +_filename text; +BEGIN + select string_to_array(name, '/') into _parts; + select _parts[array_length(_parts,1)] into _filename; + -- @todo return the last part instead of 2 + return split_part(_filename, '.', 2); +END +$$; + + +ALTER FUNCTION storage.extension(name text) OWNER TO supabase_storage_admin; + +-- +-- Name: filename(text); Type: FUNCTION; Schema: storage; Owner: supabase_storage_admin +-- + +CREATE FUNCTION storage.filename(name text) RETURNS text + LANGUAGE plpgsql + AS $$ +DECLARE +_parts text[]; +BEGIN + select string_to_array(name, '/') into _parts; + return _parts[array_length(_parts,1)]; +END +$$; + + +ALTER FUNCTION storage.filename(name text) OWNER TO supabase_storage_admin; + +-- +-- Name: foldername(text); Type: FUNCTION; Schema: storage; Owner: supabase_storage_admin +-- + +CREATE FUNCTION storage.foldername(name text) RETURNS text[] + LANGUAGE plpgsql + AS $$ +DECLARE +_parts text[]; +BEGIN + select string_to_array(name, '/') into _parts; + return _parts[1:array_length(_parts,1)-1]; +END +$$; + + +ALTER FUNCTION storage.foldername(name text) OWNER TO supabase_storage_admin; + +-- +-- Name: get_size_by_bucket(); Type: FUNCTION; Schema: storage; Owner: supabase_storage_admin +-- + +CREATE FUNCTION storage.get_size_by_bucket() RETURNS TABLE(size bigint, bucket_id text) + LANGUAGE plpgsql + AS $$ +BEGIN + return query + select sum((metadata->>'size')::int) as size, obj.bucket_id + from "storage".objects as obj + group by obj.bucket_id; +END +$$; + + +ALTER FUNCTION storage.get_size_by_bucket() OWNER TO supabase_storage_admin; + +-- +-- Name: search(text, text, integer, integer, integer, text, text, text); Type: FUNCTION; Schema: storage; Owner: supabase_storage_admin +-- + +CREATE FUNCTION storage.search(prefix text, bucketname text, limits integer DEFAULT 100, levels integer DEFAULT 1, offsets integer DEFAULT 0, search text DEFAULT ''::text, sortcolumn text DEFAULT 'name'::text, sortorder text DEFAULT 'asc'::text) RETURNS TABLE(name text, id uuid, updated_at timestamp with time zone, created_at timestamp with time zone, last_accessed_at timestamp with time zone, metadata jsonb) + LANGUAGE plpgsql STABLE + AS $_$ +declare + v_order_by text; + v_sort_order text; +begin + case + when sortcolumn = 'name' then + v_order_by = 'name'; + when sortcolumn = 'updated_at' then + v_order_by = 'updated_at'; + when sortcolumn = 'created_at' then + v_order_by = 'created_at'; + when sortcolumn = 'last_accessed_at' then + v_order_by = 'last_accessed_at'; + else + v_order_by = 'name'; + end case; + + case + when sortorder = 'asc' then + v_sort_order = 'asc'; + when sortorder = 'desc' then + v_sort_order = 'desc'; + else + v_sort_order = 'asc'; + end case; + + v_order_by = v_order_by || ' ' || v_sort_order; + + return query execute + 'with folders as ( + select path_tokens[$1] as folder + from storage.objects + where objects.name ilike $2 || $3 || ''%'' + and bucket_id = $4 + and array_length(regexp_split_to_array(objects.name, ''/''), 1) <> $1 + group by folder + order by folder ' || v_sort_order || ' + ) + (select folder as "name", + null as id, + null as updated_at, + null as created_at, + null as last_accessed_at, + null as metadata from folders) + union all + (select path_tokens[$1] as "name", + id, + updated_at, + created_at, + last_accessed_at, + metadata + from storage.objects + where objects.name ilike $2 || $3 || ''%'' + and bucket_id = $4 + and array_length(regexp_split_to_array(objects.name, ''/''), 1) = $1 + order by ' || v_order_by || ') + limit $5 + offset $6' using levels, prefix, search, bucketname, limits, offsets; +end; +$_$; + + +ALTER FUNCTION storage.search(prefix text, bucketname text, limits integer, levels integer, offsets integer, search text, sortcolumn text, sortorder text) OWNER TO supabase_storage_admin; + +-- +-- Name: update_updated_at_column(); Type: FUNCTION; Schema: storage; Owner: supabase_storage_admin +-- + +CREATE FUNCTION storage.update_updated_at_column() RETURNS trigger + LANGUAGE plpgsql + AS $$ +BEGIN + NEW.updated_at = now(); + RETURN NEW; +END; +$$; + + +ALTER FUNCTION storage.update_updated_at_column() OWNER TO supabase_storage_admin; + +-- +-- Name: secrets_encrypt_secret_secret(); Type: FUNCTION; Schema: vault; Owner: supabase_admin +-- + +CREATE FUNCTION vault.secrets_encrypt_secret_secret() RETURNS trigger + LANGUAGE plpgsql + AS $$ + BEGIN + new.secret = CASE WHEN new.secret IS NULL THEN NULL ELSE + CASE WHEN new.key_id IS NULL THEN NULL ELSE pg_catalog.encode( + pgsodium.crypto_aead_det_encrypt( + pg_catalog.convert_to(new.secret, 'utf8'), + pg_catalog.convert_to((new.id::text || new.description::text || new.created_at::text || new.updated_at::text)::text, 'utf8'), + new.key_id::uuid, + new.nonce + ), + 'base64') END END; + RETURN new; + END; + $$; + + +ALTER FUNCTION vault.secrets_encrypt_secret_secret() OWNER TO supabase_admin; + +SET default_tablespace = ''; + +SET default_table_access_method = heap; + +-- +-- Name: audit_log_entries; Type: TABLE; Schema: auth; Owner: supabase_auth_admin +-- + +CREATE TABLE auth.audit_log_entries ( + instance_id uuid, + id uuid NOT NULL, + payload json, + created_at timestamp with time zone, + ip_address character varying(64) DEFAULT ''::character varying NOT NULL +); + + +ALTER TABLE auth.audit_log_entries OWNER TO supabase_auth_admin; + +-- +-- Name: TABLE audit_log_entries; Type: COMMENT; Schema: auth; Owner: supabase_auth_admin +-- + +COMMENT ON TABLE auth.audit_log_entries IS 'Auth: Audit trail for user actions.'; + + +-- +-- Name: flow_state; Type: TABLE; Schema: auth; Owner: supabase_auth_admin +-- + +CREATE TABLE auth.flow_state ( + id uuid NOT NULL, + user_id uuid, + auth_code text NOT NULL, + code_challenge_method auth.code_challenge_method NOT NULL, + code_challenge text NOT NULL, + provider_type text NOT NULL, + provider_access_token text, + provider_refresh_token text, + created_at timestamp with time zone, + updated_at timestamp with time zone, + authentication_method text NOT NULL +); + + +ALTER TABLE auth.flow_state OWNER TO supabase_auth_admin; + +-- +-- Name: TABLE flow_state; Type: COMMENT; Schema: auth; Owner: supabase_auth_admin +-- + +COMMENT ON TABLE auth.flow_state IS 'stores metadata for pkce logins'; + + +-- +-- Name: identities; Type: TABLE; Schema: auth; Owner: supabase_auth_admin +-- + +CREATE TABLE auth.identities ( + id text NOT NULL, + user_id uuid NOT NULL, + identity_data jsonb NOT NULL, + provider text NOT NULL, + last_sign_in_at timestamp with time zone, + created_at timestamp with time zone, + updated_at timestamp with time zone, + email text GENERATED ALWAYS AS (lower((identity_data ->> 'email'::text))) STORED +); + + +ALTER TABLE auth.identities OWNER TO supabase_auth_admin; + +-- +-- Name: TABLE identities; Type: COMMENT; Schema: auth; Owner: supabase_auth_admin +-- + +COMMENT ON TABLE auth.identities IS 'Auth: Stores identities associated to a user.'; + + +-- +-- Name: COLUMN identities.email; Type: COMMENT; Schema: auth; Owner: supabase_auth_admin +-- + +COMMENT ON COLUMN auth.identities.email IS 'Auth: Email is a generated column that references the optional email property in the identity_data'; + + +-- +-- Name: instances; Type: TABLE; Schema: auth; Owner: supabase_auth_admin +-- + +CREATE TABLE auth.instances ( + id uuid NOT NULL, + uuid uuid, + raw_base_config text, + created_at timestamp with time zone, + updated_at timestamp with time zone +); + + +ALTER TABLE auth.instances OWNER TO supabase_auth_admin; + +-- +-- Name: TABLE instances; Type: COMMENT; Schema: auth; Owner: supabase_auth_admin +-- + +COMMENT ON TABLE auth.instances IS 'Auth: Manages users across multiple sites.'; + + +-- +-- Name: mfa_amr_claims; Type: TABLE; Schema: auth; Owner: supabase_auth_admin +-- + +CREATE TABLE auth.mfa_amr_claims ( + session_id uuid NOT NULL, + created_at timestamp with time zone NOT NULL, + updated_at timestamp with time zone NOT NULL, + authentication_method text NOT NULL, + id uuid NOT NULL +); + + +ALTER TABLE auth.mfa_amr_claims OWNER TO supabase_auth_admin; + +-- +-- Name: TABLE mfa_amr_claims; Type: COMMENT; Schema: auth; Owner: supabase_auth_admin +-- + +COMMENT ON TABLE auth.mfa_amr_claims IS 'auth: stores authenticator method reference claims for multi factor authentication'; + + +-- +-- Name: mfa_challenges; Type: TABLE; Schema: auth; Owner: supabase_auth_admin +-- + +CREATE TABLE auth.mfa_challenges ( + id uuid NOT NULL, + factor_id uuid NOT NULL, + created_at timestamp with time zone NOT NULL, + verified_at timestamp with time zone, + ip_address inet NOT NULL +); + + +ALTER TABLE auth.mfa_challenges OWNER TO supabase_auth_admin; + +-- +-- Name: TABLE mfa_challenges; Type: COMMENT; Schema: auth; Owner: supabase_auth_admin +-- + +COMMENT ON TABLE auth.mfa_challenges IS 'auth: stores metadata about challenge requests made'; + + +-- +-- Name: mfa_factors; Type: TABLE; Schema: auth; Owner: supabase_auth_admin +-- + +CREATE TABLE auth.mfa_factors ( + id uuid NOT NULL, + user_id uuid NOT NULL, + friendly_name text, + factor_type auth.factor_type NOT NULL, + status auth.factor_status NOT NULL, + created_at timestamp with time zone NOT NULL, + updated_at timestamp with time zone NOT NULL, + secret text +); + + +ALTER TABLE auth.mfa_factors OWNER TO supabase_auth_admin; + +-- +-- Name: TABLE mfa_factors; Type: COMMENT; Schema: auth; Owner: supabase_auth_admin +-- + +COMMENT ON TABLE auth.mfa_factors IS 'auth: stores metadata about factors'; + + +-- +-- Name: refresh_tokens; Type: TABLE; Schema: auth; Owner: supabase_auth_admin +-- + +CREATE TABLE auth.refresh_tokens ( + instance_id uuid, + id bigint NOT NULL, + token character varying(255), + user_id character varying(255), + revoked boolean, + created_at timestamp with time zone, + updated_at timestamp with time zone, + parent character varying(255), + session_id uuid +); + + +ALTER TABLE auth.refresh_tokens OWNER TO supabase_auth_admin; + +-- +-- Name: TABLE refresh_tokens; Type: COMMENT; Schema: auth; Owner: supabase_auth_admin +-- + +COMMENT ON TABLE auth.refresh_tokens IS 'Auth: Store of tokens used to refresh JWT tokens once they expire.'; + + +-- +-- Name: refresh_tokens_id_seq; Type: SEQUENCE; Schema: auth; Owner: supabase_auth_admin +-- + +CREATE SEQUENCE auth.refresh_tokens_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +ALTER TABLE auth.refresh_tokens_id_seq OWNER TO supabase_auth_admin; + +-- +-- Name: refresh_tokens_id_seq; Type: SEQUENCE OWNED BY; Schema: auth; Owner: supabase_auth_admin +-- + +ALTER SEQUENCE auth.refresh_tokens_id_seq OWNED BY auth.refresh_tokens.id; + + +-- +-- Name: saml_providers; Type: TABLE; Schema: auth; Owner: supabase_auth_admin +-- + +CREATE TABLE auth.saml_providers ( + id uuid NOT NULL, + sso_provider_id uuid NOT NULL, + entity_id text NOT NULL, + metadata_xml text NOT NULL, + metadata_url text, + attribute_mapping jsonb, + created_at timestamp with time zone, + updated_at timestamp with time zone, + CONSTRAINT "entity_id not empty" CHECK ((char_length(entity_id) > 0)), + CONSTRAINT "metadata_url not empty" CHECK (((metadata_url = NULL::text) OR (char_length(metadata_url) > 0))), + CONSTRAINT "metadata_xml not empty" CHECK ((char_length(metadata_xml) > 0)) +); + + +ALTER TABLE auth.saml_providers OWNER TO supabase_auth_admin; + +-- +-- Name: TABLE saml_providers; Type: COMMENT; Schema: auth; Owner: supabase_auth_admin +-- + +COMMENT ON TABLE auth.saml_providers IS 'Auth: Manages SAML Identity Provider connections.'; + + +-- +-- Name: saml_relay_states; Type: TABLE; Schema: auth; Owner: supabase_auth_admin +-- + +CREATE TABLE auth.saml_relay_states ( + id uuid NOT NULL, + sso_provider_id uuid NOT NULL, + request_id text NOT NULL, + for_email text, + redirect_to text, + from_ip_address inet, + created_at timestamp with time zone, + updated_at timestamp with time zone, + CONSTRAINT "request_id not empty" CHECK ((char_length(request_id) > 0)) +); + + +ALTER TABLE auth.saml_relay_states OWNER TO supabase_auth_admin; + +-- +-- Name: TABLE saml_relay_states; Type: COMMENT; Schema: auth; Owner: supabase_auth_admin +-- + +COMMENT ON TABLE auth.saml_relay_states IS 'Auth: Contains SAML Relay State information for each Service Provider initiated login.'; + + +-- +-- Name: schema_migrations; Type: TABLE; Schema: auth; Owner: supabase_auth_admin +-- + +CREATE TABLE auth.schema_migrations ( + version character varying(255) NOT NULL +); + + +ALTER TABLE auth.schema_migrations OWNER TO supabase_auth_admin; + +-- +-- Name: TABLE schema_migrations; Type: COMMENT; Schema: auth; Owner: supabase_auth_admin +-- + +COMMENT ON TABLE auth.schema_migrations IS 'Auth: Manages updates to the auth system.'; + + +-- +-- Name: sessions; Type: TABLE; Schema: auth; Owner: supabase_auth_admin +-- + +CREATE TABLE auth.sessions ( + id uuid NOT NULL, + user_id uuid NOT NULL, + created_at timestamp with time zone, + updated_at timestamp with time zone, + factor_id uuid, + aal auth.aal_level, + not_after timestamp with time zone +); + + +ALTER TABLE auth.sessions OWNER TO supabase_auth_admin; + +-- +-- Name: TABLE sessions; Type: COMMENT; Schema: auth; Owner: supabase_auth_admin +-- + +COMMENT ON TABLE auth.sessions IS 'Auth: Stores session data associated to a user.'; + + +-- +-- Name: COLUMN sessions.not_after; Type: COMMENT; Schema: auth; Owner: supabase_auth_admin +-- + +COMMENT ON COLUMN auth.sessions.not_after IS 'Auth: Not after is a nullable column that contains a timestamp after which the session should be regarded as expired.'; + + +-- +-- Name: sso_domains; Type: TABLE; Schema: auth; Owner: supabase_auth_admin +-- + +CREATE TABLE auth.sso_domains ( + id uuid NOT NULL, + sso_provider_id uuid NOT NULL, + domain text NOT NULL, + created_at timestamp with time zone, + updated_at timestamp with time zone, + CONSTRAINT "domain not empty" CHECK ((char_length(domain) > 0)) +); + + +ALTER TABLE auth.sso_domains OWNER TO supabase_auth_admin; + +-- +-- Name: TABLE sso_domains; Type: COMMENT; Schema: auth; Owner: supabase_auth_admin +-- + +COMMENT ON TABLE auth.sso_domains IS 'Auth: Manages SSO email address domain mapping to an SSO Identity Provider.'; + + +-- +-- Name: sso_providers; Type: TABLE; Schema: auth; Owner: supabase_auth_admin +-- + +CREATE TABLE auth.sso_providers ( + id uuid NOT NULL, + resource_id text, + created_at timestamp with time zone, + updated_at timestamp with time zone, + CONSTRAINT "resource_id not empty" CHECK (((resource_id = NULL::text) OR (char_length(resource_id) > 0))) +); + + +ALTER TABLE auth.sso_providers OWNER TO supabase_auth_admin; + +-- +-- Name: TABLE sso_providers; Type: COMMENT; Schema: auth; Owner: supabase_auth_admin +-- + +COMMENT ON TABLE auth.sso_providers IS 'Auth: Manages SSO identity provider information; see saml_providers for SAML.'; + + +-- +-- Name: COLUMN sso_providers.resource_id; Type: COMMENT; Schema: auth; Owner: supabase_auth_admin +-- + +COMMENT ON COLUMN auth.sso_providers.resource_id IS 'Auth: Uniquely identifies a SSO provider according to a user-chosen resource ID (case insensitive), useful in infrastructure as code.'; + + +-- +-- Name: users; Type: TABLE; Schema: auth; Owner: supabase_auth_admin +-- + +CREATE TABLE auth.users ( + instance_id uuid, + id uuid NOT NULL, + aud character varying(255), + role character varying(255), + email character varying(255), + encrypted_password character varying(255), + email_confirmed_at timestamp with time zone, + invited_at timestamp with time zone, + confirmation_token character varying(255), + confirmation_sent_at timestamp with time zone, + recovery_token character varying(255), + recovery_sent_at timestamp with time zone, + email_change_token_new character varying(255), + email_change character varying(255), + email_change_sent_at timestamp with time zone, + last_sign_in_at timestamp with time zone, + raw_app_meta_data jsonb, + raw_user_meta_data jsonb, + is_super_admin boolean, + created_at timestamp with time zone, + updated_at timestamp with time zone, + phone text DEFAULT NULL::character varying, + phone_confirmed_at timestamp with time zone, + phone_change text DEFAULT ''::character varying, + phone_change_token character varying(255) DEFAULT ''::character varying, + phone_change_sent_at timestamp with time zone, + confirmed_at timestamp with time zone GENERATED ALWAYS AS (LEAST(email_confirmed_at, phone_confirmed_at)) STORED, + email_change_token_current character varying(255) DEFAULT ''::character varying, + email_change_confirm_status smallint DEFAULT 0, + banned_until timestamp with time zone, + reauthentication_token character varying(255) DEFAULT ''::character varying, + reauthentication_sent_at timestamp with time zone, + is_sso_user boolean DEFAULT false NOT NULL, + deleted_at timestamp with time zone, + CONSTRAINT users_email_change_confirm_status_check CHECK (((email_change_confirm_status >= 0) AND (email_change_confirm_status <= 2))) +); + + +ALTER TABLE auth.users OWNER TO supabase_auth_admin; + +-- +-- Name: TABLE users; Type: COMMENT; Schema: auth; Owner: supabase_auth_admin +-- + +COMMENT ON TABLE auth.users IS 'Auth: Stores user login data within a secure schema.'; + + +-- +-- Name: COLUMN users.is_sso_user; Type: COMMENT; Schema: auth; Owner: supabase_auth_admin +-- + +COMMENT ON COLUMN auth.users.is_sso_user IS 'Auth: Set this column to true when the account comes from SSO. These accounts can have duplicate emails.'; + + +-- +-- Name: test; Type: TABLE; Schema: public; Owner: postgres +-- + +CREATE TABLE public.test ( + id bigint NOT NULL, + int2 smallint, + int4 integer, + int8 bigint, + float4 real, + float8 double precision, + "numeric" numeric, + json json, + jsonb jsonb, + text text[], + "varchar" character varying[], + uuid uuid, + date date, + timetz time with time zone, + "timestamp" timestamp without time zone, + timestamptz timestamp with time zone, + bool boolean, + boolarr boolean[] +); + + +ALTER TABLE public.test OWNER TO postgres; + +-- +-- Name: TABLE test; Type: COMMENT; Schema: public; Owner: postgres +-- + +COMMENT ON TABLE public.test IS 'test'; + + +-- +-- Name: test2; Type: TABLE; Schema: public; Owner: postgres +-- + +CREATE TABLE public.test2 ( + id bigint NOT NULL, + created_at timestamp with time zone DEFAULT now(), + int4 smallint[], + int5 integer[], + int8 bigint[], + float4 real[], + float8 double precision[], + "numeric" numeric[], + json json[], + jsonb jsonb[], + text text[], + "varchar" character varying[], + uuid uuid[], + date date[], + "time" time without time zone[], + timetz time with time zone[], + "timestamp" timestamp without time zone[], + timestamptz timestamp with time zone[], + bool boolean[] +); + + +ALTER TABLE public.test2 OWNER TO postgres; + +-- +-- Name: test2_id_seq; Type: SEQUENCE; Schema: public; Owner: postgres +-- + +ALTER TABLE public.test2 ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY ( + SEQUENCE NAME public.test2_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1 +); + + +-- +-- Name: test_id_seq; Type: SEQUENCE; Schema: public; Owner: postgres +-- + +ALTER TABLE public.test ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY ( + SEQUENCE NAME public.test_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1 +); + + +-- +-- Name: buckets; Type: TABLE; Schema: storage; Owner: supabase_storage_admin +-- + +CREATE TABLE storage.buckets ( + id text NOT NULL, + name text NOT NULL, + owner uuid, + created_at timestamp with time zone DEFAULT now(), + updated_at timestamp with time zone DEFAULT now(), + public boolean DEFAULT false, + avif_autodetection boolean DEFAULT false, + file_size_limit bigint, + allowed_mime_types text[] +); + + +ALTER TABLE storage.buckets OWNER TO supabase_storage_admin; + +-- +-- Name: migrations; Type: TABLE; Schema: storage; Owner: supabase_storage_admin +-- + +CREATE TABLE storage.migrations ( + id integer NOT NULL, + name character varying(100) NOT NULL, + hash character varying(40) NOT NULL, + executed_at timestamp without time zone DEFAULT CURRENT_TIMESTAMP +); + + +ALTER TABLE storage.migrations OWNER TO supabase_storage_admin; + +-- +-- Name: objects; Type: TABLE; Schema: storage; Owner: supabase_storage_admin +-- + +CREATE TABLE storage.objects ( + id uuid DEFAULT extensions.uuid_generate_v4() NOT NULL, + bucket_id text, + name text, + owner uuid, + created_at timestamp with time zone DEFAULT now(), + updated_at timestamp with time zone DEFAULT now(), + last_accessed_at timestamp with time zone DEFAULT now(), + metadata jsonb, + path_tokens text[] GENERATED ALWAYS AS (string_to_array(name, '/'::text)) STORED, + version text +); + + +ALTER TABLE storage.objects OWNER TO supabase_storage_admin; + +-- +-- Name: decrypted_secrets; Type: VIEW; Schema: vault; Owner: supabase_admin +-- + +CREATE VIEW vault.decrypted_secrets AS + SELECT secrets.id, + secrets.name, + secrets.description, + secrets.secret, + CASE + WHEN (secrets.secret IS NULL) THEN NULL::text + ELSE + CASE + WHEN (secrets.key_id IS NULL) THEN NULL::text + ELSE convert_from(pgsodium.crypto_aead_det_decrypt(decode(secrets.secret, 'base64'::text), convert_to(((((secrets.id)::text || secrets.description) || (secrets.created_at)::text) || (secrets.updated_at)::text), 'utf8'::name), secrets.key_id, secrets.nonce), 'utf8'::name) + END + END AS decrypted_secret, + secrets.key_id, + secrets.nonce, + secrets.created_at, + secrets.updated_at + FROM vault.secrets; + + +ALTER TABLE vault.decrypted_secrets OWNER TO supabase_admin; + +-- +-- Name: refresh_tokens id; Type: DEFAULT; Schema: auth; Owner: supabase_auth_admin +-- + +ALTER TABLE ONLY auth.refresh_tokens ALTER COLUMN id SET DEFAULT nextval('auth.refresh_tokens_id_seq'::regclass); + + +-- +-- Data for Name: audit_log_entries; Type: TABLE DATA; Schema: auth; Owner: supabase_auth_admin +-- + +COPY auth.audit_log_entries (instance_id, id, payload, created_at, ip_address) FROM stdin; +00000000-0000-0000-0000-000000000000 c1902f19-1b8c-4f4d-aed7-433edce6876e {"action":"user_invited","actor_id":"00000000-0000-0000-0000-000000000000","actor_username":"supabase_admin","log_type":"team","traits":{"user_email":"ionicisere@gmail.com","user_id":"be55c6cf-94d2-44a4-a119-61297d68c0e8"}} 2022-08-30 12:59:35.98897+00 +00000000-0000-0000-0000-000000000000 27d0dfc7-35aa-49ff-bf42-cf94e85f01b2 {"action":"user_signedup","actor_id":"be55c6cf-94d2-44a4-a119-61297d68c0e8","actor_username":"ionicisere@gmail.com","log_type":"team"} 2022-08-30 12:59:49.901362+00 +00000000-0000-0000-0000-000000000000 4cb58f1d-eabd-4ac7-9731-7eb9aabb37e1 {"action":"user_invited","actor_id":"00000000-0000-0000-0000-000000000000","actor_username":"supabase_admin","log_type":"team","traits":{"user_email":"bradley@appwrite.io","user_id":"1b48e703-cb29-4b76-b804-82a53f074b93"}} 2022-09-14 10:09:18.311141+00 +00000000-0000-0000-0000-000000000000 59a6ff0f-2ae2-4674-a05a-ce889368762a {"action":"user_signedup","actor_id":"1b48e703-cb29-4b76-b804-82a53f074b93","actor_username":"bradley@appwrite.io","log_type":"team"} 2022-09-14 10:19:09.440989+00 +00000000-0000-0000-0000-000000000000 7c93bde4-e3d6-4725-a251-dc37554f765d {"action":"user_recovery_requested","actor_id":"1b48e703-cb29-4b76-b804-82a53f074b93","actor_username":"bradley@appwrite.io","log_type":"user"} 2022-09-20 09:05:38.36561+00 +00000000-0000-0000-0000-000000000000 c9ef1c90-0ac3-4ac7-9548-80b190bdb90d {"action":"login","actor_id":"1b48e703-cb29-4b76-b804-82a53f074b93","actor_username":"bradley@appwrite.io","log_type":"account"} 2022-09-20 09:06:33.909469+00 +00000000-0000-0000-0000-000000000000 1067d0f5-59ed-4ee2-9c91-c4196e4167b5 {"action":"user_recovery_requested","actor_id":"1b48e703-cb29-4b76-b804-82a53f074b93","actor_username":"bradley@appwrite.io","log_type":"user"} 2022-09-20 09:23:34.466842+00 +00000000-0000-0000-0000-000000000000 2c794d09-7cb6-4c08-9e20-106c90389806 {"action":"login","actor_id":"1b48e703-cb29-4b76-b804-82a53f074b93","actor_username":"bradley@appwrite.io","log_type":"account"} 2022-09-20 09:23:51.627032+00 +00000000-0000-0000-0000-000000000000 a6598240-a79f-4778-b76e-0882f3534441 {"action":"user_modified","actor_id":"1b48e703-cb29-4b76-b804-82a53f074b93","actor_username":"bradley@appwrite.io","log_type":"user"} 2022-09-20 09:27:11.738268+00 +00000000-0000-0000-0000-000000000000 63009024-ef8c-42b6-822f-b1f6608f065d {"action":"user_recovery_requested","actor_id":"1b48e703-cb29-4b76-b804-82a53f074b93","actor_username":"bradley@appwrite.io","log_type":"user"} 2022-09-20 09:32:37.016522+00 +00000000-0000-0000-0000-000000000000 fe9287eb-9306-4d92-97c7-ff8cfeaf6214 {"action":"login","actor_id":"1b48e703-cb29-4b76-b804-82a53f074b93","actor_username":"bradley@appwrite.io","log_type":"account"} 2022-09-20 09:32:43.909748+00 +00000000-0000-0000-0000-000000000000 c1abbcfc-4627-4755-a1ec-f512d8316440 {"action":"user_modified","actor_id":"1b48e703-cb29-4b76-b804-82a53f074b93","actor_username":"bradley@appwrite.io","log_type":"user"} 2022-09-20 09:33:47.027108+00 +00000000-0000-0000-0000-000000000000 2b65dfee-555c-4996-9411-d824a5858f92 {"action":"user_confirmation_requested","actor_id":"88043554-4aac-46de-8437-c02fdacfdc9c","actor_username":"misael.upton98@hotmail.com","log_type":"user","traits":{"provider":"email"}} 2023-01-19 11:08:37.980112+00 +00000000-0000-0000-0000-000000000000 c87c281e-e62b-405c-92e4-5eadd49d88f0 {"action":"user_confirmation_requested","actor_id":"615357b8-c668-45ee-a749-c96db1aabc7a","actor_username":"albert.kihn95@yahoo.com","log_type":"user","traits":{"provider":"email"}} 2023-01-19 11:08:37.986291+00 +00000000-0000-0000-0000-000000000000 988b85c7-b39a-4d0d-bd95-ee975f054aed {"action":"user_confirmation_requested","actor_id":"369664ac-9358-4b51-91b5-79ddca7ef0b2","actor_username":"maida_walsh61@yahoo.com","log_type":"user","traits":{"provider":"email"}} 2023-01-19 11:08:37.992667+00 +00000000-0000-0000-0000-000000000000 7f936ded-7ac9-4f10-baa1-ef7a0633fead {"action":"user_confirmation_requested","actor_id":"e7faf866-0438-4b7c-8de3-1c134c707806","actor_username":"osvaldo.bogan15@yahoo.com","log_type":"user","traits":{"provider":"email"}} 2023-01-19 11:08:37.997493+00 +00000000-0000-0000-0000-000000000000 44f1d5d8-acc5-4c64-bc0e-b73bf6f81bc4 {"action":"user_confirmation_requested","actor_id":"af00f659-028a-4268-b6b5-fa36e40e190e","actor_username":"wyman67@gmail.com","log_type":"user","traits":{"provider":"email"}} 2023-01-19 11:08:38.005148+00 +00000000-0000-0000-0000-000000000000 cf54912a-f78d-44c8-ad1b-1611e0bf65bd {"action":"user_confirmation_requested","actor_id":"07616a1c-e8ce-489a-a74b-adab1f4e1b32","actor_username":"rylee68@yahoo.com","log_type":"user","traits":{"provider":"email"}} 2023-01-19 11:08:38.021094+00 +00000000-0000-0000-0000-000000000000 f8ce7a46-e40d-487f-b44b-74649e73e358 {"action":"user_confirmation_requested","actor_id":"ab38ad48-7b50-4e66-85b1-7d78a61a04f0","actor_username":"devin.rath@gmail.com","log_type":"user","traits":{"provider":"email"}} 2023-01-19 11:08:38.030918+00 +00000000-0000-0000-0000-000000000000 dcfaa287-546a-49aa-b4c0-45bb8ff58b52 {"action":"user_confirmation_requested","actor_id":"23e19499-63e4-4fee-9718-bd24584fdca0","actor_username":"royce_hermiston@gmail.com","log_type":"user","traits":{"provider":"email"}} 2023-01-19 11:08:38.033558+00 +00000000-0000-0000-0000-000000000000 ee411931-e9c0-4c47-b2a0-ad5a224c97f8 {"action":"user_confirmation_requested","actor_id":"932ce7f7-e57d-45c3-bc7d-63362e2d67aa","actor_username":"eino_considine67@hotmail.com","log_type":"user","traits":{"provider":"email"}} 2023-01-19 11:08:38.033417+00 +00000000-0000-0000-0000-000000000000 c2e9c5c8-9469-4a7d-931f-daf8b0386149 {"action":"user_confirmation_requested","actor_id":"b301a3c1-022f-49fa-a6fb-41c8cdd521a9","actor_username":"jerrod70@yahoo.com","log_type":"user","traits":{"provider":"email"}} 2023-01-19 11:08:38.046936+00 +00000000-0000-0000-0000-000000000000 00a03660-31a1-4648-a06f-dd9e019f0633 {"action":"user_confirmation_requested","actor_id":"a7ca4ce7-7763-4edc-869a-dc3afb4dc1c2","actor_username":"ada.parker@gmail.com","log_type":"user","traits":{"provider":"email"}} 2023-01-19 11:08:38.627452+00 +00000000-0000-0000-0000-000000000000 fd3bbe3c-6b0b-418d-99a0-e521b3465133 {"action":"user_confirmation_requested","actor_id":"d977c288-485b-44f4-aa34-94c232decbed","actor_username":"aryanna_dickens47@gmail.com","log_type":"user","traits":{"provider":"email"}} 2023-01-19 11:08:38.798551+00 +00000000-0000-0000-0000-000000000000 95152226-2a61-4d46-97ab-e1ab5e559516 {"action":"user_confirmation_requested","actor_id":"53cd7be4-c374-492c-aa18-06febf196607","actor_username":"lonnie_huels69@gmail.com","log_type":"user","traits":{"provider":"email"}} 2023-01-19 11:08:38.817815+00 +00000000-0000-0000-0000-000000000000 7c0986f6-0310-4a94-9df2-6fc31d38b84c {"action":"user_confirmation_requested","actor_id":"2b817ed6-e9b4-4ab6-a97c-8a5c5cca80b5","actor_username":"jennings.watsica71@hotmail.com","log_type":"user","traits":{"provider":"email"}} 2023-01-19 11:08:38.845262+00 +00000000-0000-0000-0000-000000000000 8e392538-f1e9-4ece-8a7c-d1bf807d5e3a {"action":"user_confirmation_requested","actor_id":"ba1d736c-18f9-4eca-8000-05bd223d097f","actor_username":"alia.emmerich42@hotmail.com","log_type":"user","traits":{"provider":"email"}} 2023-01-19 11:08:38.884705+00 +00000000-0000-0000-0000-000000000000 88ad0d66-a780-4290-81ae-1425b86d10af {"action":"user_confirmation_requested","actor_id":"1d1c4838-d7bf-4830-945e-7f1c31934340","actor_username":"mallory_kuhn@yahoo.com","log_type":"user","traits":{"provider":"email"}} 2023-01-19 11:08:38.909651+00 +00000000-0000-0000-0000-000000000000 521e9c38-506a-4373-807e-11df4a6d62ec {"action":"user_confirmation_requested","actor_id":"abb439a6-9812-4edb-8f54-8f99830a9d51","actor_username":"julianne.schinner98@gmail.com","log_type":"user","traits":{"provider":"email"}} 2023-01-19 11:08:38.944862+00 +00000000-0000-0000-0000-000000000000 feede8a4-1b06-4bbf-9210-4200b9df34a3 {"action":"user_confirmation_requested","actor_id":"acbbb7e0-44ff-42fa-a8aa-69ad98666e31","actor_username":"adonis_oconnell@yahoo.com","log_type":"user","traits":{"provider":"email"}} 2023-01-19 11:08:39.011272+00 +00000000-0000-0000-0000-000000000000 8a039a1d-b856-4ed9-839f-e3922fed90ec {"action":"user_confirmation_requested","actor_id":"67cb7f0e-c295-4d41-83a0-761568b7a13c","actor_username":"craig.dietrich31@hotmail.com","log_type":"user","traits":{"provider":"email"}} 2023-01-19 11:08:39.034958+00 +00000000-0000-0000-0000-000000000000 3c328068-021f-4d85-8cde-a25dac7e23d0 {"action":"user_confirmation_requested","actor_id":"d1c2cd22-52db-4074-b0ba-e7152df4a27d","actor_username":"sunny.welch@gmail.com","log_type":"user","traits":{"provider":"email"}} 2023-01-19 11:08:39.039284+00 +00000000-0000-0000-0000-000000000000 8d9dfb38-5510-4540-afde-19e7210d3cba {"action":"user_confirmation_requested","actor_id":"e6e80a3d-8435-4d09-bba3-e8f158ff93e4","actor_username":"elliott_goldner77@yahoo.com","log_type":"user","traits":{"provider":"email"}} 2023-01-19 11:08:39.26905+00 +00000000-0000-0000-0000-000000000000 3d1d888b-99b0-4c72-a076-9e9ead3f79de {"action":"user_confirmation_requested","actor_id":"fef209ad-b677-44a7-82a9-899c33490075","actor_username":"ana.nikolaus95@yahoo.com","log_type":"user","traits":{"provider":"email"}} 2023-01-19 14:40:58.679176+00 +00000000-0000-0000-0000-000000000000 ce8b1ede-1f4e-47f3-b34a-958c1e3d707a {"action":"user_confirmation_requested","actor_id":"7f08b69b-c277-4af8-a51e-9f80bad06430","actor_username":"kobe_bergnaum47@yahoo.com","log_type":"user","traits":{"provider":"email"}} 2023-01-19 14:40:59.742489+00 +00000000-0000-0000-0000-000000000000 ab4f4854-0853-4214-805a-d1a1cdd7c5c1 {"action":"user_confirmation_requested","actor_id":"0b448f5c-7c26-498b-8d13-7049cf82037c","actor_username":"arne_bayer91@yahoo.com","log_type":"user","traits":{"provider":"email"}} 2023-01-19 14:41:00.798011+00 +00000000-0000-0000-0000-000000000000 f8b9d51f-3e4c-4492-aa79-e6597db8707e {"action":"user_confirmation_requested","actor_id":"8e40363f-20c0-4f58-ac8f-fecaec606923","actor_username":"cheyenne_cassin@hotmail.com","log_type":"user","traits":{"provider":"email"}} 2023-01-19 11:08:39.346177+00 +00000000-0000-0000-0000-000000000000 ef6ed7c2-17e2-430b-80a9-77c52711298d {"action":"user_confirmation_requested","actor_id":"5de1bfbc-4a77-4e6b-8adf-e5572a32e12e","actor_username":"coby91@yahoo.com","log_type":"user","traits":{"provider":"email"}} 2023-01-19 14:40:58.835142+00 +00000000-0000-0000-0000-000000000000 f9509d5a-4501-4b3a-b4e0-d7e5b3fd0b30 {"action":"user_confirmation_requested","actor_id":"46867bb3-7c26-4ced-9ec0-268afda9ca10","actor_username":"lewis62@gmail.com","log_type":"user","traits":{"provider":"email"}} 2023-01-19 14:40:59.490841+00 +00000000-0000-0000-0000-000000000000 e0e19e16-1a57-49f0-8f3f-f08526126a23 {"action":"user_confirmation_requested","actor_id":"366b7862-08df-4b9e-8ba3-8e2d7fbe7a40","actor_username":"lue.dibbert26@hotmail.com","log_type":"user","traits":{"provider":"email"}} 2023-01-19 14:41:00.342348+00 +00000000-0000-0000-0000-000000000000 eb4a832f-0f02-4433-9590-fc2089fb78b7 {"action":"user_confirmation_requested","actor_id":"5c6a9645-29e1-478e-9cba-d048ab579bd1","actor_username":"magdalena_metz@gmail.com","log_type":"user","traits":{"provider":"email"}} 2023-01-19 11:08:39.459499+00 +00000000-0000-0000-0000-000000000000 11b86e41-f5ed-4034-936a-3405666e7d86 {"action":"user_confirmation_requested","actor_id":"4535c79c-f973-447b-8692-6f2207f6efcb","actor_username":"madisen_harris32@gmail.com","log_type":"user","traits":{"provider":"email"}} 2023-01-19 11:08:39.505607+00 +00000000-0000-0000-0000-000000000000 8a2c5e3f-e192-43eb-b120-2681aca4dd93 {"action":"user_confirmation_requested","actor_id":"581483e9-b39a-477c-a712-a590a6bd2e8d","actor_username":"alexandre_rodriguez@gmail.com","log_type":"user","traits":{"provider":"email"}} 2023-01-19 11:08:39.508104+00 +00000000-0000-0000-0000-000000000000 2218eaf6-d590-4f4a-9abc-b54a0a89a2ea {"action":"user_confirmation_requested","actor_id":"0d9fd639-9b14-49c8-a755-36565a345da5","actor_username":"pierre33@gmail.com","log_type":"user","traits":{"provider":"email"}} 2023-01-19 11:08:39.51246+00 +00000000-0000-0000-0000-000000000000 fccbf8d7-df33-4062-b60b-3c0c0d40906e {"action":"user_confirmation_requested","actor_id":"af4d7654-d31d-40e9-8e31-8031b29651bf","actor_username":"laney.olson@gmail.com","log_type":"user","traits":{"provider":"email"}} 2023-01-19 11:08:39.61992+00 +00000000-0000-0000-0000-000000000000 f74d8518-8bb2-4d91-921a-3fa9e56c3ff7 {"action":"user_confirmation_requested","actor_id":"c7f6fb1f-a6d9-45b1-a732-4e80877266ea","actor_username":"jodie.wunsch60@gmail.com","log_type":"user","traits":{"provider":"email"}} 2023-01-19 11:08:39.637491+00 +00000000-0000-0000-0000-000000000000 b2a7a025-9dd9-4dc7-90dc-f05deb54cf7a {"action":"user_confirmation_requested","actor_id":"bdc41348-d0ff-4716-b03e-505397255296","actor_username":"micheal.homenick@yahoo.com","log_type":"user","traits":{"provider":"email"}} 2023-01-19 11:08:39.669904+00 +00000000-0000-0000-0000-000000000000 66f804ba-5420-456e-9905-ada2c35b9911 {"action":"user_confirmation_requested","actor_id":"b5aca4b6-67f6-47b1-b2f5-794f354e183a","actor_username":"pauline.moore@hotmail.com","log_type":"user","traits":{"provider":"email"}} 2023-01-19 11:08:39.697472+00 +00000000-0000-0000-0000-000000000000 dcd63ad7-5648-40e9-af68-b185645f4ff8 {"action":"user_confirmation_requested","actor_id":"68fb3828-45a2-41ee-8c32-fd87525d955e","actor_username":"vivian_rogahn@gmail.com","log_type":"user","traits":{"provider":"email"}} 2023-01-19 14:40:58.906026+00 +00000000-0000-0000-0000-000000000000 44d7e676-d605-427a-8fb4-c77f51756325 {"action":"user_confirmation_requested","actor_id":"2d7cc448-9478-4c4b-b9c1-b6005b3b2517","actor_username":"casimir.williamson41@gmail.com","log_type":"user","traits":{"provider":"email"}} 2023-01-19 14:40:59.074583+00 +00000000-0000-0000-0000-000000000000 fe46709a-032f-43b4-8ce3-38a55e78db8b {"action":"user_confirmation_requested","actor_id":"754e01ee-8bc6-42cf-875a-c596d4b5c108","actor_username":"rod_hoppe83@hotmail.com","log_type":"user","traits":{"provider":"email"}} 2023-01-19 14:40:59.126499+00 +00000000-0000-0000-0000-000000000000 d7a8a31c-c1db-4d92-a1b0-b1e2756ec997 {"action":"user_confirmation_requested","actor_id":"fe8d52b3-9bb2-4063-95da-733f61763be0","actor_username":"demetris99@yahoo.com","log_type":"user","traits":{"provider":"email"}} 2023-01-19 14:40:59.120626+00 +00000000-0000-0000-0000-000000000000 9ef5e545-5858-45d2-a4b0-b30cfd4ee0d9 {"action":"user_confirmation_requested","actor_id":"27b98fb2-2a63-4ea2-92da-845e45c1f530","actor_username":"marlon.torp45@gmail.com","log_type":"user","traits":{"provider":"email"}} 2023-01-19 14:40:59.175015+00 +00000000-0000-0000-0000-000000000000 9e4acd5e-bcf4-49b9-9f56-3b9eea2bb06f {"action":"user_confirmation_requested","actor_id":"88847f43-2f69-4c26-b00b-6b59a38ef164","actor_username":"cali_orn71@yahoo.com","log_type":"user","traits":{"provider":"email"}} 2023-01-19 14:40:59.196527+00 +00000000-0000-0000-0000-000000000000 ca7a089c-702a-4bbf-a87e-c354abae8d6b {"action":"user_confirmation_requested","actor_id":"561e28e6-7ce5-4282-9f51-7f59722170ca","actor_username":"rey32@gmail.com","log_type":"user","traits":{"provider":"email"}} 2023-01-19 14:40:59.197341+00 +00000000-0000-0000-0000-000000000000 a17507f3-5447-4ac3-a6b5-3ebf9a16c79b {"action":"user_confirmation_requested","actor_id":"a23c4ec2-eb31-420d-bd89-ec49065bed3f","actor_username":"ansel.kessler89@yahoo.com","log_type":"user","traits":{"provider":"email"}} 2023-01-19 14:40:59.19806+00 +00000000-0000-0000-0000-000000000000 a498aa55-2d71-4299-9aad-df1628d66dab {"action":"user_confirmation_requested","actor_id":"2e320c50-4640-43d4-8e82-5e66c430decd","actor_username":"anibal61@yahoo.com","log_type":"user","traits":{"provider":"email"}} 2023-01-19 14:40:59.47539+00 +00000000-0000-0000-0000-000000000000 a3ed6e6c-12c7-4bc8-a75f-c604f4336370 {"action":"user_confirmation_requested","actor_id":"e42df43c-3603-45ea-a281-1c1c1b29965c","actor_username":"thora.renner@yahoo.com","log_type":"user","traits":{"provider":"email"}} 2023-01-19 14:40:59.61507+00 +00000000-0000-0000-0000-000000000000 e7e6f7bb-eb45-4352-8b2a-677e5daa1e2a {"action":"user_confirmation_requested","actor_id":"07ede456-d7f3-4840-a2b2-6394cb6e44c6","actor_username":"estel.kovacek68@gmail.com","log_type":"user","traits":{"provider":"email"}} 2023-01-19 14:40:59.655022+00 +00000000-0000-0000-0000-000000000000 632de9d3-c69c-4529-9694-e557670cbb0d {"action":"user_confirmation_requested","actor_id":"8858520b-4d3e-4804-937c-e1fa8b355e1e","actor_username":"althea_dickens@hotmail.com","log_type":"user","traits":{"provider":"email"}} 2023-01-19 14:40:59.75571+00 +00000000-0000-0000-0000-000000000000 75ab9005-95cc-43c4-98bc-6aa967b86c01 {"action":"user_confirmation_requested","actor_id":"26889fa5-94d3-4150-8b17-d7413e682652","actor_username":"francis_lockman21@yahoo.com","log_type":"user","traits":{"provider":"email"}} 2023-01-19 14:40:59.809823+00 +00000000-0000-0000-0000-000000000000 e339fa91-ea17-4d6f-a0f9-2b06510b85a0 {"action":"user_confirmation_requested","actor_id":"9beabeb7-b51b-416c-9d3b-d23badeb7c5c","actor_username":"adonis_lemke@gmail.com","log_type":"user","traits":{"provider":"email"}} 2023-01-19 14:40:59.816807+00 +00000000-0000-0000-0000-000000000000 c4962a31-a422-4fde-ae3c-a117f0a70256 {"action":"user_confirmation_requested","actor_id":"b1262b76-e470-4f71-ad33-86919606391d","actor_username":"humberto_wolf@gmail.com","log_type":"user","traits":{"provider":"email"}} 2023-01-19 14:40:59.860563+00 +00000000-0000-0000-0000-000000000000 b2647794-d7b9-4bf6-b2ac-a94d41efee3c {"action":"user_confirmation_requested","actor_id":"3298ca3a-400a-459e-b0a7-426fd6e9b716","actor_username":"buddy_hintz@gmail.com","log_type":"user","traits":{"provider":"email"}} 2023-01-19 14:40:59.889159+00 +00000000-0000-0000-0000-000000000000 946fb286-eebb-4e19-ac7e-dd13f6e04015 {"action":"user_confirmation_requested","actor_id":"a413cb80-834e-4d8a-b29a-a02ecf139f7c","actor_username":"brando_treutel@gmail.com","log_type":"user","traits":{"provider":"email"}} 2023-01-19 14:41:00.296428+00 +00000000-0000-0000-0000-000000000000 e45fe72b-6828-4634-9165-decdd88046b4 {"action":"user_confirmation_requested","actor_id":"ca9f8106-a060-4bd5-be47-d9f384e415ca","actor_username":"gavin33@hotmail.com","log_type":"user","traits":{"provider":"email"}} 2023-01-19 14:41:00.352987+00 +00000000-0000-0000-0000-000000000000 8f7f9be0-e1ee-43c3-a4e0-bc6dcac9727f {"action":"user_confirmation_requested","actor_id":"b096f178-9502-4837-bf3c-37acccf61eb9","actor_username":"ara_volkman7@gmail.com","log_type":"user","traits":{"provider":"email"}} 2023-01-19 14:41:00.381204+00 +00000000-0000-0000-0000-000000000000 ac7061d4-b248-488e-b31d-7c09097a2a63 {"action":"user_confirmation_requested","actor_id":"54b2da7d-4765-44f1-9e95-b6ebd5494530","actor_username":"horace_borer96@yahoo.com","log_type":"user","traits":{"provider":"email"}} 2023-01-19 14:41:00.425255+00 +00000000-0000-0000-0000-000000000000 ca21b645-cf89-44d0-b70e-4af637b45548 {"action":"user_confirmation_requested","actor_id":"88e9b920-6bc9-4242-a245-2c2164c01092","actor_username":"princess24@gmail.com","log_type":"user","traits":{"provider":"email"}} 2023-01-19 14:41:00.728858+00 +00000000-0000-0000-0000-000000000000 f8e8223a-5e49-471a-a916-0863fa8c8f88 {"action":"user_confirmation_requested","actor_id":"7dff3d1f-c92e-45d8-ad4f-47976249728f","actor_username":"heather.corwin32@hotmail.com","log_type":"user","traits":{"provider":"email"}} 2023-01-19 14:41:00.733868+00 +00000000-0000-0000-0000-000000000000 f30a282a-e7b0-419b-ad4c-64c923e03afc {"action":"user_confirmation_requested","actor_id":"ec352166-6475-453f-825b-01bee04214d9","actor_username":"jamarcus94@hotmail.com","log_type":"user","traits":{"provider":"email"}} 2023-01-19 14:41:00.802217+00 +00000000-0000-0000-0000-000000000000 b67c4f21-d71b-4148-852a-6ca6c14031e3 {"action":"user_confirmation_requested","actor_id":"b576f0a4-3369-4e89-8221-72a89807dde9","actor_username":"efrain37@yahoo.com","log_type":"user","traits":{"provider":"email"}} 2023-01-19 14:41:00.825823+00 +\. + + +-- +-- Data for Name: flow_state; Type: TABLE DATA; Schema: auth; Owner: supabase_auth_admin +-- + +COPY auth.flow_state (id, user_id, auth_code, code_challenge_method, code_challenge, provider_type, provider_access_token, provider_refresh_token, created_at, updated_at, authentication_method) FROM stdin; +\. + + +-- +-- Data for Name: identities; Type: TABLE DATA; Schema: auth; Owner: supabase_auth_admin +-- + +COPY auth.identities (id, user_id, identity_data, provider, last_sign_in_at, created_at, updated_at) FROM stdin; +be55c6cf-94d2-44a4-a119-61297d68c0e8 be55c6cf-94d2-44a4-a119-61297d68c0e8 {"sub": "be55c6cf-94d2-44a4-a119-61297d68c0e8", "email": "ionicisere@gmail.com"} email 2022-11-25 00:00:00+00 2022-11-25 00:00:00+00 2022-11-25 00:00:00+00 +1b48e703-cb29-4b76-b804-82a53f074b93 1b48e703-cb29-4b76-b804-82a53f074b93 {"sub": "1b48e703-cb29-4b76-b804-82a53f074b93", "email": "bradley@appwrite.io"} email 2022-11-25 00:00:00+00 2022-11-25 00:00:00+00 2022-11-25 00:00:00+00 +88043554-4aac-46de-8437-c02fdacfdc9c 88043554-4aac-46de-8437-c02fdacfdc9c {"sub": "88043554-4aac-46de-8437-c02fdacfdc9c", "email": "misael.upton98@hotmail.com"} email 2023-01-19 11:08:37.978799+00 2023-01-19 11:08:37.97884+00 2023-01-19 11:08:37.97884+00 +615357b8-c668-45ee-a749-c96db1aabc7a 615357b8-c668-45ee-a749-c96db1aabc7a {"sub": "615357b8-c668-45ee-a749-c96db1aabc7a", "email": "albert.kihn95@yahoo.com"} email 2023-01-19 11:08:37.985094+00 2023-01-19 11:08:37.98513+00 2023-01-19 11:08:37.98513+00 +369664ac-9358-4b51-91b5-79ddca7ef0b2 369664ac-9358-4b51-91b5-79ddca7ef0b2 {"sub": "369664ac-9358-4b51-91b5-79ddca7ef0b2", "email": "maida_walsh61@yahoo.com"} email 2023-01-19 11:08:37.991451+00 2023-01-19 11:08:37.991486+00 2023-01-19 11:08:37.991486+00 +e7faf866-0438-4b7c-8de3-1c134c707806 e7faf866-0438-4b7c-8de3-1c134c707806 {"sub": "e7faf866-0438-4b7c-8de3-1c134c707806", "email": "osvaldo.bogan15@yahoo.com"} email 2023-01-19 11:08:37.996337+00 2023-01-19 11:08:37.996377+00 2023-01-19 11:08:37.996377+00 +af00f659-028a-4268-b6b5-fa36e40e190e af00f659-028a-4268-b6b5-fa36e40e190e {"sub": "af00f659-028a-4268-b6b5-fa36e40e190e", "email": "wyman67@gmail.com"} email 2023-01-19 11:08:38.002463+00 2023-01-19 11:08:38.002505+00 2023-01-19 11:08:38.002505+00 +07616a1c-e8ce-489a-a74b-adab1f4e1b32 07616a1c-e8ce-489a-a74b-adab1f4e1b32 {"sub": "07616a1c-e8ce-489a-a74b-adab1f4e1b32", "email": "rylee68@yahoo.com"} email 2023-01-19 11:08:38.010583+00 2023-01-19 11:08:38.010667+00 2023-01-19 11:08:38.010667+00 +ab38ad48-7b50-4e66-85b1-7d78a61a04f0 ab38ad48-7b50-4e66-85b1-7d78a61a04f0 {"sub": "ab38ad48-7b50-4e66-85b1-7d78a61a04f0", "email": "devin.rath@gmail.com"} email 2023-01-19 11:08:38.018797+00 2023-01-19 11:08:38.018847+00 2023-01-19 11:08:38.018847+00 +932ce7f7-e57d-45c3-bc7d-63362e2d67aa 932ce7f7-e57d-45c3-bc7d-63362e2d67aa {"sub": "932ce7f7-e57d-45c3-bc7d-63362e2d67aa", "email": "eino_considine67@hotmail.com"} email 2023-01-19 11:08:38.010854+00 2023-01-19 11:08:38.01089+00 2023-01-19 11:08:38.01089+00 +23e19499-63e4-4fee-9718-bd24584fdca0 23e19499-63e4-4fee-9718-bd24584fdca0 {"sub": "23e19499-63e4-4fee-9718-bd24584fdca0", "email": "royce_hermiston@gmail.com"} email 2023-01-19 11:08:38.021799+00 2023-01-19 11:08:38.02184+00 2023-01-19 11:08:38.02184+00 +b301a3c1-022f-49fa-a6fb-41c8cdd521a9 b301a3c1-022f-49fa-a6fb-41c8cdd521a9 {"sub": "b301a3c1-022f-49fa-a6fb-41c8cdd521a9", "email": "jerrod70@yahoo.com"} email 2023-01-19 11:08:38.04489+00 2023-01-19 11:08:38.044935+00 2023-01-19 11:08:38.044935+00 +a7ca4ce7-7763-4edc-869a-dc3afb4dc1c2 a7ca4ce7-7763-4edc-869a-dc3afb4dc1c2 {"sub": "a7ca4ce7-7763-4edc-869a-dc3afb4dc1c2", "email": "ada.parker@gmail.com"} email 2023-01-19 11:08:38.626499+00 2023-01-19 11:08:38.626538+00 2023-01-19 11:08:38.626538+00 +d977c288-485b-44f4-aa34-94c232decbed d977c288-485b-44f4-aa34-94c232decbed {"sub": "d977c288-485b-44f4-aa34-94c232decbed", "email": "aryanna_dickens47@gmail.com"} email 2023-01-19 11:08:38.797556+00 2023-01-19 11:08:38.797606+00 2023-01-19 11:08:38.797606+00 +53cd7be4-c374-492c-aa18-06febf196607 53cd7be4-c374-492c-aa18-06febf196607 {"sub": "53cd7be4-c374-492c-aa18-06febf196607", "email": "lonnie_huels69@gmail.com"} email 2023-01-19 11:08:38.816211+00 2023-01-19 11:08:38.816253+00 2023-01-19 11:08:38.816253+00 +2b817ed6-e9b4-4ab6-a97c-8a5c5cca80b5 2b817ed6-e9b4-4ab6-a97c-8a5c5cca80b5 {"sub": "2b817ed6-e9b4-4ab6-a97c-8a5c5cca80b5", "email": "jennings.watsica71@hotmail.com"} email 2023-01-19 11:08:38.844286+00 2023-01-19 11:08:38.844322+00 2023-01-19 11:08:38.844322+00 +ba1d736c-18f9-4eca-8000-05bd223d097f ba1d736c-18f9-4eca-8000-05bd223d097f {"sub": "ba1d736c-18f9-4eca-8000-05bd223d097f", "email": "alia.emmerich42@hotmail.com"} email 2023-01-19 11:08:38.883805+00 2023-01-19 11:08:38.883849+00 2023-01-19 11:08:38.883849+00 +1d1c4838-d7bf-4830-945e-7f1c31934340 1d1c4838-d7bf-4830-945e-7f1c31934340 {"sub": "1d1c4838-d7bf-4830-945e-7f1c31934340", "email": "mallory_kuhn@yahoo.com"} email 2023-01-19 11:08:38.908677+00 2023-01-19 11:08:38.908717+00 2023-01-19 11:08:38.908717+00 +abb439a6-9812-4edb-8f54-8f99830a9d51 abb439a6-9812-4edb-8f54-8f99830a9d51 {"sub": "abb439a6-9812-4edb-8f54-8f99830a9d51", "email": "julianne.schinner98@gmail.com"} email 2023-01-19 11:08:38.943988+00 2023-01-19 11:08:38.944025+00 2023-01-19 11:08:38.944025+00 +acbbb7e0-44ff-42fa-a8aa-69ad98666e31 acbbb7e0-44ff-42fa-a8aa-69ad98666e31 {"sub": "acbbb7e0-44ff-42fa-a8aa-69ad98666e31", "email": "adonis_oconnell@yahoo.com"} email 2023-01-19 11:08:39.009983+00 2023-01-19 11:08:39.010021+00 2023-01-19 11:08:39.010021+00 +67cb7f0e-c295-4d41-83a0-761568b7a13c 67cb7f0e-c295-4d41-83a0-761568b7a13c {"sub": "67cb7f0e-c295-4d41-83a0-761568b7a13c", "email": "craig.dietrich31@hotmail.com"} email 2023-01-19 11:08:39.032835+00 2023-01-19 11:08:39.032875+00 2023-01-19 11:08:39.032875+00 +d1c2cd22-52db-4074-b0ba-e7152df4a27d d1c2cd22-52db-4074-b0ba-e7152df4a27d {"sub": "d1c2cd22-52db-4074-b0ba-e7152df4a27d", "email": "sunny.welch@gmail.com"} email 2023-01-19 11:08:39.03801+00 2023-01-19 11:08:39.038065+00 2023-01-19 11:08:39.038065+00 +e6e80a3d-8435-4d09-bba3-e8f158ff93e4 e6e80a3d-8435-4d09-bba3-e8f158ff93e4 {"sub": "e6e80a3d-8435-4d09-bba3-e8f158ff93e4", "email": "elliott_goldner77@yahoo.com"} email 2023-01-19 11:08:39.268192+00 2023-01-19 11:08:39.268229+00 2023-01-19 11:08:39.268229+00 +8e40363f-20c0-4f58-ac8f-fecaec606923 8e40363f-20c0-4f58-ac8f-fecaec606923 {"sub": "8e40363f-20c0-4f58-ac8f-fecaec606923", "email": "cheyenne_cassin@hotmail.com"} email 2023-01-19 11:08:39.345276+00 2023-01-19 11:08:39.345311+00 2023-01-19 11:08:39.345311+00 +5c6a9645-29e1-478e-9cba-d048ab579bd1 5c6a9645-29e1-478e-9cba-d048ab579bd1 {"sub": "5c6a9645-29e1-478e-9cba-d048ab579bd1", "email": "magdalena_metz@gmail.com"} email 2023-01-19 11:08:39.458571+00 2023-01-19 11:08:39.458609+00 2023-01-19 11:08:39.458609+00 +4535c79c-f973-447b-8692-6f2207f6efcb 4535c79c-f973-447b-8692-6f2207f6efcb {"sub": "4535c79c-f973-447b-8692-6f2207f6efcb", "email": "madisen_harris32@gmail.com"} email 2023-01-19 11:08:39.50468+00 2023-01-19 11:08:39.504717+00 2023-01-19 11:08:39.504717+00 +581483e9-b39a-477c-a712-a590a6bd2e8d 581483e9-b39a-477c-a712-a590a6bd2e8d {"sub": "581483e9-b39a-477c-a712-a590a6bd2e8d", "email": "alexandre_rodriguez@gmail.com"} email 2023-01-19 11:08:39.507102+00 2023-01-19 11:08:39.507136+00 2023-01-19 11:08:39.507136+00 +0d9fd639-9b14-49c8-a755-36565a345da5 0d9fd639-9b14-49c8-a755-36565a345da5 {"sub": "0d9fd639-9b14-49c8-a755-36565a345da5", "email": "pierre33@gmail.com"} email 2023-01-19 11:08:39.511573+00 2023-01-19 11:08:39.51161+00 2023-01-19 11:08:39.51161+00 +af4d7654-d31d-40e9-8e31-8031b29651bf af4d7654-d31d-40e9-8e31-8031b29651bf {"sub": "af4d7654-d31d-40e9-8e31-8031b29651bf", "email": "laney.olson@gmail.com"} email 2023-01-19 11:08:39.619015+00 2023-01-19 11:08:39.619079+00 2023-01-19 11:08:39.619079+00 +c7f6fb1f-a6d9-45b1-a732-4e80877266ea c7f6fb1f-a6d9-45b1-a732-4e80877266ea {"sub": "c7f6fb1f-a6d9-45b1-a732-4e80877266ea", "email": "jodie.wunsch60@gmail.com"} email 2023-01-19 11:08:39.636656+00 2023-01-19 11:08:39.636691+00 2023-01-19 11:08:39.636691+00 +bdc41348-d0ff-4716-b03e-505397255296 bdc41348-d0ff-4716-b03e-505397255296 {"sub": "bdc41348-d0ff-4716-b03e-505397255296", "email": "micheal.homenick@yahoo.com"} email 2023-01-19 11:08:39.669035+00 2023-01-19 11:08:39.669077+00 2023-01-19 11:08:39.669077+00 +b5aca4b6-67f6-47b1-b2f5-794f354e183a b5aca4b6-67f6-47b1-b2f5-794f354e183a {"sub": "b5aca4b6-67f6-47b1-b2f5-794f354e183a", "email": "pauline.moore@hotmail.com"} email 2023-01-19 11:08:39.696557+00 2023-01-19 11:08:39.696592+00 2023-01-19 11:08:39.696592+00 +fef209ad-b677-44a7-82a9-899c33490075 fef209ad-b677-44a7-82a9-899c33490075 {"sub": "fef209ad-b677-44a7-82a9-899c33490075", "email": "ana.nikolaus95@yahoo.com"} email 2023-01-19 14:40:58.677532+00 2023-01-19 14:40:58.67758+00 2023-01-19 14:40:58.67758+00 +5de1bfbc-4a77-4e6b-8adf-e5572a32e12e 5de1bfbc-4a77-4e6b-8adf-e5572a32e12e {"sub": "5de1bfbc-4a77-4e6b-8adf-e5572a32e12e", "email": "coby91@yahoo.com"} email 2023-01-19 14:40:58.672691+00 2023-01-19 14:40:58.67274+00 2023-01-19 14:40:58.67274+00 +68fb3828-45a2-41ee-8c32-fd87525d955e 68fb3828-45a2-41ee-8c32-fd87525d955e {"sub": "68fb3828-45a2-41ee-8c32-fd87525d955e", "email": "vivian_rogahn@gmail.com"} email 2023-01-19 14:40:58.904839+00 2023-01-19 14:40:58.904882+00 2023-01-19 14:40:58.904882+00 +fe8d52b3-9bb2-4063-95da-733f61763be0 fe8d52b3-9bb2-4063-95da-733f61763be0 {"sub": "fe8d52b3-9bb2-4063-95da-733f61763be0", "email": "demetris99@yahoo.com"} email 2023-01-19 14:40:59.04053+00 2023-01-19 14:40:59.040568+00 2023-01-19 14:40:59.040568+00 +2d7cc448-9478-4c4b-b9c1-b6005b3b2517 2d7cc448-9478-4c4b-b9c1-b6005b3b2517 {"sub": "2d7cc448-9478-4c4b-b9c1-b6005b3b2517", "email": "casimir.williamson41@gmail.com"} email 2023-01-19 14:40:59.073058+00 2023-01-19 14:40:59.073104+00 2023-01-19 14:40:59.073104+00 +754e01ee-8bc6-42cf-875a-c596d4b5c108 754e01ee-8bc6-42cf-875a-c596d4b5c108 {"sub": "754e01ee-8bc6-42cf-875a-c596d4b5c108", "email": "rod_hoppe83@hotmail.com"} email 2023-01-19 14:40:59.125133+00 2023-01-19 14:40:59.125176+00 2023-01-19 14:40:59.125176+00 +27b98fb2-2a63-4ea2-92da-845e45c1f530 27b98fb2-2a63-4ea2-92da-845e45c1f530 {"sub": "27b98fb2-2a63-4ea2-92da-845e45c1f530", "email": "marlon.torp45@gmail.com"} email 2023-01-19 14:40:59.164371+00 2023-01-19 14:40:59.164412+00 2023-01-19 14:40:59.164412+00 +a23c4ec2-eb31-420d-bd89-ec49065bed3f a23c4ec2-eb31-420d-bd89-ec49065bed3f {"sub": "a23c4ec2-eb31-420d-bd89-ec49065bed3f", "email": "ansel.kessler89@yahoo.com"} email 2023-01-19 14:40:59.194516+00 2023-01-19 14:40:59.194557+00 2023-01-19 14:40:59.194557+00 +88847f43-2f69-4c26-b00b-6b59a38ef164 88847f43-2f69-4c26-b00b-6b59a38ef164 {"sub": "88847f43-2f69-4c26-b00b-6b59a38ef164", "email": "cali_orn71@yahoo.com"} email 2023-01-19 14:40:59.195309+00 2023-01-19 14:40:59.195346+00 2023-01-19 14:40:59.195346+00 +561e28e6-7ce5-4282-9f51-7f59722170ca 561e28e6-7ce5-4282-9f51-7f59722170ca {"sub": "561e28e6-7ce5-4282-9f51-7f59722170ca", "email": "rey32@gmail.com"} email 2023-01-19 14:40:59.192048+00 2023-01-19 14:40:59.192086+00 2023-01-19 14:40:59.192086+00 +e42df43c-3603-45ea-a281-1c1c1b29965c e42df43c-3603-45ea-a281-1c1c1b29965c {"sub": "e42df43c-3603-45ea-a281-1c1c1b29965c", "email": "thora.renner@yahoo.com"} email 2023-01-19 14:40:59.614062+00 2023-01-19 14:40:59.614098+00 2023-01-19 14:40:59.614098+00 +07ede456-d7f3-4840-a2b2-6394cb6e44c6 07ede456-d7f3-4840-a2b2-6394cb6e44c6 {"sub": "07ede456-d7f3-4840-a2b2-6394cb6e44c6", "email": "estel.kovacek68@gmail.com"} email 2023-01-19 14:40:59.653972+00 2023-01-19 14:40:59.654015+00 2023-01-19 14:40:59.654015+00 +8858520b-4d3e-4804-937c-e1fa8b355e1e 8858520b-4d3e-4804-937c-e1fa8b355e1e {"sub": "8858520b-4d3e-4804-937c-e1fa8b355e1e", "email": "althea_dickens@hotmail.com"} email 2023-01-19 14:40:59.754789+00 2023-01-19 14:40:59.754826+00 2023-01-19 14:40:59.754826+00 +26889fa5-94d3-4150-8b17-d7413e682652 26889fa5-94d3-4150-8b17-d7413e682652 {"sub": "26889fa5-94d3-4150-8b17-d7413e682652", "email": "francis_lockman21@yahoo.com"} email 2023-01-19 14:40:59.808917+00 2023-01-19 14:40:59.808954+00 2023-01-19 14:40:59.808954+00 +9beabeb7-b51b-416c-9d3b-d23badeb7c5c 9beabeb7-b51b-416c-9d3b-d23badeb7c5c {"sub": "9beabeb7-b51b-416c-9d3b-d23badeb7c5c", "email": "adonis_lemke@gmail.com"} email 2023-01-19 14:40:59.815894+00 2023-01-19 14:40:59.815928+00 2023-01-19 14:40:59.815928+00 +b1262b76-e470-4f71-ad33-86919606391d b1262b76-e470-4f71-ad33-86919606391d {"sub": "b1262b76-e470-4f71-ad33-86919606391d", "email": "humberto_wolf@gmail.com"} email 2023-01-19 14:40:59.859656+00 2023-01-19 14:40:59.859692+00 2023-01-19 14:40:59.859692+00 +3298ca3a-400a-459e-b0a7-426fd6e9b716 3298ca3a-400a-459e-b0a7-426fd6e9b716 {"sub": "3298ca3a-400a-459e-b0a7-426fd6e9b716", "email": "buddy_hintz@gmail.com"} email 2023-01-19 14:40:59.888234+00 2023-01-19 14:40:59.888268+00 2023-01-19 14:40:59.888268+00 +a413cb80-834e-4d8a-b29a-a02ecf139f7c a413cb80-834e-4d8a-b29a-a02ecf139f7c {"sub": "a413cb80-834e-4d8a-b29a-a02ecf139f7c", "email": "brando_treutel@gmail.com"} email 2023-01-19 14:41:00.294838+00 2023-01-19 14:41:00.294879+00 2023-01-19 14:41:00.294879+00 +b096f178-9502-4837-bf3c-37acccf61eb9 b096f178-9502-4837-bf3c-37acccf61eb9 {"sub": "b096f178-9502-4837-bf3c-37acccf61eb9", "email": "ara_volkman7@gmail.com"} email 2023-01-19 14:41:00.380085+00 2023-01-19 14:41:00.380125+00 2023-01-19 14:41:00.380125+00 +54b2da7d-4765-44f1-9e95-b6ebd5494530 54b2da7d-4765-44f1-9e95-b6ebd5494530 {"sub": "54b2da7d-4765-44f1-9e95-b6ebd5494530", "email": "horace_borer96@yahoo.com"} email 2023-01-19 14:41:00.424166+00 2023-01-19 14:41:00.424205+00 2023-01-19 14:41:00.424205+00 +88e9b920-6bc9-4242-a245-2c2164c01092 88e9b920-6bc9-4242-a245-2c2164c01092 {"sub": "88e9b920-6bc9-4242-a245-2c2164c01092", "email": "princess24@gmail.com"} email 2023-01-19 14:41:00.72796+00 2023-01-19 14:41:00.727998+00 2023-01-19 14:41:00.727998+00 +7dff3d1f-c92e-45d8-ad4f-47976249728f 7dff3d1f-c92e-45d8-ad4f-47976249728f {"sub": "7dff3d1f-c92e-45d8-ad4f-47976249728f", "email": "heather.corwin32@hotmail.com"} email 2023-01-19 14:41:00.732996+00 2023-01-19 14:41:00.733031+00 2023-01-19 14:41:00.733031+00 +ec352166-6475-453f-825b-01bee04214d9 ec352166-6475-453f-825b-01bee04214d9 {"sub": "ec352166-6475-453f-825b-01bee04214d9", "email": "jamarcus94@hotmail.com"} email 2023-01-19 14:41:00.801386+00 2023-01-19 14:41:00.801419+00 2023-01-19 14:41:00.801419+00 +b576f0a4-3369-4e89-8221-72a89807dde9 b576f0a4-3369-4e89-8221-72a89807dde9 {"sub": "b576f0a4-3369-4e89-8221-72a89807dde9", "email": "efrain37@yahoo.com"} email 2023-01-19 14:41:00.824851+00 2023-01-19 14:41:00.824888+00 2023-01-19 14:41:00.824888+00 +2e320c50-4640-43d4-8e82-5e66c430decd 2e320c50-4640-43d4-8e82-5e66c430decd {"sub": "2e320c50-4640-43d4-8e82-5e66c430decd", "email": "anibal61@yahoo.com"} email 2023-01-19 14:40:59.474355+00 2023-01-19 14:40:59.474391+00 2023-01-19 14:40:59.474391+00 +ca9f8106-a060-4bd5-be47-d9f384e415ca ca9f8106-a060-4bd5-be47-d9f384e415ca {"sub": "ca9f8106-a060-4bd5-be47-d9f384e415ca", "email": "gavin33@hotmail.com"} email 2023-01-19 14:41:00.351925+00 2023-01-19 14:41:00.351966+00 2023-01-19 14:41:00.351966+00 +46867bb3-7c26-4ced-9ec0-268afda9ca10 46867bb3-7c26-4ced-9ec0-268afda9ca10 {"sub": "46867bb3-7c26-4ced-9ec0-268afda9ca10", "email": "lewis62@gmail.com"} email 2023-01-19 14:40:59.489324+00 2023-01-19 14:40:59.489361+00 2023-01-19 14:40:59.489361+00 +366b7862-08df-4b9e-8ba3-8e2d7fbe7a40 366b7862-08df-4b9e-8ba3-8e2d7fbe7a40 {"sub": "366b7862-08df-4b9e-8ba3-8e2d7fbe7a40", "email": "lue.dibbert26@hotmail.com"} email 2023-01-19 14:41:00.341378+00 2023-01-19 14:41:00.341415+00 2023-01-19 14:41:00.341415+00 +7f08b69b-c277-4af8-a51e-9f80bad06430 7f08b69b-c277-4af8-a51e-9f80bad06430 {"sub": "7f08b69b-c277-4af8-a51e-9f80bad06430", "email": "kobe_bergnaum47@yahoo.com"} email 2023-01-19 14:40:59.74144+00 2023-01-19 14:40:59.741477+00 2023-01-19 14:40:59.741477+00 +0b448f5c-7c26-498b-8d13-7049cf82037c 0b448f5c-7c26-498b-8d13-7049cf82037c {"sub": "0b448f5c-7c26-498b-8d13-7049cf82037c", "email": "arne_bayer91@yahoo.com"} email 2023-01-19 14:41:00.797067+00 2023-01-19 14:41:00.79713+00 2023-01-19 14:41:00.79713+00 +\. + + +-- +-- Data for Name: instances; Type: TABLE DATA; Schema: auth; Owner: supabase_auth_admin +-- + +COPY auth.instances (id, uuid, raw_base_config, created_at, updated_at) FROM stdin; +\. + + +-- +-- Data for Name: mfa_amr_claims; Type: TABLE DATA; Schema: auth; Owner: supabase_auth_admin +-- + +COPY auth.mfa_amr_claims (session_id, created_at, updated_at, authentication_method, id) FROM stdin; +\. + + +-- +-- Data for Name: mfa_challenges; Type: TABLE DATA; Schema: auth; Owner: supabase_auth_admin +-- + +COPY auth.mfa_challenges (id, factor_id, created_at, verified_at, ip_address) FROM stdin; +\. + + +-- +-- Data for Name: mfa_factors; Type: TABLE DATA; Schema: auth; Owner: supabase_auth_admin +-- + +COPY auth.mfa_factors (id, user_id, friendly_name, factor_type, status, created_at, updated_at, secret) FROM stdin; +\. + + +-- +-- Data for Name: refresh_tokens; Type: TABLE DATA; Schema: auth; Owner: supabase_auth_admin +-- + +COPY auth.refresh_tokens (instance_id, id, token, user_id, revoked, created_at, updated_at, parent, session_id) FROM stdin; +00000000-0000-0000-0000-000000000000 1 pBxe3O0yvpz4MqgUFS_wnQ be55c6cf-94d2-44a4-a119-61297d68c0e8 f 2022-08-30 12:59:49.906193+00 2022-08-30 12:59:49.906197+00 \N 0511ce36-dba0-4a11-9b4c-0b68534c061f +00000000-0000-0000-0000-000000000000 2 bhzSsrlwilQCq_Wh50aOPw 1b48e703-cb29-4b76-b804-82a53f074b93 f 2022-09-14 10:19:09.444837+00 2022-09-14 10:19:09.444841+00 \N 2cd6bfe6-1132-4ff4-95b4-026db86e8bcf +00000000-0000-0000-0000-000000000000 3 zU3aesC_qCRQ9wchR_CpFQ 1b48e703-cb29-4b76-b804-82a53f074b93 f 2022-09-20 09:06:33.911565+00 2022-09-20 09:06:33.911568+00 \N 5f93a2d7-5712-4216-a6e7-4c7de40bafa4 +00000000-0000-0000-0000-000000000000 4 kW81POOeB4Ts45afIog8WA 1b48e703-cb29-4b76-b804-82a53f074b93 f 2022-09-20 09:23:51.629517+00 2022-09-20 09:23:51.629521+00 \N 379572ef-778e-4c81-8f42-0b2b6961d004 +00000000-0000-0000-0000-000000000000 5 r60ODOU2UxrvencX5E1U-g 1b48e703-cb29-4b76-b804-82a53f074b93 f 2022-09-20 09:32:43.911888+00 2022-09-20 09:32:43.911891+00 \N b1d77e87-9a68-448d-9c6a-7580f1649f08 +\. + + +-- +-- Data for Name: saml_providers; Type: TABLE DATA; Schema: auth; Owner: supabase_auth_admin +-- + +COPY auth.saml_providers (id, sso_provider_id, entity_id, metadata_xml, metadata_url, attribute_mapping, created_at, updated_at) FROM stdin; +\. + + +-- +-- Data for Name: saml_relay_states; Type: TABLE DATA; Schema: auth; Owner: supabase_auth_admin +-- + +COPY auth.saml_relay_states (id, sso_provider_id, request_id, for_email, redirect_to, from_ip_address, created_at, updated_at) FROM stdin; +\. + + +-- +-- Data for Name: schema_migrations; Type: TABLE DATA; Schema: auth; Owner: supabase_auth_admin +-- + +COPY auth.schema_migrations (version) FROM stdin; +20171026211738 +20171026211808 +20171026211834 +20180103212743 +20180108183307 +20180119214651 +20180125194653 +00 +20210710035447 +20210722035447 +20210730183235 +20210909172000 +20210927181326 +20211122151130 +20211124214934 +20211202183645 +20220114185221 +20220114185340 +20220224000811 +20220323170000 +20220429102000 +20220531120530 +20220614074223 +20220811173540 +20221003041349 +20221003041400 +20221011041400 +20221020193600 +20221021073300 +20221021082433 +20221027105023 +20221114143122 +20221114143410 +20221125140132 +20221208132122 +20221215195500 +20221215195800 +20221215195900 +20230116124310 +20230116124412 +20230131181311 +20230322519590 +20230402418590 +20230411005111 +\. + + +-- +-- Data for Name: sessions; Type: TABLE DATA; Schema: auth; Owner: supabase_auth_admin +-- + +COPY auth.sessions (id, user_id, created_at, updated_at, factor_id, aal, not_after) FROM stdin; +0511ce36-dba0-4a11-9b4c-0b68534c061f be55c6cf-94d2-44a4-a119-61297d68c0e8 2022-08-30 12:59:49.904128+00 2022-08-30 12:59:49.904133+00 \N \N \N +2cd6bfe6-1132-4ff4-95b4-026db86e8bcf 1b48e703-cb29-4b76-b804-82a53f074b93 2022-09-14 10:19:09.443186+00 2022-09-14 10:19:09.44319+00 \N \N \N +5f93a2d7-5712-4216-a6e7-4c7de40bafa4 1b48e703-cb29-4b76-b804-82a53f074b93 2022-09-20 09:06:33.910434+00 2022-09-20 09:06:33.910436+00 \N \N \N +379572ef-778e-4c81-8f42-0b2b6961d004 1b48e703-cb29-4b76-b804-82a53f074b93 2022-09-20 09:23:51.627974+00 2022-09-20 09:23:51.627976+00 \N \N \N +b1d77e87-9a68-448d-9c6a-7580f1649f08 1b48e703-cb29-4b76-b804-82a53f074b93 2022-09-20 09:32:43.910773+00 2022-09-20 09:32:43.910776+00 \N \N \N +\. + + +-- +-- Data for Name: sso_domains; Type: TABLE DATA; Schema: auth; Owner: supabase_auth_admin +-- + +COPY auth.sso_domains (id, sso_provider_id, domain, created_at, updated_at) FROM stdin; +\. + + +-- +-- Data for Name: sso_providers; Type: TABLE DATA; Schema: auth; Owner: supabase_auth_admin +-- + +COPY auth.sso_providers (id, resource_id, created_at, updated_at) FROM stdin; +\. + + +-- +-- Data for Name: users; Type: TABLE DATA; Schema: auth; Owner: supabase_auth_admin +-- + +COPY auth.users (instance_id, id, aud, role, email, encrypted_password, email_confirmed_at, invited_at, confirmation_token, confirmation_sent_at, recovery_token, recovery_sent_at, email_change_token_new, email_change, email_change_sent_at, last_sign_in_at, raw_app_meta_data, raw_user_meta_data, is_super_admin, created_at, updated_at, phone, phone_confirmed_at, phone_change, phone_change_token, phone_change_sent_at, email_change_token_current, email_change_confirm_status, banned_until, reauthentication_token, reauthentication_sent_at, is_sso_user, deleted_at) FROM stdin; +00000000-0000-0000-0000-000000000000 be55c6cf-94d2-44a4-a119-61297d68c0e8 authenticated authenticated ionicisere@gmail.com $2a$10$r/w9qJ9Z3yr5hYHubUweZejnj7tqhnTkuiGPo5HJ/JDigTfEWbDxS 2022-08-30 12:59:49.902581+00 2022-08-30 12:59:35.990761+00 2022-08-30 12:59:35.990761+00 \N \N 2022-08-30 12:59:49.904092+00 {"provider": "email", "providers": ["email"]} {} \N 2022-08-30 12:59:35.982779+00 2022-08-30 12:59:49.909025+00 \N \N \N 0 \N \N f \N +00000000-0000-0000-0000-000000000000 1b48e703-cb29-4b76-b804-82a53f074b93 authenticated authenticated bradley@appwrite.io $2a$10$MjBZgSYw.PbNwWogjjA44eFDi1Wxj3qR9xwL57r38iS1W/g7mqD1e 2022-09-14 10:19:09.44204+00 2022-09-14 10:09:18.313665+00 2022-09-14 10:09:18.313665+00 2022-09-20 09:32:37.017851+00 \N 2022-09-20 09:32:43.910738+00 {"provider": "email", "providers": ["email"]} {} \N 2022-09-14 10:09:18.30351+00 2022-09-20 09:33:47.026002+00 \N \N \N 0 \N \N f \N +00000000-0000-0000-0000-000000000000 a7ca4ce7-7763-4edc-869a-dc3afb4dc1c2 authenticated authenticated ada.parker@gmail.com $2a$10$GeeaVuNxnYP2iBpMmLl.oOWHCge9/ICG4OeduhRC9oh4WJ3bQ3E2O \N \N f40c311041b24216ebe846312fe65dd159569a3472fa477e66650d02 2023-01-19 11:08:38.627948+00 \N \N \N {"provider": "email", "providers": ["email"]} {} \N 2023-01-19 11:08:38.621709+00 2023-01-19 11:08:39.171258+00 \N \N \N 0 \N \N f \N +00000000-0000-0000-0000-000000000000 e6e80a3d-8435-4d09-bba3-e8f158ff93e4 authenticated authenticated elliott_goldner77@yahoo.com $2a$10$K5FN9Y17r2M3vRnJjdBxxOc02akQY9JxN38Ps09cUuh/FdNUveoSq \N \N e5e0df9fc30adeb96c73901e04ee086ee1f1045563f16705e6e00252 2023-01-19 11:08:39.294093+00 \N \N \N {"provider": "email", "providers": ["email"]} {} \N 2023-01-19 11:08:39.2656+00 2023-01-19 11:08:39.856707+00 \N \N \N 0 \N \N f \N +00000000-0000-0000-0000-000000000000 615357b8-c668-45ee-a749-c96db1aabc7a authenticated authenticated albert.kihn95@yahoo.com $2a$10$NGZAAOfXeheUoH9V3dnRoeR.r3J5ynnSZ6KjvHxOUlV8XUrulJzQa \N \N 7c6d17d95f79959cb5c2e78bcfce0f3b4d79a64d6e944c8f1c17fd0b 2023-01-19 11:08:37.987925+00 \N \N \N {"provider": "email", "providers": ["email"]} {} \N 2023-01-19 11:08:37.961105+00 2023-01-19 11:08:38.533265+00 \N \N \N 0 \N \N f \N +00000000-0000-0000-0000-000000000000 932ce7f7-e57d-45c3-bc7d-63362e2d67aa authenticated authenticated eino_considine67@hotmail.com $2a$10$UAtycuP68R6/s7BBXckGbeGVg26N/CuIBqvwdTPV.//yoCaaW9sNO \N \N fe79c5e0ddc151b8d0b3d99a86b6f9c02f4fc4e3b5f8faae11ec5d4b 2023-01-19 11:08:38.04048+00 \N \N \N {"provider": "email", "providers": ["email"]} {} \N 2023-01-19 11:08:37.890482+00 2023-01-19 11:08:38.546294+00 \N \N \N 0 \N \N f \N +00000000-0000-0000-0000-000000000000 e7faf866-0438-4b7c-8de3-1c134c707806 authenticated authenticated osvaldo.bogan15@yahoo.com $2a$10$hK1ujHgvMcBgzLJ4FeKH6Omz7tX6voVKzFvIVoGpEvf0R7.0HfqLW \N \N 88fa60647707bca81b0dfdb171317655fe6258bca6967a3d8f5a7147 2023-01-19 11:08:37.99834+00 \N \N \N {"provider": "email", "providers": ["email"]} {} \N 2023-01-19 11:08:37.832183+00 2023-01-19 11:08:38.622417+00 \N \N \N 0 \N \N f \N +00000000-0000-0000-0000-000000000000 b301a3c1-022f-49fa-a6fb-41c8cdd521a9 authenticated authenticated jerrod70@yahoo.com $2a$10$OJSneRhPXRpWXn05ZkH6X..5/7iRVRbnU7sBtvFFhkLfSUTqAtEtu \N \N f547effd9a91b0f30b777e9696f692dfb20d56a4254c8b1ec9fa67f5 2023-01-19 11:08:38.048714+00 \N \N \N {"provider": "email", "providers": ["email"]} {} \N 2023-01-19 11:08:37.976452+00 2023-01-19 11:08:38.622149+00 \N \N \N 0 \N \N f \N +00000000-0000-0000-0000-000000000000 23e19499-63e4-4fee-9718-bd24584fdca0 authenticated authenticated royce_hermiston@gmail.com $2a$10$5M2B42RfDcKhqgr9q5SoDO4OXhxYJ7i/E3XDnX8TMiGLH1U2LpDa2 \N \N 077fa18a56afb21c750fc8398d9814d3bc0b9bf0a9b255c810594607 2023-01-19 11:08:38.040758+00 \N \N \N {"provider": "email", "providers": ["email"]} {} \N 2023-01-19 11:08:37.915266+00 2023-01-19 11:08:38.623762+00 \N \N \N 0 \N \N f \N +00000000-0000-0000-0000-000000000000 07616a1c-e8ce-489a-a74b-adab1f4e1b32 authenticated authenticated rylee68@yahoo.com $2a$10$ZpClmjACR4TcfxpZ1lVoWuisi5K.GCbALgFJwrOJQSJuvLF48um0K \N \N 892319f33d84c697a7f743de2afd667f39cf020eff19b0ac524257c0 2023-01-19 11:08:38.033036+00 \N \N \N {"provider": "email", "providers": ["email"]} {} \N 2023-01-19 11:08:37.998954+00 2023-01-19 11:08:38.641677+00 \N \N \N 0 \N \N f \N +00000000-0000-0000-0000-000000000000 53cd7be4-c374-492c-aa18-06febf196607 authenticated authenticated lonnie_huels69@gmail.com $2a$10$DOxK2zFxiouc5o1cPMnkcuTdM4g4FoJs3tMs49Xr1zuO/HwRTGebe \N \N 9826cf5f7c345b3af8ee746cd644d917a0ea83e235e09cb866429f9c 2023-01-19 11:08:38.818433+00 \N \N \N {"provider": "email", "providers": ["email"]} {} \N 2023-01-19 11:08:38.641261+00 2023-01-19 11:08:39.293169+00 \N \N \N 0 \N \N f \N +00000000-0000-0000-0000-000000000000 4535c79c-f973-447b-8692-6f2207f6efcb authenticated authenticated madisen_harris32@gmail.com $2a$10$iDDzjps6TkD.ld6Ym5L.cuKBCoTTZPGXgTfebkoLNXRF8.svFfz5. \N \N f326f2090e51ceab905155061f777c98c549cc95888cd7c1ba344d22 2023-01-19 11:08:39.506209+00 \N \N \N {"provider": "email", "providers": ["email"]} {} \N 2023-01-19 11:08:39.502072+00 2023-01-19 11:08:39.904436+00 \N \N \N 0 \N \N f \N +00000000-0000-0000-0000-000000000000 ab38ad48-7b50-4e66-85b1-7d78a61a04f0 authenticated authenticated devin.rath@gmail.com $2a$10$5kCdSNjRcHN8npUjzsQ7FeHhl3nlBh79iJvzrVK461yIYbJfaWOgG \N \N 989e63c456f64ef55f43796f35b57c57114ab81ca3d1675b765e98e5 2023-01-19 11:08:38.033607+00 \N \N \N {"provider": "email", "providers": ["email"]} {} \N 2023-01-19 11:08:37.984272+00 2023-01-19 11:08:38.724608+00 \N \N \N 0 \N \N f \N +00000000-0000-0000-0000-000000000000 abb439a6-9812-4edb-8f54-8f99830a9d51 authenticated authenticated julianne.schinner98@gmail.com $2a$10$vb3H97UZyGnxRnH.fFWj2ORQJsWAKAGIIg/VxVoj6H/vPrtMuGfEW \N \N 018c2302f9a0f2d411f075cfefd083d71e0f4a010341f4b68b529558 2023-01-19 11:08:38.945423+00 \N \N \N {"provider": "email", "providers": ["email"]} {} \N 2023-01-19 11:08:38.941342+00 2023-01-19 11:08:39.414274+00 \N \N \N 0 \N \N f \N +00000000-0000-0000-0000-000000000000 bdc41348-d0ff-4716-b03e-505397255296 authenticated authenticated micheal.homenick@yahoo.com $2a$10$3zaSVvuuXN1R8pDFUVcDPu7UdgwJDBMVZ8h7lF2ItZ1fOCfnP1FCa \N \N b0ab2941b518793bc3dd6b78a8f7d22151a6034ea91cdfd350b76702 2023-01-19 11:08:39.670486+00 \N \N \N {"provider": "email", "providers": ["email"]} {} \N 2023-01-19 11:08:39.666419+00 2023-01-19 11:08:39.974713+00 \N \N \N 0 \N \N f \N +00000000-0000-0000-0000-000000000000 369664ac-9358-4b51-91b5-79ddca7ef0b2 authenticated authenticated maida_walsh61@yahoo.com $2a$10$Q7QA.QW6tPPtX86NRzxqcORC9wR58PWaS5ixYB.9tSz/Txm8RJkJ6 \N \N e8954d47bb74535ecdd509216fbf6b7bb2bc0d82f2d167c334d2f66e 2023-01-19 11:08:37.993574+00 \N \N \N {"provider": "email", "providers": ["email"]} {} \N 2023-01-19 11:08:37.92671+00 2023-01-19 11:08:38.726342+00 \N \N \N 0 \N \N f \N +00000000-0000-0000-0000-000000000000 67cb7f0e-c295-4d41-83a0-761568b7a13c authenticated authenticated craig.dietrich31@hotmail.com $2a$10$wg.NDIAgvj0zVc0udeucgO0Boc/3x2UV1pE6tWqDonXNTR1jNBc6K \N \N d477bd867d61c1ba94a4384eb406c9ab28d5cda8742cc6057fc639a1 2023-01-19 11:08:39.035758+00 \N \N \N {"provider": "email", "providers": ["email"]} {} \N 2023-01-19 11:08:39.015133+00 2023-01-19 11:08:39.413947+00 \N \N \N 0 \N \N f \N +00000000-0000-0000-0000-000000000000 c7f6fb1f-a6d9-45b1-a732-4e80877266ea authenticated authenticated jodie.wunsch60@gmail.com $2a$10$Fo0K6NTjFcm5Aoq/xkhYBui0hjBNQIFlKY87aXZGImuTHrCVvn3.y \N \N 4ae77463d8a990faa1989907962d8449bf6866f196ff7ae35a1388a4 2023-01-19 11:08:39.638061+00 \N \N \N {"provider": "email", "providers": ["email"]} {} \N 2023-01-19 11:08:39.630447+00 2023-01-19 11:08:40.031006+00 \N \N \N 0 \N \N f \N +00000000-0000-0000-0000-000000000000 88043554-4aac-46de-8437-c02fdacfdc9c authenticated authenticated misael.upton98@hotmail.com $2a$10$6nnhh/4pvc7ENxqSgxKeFu1k9K3/IWcXMLFngooQopnx7QRhl.xaG \N \N 12340b0d13e0b3b9ca1bcb93c8dea120c52c9d9c61cc04754b5efb73 2023-01-19 11:08:38.006529+00 \N \N \N {"provider": "email", "providers": ["email"]} {} \N 2023-01-19 11:08:37.952335+00 2023-01-19 11:08:38.725996+00 \N \N \N 0 \N \N f \N +00000000-0000-0000-0000-000000000000 d1c2cd22-52db-4074-b0ba-e7152df4a27d authenticated authenticated sunny.welch@gmail.com $2a$10$MxZcZmD0RpqUVE2n.t.Vg.7/TMV2t7qPn8Jwh6Y1sK/1tgSARu3Py \N \N dab91bd4244420789fe50b5cd065930ebcdf4dcf4e3acee439e40ae8 2023-01-19 11:08:39.039895+00 \N \N \N {"provider": "email", "providers": ["email"]} {} \N 2023-01-19 11:08:39.029386+00 2023-01-19 11:08:39.461112+00 \N \N \N 0 \N \N f \N +00000000-0000-0000-0000-000000000000 af4d7654-d31d-40e9-8e31-8031b29651bf authenticated authenticated laney.olson@gmail.com $2a$10$73D85ZzhLD62lQnMog4K0uRZvMlcf0fcvs3fE7NAWNqD5znfoX6aa \N \N bf0f017bc6c07aa07ac4386a62b78e09b9ca0a3060ca5052ee9fdbf9 2023-01-19 11:08:39.620535+00 \N \N \N {"provider": "email", "providers": ["email"]} {} \N 2023-01-19 11:08:39.595651+00 2023-01-19 11:08:39.948053+00 \N \N \N 0 \N \N f \N +00000000-0000-0000-0000-000000000000 af00f659-028a-4268-b6b5-fa36e40e190e authenticated authenticated wyman67@gmail.com $2a$10$CtF/ECNwNRi.on9UvEmutO/5LgqlXlfLDjtI6Qqkd0tgWngfXCRP2 \N \N 05bc7419506b52d336ea2d804f94e56f52660606dd129c4c5a6b378d 2023-01-19 11:08:38.006106+00 \N \N \N {"provider": "email", "providers": ["email"]} {} \N 2023-01-19 11:08:37.88958+00 2023-01-19 11:08:38.778102+00 \N \N \N 0 \N \N f \N +00000000-0000-0000-0000-000000000000 acbbb7e0-44ff-42fa-a8aa-69ad98666e31 authenticated authenticated adonis_oconnell@yahoo.com $2a$10$gYz/6D4ThCKCwIamrR/DQ.uJt8SM6vq5T3Jy2ux9V3t7o/nVkx2cC \N \N 308e4d2501a2cd6b1fd5c441d868580ad34b6637fb5a4d9362155a60 2023-01-19 11:08:39.011778+00 \N \N \N {"provider": "email", "providers": ["email"]} {} \N 2023-01-19 11:08:38.998524+00 2023-01-19 11:08:39.513701+00 \N \N \N 0 \N \N f \N +00000000-0000-0000-0000-000000000000 b5aca4b6-67f6-47b1-b2f5-794f354e183a authenticated authenticated pauline.moore@hotmail.com $2a$10$8mzGxW07TrUW3YKfpjlvReoineG5PWWopHIR3hnER107vi4VjTaCq \N \N 0f8bc3154f92e7cd2d2c2af960662770cbc233a16699b372fe46e24a 2023-01-19 11:08:39.698148+00 \N \N \N {"provider": "email", "providers": ["email"]} {} \N 2023-01-19 11:08:39.693733+00 2023-01-19 11:08:40.046381+00 \N \N \N 0 \N \N f \N +00000000-0000-0000-0000-000000000000 d977c288-485b-44f4-aa34-94c232decbed authenticated authenticated aryanna_dickens47@gmail.com $2a$10$acKiPRIGQc19DLIr2clTzesFMJ86HY0io3JnXIK9hHAna.G2Sh2J6 \N \N acec97cf6dee7adce3fd551a065839a78c2b389f880a285ea859838d 2023-01-19 11:08:38.799099+00 \N \N \N {"provider": "email", "providers": ["email"]} {} \N 2023-01-19 11:08:38.794485+00 2023-01-19 11:08:39.198817+00 \N \N \N 0 \N \N f \N +00000000-0000-0000-0000-000000000000 8e40363f-20c0-4f58-ac8f-fecaec606923 authenticated authenticated cheyenne_cassin@hotmail.com $2a$10$4vED.s6uCIYpS5oHN/Ge3uUVo4EJ7p47Io.E7x0JiU8H3B/DpsYaW \N \N dc2e42e3c4b43bb7716cb2576e04b9b59ca7af22c43e5e27dcc24357 2023-01-19 11:08:39.346821+00 \N \N \N {"provider": "email", "providers": ["email"]} {} \N 2023-01-19 11:08:39.314765+00 2023-01-19 11:08:39.84668+00 \N \N \N 0 \N \N f \N +00000000-0000-0000-0000-000000000000 2b817ed6-e9b4-4ab6-a97c-8a5c5cca80b5 authenticated authenticated jennings.watsica71@hotmail.com $2a$10$agWJM753ThOSQdlZNkEBa.RnNUK4oFiXywyfo.5D8D8Cuohj1ZJRe \N \N 5571f3f9f2f5bcf33c0be925871401d3a83256865b77d350082b590d 2023-01-19 11:08:38.845784+00 \N \N \N {"provider": "email", "providers": ["email"]} {} \N 2023-01-19 11:08:38.825053+00 2023-01-19 11:08:39.239774+00 \N \N \N 0 \N \N f \N +00000000-0000-0000-0000-000000000000 581483e9-b39a-477c-a712-a590a6bd2e8d authenticated authenticated alexandre_rodriguez@gmail.com $2a$10$JVPIgsLvTNqwQX73f724m.k0dwQ8GKvnPdkeech9sUuxb0pdik5Vm \N \N 9f09631bdf96f286f870d52b058d2bb7daa24d97704e971f7f7ba084 2023-01-19 11:08:39.508709+00 \N \N \N {"provider": "email", "providers": ["email"]} {} \N 2023-01-19 11:08:39.411206+00 2023-01-19 11:08:39.892652+00 \N \N \N 0 \N \N f \N +00000000-0000-0000-0000-000000000000 1d1c4838-d7bf-4830-945e-7f1c31934340 authenticated authenticated mallory_kuhn@yahoo.com $2a$10$fmrY.TdL/f.7YHnDyP3ZUODjglDno405Munb8K3yJ3Tw7TUpXMdCq \N \N b6818ad2fa7592e2c6bd39e5cc608aa3b2ac8f920457732a2c07eb6d 2023-01-19 11:08:38.910173+00 \N \N \N {"provider": "email", "providers": ["email"]} {} \N 2023-01-19 11:08:38.840443+00 2023-01-19 11:08:39.280097+00 \N \N \N 0 \N \N f \N +00000000-0000-0000-0000-000000000000 5c6a9645-29e1-478e-9cba-d048ab579bd1 authenticated authenticated magdalena_metz@gmail.com $2a$10$14tT9gkqI2nTMWWQFaXT..qnSzbdTQGr5bnZQGkhcjgQT041sIxty \N \N a7d1a730cf9c469069d36270bf16a8f7b719efbb35f498df854b11f4 2023-01-19 11:08:39.482243+00 \N \N \N {"provider": "email", "providers": ["email"]} {} \N 2023-01-19 11:08:39.455923+00 2023-01-19 11:08:39.925029+00 \N \N \N 0 \N \N f \N +00000000-0000-0000-0000-000000000000 5de1bfbc-4a77-4e6b-8adf-e5572a32e12e authenticated authenticated coby91@yahoo.com $2a$10$ZarjL3uOinIDiJaqXXdQNuCj9Ol6E0pHmTwGYjnDtTV8fjvwLU4p2 \N \N e7cb8b0c7e3f84612f5b127411fc0431e8f2c3f146feb3e3660e8986 2023-01-19 14:40:58.835941+00 \N \N \N {"provider": "email", "providers": ["email"]} {} \N 2023-01-19 14:40:58.617251+00 2023-01-19 14:40:59.393384+00 \N \N \N 0 \N \N f \N +00000000-0000-0000-0000-000000000000 ba1d736c-18f9-4eca-8000-05bd223d097f authenticated authenticated alia.emmerich42@hotmail.com $2a$10$DfXcVwodmMCPyUkkVJhpvOY2qPX1elMpnkmY5RmVfJ6/QYZlluPSG \N \N 49132c9b2f2495e94ffe7e5b51c37f1080ad244941c79ae0cd3ad107 2023-01-19 11:08:38.885235+00 \N \N \N {"provider": "email", "providers": ["email"]} {} \N 2023-01-19 11:08:38.880933+00 2023-01-19 11:08:39.260143+00 \N \N \N 0 \N \N f \N +00000000-0000-0000-0000-000000000000 0d9fd639-9b14-49c8-a755-36565a345da5 authenticated authenticated pierre33@gmail.com $2a$10$VzskcnSwVR3WJflIBh9foeP9zvSIAEs2DUgpl4YnEbZcFMW9O5mmC \N \N 48788ad0d702a5613ba8528faa6d8c5bfe747e1d53981fb37bc02d90 2023-01-19 11:08:39.513064+00 \N \N \N {"provider": "email", "providers": ["email"]} {} \N 2023-01-19 11:08:39.481569+00 2023-01-19 11:08:39.85781+00 \N \N \N 0 \N \N f \N +00000000-0000-0000-0000-000000000000 07ede456-d7f3-4840-a2b2-6394cb6e44c6 authenticated authenticated estel.kovacek68@gmail.com $2a$10$OkPbpEQ7cFTlJBZbAMLNWu0wvSKI3O11P56RqzN7h.8nwy6aSl2J2 \N \N e4eb7055f04178cd8cf9ece4bb45ba9ad57d1d6ff0eb75aefcb26c64 2023-01-19 14:40:59.655644+00 \N \N \N {"provider": "email", "providers": ["email"]} {} \N 2023-01-19 14:40:59.619794+00 2023-01-19 14:41:00.151257+00 \N \N \N 0 \N \N f \N +00000000-0000-0000-0000-000000000000 a23c4ec2-eb31-420d-bd89-ec49065bed3f authenticated authenticated ansel.kessler89@yahoo.com $2a$10$90aSXQyDc.VfwmkQX9tFp.DGOx5lknqC/nA8M8yOqaufGCktFmVHW \N \N 795af0b942dd0fbf8860286b6072f592aa0dd21b64a264500468c540 2023-01-19 14:40:59.201093+00 \N \N \N {"provider": "email", "providers": ["email"]} {} \N 2023-01-19 14:40:59.144565+00 2023-01-19 14:40:59.616499+00 \N \N \N 0 \N \N f \N +00000000-0000-0000-0000-000000000000 27b98fb2-2a63-4ea2-92da-845e45c1f530 authenticated authenticated marlon.torp45@gmail.com $2a$10$ltqikNmlYh/bOgUnnUh.1OiUHTndQUpl9d856BHM7NmlrsJEhqPzy \N \N a912390b874a3a88ac09aad04eb80b509392e427acfbce093e4d58a9 2023-01-19 14:40:59.179137+00 \N \N \N {"provider": "email", "providers": ["email"]} {} \N 2023-01-19 14:40:59.158889+00 2023-01-19 14:40:59.620199+00 \N \N \N 0 \N \N f \N +00000000-0000-0000-0000-000000000000 88847f43-2f69-4c26-b00b-6b59a38ef164 authenticated authenticated cali_orn71@yahoo.com $2a$10$i/VNgA5Q22gGQ0OCyvcUauCpwVbFUmGx5reFaFoX8ac7a34rTZyCS \N \N a89d90f30b2859d523e8ee9702311dfadfadcd55f9c2e78aa3366149 2023-01-19 14:40:59.197369+00 \N \N \N {"provider": "email", "providers": ["email"]} {} \N 2023-01-19 14:40:59.172183+00 2023-01-19 14:40:59.725418+00 \N \N \N 0 \N \N f \N +00000000-0000-0000-0000-000000000000 b1262b76-e470-4f71-ad33-86919606391d authenticated authenticated humberto_wolf@gmail.com $2a$10$UDK5nDmKCwix.PYQdpFiQ.KPRDn7cYaL1xuI7dpYKxS4FZtpnCuRO \N \N 2710337612b98c95e51b8bc7900323999363f5a96f163d6fabf73a97 2023-01-19 14:40:59.861071+00 \N \N \N {"provider": "email", "providers": ["email"]} {} \N 2023-01-19 14:40:59.856837+00 2023-01-19 14:41:00.641247+00 \N \N \N 0 \N \N f \N +00000000-0000-0000-0000-000000000000 366b7862-08df-4b9e-8ba3-8e2d7fbe7a40 authenticated authenticated lue.dibbert26@hotmail.com $2a$10$rH9sNLFro/A32zhE3KJ14eezpNV6axRauEUrvkVVHsiCdiw1QBnti \N \N 10e028c37d3e4ae9eb741c9da1a3615b8b381d81c70f7f71e1e420e4 2023-01-19 14:41:00.342917+00 \N \N \N {"provider": "email", "providers": ["email"]} {} \N 2023-01-19 14:41:00.297873+00 2023-01-19 14:41:00.80361+00 \N \N \N 0 \N \N f \N +00000000-0000-0000-0000-000000000000 68fb3828-45a2-41ee-8c32-fd87525d955e authenticated authenticated vivian_rogahn@gmail.com $2a$10$L2qcapE93Sq35WR.lHeQ3eRN641Axr9F.QNM/5M6OeqnZdxCzaJIi \N \N a9e8d2db7d93206e509b05a1dd7a3cb10e82ab0bc6465cbd171d634e 2023-01-19 14:40:58.906891+00 \N \N \N {"provider": "email", "providers": ["email"]} {} \N 2023-01-19 14:40:58.899728+00 2023-01-19 14:40:59.357833+00 \N \N \N 0 \N \N f \N +00000000-0000-0000-0000-000000000000 2d7cc448-9478-4c4b-b9c1-b6005b3b2517 authenticated authenticated casimir.williamson41@gmail.com $2a$10$ImEdz6ANAscNyInjDuYGHOCySiEWzV031mQElBoEUp9Q6B7V6EVom \N \N 1848d5557a10b20be09448fc4e8cbf35154ec4cd75f6df06616e113a 2023-01-19 14:40:59.076775+00 \N \N \N {"provider": "email", "providers": ["email"]} {} \N 2023-01-19 14:40:59.017546+00 2023-01-19 14:40:59.488707+00 \N \N \N 0 \N \N f \N +00000000-0000-0000-0000-000000000000 754e01ee-8bc6-42cf-875a-c596d4b5c108 authenticated authenticated rod_hoppe83@hotmail.com $2a$10$2Is3g0GMBSrY48CZt8OF6u6/tlhzX6OutrWnzUpHt9hZVpp2IQCt. \N \N 923b1d84244e5473b83d8d904e3ca4db70756a21556e85a324be07f1 2023-01-19 14:40:59.127685+00 \N \N \N {"provider": "email", "providers": ["email"]} {} \N 2023-01-19 14:40:58.989152+00 2023-01-19 14:40:59.50326+00 \N \N \N 0 \N \N f \N +00000000-0000-0000-0000-000000000000 fe8d52b3-9bb2-4063-95da-733f61763be0 authenticated authenticated demetris99@yahoo.com $2a$10$qH3xbDG8SP9m5tGLI.dRh.XW31lamljcDfPG0ygyGYV45FIrsVRyu \N \N e99c1d4bdb036ab3073293832c8a78ca4381e6f75a033fa955cd8e41 2023-01-19 14:40:59.173555+00 \N \N \N {"provider": "email", "providers": ["email"]} {} \N 2023-01-19 14:40:58.917486+00 2023-01-19 14:40:59.621773+00 \N \N \N 0 \N \N f \N +00000000-0000-0000-0000-000000000000 b576f0a4-3369-4e89-8221-72a89807dde9 authenticated authenticated efrain37@yahoo.com $2a$10$86vlB0oTb9cdD96.qJ4IDe6pFw.GcjKdaVwwWBpapfGuTYvbEkT8O \N \N 7209056df97fcceaaae6e0782e945ff1ad4ce93cc308596a7a1d776c 2023-01-19 14:41:00.826379+00 \N \N \N {"provider": "email", "providers": ["email"]} {} \N 2023-01-19 14:41:00.821857+00 2023-01-19 14:41:01.163667+00 \N \N \N 0 \N \N f \N +00000000-0000-0000-0000-000000000000 ca9f8106-a060-4bd5-be47-d9f384e415ca authenticated authenticated gavin33@hotmail.com $2a$10$zvkZ82K9xbd.0NUQpl2.JuAe4mAN9rsd1lMAASR9mRneM.8C530/K \N \N 82eab8491e8bb28564e3ce4e25b7f5e35b280d81a46faa6b14d827fa 2023-01-19 14:41:00.353569+00 \N \N \N {"provider": "email", "providers": ["email"]} {} \N 2023-01-19 14:41:00.338685+00 2023-01-19 14:41:01.164308+00 \N \N \N 0 \N \N f \N +00000000-0000-0000-0000-000000000000 46867bb3-7c26-4ced-9ec0-268afda9ca10 authenticated authenticated lewis62@gmail.com $2a$10$Ao8UXsOm.mYJx5Y5uxMIXeIySE8TxuS99tTLm2B3iOWwmgV/aCbQm \N \N 98912a145b10712750d9cca55eed5429d0a3b9833bdddaf82322e907 2023-01-19 14:40:59.491954+00 \N \N \N {"provider": "email", "providers": ["email"]} {} \N 2023-01-19 14:40:59.485996+00 2023-01-19 14:41:00.022215+00 \N \N \N 0 \N \N f \N +00000000-0000-0000-0000-000000000000 2e320c50-4640-43d4-8e82-5e66c430decd authenticated authenticated anibal61@yahoo.com $2a$10$G8qYQ/rI8yteJwQ0hVuqVeaG63IbA.JpU/y19C6yida7Zsq47CVFG \N \N 2cad37b24957edc589b422386958fcb15d676a4339cce9dfbeacc9ea 2023-01-19 14:40:59.475874+00 \N \N \N {"provider": "email", "providers": ["email"]} {} \N 2023-01-19 14:40:59.471653+00 2023-01-19 14:41:00.039101+00 \N \N \N 0 \N \N f \N +00000000-0000-0000-0000-000000000000 fef209ad-b677-44a7-82a9-899c33490075 authenticated authenticated ana.nikolaus95@yahoo.com $2a$10$xL1HjHXBtGkD05wTk3cx6eefp8GQgb0gngGEhoRu0ipGntps7Kdqm \N \N 20d90286908d350ab9ce64d82bfc26f86abbd0619bebfc25c0c42d21 2023-01-19 14:40:58.725194+00 \N \N \N {"provider": "email", "providers": ["email"]} {} \N 2023-01-19 14:40:58.637887+00 2023-01-19 14:40:59.552296+00 \N \N \N 0 \N \N f \N +00000000-0000-0000-0000-000000000000 7f08b69b-c277-4af8-a51e-9f80bad06430 authenticated authenticated kobe_bergnaum47@yahoo.com $2a$10$FTYKtvyg3bUT6C17QrmGp.FWF91QYzkX5QMMj1YoyqUmtb4VmiWru \N \N 13b88b3e70e3d7f0be7d139a584f87a34673c26613744b532e6d09dc 2023-01-19 14:40:59.833432+00 \N \N \N {"provider": "email", "providers": ["email"]} {} \N 2023-01-19 14:40:59.738616+00 2023-01-19 14:41:00.620983+00 \N \N \N 0 \N \N f \N +00000000-0000-0000-0000-000000000000 0b448f5c-7c26-498b-8d13-7049cf82037c authenticated authenticated arne_bayer91@yahoo.com $2a$10$FAGausswNagoC2.o5zAEaeGkqcyHYgbbYvM/NpSKki7IsD.Pf2d3C \N \N 5d02b72c31d4551b0e8206a8e2f99ac6a03637614ee91e9549df59ed 2023-01-19 14:41:00.798582+00 \N \N \N {"provider": "email", "providers": ["email"]} {} \N 2023-01-19 14:41:00.794406+00 2023-01-19 14:41:01.192666+00 \N \N \N 0 \N \N f \N +00000000-0000-0000-0000-000000000000 561e28e6-7ce5-4282-9f51-7f59722170ca authenticated authenticated rey32@gmail.com $2a$10$n9whZrraPvrDrwDORkWUB.9QIZr/8ej10iQWfB5KS2eN3Vqki6S/W \N \N a409dd4395aac24d5dbeff7bfec69b585815213f8b100f5f5a7d222b 2023-01-19 14:40:59.19846+00 \N \N \N {"provider": "email", "providers": ["email"]} {} \N 2023-01-19 14:40:59.150226+00 2023-01-19 14:40:59.574338+00 \N \N \N 0 \N \N f \N +00000000-0000-0000-0000-000000000000 8858520b-4d3e-4804-937c-e1fa8b355e1e authenticated authenticated althea_dickens@hotmail.com $2a$10$f5rqFcHddSeTVflKLC94/u5x.a9VcISaLnzhussrHbvMlCf/.Gjda \N \N dbc774b5a842c40dc355b06c28af7a610279eb4ee3c0200443a27d13 2023-01-19 14:40:59.756207+00 \N \N \N {"provider": "email", "providers": ["email"]} {} \N 2023-01-19 14:40:59.709773+00 2023-01-19 14:41:00.186285+00 \N \N \N 0 \N \N f \N +00000000-0000-0000-0000-000000000000 54b2da7d-4765-44f1-9e95-b6ebd5494530 authenticated authenticated horace_borer96@yahoo.com $2a$10$uiyeTSCdmPN639oB.jNXueIHvxlY0rPAhMR2vSg9QTLjW.5h2vL.u \N \N e8c0615575cd86a33292106b3379d6a35798d1fb7c1a0288aa7ce15f 2023-01-19 14:41:00.425868+00 \N \N \N {"provider": "email", "providers": ["email"]} {} \N 2023-01-19 14:41:00.420619+00 2023-01-19 14:41:00.968277+00 \N \N \N 0 \N \N f \N +00000000-0000-0000-0000-000000000000 e42df43c-3603-45ea-a281-1c1c1b29965c authenticated authenticated thora.renner@yahoo.com $2a$10$mk8kCvtTJUEvNAmpZiqel.F1CUQEefQTlay/ydx3iUcK4v5jG6TDi \N \N 6b02a01a66b56e26e56e7cdd6516ab062f17e162490873002135236e 2023-01-19 14:40:59.615636+00 \N \N \N {"provider": "email", "providers": ["email"]} {} \N 2023-01-19 14:40:59.597518+00 2023-01-19 14:41:00.036891+00 \N \N \N 0 \N \N f \N +00000000-0000-0000-0000-000000000000 a413cb80-834e-4d8a-b29a-a02ecf139f7c authenticated authenticated brando_treutel@gmail.com $2a$10$EWlXNwEQ4emuyb.BKr2lNuayuBZs/pZ.Ho.KPCNf8wp7h4s/UNDYG \N \N e22020c1d53d0768bbbf2663041053fc141da7a5f48ac3210bec12af 2023-01-19 14:41:00.354617+00 \N \N \N {"provider": "email", "providers": ["email"]} {} \N 2023-01-19 14:41:00.291317+00 2023-01-19 14:41:00.799496+00 \N \N \N 0 \N \N f \N +00000000-0000-0000-0000-000000000000 b096f178-9502-4837-bf3c-37acccf61eb9 authenticated authenticated ara_volkman7@gmail.com $2a$10$A6db028bqFS8JDrqyVuecOrB2Xq4qgGJgKi.wGhRVgLCSYSVa0jeS \N \N b5fe693c8643381f1bf66e71ba5990e21d870001b638f6b46a5b853b 2023-01-19 14:41:00.382393+00 \N \N \N {"provider": "email", "providers": ["email"]} {} \N 2023-01-19 14:41:00.372818+00 2023-01-19 14:41:00.761849+00 \N \N \N 0 \N \N f \N +00000000-0000-0000-0000-000000000000 9beabeb7-b51b-416c-9d3b-d23badeb7c5c authenticated authenticated adonis_lemke@gmail.com $2a$10$R5M4FJIYUkuycpYnHdj.oOKulZ2qx1ckeIwvk/.DQ3sFJjFVr7Ox6 \N \N fbbaf841ffa9496051531ca30065e4ff9b74fb8680fd03dccd07961c 2023-01-19 14:40:59.817341+00 \N \N \N {"provider": "email", "providers": ["email"]} {} \N 2023-01-19 14:40:59.813319+00 2023-01-19 14:41:00.560604+00 \N \N \N 0 \N \N f \N +00000000-0000-0000-0000-000000000000 7dff3d1f-c92e-45d8-ad4f-47976249728f authenticated authenticated heather.corwin32@hotmail.com $2a$10$xwYuDq7WxW6XS3Y6QrRaDu041AzLwqQSc6E/dvlbpIz4U8MvVXsbe \N \N 3d48dacf155e6d9559b3db695c39306b78a39db07fbf6d016a497279 2023-01-19 14:41:00.734371+00 \N \N \N {"provider": "email", "providers": ["email"]} {} \N 2023-01-19 14:41:00.671941+00 2023-01-19 14:41:01.017625+00 \N \N \N 0 \N \N f \N +00000000-0000-0000-0000-000000000000 3298ca3a-400a-459e-b0a7-426fd6e9b716 authenticated authenticated buddy_hintz@gmail.com $2a$10$wfmwKnT/D3XfYNZo0kljv.wf5GkuUoDZuPOnC7gfc1mRlxtAxPW.i \N \N 9778ff31bdb0115f7448a51f8d3465e0d2bc27372c1785f22bd173f5 2023-01-19 14:40:59.889694+00 \N \N \N {"provider": "email", "providers": ["email"]} {} \N 2023-01-19 14:40:59.885268+00 2023-01-19 14:41:00.590039+00 \N \N \N 0 \N \N f \N +00000000-0000-0000-0000-000000000000 88e9b920-6bc9-4242-a245-2c2164c01092 authenticated authenticated princess24@gmail.com $2a$10$MKA4HJHa65fZszstcmJ/aOwrV1E.ZbzX4PmIvCi8LKClVgBvYtE4O \N \N eca97274e1ff99f1c5492710315e4e6e00733bd7a47a37eb46d7e585 2023-01-19 14:41:00.729372+00 \N \N \N {"provider": "email", "providers": ["email"]} {} \N 2023-01-19 14:41:00.725169+00 2023-01-19 14:41:01.098846+00 \N \N \N 0 \N \N f \N +00000000-0000-0000-0000-000000000000 26889fa5-94d3-4150-8b17-d7413e682652 authenticated authenticated francis_lockman21@yahoo.com $2a$10$SzpahifmcMr6M21Hj50C1OQdxNZVDhR2K8uI0uvORHSMkSUQz5dN6 \N \N 1cfa2c89ec0760892cd8b5c8e87bc869c5fc23bd3d85267b7f127d11 2023-01-19 14:40:59.810331+00 \N \N \N {"provider": "email", "providers": ["email"]} {} \N 2023-01-19 14:40:59.804253+00 2023-01-19 14:41:00.622202+00 \N \N \N 0 \N \N f \N +00000000-0000-0000-0000-000000000000 ec352166-6475-453f-825b-01bee04214d9 authenticated authenticated jamarcus94@hotmail.com $2a$10$7s5QCZDmZR2hBQim6SvCcuXfKFgSRczFq0HhDaKAgPd2005YfzGuG \N \N 11b9173d4ef911208f3a14d042c6dcd504d14026ef2654799ad53e5e 2023-01-19 14:41:00.802744+00 \N \N \N {"provider": "email", "providers": ["email"]} {} \N 2023-01-19 14:41:00.798465+00 2023-01-19 14:41:01.173877+00 \N \N \N 0 \N \N f \N +\. + + +-- +-- Data for Name: key; Type: TABLE DATA; Schema: pgsodium; Owner: supabase_admin +-- + +COPY pgsodium.key (id, status, created, expires, key_type, key_id, key_context, name, associated_data, raw_key, raw_key_nonce, parent_key, comment, user_data) FROM stdin; +ed81f770-2c2e-4e89-a64b-a6c7b1dfac1b default 2022-08-22 07:44:16.9012+00 \N \N 1 \\x7067736f6469756d \N associated \N \N \N This is the default key used for vault.secrets \N +\. + + +-- +-- Data for Name: test; Type: TABLE DATA; Schema: public; Owner: postgres +-- + +COPY public.test (id, int2, int4, int8, float4, float8, "numeric", json, jsonb, text, "varchar", uuid, date, timetz, "timestamp", timestamptz, bool, boolarr) FROM stdin; +1 1 2 3 4.14 4.1239 100 {"hello":"world"} {"hello": "world"} {hello,world} {hello,world} 2469ffd8-067a-47e1-b5fc-a9ac41a5fd45 2023-03-28 \N 2023-03-28 07:23:57.205926 2023-03-28 07:23:57.205926+00 t {t,t} +\. + + +-- +-- Data for Name: test2; Type: TABLE DATA; Schema: public; Owner: postgres +-- + +COPY public.test2 (id, created_at, int4, int5, int8, float4, float8, "numeric", json, jsonb, text, "varchar", uuid, date, "time", timetz, "timestamp", timestamptz, bool) FROM stdin; +1 2023-03-28 07:20:26.831742+00 \N \N \N \N \N \N \N \N \N \N \N \N \N \N \N \N \N +\. + + +-- +-- Data for Name: buckets; Type: TABLE DATA; Schema: storage; Owner: supabase_storage_admin +-- + +COPY storage.buckets (id, name, owner, created_at, updated_at, public, avif_autodetection, file_size_limit, allowed_mime_types) FROM stdin; +Test Bucket 1 Test Bucket 1 \N 2023-04-26 03:40:34.087782+00 2023-04-26 03:40:34.087782+00 f f \N \N +\. + + +-- +-- Data for Name: migrations; Type: TABLE DATA; Schema: storage; Owner: supabase_storage_admin +-- + +COPY storage.migrations (id, name, hash, executed_at) FROM stdin; +0 create-migrations-table e18db593bcde2aca2a408c4d1100f6abba2195df 2022-08-30 09:34:04.331172 +1 initialmigration 6ab16121fbaa08bbd11b712d05f358f9b555d777 2022-08-30 09:34:04.339817 +2 pathtoken-column 49756be03be4c17bb85fe70d4a861f27de7e49ad 2022-08-30 09:34:04.345119 +3 add-migrations-rls bb5d124c53d68635a883e399426c6a5a25fc893d 2022-08-30 09:34:04.373099 +4 add-size-functions 6d79007d04f5acd288c9c250c42d2d5fd286c54d 2022-08-30 09:34:04.392693 +5 change-column-name-in-get-size fd65688505d2ffa9fbdc58a944348dd8604d688c 2022-08-30 09:34:04.399423 +6 add-rls-to-buckets 63e2bab75a2040fee8e3fb3f15a0d26f3380e9b6 2022-08-30 09:34:04.408055 +7 add-public-to-buckets 82568934f8a4d9e0a85f126f6fb483ad8214c418 2022-08-30 09:34:04.414918 +8 fix-search-function 1a43a40eddb525f2e2f26efd709e6c06e58e059c 2022-08-30 09:34:04.421464 +9 search-files-search-function 34c096597eb8b9d077fdfdde9878c88501b2fafc 2022-08-30 09:34:04.427454 +10 add-trigger-to-auto-update-updated_at-column 37d6bb964a70a822e6d37f22f457b9bca7885928 2022-08-30 09:34:04.434434 +11 add-automatic-avif-detection-flag bd76c53a9c564c80d98d119c1b3a28e16c8152db 2023-04-04 10:26:55.90276 +12 add-bucket-custom-limits cbe0a4c32a0e891554a21020433b7a4423c07ee7 2023-04-04 10:26:55.941199 +13 use-bytes-for-max-size 7a158ebce8a0c2801c9c65b7e9b2f98f68b3874e 2023-04-04 10:26:55.950159 +14 add-can-insert-object-function 273193826bca7e0990b458d1ba72f8aa27c0d825 2023-04-04 10:26:56.159325 +15 add-version e821a779d26612899b8c2dfe20245f904a327c4f 2023-04-04 10:26:56.173019 +\. + + +-- +-- Data for Name: objects; Type: TABLE DATA; Schema: storage; Owner: supabase_storage_admin +-- + +COPY storage.objects (id, bucket_id, name, owner, created_at, updated_at, last_accessed_at, metadata, version) FROM stdin; +2693082f-39c6-4750-8ed4-47e11269ae25 Test Bucket 1 25MiB.bin \N 2023-04-26 05:36:24.101743+00 2023-04-26 05:36:26.52988+00 2023-04-26 05:36:24.101743+00 {"eTag": "\\"eeb74bf4aa3e578d69f97e8053b34ede-6\\"", "size": 26214400, "mimetype": "application/macbinary", "cacheControl": "max-age=3600", "lastModified": "2023-04-26T05:36:26.000Z", "contentLength": 26214400, "httpStatusCode": 200} \N +808135d7-ee5b-4b7b-a5be-cfd007ae157d Test Bucket 1 tulips.png \N 2023-05-22 05:33:26.676802+00 2023-05-22 05:33:27.307468+00 2023-05-22 05:33:26.676802+00 {"eTag": "\\"2e57bf7a8a9bc49b3eacca90c921a4ae\\"", "size": 679233, "mimetype": "image/png", "cacheControl": "max-age=3600", "lastModified": "2023-05-22T05:33:28.000Z", "contentLength": 679233, "httpStatusCode": 200} \N +6684d39c-723f-446e-b8f2-195defc2b132 Test Bucket 1 pictures/tulips.png \N 2023-05-22 05:33:41.44723+00 2023-05-22 05:33:41.619794+00 2023-05-22 05:33:41.44723+00 {"eTag": "\\"2e57bf7a8a9bc49b3eacca90c921a4ae\\"", "size": 679233, "mimetype": "image/png", "cacheControl": "max-age=3600", "lastModified": "2023-05-22T05:33:42.000Z", "contentLength": 679233, "httpStatusCode": 200} \N +\. + + +-- +-- Data for Name: secrets; Type: TABLE DATA; Schema: vault; Owner: supabase_admin +-- + +COPY vault.secrets (id, name, description, secret, key_id, nonce, created_at, updated_at) FROM stdin; +\. + + +-- +-- Name: refresh_tokens_id_seq; Type: SEQUENCE SET; Schema: auth; Owner: supabase_auth_admin +-- + +SELECT pg_catalog.setval('auth.refresh_tokens_id_seq', 5, true); + + +-- +-- Name: key_key_id_seq; Type: SEQUENCE SET; Schema: pgsodium; Owner: supabase_admin +-- + +SELECT pg_catalog.setval('pgsodium.key_key_id_seq', 1, false); + + +-- +-- Name: test2_id_seq; Type: SEQUENCE SET; Schema: public; Owner: postgres +-- + +SELECT pg_catalog.setval('public.test2_id_seq', 1, false); + + +-- +-- Name: test_id_seq; Type: SEQUENCE SET; Schema: public; Owner: postgres +-- + +SELECT pg_catalog.setval('public.test_id_seq', 1, true); + + +-- +-- Name: mfa_amr_claims amr_id_pk; Type: CONSTRAINT; Schema: auth; Owner: supabase_auth_admin +-- + +ALTER TABLE ONLY auth.mfa_amr_claims + ADD CONSTRAINT amr_id_pk PRIMARY KEY (id); + + +-- +-- Name: audit_log_entries audit_log_entries_pkey; Type: CONSTRAINT; Schema: auth; Owner: supabase_auth_admin +-- + +ALTER TABLE ONLY auth.audit_log_entries + ADD CONSTRAINT audit_log_entries_pkey PRIMARY KEY (id); + + +-- +-- Name: flow_state flow_state_pkey; Type: CONSTRAINT; Schema: auth; Owner: supabase_auth_admin +-- + +ALTER TABLE ONLY auth.flow_state + ADD CONSTRAINT flow_state_pkey PRIMARY KEY (id); + + +-- +-- Name: identities identities_pkey; Type: CONSTRAINT; Schema: auth; Owner: supabase_auth_admin +-- + +ALTER TABLE ONLY auth.identities + ADD CONSTRAINT identities_pkey PRIMARY KEY (provider, id); + + +-- +-- Name: instances instances_pkey; Type: CONSTRAINT; Schema: auth; Owner: supabase_auth_admin +-- + +ALTER TABLE ONLY auth.instances + ADD CONSTRAINT instances_pkey PRIMARY KEY (id); + + +-- +-- Name: mfa_amr_claims mfa_amr_claims_session_id_authentication_method_pkey; Type: CONSTRAINT; Schema: auth; Owner: supabase_auth_admin +-- + +ALTER TABLE ONLY auth.mfa_amr_claims + ADD CONSTRAINT mfa_amr_claims_session_id_authentication_method_pkey UNIQUE (session_id, authentication_method); + + +-- +-- Name: mfa_challenges mfa_challenges_pkey; Type: CONSTRAINT; Schema: auth; Owner: supabase_auth_admin +-- + +ALTER TABLE ONLY auth.mfa_challenges + ADD CONSTRAINT mfa_challenges_pkey PRIMARY KEY (id); + + +-- +-- Name: mfa_factors mfa_factors_pkey; Type: CONSTRAINT; Schema: auth; Owner: supabase_auth_admin +-- + +ALTER TABLE ONLY auth.mfa_factors + ADD CONSTRAINT mfa_factors_pkey PRIMARY KEY (id); + + +-- +-- Name: refresh_tokens refresh_tokens_pkey; Type: CONSTRAINT; Schema: auth; Owner: supabase_auth_admin +-- + +ALTER TABLE ONLY auth.refresh_tokens + ADD CONSTRAINT refresh_tokens_pkey PRIMARY KEY (id); + + +-- +-- Name: refresh_tokens refresh_tokens_token_unique; Type: CONSTRAINT; Schema: auth; Owner: supabase_auth_admin +-- + +ALTER TABLE ONLY auth.refresh_tokens + ADD CONSTRAINT refresh_tokens_token_unique UNIQUE (token); + + +-- +-- Name: saml_providers saml_providers_entity_id_key; Type: CONSTRAINT; Schema: auth; Owner: supabase_auth_admin +-- + +ALTER TABLE ONLY auth.saml_providers + ADD CONSTRAINT saml_providers_entity_id_key UNIQUE (entity_id); + + +-- +-- Name: saml_providers saml_providers_pkey; Type: CONSTRAINT; Schema: auth; Owner: supabase_auth_admin +-- + +ALTER TABLE ONLY auth.saml_providers + ADD CONSTRAINT saml_providers_pkey PRIMARY KEY (id); + + +-- +-- Name: saml_relay_states saml_relay_states_pkey; Type: CONSTRAINT; Schema: auth; Owner: supabase_auth_admin +-- + +ALTER TABLE ONLY auth.saml_relay_states + ADD CONSTRAINT saml_relay_states_pkey PRIMARY KEY (id); + + +-- +-- Name: schema_migrations schema_migrations_pkey; Type: CONSTRAINT; Schema: auth; Owner: supabase_auth_admin +-- + +ALTER TABLE ONLY auth.schema_migrations + ADD CONSTRAINT schema_migrations_pkey PRIMARY KEY (version); + + +-- +-- Name: sessions sessions_pkey; Type: CONSTRAINT; Schema: auth; Owner: supabase_auth_admin +-- + +ALTER TABLE ONLY auth.sessions + ADD CONSTRAINT sessions_pkey PRIMARY KEY (id); + + +-- +-- Name: sso_domains sso_domains_pkey; Type: CONSTRAINT; Schema: auth; Owner: supabase_auth_admin +-- + +ALTER TABLE ONLY auth.sso_domains + ADD CONSTRAINT sso_domains_pkey PRIMARY KEY (id); + + +-- +-- Name: sso_providers sso_providers_pkey; Type: CONSTRAINT; Schema: auth; Owner: supabase_auth_admin +-- + +ALTER TABLE ONLY auth.sso_providers + ADD CONSTRAINT sso_providers_pkey PRIMARY KEY (id); + + +-- +-- Name: users users_phone_key; Type: CONSTRAINT; Schema: auth; Owner: supabase_auth_admin +-- + +ALTER TABLE ONLY auth.users + ADD CONSTRAINT users_phone_key UNIQUE (phone); + + +-- +-- Name: users users_pkey; Type: CONSTRAINT; Schema: auth; Owner: supabase_auth_admin +-- + +ALTER TABLE ONLY auth.users + ADD CONSTRAINT users_pkey PRIMARY KEY (id); + + +-- +-- Name: test2 test2_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.test2 + ADD CONSTRAINT test2_pkey PRIMARY KEY (id); + + +-- +-- Name: test test_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.test + ADD CONSTRAINT test_pkey PRIMARY KEY (id); + + +-- +-- Name: buckets buckets_pkey; Type: CONSTRAINT; Schema: storage; Owner: supabase_storage_admin +-- + +ALTER TABLE ONLY storage.buckets + ADD CONSTRAINT buckets_pkey PRIMARY KEY (id); + + +-- +-- Name: migrations migrations_name_key; Type: CONSTRAINT; Schema: storage; Owner: supabase_storage_admin +-- + +ALTER TABLE ONLY storage.migrations + ADD CONSTRAINT migrations_name_key UNIQUE (name); + + +-- +-- Name: migrations migrations_pkey; Type: CONSTRAINT; Schema: storage; Owner: supabase_storage_admin +-- + +ALTER TABLE ONLY storage.migrations + ADD CONSTRAINT migrations_pkey PRIMARY KEY (id); + + +-- +-- Name: objects objects_pkey; Type: CONSTRAINT; Schema: storage; Owner: supabase_storage_admin +-- + +ALTER TABLE ONLY storage.objects + ADD CONSTRAINT objects_pkey PRIMARY KEY (id); + + +-- +-- Name: audit_logs_instance_id_idx; Type: INDEX; Schema: auth; Owner: supabase_auth_admin +-- + +CREATE INDEX audit_logs_instance_id_idx ON auth.audit_log_entries USING btree (instance_id); + + +-- +-- Name: confirmation_token_idx; Type: INDEX; Schema: auth; Owner: supabase_auth_admin +-- + +CREATE UNIQUE INDEX confirmation_token_idx ON auth.users USING btree (confirmation_token) WHERE ((confirmation_token)::text !~ '^[0-9 ]*$'::text); + + +-- +-- Name: email_change_token_current_idx; Type: INDEX; Schema: auth; Owner: supabase_auth_admin +-- + +CREATE UNIQUE INDEX email_change_token_current_idx ON auth.users USING btree (email_change_token_current) WHERE ((email_change_token_current)::text !~ '^[0-9 ]*$'::text); + + +-- +-- Name: email_change_token_new_idx; Type: INDEX; Schema: auth; Owner: supabase_auth_admin +-- + +CREATE UNIQUE INDEX email_change_token_new_idx ON auth.users USING btree (email_change_token_new) WHERE ((email_change_token_new)::text !~ '^[0-9 ]*$'::text); + + +-- +-- Name: factor_id_created_at_idx; Type: INDEX; Schema: auth; Owner: supabase_auth_admin +-- + +CREATE INDEX factor_id_created_at_idx ON auth.mfa_factors USING btree (user_id, created_at); + + +-- +-- Name: identities_email_idx; Type: INDEX; Schema: auth; Owner: supabase_auth_admin +-- + +CREATE INDEX identities_email_idx ON auth.identities USING btree (email text_pattern_ops); + + +-- +-- Name: INDEX identities_email_idx; Type: COMMENT; Schema: auth; Owner: supabase_auth_admin +-- + +COMMENT ON INDEX auth.identities_email_idx IS 'Auth: Ensures indexed queries on the email column'; + + +-- +-- Name: identities_user_id_idx; Type: INDEX; Schema: auth; Owner: supabase_auth_admin +-- + +CREATE INDEX identities_user_id_idx ON auth.identities USING btree (user_id); + + +-- +-- Name: idx_auth_code; Type: INDEX; Schema: auth; Owner: supabase_auth_admin +-- + +CREATE INDEX idx_auth_code ON auth.flow_state USING btree (auth_code); + + +-- +-- Name: idx_user_id_auth_method; Type: INDEX; Schema: auth; Owner: supabase_auth_admin +-- + +CREATE INDEX idx_user_id_auth_method ON auth.flow_state USING btree (user_id, authentication_method); + + +-- +-- Name: mfa_factors_user_friendly_name_unique; Type: INDEX; Schema: auth; Owner: supabase_auth_admin +-- + +CREATE UNIQUE INDEX mfa_factors_user_friendly_name_unique ON auth.mfa_factors USING btree (friendly_name, user_id) WHERE (TRIM(BOTH FROM friendly_name) <> ''::text); + + +-- +-- Name: reauthentication_token_idx; Type: INDEX; Schema: auth; Owner: supabase_auth_admin +-- + +CREATE UNIQUE INDEX reauthentication_token_idx ON auth.users USING btree (reauthentication_token) WHERE ((reauthentication_token)::text !~ '^[0-9 ]*$'::text); + + +-- +-- Name: recovery_token_idx; Type: INDEX; Schema: auth; Owner: supabase_auth_admin +-- + +CREATE UNIQUE INDEX recovery_token_idx ON auth.users USING btree (recovery_token) WHERE ((recovery_token)::text !~ '^[0-9 ]*$'::text); + + +-- +-- Name: refresh_tokens_instance_id_idx; Type: INDEX; Schema: auth; Owner: supabase_auth_admin +-- + +CREATE INDEX refresh_tokens_instance_id_idx ON auth.refresh_tokens USING btree (instance_id); + + +-- +-- Name: refresh_tokens_instance_id_user_id_idx; Type: INDEX; Schema: auth; Owner: supabase_auth_admin +-- + +CREATE INDEX refresh_tokens_instance_id_user_id_idx ON auth.refresh_tokens USING btree (instance_id, user_id); + + +-- +-- Name: refresh_tokens_parent_idx; Type: INDEX; Schema: auth; Owner: supabase_auth_admin +-- + +CREATE INDEX refresh_tokens_parent_idx ON auth.refresh_tokens USING btree (parent); + + +-- +-- Name: refresh_tokens_session_id_revoked_idx; Type: INDEX; Schema: auth; Owner: supabase_auth_admin +-- + +CREATE INDEX refresh_tokens_session_id_revoked_idx ON auth.refresh_tokens USING btree (session_id, revoked); + + +-- +-- Name: saml_providers_sso_provider_id_idx; Type: INDEX; Schema: auth; Owner: supabase_auth_admin +-- + +CREATE INDEX saml_providers_sso_provider_id_idx ON auth.saml_providers USING btree (sso_provider_id); + + +-- +-- Name: saml_relay_states_for_email_idx; Type: INDEX; Schema: auth; Owner: supabase_auth_admin +-- + +CREATE INDEX saml_relay_states_for_email_idx ON auth.saml_relay_states USING btree (for_email); + + +-- +-- Name: saml_relay_states_sso_provider_id_idx; Type: INDEX; Schema: auth; Owner: supabase_auth_admin +-- + +CREATE INDEX saml_relay_states_sso_provider_id_idx ON auth.saml_relay_states USING btree (sso_provider_id); + + +-- +-- Name: sessions_user_id_idx; Type: INDEX; Schema: auth; Owner: supabase_auth_admin +-- + +CREATE INDEX sessions_user_id_idx ON auth.sessions USING btree (user_id); + + +-- +-- Name: sso_domains_domain_idx; Type: INDEX; Schema: auth; Owner: supabase_auth_admin +-- + +CREATE UNIQUE INDEX sso_domains_domain_idx ON auth.sso_domains USING btree (lower(domain)); + + +-- +-- Name: sso_domains_sso_provider_id_idx; Type: INDEX; Schema: auth; Owner: supabase_auth_admin +-- + +CREATE INDEX sso_domains_sso_provider_id_idx ON auth.sso_domains USING btree (sso_provider_id); + + +-- +-- Name: sso_providers_resource_id_idx; Type: INDEX; Schema: auth; Owner: supabase_auth_admin +-- + +CREATE UNIQUE INDEX sso_providers_resource_id_idx ON auth.sso_providers USING btree (lower(resource_id)); + + +-- +-- Name: user_id_created_at_idx; Type: INDEX; Schema: auth; Owner: supabase_auth_admin +-- + +CREATE INDEX user_id_created_at_idx ON auth.sessions USING btree (user_id, created_at); + + +-- +-- Name: users_email_partial_key; Type: INDEX; Schema: auth; Owner: supabase_auth_admin +-- + +CREATE UNIQUE INDEX users_email_partial_key ON auth.users USING btree (email) WHERE (is_sso_user = false); + + +-- +-- Name: INDEX users_email_partial_key; Type: COMMENT; Schema: auth; Owner: supabase_auth_admin +-- + +COMMENT ON INDEX auth.users_email_partial_key IS 'Auth: A partial unique index that applies only when is_sso_user is false'; + + +-- +-- Name: users_instance_id_email_idx; Type: INDEX; Schema: auth; Owner: supabase_auth_admin +-- + +CREATE INDEX users_instance_id_email_idx ON auth.users USING btree (instance_id, lower((email)::text)); + + +-- +-- Name: users_instance_id_idx; Type: INDEX; Schema: auth; Owner: supabase_auth_admin +-- + +CREATE INDEX users_instance_id_idx ON auth.users USING btree (instance_id); + + +-- +-- Name: bname; Type: INDEX; Schema: storage; Owner: supabase_storage_admin +-- + +CREATE UNIQUE INDEX bname ON storage.buckets USING btree (name); + + +-- +-- Name: bucketid_objname; Type: INDEX; Schema: storage; Owner: supabase_storage_admin +-- + +CREATE UNIQUE INDEX bucketid_objname ON storage.objects USING btree (bucket_id, name); + + +-- +-- Name: name_prefix_search; Type: INDEX; Schema: storage; Owner: supabase_storage_admin +-- + +CREATE INDEX name_prefix_search ON storage.objects USING btree (name text_pattern_ops); + + +-- +-- Name: objects update_objects_updated_at; Type: TRIGGER; Schema: storage; Owner: supabase_storage_admin +-- + +CREATE TRIGGER update_objects_updated_at BEFORE UPDATE ON storage.objects FOR EACH ROW EXECUTE FUNCTION storage.update_updated_at_column(); + + +-- +-- Name: identities identities_user_id_fkey; Type: FK CONSTRAINT; Schema: auth; Owner: supabase_auth_admin +-- + +ALTER TABLE ONLY auth.identities + ADD CONSTRAINT identities_user_id_fkey FOREIGN KEY (user_id) REFERENCES auth.users(id) ON DELETE CASCADE; + + +-- +-- Name: mfa_amr_claims mfa_amr_claims_session_id_fkey; Type: FK CONSTRAINT; Schema: auth; Owner: supabase_auth_admin +-- + +ALTER TABLE ONLY auth.mfa_amr_claims + ADD CONSTRAINT mfa_amr_claims_session_id_fkey FOREIGN KEY (session_id) REFERENCES auth.sessions(id) ON DELETE CASCADE; + + +-- +-- Name: mfa_challenges mfa_challenges_auth_factor_id_fkey; Type: FK CONSTRAINT; Schema: auth; Owner: supabase_auth_admin +-- + +ALTER TABLE ONLY auth.mfa_challenges + ADD CONSTRAINT mfa_challenges_auth_factor_id_fkey FOREIGN KEY (factor_id) REFERENCES auth.mfa_factors(id) ON DELETE CASCADE; + + +-- +-- Name: mfa_factors mfa_factors_user_id_fkey; Type: FK CONSTRAINT; Schema: auth; Owner: supabase_auth_admin +-- + +ALTER TABLE ONLY auth.mfa_factors + ADD CONSTRAINT mfa_factors_user_id_fkey FOREIGN KEY (user_id) REFERENCES auth.users(id) ON DELETE CASCADE; + + +-- +-- Name: refresh_tokens refresh_tokens_session_id_fkey; Type: FK CONSTRAINT; Schema: auth; Owner: supabase_auth_admin +-- + +ALTER TABLE ONLY auth.refresh_tokens + ADD CONSTRAINT refresh_tokens_session_id_fkey FOREIGN KEY (session_id) REFERENCES auth.sessions(id) ON DELETE CASCADE; + + +-- +-- Name: saml_providers saml_providers_sso_provider_id_fkey; Type: FK CONSTRAINT; Schema: auth; Owner: supabase_auth_admin +-- + +ALTER TABLE ONLY auth.saml_providers + ADD CONSTRAINT saml_providers_sso_provider_id_fkey FOREIGN KEY (sso_provider_id) REFERENCES auth.sso_providers(id) ON DELETE CASCADE; + + +-- +-- Name: saml_relay_states saml_relay_states_sso_provider_id_fkey; Type: FK CONSTRAINT; Schema: auth; Owner: supabase_auth_admin +-- + +ALTER TABLE ONLY auth.saml_relay_states + ADD CONSTRAINT saml_relay_states_sso_provider_id_fkey FOREIGN KEY (sso_provider_id) REFERENCES auth.sso_providers(id) ON DELETE CASCADE; + + +-- +-- Name: sessions sessions_user_id_fkey; Type: FK CONSTRAINT; Schema: auth; Owner: supabase_auth_admin +-- + +ALTER TABLE ONLY auth.sessions + ADD CONSTRAINT sessions_user_id_fkey FOREIGN KEY (user_id) REFERENCES auth.users(id) ON DELETE CASCADE; + + +-- +-- Name: sso_domains sso_domains_sso_provider_id_fkey; Type: FK CONSTRAINT; Schema: auth; Owner: supabase_auth_admin +-- + +ALTER TABLE ONLY auth.sso_domains + ADD CONSTRAINT sso_domains_sso_provider_id_fkey FOREIGN KEY (sso_provider_id) REFERENCES auth.sso_providers(id) ON DELETE CASCADE; + + +-- +-- Name: buckets buckets_owner_fkey; Type: FK CONSTRAINT; Schema: storage; Owner: supabase_storage_admin +-- + +ALTER TABLE ONLY storage.buckets + ADD CONSTRAINT buckets_owner_fkey FOREIGN KEY (owner) REFERENCES auth.users(id); + + +-- +-- Name: objects objects_bucketId_fkey; Type: FK CONSTRAINT; Schema: storage; Owner: supabase_storage_admin +-- + +ALTER TABLE ONLY storage.objects + ADD CONSTRAINT "objects_bucketId_fkey" FOREIGN KEY (bucket_id) REFERENCES storage.buckets(id); + + +-- +-- Name: objects objects_owner_fkey; Type: FK CONSTRAINT; Schema: storage; Owner: supabase_storage_admin +-- + +ALTER TABLE ONLY storage.objects + ADD CONSTRAINT objects_owner_fkey FOREIGN KEY (owner) REFERENCES auth.users(id); + + +-- +-- Name: test; Type: ROW SECURITY; Schema: public; Owner: postgres +-- + +ALTER TABLE public.test ENABLE ROW LEVEL SECURITY; + +-- +-- Name: test2; Type: ROW SECURITY; Schema: public; Owner: postgres +-- + +ALTER TABLE public.test2 ENABLE ROW LEVEL SECURITY; + +-- +-- Name: buckets; Type: ROW SECURITY; Schema: storage; Owner: supabase_storage_admin +-- + +ALTER TABLE storage.buckets ENABLE ROW LEVEL SECURITY; + +-- +-- Name: migrations; Type: ROW SECURITY; Schema: storage; Owner: supabase_storage_admin +-- + +ALTER TABLE storage.migrations ENABLE ROW LEVEL SECURITY; + +-- +-- Name: objects; Type: ROW SECURITY; Schema: storage; Owner: supabase_storage_admin +-- + +ALTER TABLE storage.objects ENABLE ROW LEVEL SECURITY; + +-- +-- Name: supabase_realtime; Type: PUBLICATION; Schema: -; Owner: postgres +-- + +CREATE PUBLICATION supabase_realtime WITH (publish = 'insert, update, delete, truncate'); + + +ALTER PUBLICATION supabase_realtime OWNER TO postgres; + +-- +-- Name: SCHEMA auth; Type: ACL; Schema: -; Owner: supabase_admin +-- + +GRANT USAGE ON SCHEMA auth TO anon; +GRANT USAGE ON SCHEMA auth TO authenticated; +GRANT USAGE ON SCHEMA auth TO service_role; +GRANT ALL ON SCHEMA auth TO supabase_auth_admin; +GRANT ALL ON SCHEMA auth TO dashboard_user; +GRANT ALL ON SCHEMA auth TO postgres; + + +-- +-- Name: SCHEMA extensions; Type: ACL; Schema: -; Owner: postgres +-- + +GRANT USAGE ON SCHEMA extensions TO anon; +GRANT USAGE ON SCHEMA extensions TO authenticated; +GRANT USAGE ON SCHEMA extensions TO service_role; +GRANT ALL ON SCHEMA extensions TO dashboard_user; + + +-- +-- Name: SCHEMA graphql_public; Type: ACL; Schema: -; Owner: supabase_admin +-- + +GRANT USAGE ON SCHEMA graphql_public TO postgres; +GRANT USAGE ON SCHEMA graphql_public TO anon; +GRANT USAGE ON SCHEMA graphql_public TO authenticated; +GRANT USAGE ON SCHEMA graphql_public TO service_role; + + +-- +-- Name: SCHEMA pgsodium_masks; Type: ACL; Schema: -; Owner: supabase_admin +-- + +REVOKE ALL ON SCHEMA pgsodium_masks FROM supabase_admin; +GRANT ALL ON SCHEMA pgsodium_masks TO postgres; + + +-- +-- Name: SCHEMA public; Type: ACL; Schema: -; Owner: postgres +-- + +REVOKE USAGE ON SCHEMA public FROM PUBLIC; +GRANT USAGE ON SCHEMA public TO anon; +GRANT USAGE ON SCHEMA public TO authenticated; +GRANT USAGE ON SCHEMA public TO service_role; + + +-- +-- Name: SCHEMA realtime; Type: ACL; Schema: -; Owner: supabase_admin +-- + +GRANT USAGE ON SCHEMA realtime TO postgres; + + +-- +-- Name: SCHEMA storage; Type: ACL; Schema: -; Owner: supabase_admin +-- + +GRANT ALL ON SCHEMA storage TO postgres; +GRANT USAGE ON SCHEMA storage TO anon; +GRANT USAGE ON SCHEMA storage TO authenticated; +GRANT USAGE ON SCHEMA storage TO service_role; +GRANT ALL ON SCHEMA storage TO supabase_storage_admin; +GRANT ALL ON SCHEMA storage TO dashboard_user; + + +-- +-- Name: FUNCTION email(); Type: ACL; Schema: auth; Owner: supabase_auth_admin +-- + +GRANT ALL ON FUNCTION auth.email() TO dashboard_user; +GRANT ALL ON FUNCTION auth.email() TO postgres; + + +-- +-- Name: FUNCTION jwt(); Type: ACL; Schema: auth; Owner: supabase_auth_admin +-- + +GRANT ALL ON FUNCTION auth.jwt() TO postgres; +GRANT ALL ON FUNCTION auth.jwt() TO dashboard_user; + + +-- +-- Name: FUNCTION role(); Type: ACL; Schema: auth; Owner: supabase_auth_admin +-- + +GRANT ALL ON FUNCTION auth.role() TO dashboard_user; +GRANT ALL ON FUNCTION auth.role() TO postgres; + + +-- +-- Name: FUNCTION uid(); Type: ACL; Schema: auth; Owner: supabase_auth_admin +-- + +GRANT ALL ON FUNCTION auth.uid() TO dashboard_user; +GRANT ALL ON FUNCTION auth.uid() TO postgres; + + +-- +-- Name: FUNCTION algorithm_sign(signables text, secret text, algorithm text); Type: ACL; Schema: extensions; Owner: supabase_admin +-- + +GRANT ALL ON FUNCTION extensions.algorithm_sign(signables text, secret text, algorithm text) TO dashboard_user; +GRANT ALL ON FUNCTION extensions.algorithm_sign(signables text, secret text, algorithm text) TO postgres WITH GRANT OPTION; + + +-- +-- Name: FUNCTION armor(bytea); Type: ACL; Schema: extensions; Owner: supabase_admin +-- + +GRANT ALL ON FUNCTION extensions.armor(bytea) TO dashboard_user; +GRANT ALL ON FUNCTION extensions.armor(bytea) TO postgres WITH GRANT OPTION; + + +-- +-- Name: FUNCTION armor(bytea, text[], text[]); Type: ACL; Schema: extensions; Owner: supabase_admin +-- + +GRANT ALL ON FUNCTION extensions.armor(bytea, text[], text[]) TO dashboard_user; +GRANT ALL ON FUNCTION extensions.armor(bytea, text[], text[]) TO postgres WITH GRANT OPTION; + + +-- +-- Name: FUNCTION crypt(text, text); Type: ACL; Schema: extensions; Owner: supabase_admin +-- + +GRANT ALL ON FUNCTION extensions.crypt(text, text) TO dashboard_user; +GRANT ALL ON FUNCTION extensions.crypt(text, text) TO postgres WITH GRANT OPTION; + + +-- +-- Name: FUNCTION dearmor(text); Type: ACL; Schema: extensions; Owner: supabase_admin +-- + +GRANT ALL ON FUNCTION extensions.dearmor(text) TO dashboard_user; +GRANT ALL ON FUNCTION extensions.dearmor(text) TO postgres WITH GRANT OPTION; + + +-- +-- Name: FUNCTION decrypt(bytea, bytea, text); Type: ACL; Schema: extensions; Owner: supabase_admin +-- + +GRANT ALL ON FUNCTION extensions.decrypt(bytea, bytea, text) TO dashboard_user; +GRANT ALL ON FUNCTION extensions.decrypt(bytea, bytea, text) TO postgres WITH GRANT OPTION; + + +-- +-- Name: FUNCTION decrypt_iv(bytea, bytea, bytea, text); Type: ACL; Schema: extensions; Owner: supabase_admin +-- + +GRANT ALL ON FUNCTION extensions.decrypt_iv(bytea, bytea, bytea, text) TO dashboard_user; +GRANT ALL ON FUNCTION extensions.decrypt_iv(bytea, bytea, bytea, text) TO postgres WITH GRANT OPTION; + + +-- +-- Name: FUNCTION digest(bytea, text); Type: ACL; Schema: extensions; Owner: supabase_admin +-- + +GRANT ALL ON FUNCTION extensions.digest(bytea, text) TO dashboard_user; +GRANT ALL ON FUNCTION extensions.digest(bytea, text) TO postgres WITH GRANT OPTION; + + +-- +-- Name: FUNCTION digest(text, text); Type: ACL; Schema: extensions; Owner: supabase_admin +-- + +GRANT ALL ON FUNCTION extensions.digest(text, text) TO dashboard_user; +GRANT ALL ON FUNCTION extensions.digest(text, text) TO postgres WITH GRANT OPTION; + + +-- +-- Name: FUNCTION encrypt(bytea, bytea, text); Type: ACL; Schema: extensions; Owner: supabase_admin +-- + +GRANT ALL ON FUNCTION extensions.encrypt(bytea, bytea, text) TO dashboard_user; +GRANT ALL ON FUNCTION extensions.encrypt(bytea, bytea, text) TO postgres WITH GRANT OPTION; + + +-- +-- Name: FUNCTION encrypt_iv(bytea, bytea, bytea, text); Type: ACL; Schema: extensions; Owner: supabase_admin +-- + +GRANT ALL ON FUNCTION extensions.encrypt_iv(bytea, bytea, bytea, text) TO dashboard_user; +GRANT ALL ON FUNCTION extensions.encrypt_iv(bytea, bytea, bytea, text) TO postgres WITH GRANT OPTION; + + +-- +-- Name: FUNCTION gen_random_bytes(integer); Type: ACL; Schema: extensions; Owner: supabase_admin +-- + +GRANT ALL ON FUNCTION extensions.gen_random_bytes(integer) TO dashboard_user; +GRANT ALL ON FUNCTION extensions.gen_random_bytes(integer) TO postgres WITH GRANT OPTION; + + +-- +-- Name: FUNCTION gen_random_uuid(); Type: ACL; Schema: extensions; Owner: supabase_admin +-- + +GRANT ALL ON FUNCTION extensions.gen_random_uuid() TO dashboard_user; +GRANT ALL ON FUNCTION extensions.gen_random_uuid() TO postgres WITH GRANT OPTION; + + +-- +-- Name: FUNCTION gen_salt(text); Type: ACL; Schema: extensions; Owner: supabase_admin +-- + +GRANT ALL ON FUNCTION extensions.gen_salt(text) TO dashboard_user; +GRANT ALL ON FUNCTION extensions.gen_salt(text) TO postgres WITH GRANT OPTION; + + +-- +-- Name: FUNCTION gen_salt(text, integer); Type: ACL; Schema: extensions; Owner: supabase_admin +-- + +GRANT ALL ON FUNCTION extensions.gen_salt(text, integer) TO dashboard_user; +GRANT ALL ON FUNCTION extensions.gen_salt(text, integer) TO postgres WITH GRANT OPTION; + + +-- +-- Name: FUNCTION grant_pg_cron_access(); Type: ACL; Schema: extensions; Owner: postgres +-- + +REVOKE ALL ON FUNCTION extensions.grant_pg_cron_access() FROM postgres; +GRANT ALL ON FUNCTION extensions.grant_pg_cron_access() TO postgres WITH GRANT OPTION; +GRANT ALL ON FUNCTION extensions.grant_pg_cron_access() TO dashboard_user; + + +-- +-- Name: FUNCTION grant_pg_graphql_access(); Type: ACL; Schema: extensions; Owner: supabase_admin +-- + +GRANT ALL ON FUNCTION extensions.grant_pg_graphql_access() TO postgres WITH GRANT OPTION; + + +-- +-- Name: FUNCTION grant_pg_net_access(); Type: ACL; Schema: extensions; Owner: postgres +-- + +REVOKE ALL ON FUNCTION extensions.grant_pg_net_access() FROM postgres; +GRANT ALL ON FUNCTION extensions.grant_pg_net_access() TO postgres WITH GRANT OPTION; +GRANT ALL ON FUNCTION extensions.grant_pg_net_access() TO dashboard_user; + + +-- +-- Name: FUNCTION hmac(bytea, bytea, text); Type: ACL; Schema: extensions; Owner: supabase_admin +-- + +GRANT ALL ON FUNCTION extensions.hmac(bytea, bytea, text) TO dashboard_user; +GRANT ALL ON FUNCTION extensions.hmac(bytea, bytea, text) TO postgres WITH GRANT OPTION; + + +-- +-- Name: FUNCTION hmac(text, text, text); Type: ACL; Schema: extensions; Owner: supabase_admin +-- + +GRANT ALL ON FUNCTION extensions.hmac(text, text, text) TO dashboard_user; +GRANT ALL ON FUNCTION extensions.hmac(text, text, text) TO postgres WITH GRANT OPTION; + + +-- +-- Name: FUNCTION pg_stat_statements(showtext boolean, OUT userid oid, OUT dbid oid, OUT toplevel boolean, OUT queryid bigint, OUT query text, OUT plans bigint, OUT total_plan_time double precision, OUT min_plan_time double precision, OUT max_plan_time double precision, OUT mean_plan_time double precision, OUT stddev_plan_time double precision, OUT calls bigint, OUT total_exec_time double precision, OUT min_exec_time double precision, OUT max_exec_time double precision, OUT mean_exec_time double precision, OUT stddev_exec_time double precision, OUT rows bigint, OUT shared_blks_hit bigint, OUT shared_blks_read bigint, OUT shared_blks_dirtied bigint, OUT shared_blks_written bigint, OUT local_blks_hit bigint, OUT local_blks_read bigint, OUT local_blks_dirtied bigint, OUT local_blks_written bigint, OUT temp_blks_read bigint, OUT temp_blks_written bigint, OUT blk_read_time double precision, OUT blk_write_time double precision, OUT temp_blk_read_time double precision, OUT temp_blk_write_time double precision, OUT wal_records bigint, OUT wal_fpi bigint, OUT wal_bytes numeric, OUT jit_functions bigint, OUT jit_generation_time double precision, OUT jit_inlining_count bigint, OUT jit_inlining_time double precision, OUT jit_optimization_count bigint, OUT jit_optimization_time double precision, OUT jit_emission_count bigint, OUT jit_emission_time double precision); Type: ACL; Schema: extensions; Owner: supabase_admin +-- + +GRANT ALL ON FUNCTION extensions.pg_stat_statements(showtext boolean, OUT userid oid, OUT dbid oid, OUT toplevel boolean, OUT queryid bigint, OUT query text, OUT plans bigint, OUT total_plan_time double precision, OUT min_plan_time double precision, OUT max_plan_time double precision, OUT mean_plan_time double precision, OUT stddev_plan_time double precision, OUT calls bigint, OUT total_exec_time double precision, OUT min_exec_time double precision, OUT max_exec_time double precision, OUT mean_exec_time double precision, OUT stddev_exec_time double precision, OUT rows bigint, OUT shared_blks_hit bigint, OUT shared_blks_read bigint, OUT shared_blks_dirtied bigint, OUT shared_blks_written bigint, OUT local_blks_hit bigint, OUT local_blks_read bigint, OUT local_blks_dirtied bigint, OUT local_blks_written bigint, OUT temp_blks_read bigint, OUT temp_blks_written bigint, OUT blk_read_time double precision, OUT blk_write_time double precision, OUT temp_blk_read_time double precision, OUT temp_blk_write_time double precision, OUT wal_records bigint, OUT wal_fpi bigint, OUT wal_bytes numeric, OUT jit_functions bigint, OUT jit_generation_time double precision, OUT jit_inlining_count bigint, OUT jit_inlining_time double precision, OUT jit_optimization_count bigint, OUT jit_optimization_time double precision, OUT jit_emission_count bigint, OUT jit_emission_time double precision) TO dashboard_user; +GRANT ALL ON FUNCTION extensions.pg_stat_statements(showtext boolean, OUT userid oid, OUT dbid oid, OUT toplevel boolean, OUT queryid bigint, OUT query text, OUT plans bigint, OUT total_plan_time double precision, OUT min_plan_time double precision, OUT max_plan_time double precision, OUT mean_plan_time double precision, OUT stddev_plan_time double precision, OUT calls bigint, OUT total_exec_time double precision, OUT min_exec_time double precision, OUT max_exec_time double precision, OUT mean_exec_time double precision, OUT stddev_exec_time double precision, OUT rows bigint, OUT shared_blks_hit bigint, OUT shared_blks_read bigint, OUT shared_blks_dirtied bigint, OUT shared_blks_written bigint, OUT local_blks_hit bigint, OUT local_blks_read bigint, OUT local_blks_dirtied bigint, OUT local_blks_written bigint, OUT temp_blks_read bigint, OUT temp_blks_written bigint, OUT blk_read_time double precision, OUT blk_write_time double precision, OUT temp_blk_read_time double precision, OUT temp_blk_write_time double precision, OUT wal_records bigint, OUT wal_fpi bigint, OUT wal_bytes numeric, OUT jit_functions bigint, OUT jit_generation_time double precision, OUT jit_inlining_count bigint, OUT jit_inlining_time double precision, OUT jit_optimization_count bigint, OUT jit_optimization_time double precision, OUT jit_emission_count bigint, OUT jit_emission_time double precision) TO postgres WITH GRANT OPTION; + + +-- +-- Name: FUNCTION pg_stat_statements_info(OUT dealloc bigint, OUT stats_reset timestamp with time zone); Type: ACL; Schema: extensions; Owner: supabase_admin +-- + +GRANT ALL ON FUNCTION extensions.pg_stat_statements_info(OUT dealloc bigint, OUT stats_reset timestamp with time zone) TO dashboard_user; +GRANT ALL ON FUNCTION extensions.pg_stat_statements_info(OUT dealloc bigint, OUT stats_reset timestamp with time zone) TO postgres WITH GRANT OPTION; + + +-- +-- Name: FUNCTION pg_stat_statements_reset(userid oid, dbid oid, queryid bigint); Type: ACL; Schema: extensions; Owner: supabase_admin +-- + +GRANT ALL ON FUNCTION extensions.pg_stat_statements_reset(userid oid, dbid oid, queryid bigint) TO dashboard_user; +GRANT ALL ON FUNCTION extensions.pg_stat_statements_reset(userid oid, dbid oid, queryid bigint) TO postgres WITH GRANT OPTION; + + +-- +-- Name: FUNCTION pgp_armor_headers(text, OUT key text, OUT value text); Type: ACL; Schema: extensions; Owner: supabase_admin +-- + +GRANT ALL ON FUNCTION extensions.pgp_armor_headers(text, OUT key text, OUT value text) TO dashboard_user; +GRANT ALL ON FUNCTION extensions.pgp_armor_headers(text, OUT key text, OUT value text) TO postgres WITH GRANT OPTION; + + +-- +-- Name: FUNCTION pgp_key_id(bytea); Type: ACL; Schema: extensions; Owner: supabase_admin +-- + +GRANT ALL ON FUNCTION extensions.pgp_key_id(bytea) TO dashboard_user; +GRANT ALL ON FUNCTION extensions.pgp_key_id(bytea) TO postgres WITH GRANT OPTION; + + +-- +-- Name: FUNCTION pgp_pub_decrypt(bytea, bytea); Type: ACL; Schema: extensions; Owner: supabase_admin +-- + +GRANT ALL ON FUNCTION extensions.pgp_pub_decrypt(bytea, bytea) TO dashboard_user; +GRANT ALL ON FUNCTION extensions.pgp_pub_decrypt(bytea, bytea) TO postgres WITH GRANT OPTION; + + +-- +-- Name: FUNCTION pgp_pub_decrypt(bytea, bytea, text); Type: ACL; Schema: extensions; Owner: supabase_admin +-- + +GRANT ALL ON FUNCTION extensions.pgp_pub_decrypt(bytea, bytea, text) TO dashboard_user; +GRANT ALL ON FUNCTION extensions.pgp_pub_decrypt(bytea, bytea, text) TO postgres WITH GRANT OPTION; + + +-- +-- Name: FUNCTION pgp_pub_decrypt(bytea, bytea, text, text); Type: ACL; Schema: extensions; Owner: supabase_admin +-- + +GRANT ALL ON FUNCTION extensions.pgp_pub_decrypt(bytea, bytea, text, text) TO dashboard_user; +GRANT ALL ON FUNCTION extensions.pgp_pub_decrypt(bytea, bytea, text, text) TO postgres WITH GRANT OPTION; + + +-- +-- Name: FUNCTION pgp_pub_decrypt_bytea(bytea, bytea); Type: ACL; Schema: extensions; Owner: supabase_admin +-- + +GRANT ALL ON FUNCTION extensions.pgp_pub_decrypt_bytea(bytea, bytea) TO dashboard_user; +GRANT ALL ON FUNCTION extensions.pgp_pub_decrypt_bytea(bytea, bytea) TO postgres WITH GRANT OPTION; + + +-- +-- Name: FUNCTION pgp_pub_decrypt_bytea(bytea, bytea, text); Type: ACL; Schema: extensions; Owner: supabase_admin +-- + +GRANT ALL ON FUNCTION extensions.pgp_pub_decrypt_bytea(bytea, bytea, text) TO dashboard_user; +GRANT ALL ON FUNCTION extensions.pgp_pub_decrypt_bytea(bytea, bytea, text) TO postgres WITH GRANT OPTION; + + +-- +-- Name: FUNCTION pgp_pub_decrypt_bytea(bytea, bytea, text, text); Type: ACL; Schema: extensions; Owner: supabase_admin +-- + +GRANT ALL ON FUNCTION extensions.pgp_pub_decrypt_bytea(bytea, bytea, text, text) TO dashboard_user; +GRANT ALL ON FUNCTION extensions.pgp_pub_decrypt_bytea(bytea, bytea, text, text) TO postgres WITH GRANT OPTION; + + +-- +-- Name: FUNCTION pgp_pub_encrypt(text, bytea); Type: ACL; Schema: extensions; Owner: supabase_admin +-- + +GRANT ALL ON FUNCTION extensions.pgp_pub_encrypt(text, bytea) TO dashboard_user; +GRANT ALL ON FUNCTION extensions.pgp_pub_encrypt(text, bytea) TO postgres WITH GRANT OPTION; + + +-- +-- Name: FUNCTION pgp_pub_encrypt(text, bytea, text); Type: ACL; Schema: extensions; Owner: supabase_admin +-- + +GRANT ALL ON FUNCTION extensions.pgp_pub_encrypt(text, bytea, text) TO dashboard_user; +GRANT ALL ON FUNCTION extensions.pgp_pub_encrypt(text, bytea, text) TO postgres WITH GRANT OPTION; + + +-- +-- Name: FUNCTION pgp_pub_encrypt_bytea(bytea, bytea); Type: ACL; Schema: extensions; Owner: supabase_admin +-- + +GRANT ALL ON FUNCTION extensions.pgp_pub_encrypt_bytea(bytea, bytea) TO dashboard_user; +GRANT ALL ON FUNCTION extensions.pgp_pub_encrypt_bytea(bytea, bytea) TO postgres WITH GRANT OPTION; + + +-- +-- Name: FUNCTION pgp_pub_encrypt_bytea(bytea, bytea, text); Type: ACL; Schema: extensions; Owner: supabase_admin +-- + +GRANT ALL ON FUNCTION extensions.pgp_pub_encrypt_bytea(bytea, bytea, text) TO dashboard_user; +GRANT ALL ON FUNCTION extensions.pgp_pub_encrypt_bytea(bytea, bytea, text) TO postgres WITH GRANT OPTION; + + +-- +-- Name: FUNCTION pgp_sym_decrypt(bytea, text); Type: ACL; Schema: extensions; Owner: supabase_admin +-- + +GRANT ALL ON FUNCTION extensions.pgp_sym_decrypt(bytea, text) TO dashboard_user; +GRANT ALL ON FUNCTION extensions.pgp_sym_decrypt(bytea, text) TO postgres WITH GRANT OPTION; + + +-- +-- Name: FUNCTION pgp_sym_decrypt(bytea, text, text); Type: ACL; Schema: extensions; Owner: supabase_admin +-- + +GRANT ALL ON FUNCTION extensions.pgp_sym_decrypt(bytea, text, text) TO dashboard_user; +GRANT ALL ON FUNCTION extensions.pgp_sym_decrypt(bytea, text, text) TO postgres WITH GRANT OPTION; + + +-- +-- Name: FUNCTION pgp_sym_decrypt_bytea(bytea, text); Type: ACL; Schema: extensions; Owner: supabase_admin +-- + +GRANT ALL ON FUNCTION extensions.pgp_sym_decrypt_bytea(bytea, text) TO dashboard_user; +GRANT ALL ON FUNCTION extensions.pgp_sym_decrypt_bytea(bytea, text) TO postgres WITH GRANT OPTION; + + +-- +-- Name: FUNCTION pgp_sym_decrypt_bytea(bytea, text, text); Type: ACL; Schema: extensions; Owner: supabase_admin +-- + +GRANT ALL ON FUNCTION extensions.pgp_sym_decrypt_bytea(bytea, text, text) TO dashboard_user; +GRANT ALL ON FUNCTION extensions.pgp_sym_decrypt_bytea(bytea, text, text) TO postgres WITH GRANT OPTION; + + +-- +-- Name: FUNCTION pgp_sym_encrypt(text, text); Type: ACL; Schema: extensions; Owner: supabase_admin +-- + +GRANT ALL ON FUNCTION extensions.pgp_sym_encrypt(text, text) TO dashboard_user; +GRANT ALL ON FUNCTION extensions.pgp_sym_encrypt(text, text) TO postgres WITH GRANT OPTION; + + +-- +-- Name: FUNCTION pgp_sym_encrypt(text, text, text); Type: ACL; Schema: extensions; Owner: supabase_admin +-- + +GRANT ALL ON FUNCTION extensions.pgp_sym_encrypt(text, text, text) TO dashboard_user; +GRANT ALL ON FUNCTION extensions.pgp_sym_encrypt(text, text, text) TO postgres WITH GRANT OPTION; + + +-- +-- Name: FUNCTION pgp_sym_encrypt_bytea(bytea, text); Type: ACL; Schema: extensions; Owner: supabase_admin +-- + +GRANT ALL ON FUNCTION extensions.pgp_sym_encrypt_bytea(bytea, text) TO dashboard_user; +GRANT ALL ON FUNCTION extensions.pgp_sym_encrypt_bytea(bytea, text) TO postgres WITH GRANT OPTION; + + +-- +-- Name: FUNCTION pgp_sym_encrypt_bytea(bytea, text, text); Type: ACL; Schema: extensions; Owner: supabase_admin +-- + +GRANT ALL ON FUNCTION extensions.pgp_sym_encrypt_bytea(bytea, text, text) TO dashboard_user; +GRANT ALL ON FUNCTION extensions.pgp_sym_encrypt_bytea(bytea, text, text) TO postgres WITH GRANT OPTION; + + +-- +-- Name: FUNCTION pgrst_ddl_watch(); Type: ACL; Schema: extensions; Owner: supabase_admin +-- + +GRANT ALL ON FUNCTION extensions.pgrst_ddl_watch() TO postgres WITH GRANT OPTION; + + +-- +-- Name: FUNCTION pgrst_drop_watch(); Type: ACL; Schema: extensions; Owner: supabase_admin +-- + +GRANT ALL ON FUNCTION extensions.pgrst_drop_watch() TO postgres WITH GRANT OPTION; + + +-- +-- Name: FUNCTION set_graphql_placeholder(); Type: ACL; Schema: extensions; Owner: supabase_admin +-- + +GRANT ALL ON FUNCTION extensions.set_graphql_placeholder() TO postgres WITH GRANT OPTION; + + +-- +-- Name: FUNCTION sign(payload json, secret text, algorithm text); Type: ACL; Schema: extensions; Owner: supabase_admin +-- + +GRANT ALL ON FUNCTION extensions.sign(payload json, secret text, algorithm text) TO dashboard_user; +GRANT ALL ON FUNCTION extensions.sign(payload json, secret text, algorithm text) TO postgres WITH GRANT OPTION; + + +-- +-- Name: FUNCTION try_cast_double(inp text); Type: ACL; Schema: extensions; Owner: supabase_admin +-- + +GRANT ALL ON FUNCTION extensions.try_cast_double(inp text) TO dashboard_user; +GRANT ALL ON FUNCTION extensions.try_cast_double(inp text) TO postgres WITH GRANT OPTION; + + +-- +-- Name: FUNCTION url_decode(data text); Type: ACL; Schema: extensions; Owner: supabase_admin +-- + +GRANT ALL ON FUNCTION extensions.url_decode(data text) TO dashboard_user; +GRANT ALL ON FUNCTION extensions.url_decode(data text) TO postgres WITH GRANT OPTION; + + +-- +-- Name: FUNCTION url_encode(data bytea); Type: ACL; Schema: extensions; Owner: supabase_admin +-- + +GRANT ALL ON FUNCTION extensions.url_encode(data bytea) TO dashboard_user; +GRANT ALL ON FUNCTION extensions.url_encode(data bytea) TO postgres WITH GRANT OPTION; + + +-- +-- Name: FUNCTION uuid_generate_v1(); Type: ACL; Schema: extensions; Owner: supabase_admin +-- + +GRANT ALL ON FUNCTION extensions.uuid_generate_v1() TO dashboard_user; +GRANT ALL ON FUNCTION extensions.uuid_generate_v1() TO postgres WITH GRANT OPTION; + + +-- +-- Name: FUNCTION uuid_generate_v1mc(); Type: ACL; Schema: extensions; Owner: supabase_admin +-- + +GRANT ALL ON FUNCTION extensions.uuid_generate_v1mc() TO dashboard_user; +GRANT ALL ON FUNCTION extensions.uuid_generate_v1mc() TO postgres WITH GRANT OPTION; + + +-- +-- Name: FUNCTION uuid_generate_v3(namespace uuid, name text); Type: ACL; Schema: extensions; Owner: supabase_admin +-- + +GRANT ALL ON FUNCTION extensions.uuid_generate_v3(namespace uuid, name text) TO dashboard_user; +GRANT ALL ON FUNCTION extensions.uuid_generate_v3(namespace uuid, name text) TO postgres WITH GRANT OPTION; + + +-- +-- Name: FUNCTION uuid_generate_v4(); Type: ACL; Schema: extensions; Owner: supabase_admin +-- + +GRANT ALL ON FUNCTION extensions.uuid_generate_v4() TO dashboard_user; +GRANT ALL ON FUNCTION extensions.uuid_generate_v4() TO postgres WITH GRANT OPTION; + + +-- +-- Name: FUNCTION uuid_generate_v5(namespace uuid, name text); Type: ACL; Schema: extensions; Owner: supabase_admin +-- + +GRANT ALL ON FUNCTION extensions.uuid_generate_v5(namespace uuid, name text) TO dashboard_user; +GRANT ALL ON FUNCTION extensions.uuid_generate_v5(namespace uuid, name text) TO postgres WITH GRANT OPTION; + + +-- +-- Name: FUNCTION uuid_nil(); Type: ACL; Schema: extensions; Owner: supabase_admin +-- + +GRANT ALL ON FUNCTION extensions.uuid_nil() TO dashboard_user; +GRANT ALL ON FUNCTION extensions.uuid_nil() TO postgres WITH GRANT OPTION; + + +-- +-- Name: FUNCTION uuid_ns_dns(); Type: ACL; Schema: extensions; Owner: supabase_admin +-- + +GRANT ALL ON FUNCTION extensions.uuid_ns_dns() TO dashboard_user; +GRANT ALL ON FUNCTION extensions.uuid_ns_dns() TO postgres WITH GRANT OPTION; + + +-- +-- Name: FUNCTION uuid_ns_oid(); Type: ACL; Schema: extensions; Owner: supabase_admin +-- + +GRANT ALL ON FUNCTION extensions.uuid_ns_oid() TO dashboard_user; +GRANT ALL ON FUNCTION extensions.uuid_ns_oid() TO postgres WITH GRANT OPTION; + + +-- +-- Name: FUNCTION uuid_ns_url(); Type: ACL; Schema: extensions; Owner: supabase_admin +-- + +GRANT ALL ON FUNCTION extensions.uuid_ns_url() TO dashboard_user; +GRANT ALL ON FUNCTION extensions.uuid_ns_url() TO postgres WITH GRANT OPTION; + + +-- +-- Name: FUNCTION uuid_ns_x500(); Type: ACL; Schema: extensions; Owner: supabase_admin +-- + +GRANT ALL ON FUNCTION extensions.uuid_ns_x500() TO dashboard_user; +GRANT ALL ON FUNCTION extensions.uuid_ns_x500() TO postgres WITH GRANT OPTION; + + +-- +-- Name: FUNCTION verify(token text, secret text, algorithm text); Type: ACL; Schema: extensions; Owner: supabase_admin +-- + +GRANT ALL ON FUNCTION extensions.verify(token text, secret text, algorithm text) TO dashboard_user; +GRANT ALL ON FUNCTION extensions.verify(token text, secret text, algorithm text) TO postgres WITH GRANT OPTION; + + +-- +-- Name: FUNCTION comment_directive(comment_ text); Type: ACL; Schema: graphql; Owner: supabase_admin +-- + +GRANT ALL ON FUNCTION graphql.comment_directive(comment_ text) TO postgres; +GRANT ALL ON FUNCTION graphql.comment_directive(comment_ text) TO anon; +GRANT ALL ON FUNCTION graphql.comment_directive(comment_ text) TO authenticated; +GRANT ALL ON FUNCTION graphql.comment_directive(comment_ text) TO service_role; + + +-- +-- Name: FUNCTION exception(message text); Type: ACL; Schema: graphql; Owner: supabase_admin +-- + +GRANT ALL ON FUNCTION graphql.exception(message text) TO postgres; +GRANT ALL ON FUNCTION graphql.exception(message text) TO anon; +GRANT ALL ON FUNCTION graphql.exception(message text) TO authenticated; +GRANT ALL ON FUNCTION graphql.exception(message text) TO service_role; + + +-- +-- Name: FUNCTION get_schema_version(); Type: ACL; Schema: graphql; Owner: supabase_admin +-- + +GRANT ALL ON FUNCTION graphql.get_schema_version() TO postgres; +GRANT ALL ON FUNCTION graphql.get_schema_version() TO anon; +GRANT ALL ON FUNCTION graphql.get_schema_version() TO authenticated; +GRANT ALL ON FUNCTION graphql.get_schema_version() TO service_role; + + +-- +-- Name: FUNCTION increment_schema_version(); Type: ACL; Schema: graphql; Owner: supabase_admin +-- + +GRANT ALL ON FUNCTION graphql.increment_schema_version() TO postgres; +GRANT ALL ON FUNCTION graphql.increment_schema_version() TO anon; +GRANT ALL ON FUNCTION graphql.increment_schema_version() TO authenticated; +GRANT ALL ON FUNCTION graphql.increment_schema_version() TO service_role; + + +-- +-- Name: FUNCTION graphql("operationName" text, query text, variables jsonb, extensions jsonb); Type: ACL; Schema: graphql_public; Owner: supabase_admin +-- + +GRANT ALL ON FUNCTION graphql_public.graphql("operationName" text, query text, variables jsonb, extensions jsonb) TO postgres; +GRANT ALL ON FUNCTION graphql_public.graphql("operationName" text, query text, variables jsonb, extensions jsonb) TO anon; +GRANT ALL ON FUNCTION graphql_public.graphql("operationName" text, query text, variables jsonb, extensions jsonb) TO authenticated; +GRANT ALL ON FUNCTION graphql_public.graphql("operationName" text, query text, variables jsonb, extensions jsonb) TO service_role; + + +-- +-- Name: FUNCTION get_auth(p_usename text); Type: ACL; Schema: pgbouncer; Owner: postgres +-- + +REVOKE ALL ON FUNCTION pgbouncer.get_auth(p_usename text) FROM PUBLIC; +GRANT ALL ON FUNCTION pgbouncer.get_auth(p_usename text) TO pgbouncer; + + +-- +-- Name: TABLE key; Type: ACL; Schema: pgsodium; Owner: supabase_admin +-- + +REVOKE ALL ON TABLE pgsodium.key FROM supabase_admin; +GRANT ALL ON TABLE pgsodium.key TO postgres; + + +-- +-- Name: TABLE valid_key; Type: ACL; Schema: pgsodium; Owner: supabase_admin +-- + +REVOKE ALL ON TABLE pgsodium.valid_key FROM supabase_admin; +REVOKE SELECT ON TABLE pgsodium.valid_key FROM pgsodium_keyiduser; +GRANT ALL ON TABLE pgsodium.valid_key TO postgres; +GRANT ALL ON TABLE pgsodium.valid_key TO pgsodium_keyiduser; + + +-- +-- Name: FUNCTION crypto_aead_det_decrypt(ciphertext bytea, additional bytea, key bytea, nonce bytea); Type: ACL; Schema: pgsodium; Owner: supabase_admin +-- + +REVOKE ALL ON FUNCTION pgsodium.crypto_aead_det_decrypt(ciphertext bytea, additional bytea, key bytea, nonce bytea) FROM supabase_admin; +GRANT ALL ON FUNCTION pgsodium.crypto_aead_det_decrypt(ciphertext bytea, additional bytea, key bytea, nonce bytea) TO postgres; + + +-- +-- Name: FUNCTION crypto_aead_det_decrypt(message bytea, additional bytea, key_uuid uuid, nonce bytea); Type: ACL; Schema: pgsodium; Owner: pgsodium_keymaker +-- + +GRANT ALL ON FUNCTION pgsodium.crypto_aead_det_decrypt(message bytea, additional bytea, key_uuid uuid, nonce bytea) TO service_role; + + +-- +-- Name: FUNCTION crypto_aead_det_decrypt(message bytea, additional bytea, key_id bigint, context bytea, nonce bytea); Type: ACL; Schema: pgsodium; Owner: supabase_admin +-- + +REVOKE ALL ON FUNCTION pgsodium.crypto_aead_det_decrypt(message bytea, additional bytea, key_id bigint, context bytea, nonce bytea) FROM supabase_admin; +GRANT ALL ON FUNCTION pgsodium.crypto_aead_det_decrypt(message bytea, additional bytea, key_id bigint, context bytea, nonce bytea) TO postgres; + + +-- +-- Name: FUNCTION crypto_aead_det_encrypt(message bytea, additional bytea, key bytea, nonce bytea); Type: ACL; Schema: pgsodium; Owner: supabase_admin +-- + +REVOKE ALL ON FUNCTION pgsodium.crypto_aead_det_encrypt(message bytea, additional bytea, key bytea, nonce bytea) FROM supabase_admin; +GRANT ALL ON FUNCTION pgsodium.crypto_aead_det_encrypt(message bytea, additional bytea, key bytea, nonce bytea) TO postgres; + + +-- +-- Name: FUNCTION crypto_aead_det_encrypt(message bytea, additional bytea, key_uuid uuid, nonce bytea); Type: ACL; Schema: pgsodium; Owner: pgsodium_keymaker +-- + +GRANT ALL ON FUNCTION pgsodium.crypto_aead_det_encrypt(message bytea, additional bytea, key_uuid uuid, nonce bytea) TO service_role; + + +-- +-- Name: FUNCTION crypto_aead_det_encrypt(message bytea, additional bytea, key_id bigint, context bytea, nonce bytea); Type: ACL; Schema: pgsodium; Owner: supabase_admin +-- + +REVOKE ALL ON FUNCTION pgsodium.crypto_aead_det_encrypt(message bytea, additional bytea, key_id bigint, context bytea, nonce bytea) FROM supabase_admin; +GRANT ALL ON FUNCTION pgsodium.crypto_aead_det_encrypt(message bytea, additional bytea, key_id bigint, context bytea, nonce bytea) TO postgres; + + +-- +-- Name: FUNCTION crypto_aead_det_keygen(); Type: ACL; Schema: pgsodium; Owner: supabase_admin +-- + +REVOKE ALL ON FUNCTION pgsodium.crypto_aead_det_keygen() FROM supabase_admin; +GRANT ALL ON FUNCTION pgsodium.crypto_aead_det_keygen() TO service_role; +GRANT ALL ON FUNCTION pgsodium.crypto_aead_det_keygen() TO postgres; + + +-- +-- Name: FUNCTION crypto_aead_det_noncegen(); Type: ACL; Schema: pgsodium; Owner: supabase_admin +-- + +REVOKE ALL ON FUNCTION pgsodium.crypto_aead_det_noncegen() FROM supabase_admin; +GRANT ALL ON FUNCTION pgsodium.crypto_aead_det_noncegen() TO postgres; + + +-- +-- Name: FUNCTION crypto_aead_ietf_decrypt(message bytea, additional bytea, nonce bytea, key bytea); Type: ACL; Schema: pgsodium; Owner: supabase_admin +-- + +REVOKE ALL ON FUNCTION pgsodium.crypto_aead_ietf_decrypt(message bytea, additional bytea, nonce bytea, key bytea) FROM supabase_admin; +GRANT ALL ON FUNCTION pgsodium.crypto_aead_ietf_decrypt(message bytea, additional bytea, nonce bytea, key bytea) TO postgres; + + +-- +-- Name: FUNCTION crypto_aead_ietf_decrypt(message bytea, additional bytea, nonce bytea, key_id bigint, context bytea); Type: ACL; Schema: pgsodium; Owner: supabase_admin +-- + +REVOKE ALL ON FUNCTION pgsodium.crypto_aead_ietf_decrypt(message bytea, additional bytea, nonce bytea, key_id bigint, context bytea) FROM supabase_admin; +GRANT ALL ON FUNCTION pgsodium.crypto_aead_ietf_decrypt(message bytea, additional bytea, nonce bytea, key_id bigint, context bytea) TO postgres; + + +-- +-- Name: FUNCTION crypto_aead_ietf_encrypt(message bytea, additional bytea, nonce bytea, key bytea); Type: ACL; Schema: pgsodium; Owner: supabase_admin +-- + +REVOKE ALL ON FUNCTION pgsodium.crypto_aead_ietf_encrypt(message bytea, additional bytea, nonce bytea, key bytea) FROM supabase_admin; +GRANT ALL ON FUNCTION pgsodium.crypto_aead_ietf_encrypt(message bytea, additional bytea, nonce bytea, key bytea) TO postgres; + + +-- +-- Name: FUNCTION crypto_aead_ietf_encrypt(message bytea, additional bytea, nonce bytea, key_id bigint, context bytea); Type: ACL; Schema: pgsodium; Owner: supabase_admin +-- + +REVOKE ALL ON FUNCTION pgsodium.crypto_aead_ietf_encrypt(message bytea, additional bytea, nonce bytea, key_id bigint, context bytea) FROM supabase_admin; +GRANT ALL ON FUNCTION pgsodium.crypto_aead_ietf_encrypt(message bytea, additional bytea, nonce bytea, key_id bigint, context bytea) TO postgres; + + +-- +-- Name: FUNCTION crypto_aead_ietf_keygen(); Type: ACL; Schema: pgsodium; Owner: supabase_admin +-- + +REVOKE ALL ON FUNCTION pgsodium.crypto_aead_ietf_keygen() FROM supabase_admin; +GRANT ALL ON FUNCTION pgsodium.crypto_aead_ietf_keygen() TO postgres; + + +-- +-- Name: FUNCTION crypto_aead_ietf_noncegen(); Type: ACL; Schema: pgsodium; Owner: supabase_admin +-- + +REVOKE ALL ON FUNCTION pgsodium.crypto_aead_ietf_noncegen() FROM supabase_admin; +GRANT ALL ON FUNCTION pgsodium.crypto_aead_ietf_noncegen() TO postgres; + + +-- +-- Name: FUNCTION crypto_auth(message bytea, key bytea); Type: ACL; Schema: pgsodium; Owner: supabase_admin +-- + +REVOKE ALL ON FUNCTION pgsodium.crypto_auth(message bytea, key bytea) FROM supabase_admin; +GRANT ALL ON FUNCTION pgsodium.crypto_auth(message bytea, key bytea) TO postgres; + + +-- +-- Name: FUNCTION crypto_auth(message bytea, key_id bigint, context bytea); Type: ACL; Schema: pgsodium; Owner: supabase_admin +-- + +REVOKE ALL ON FUNCTION pgsodium.crypto_auth(message bytea, key_id bigint, context bytea) FROM supabase_admin; +GRANT ALL ON FUNCTION pgsodium.crypto_auth(message bytea, key_id bigint, context bytea) TO postgres; + + +-- +-- Name: FUNCTION crypto_auth_hmacsha256(message bytea, secret bytea); Type: ACL; Schema: pgsodium; Owner: supabase_admin +-- + +REVOKE ALL ON FUNCTION pgsodium.crypto_auth_hmacsha256(message bytea, secret bytea) FROM supabase_admin; +GRANT ALL ON FUNCTION pgsodium.crypto_auth_hmacsha256(message bytea, secret bytea) TO postgres; + + +-- +-- Name: FUNCTION crypto_auth_hmacsha256(message bytea, key_id bigint, context bytea); Type: ACL; Schema: pgsodium; Owner: supabase_admin +-- + +REVOKE ALL ON FUNCTION pgsodium.crypto_auth_hmacsha256(message bytea, key_id bigint, context bytea) FROM supabase_admin; +GRANT ALL ON FUNCTION pgsodium.crypto_auth_hmacsha256(message bytea, key_id bigint, context bytea) TO postgres; + + +-- +-- Name: FUNCTION crypto_auth_hmacsha256_keygen(); Type: ACL; Schema: pgsodium; Owner: supabase_admin +-- + +REVOKE ALL ON FUNCTION pgsodium.crypto_auth_hmacsha256_keygen() FROM supabase_admin; +GRANT ALL ON FUNCTION pgsodium.crypto_auth_hmacsha256_keygen() TO postgres; + + +-- +-- Name: FUNCTION crypto_auth_hmacsha256_verify(hash bytea, message bytea, secret bytea); Type: ACL; Schema: pgsodium; Owner: supabase_admin +-- + +REVOKE ALL ON FUNCTION pgsodium.crypto_auth_hmacsha256_verify(hash bytea, message bytea, secret bytea) FROM supabase_admin; +GRANT ALL ON FUNCTION pgsodium.crypto_auth_hmacsha256_verify(hash bytea, message bytea, secret bytea) TO postgres; + + +-- +-- Name: FUNCTION crypto_auth_hmacsha256_verify(hash bytea, message bytea, key_id bigint, context bytea); Type: ACL; Schema: pgsodium; Owner: supabase_admin +-- + +REVOKE ALL ON FUNCTION pgsodium.crypto_auth_hmacsha256_verify(hash bytea, message bytea, key_id bigint, context bytea) FROM supabase_admin; +GRANT ALL ON FUNCTION pgsodium.crypto_auth_hmacsha256_verify(hash bytea, message bytea, key_id bigint, context bytea) TO postgres; + + +-- +-- Name: FUNCTION crypto_auth_hmacsha512(message bytea, secret bytea); Type: ACL; Schema: pgsodium; Owner: supabase_admin +-- + +REVOKE ALL ON FUNCTION pgsodium.crypto_auth_hmacsha512(message bytea, secret bytea) FROM supabase_admin; +GRANT ALL ON FUNCTION pgsodium.crypto_auth_hmacsha512(message bytea, secret bytea) TO postgres; + + +-- +-- Name: FUNCTION crypto_auth_hmacsha512(message bytea, key_id bigint, context bytea); Type: ACL; Schema: pgsodium; Owner: supabase_admin +-- + +REVOKE ALL ON FUNCTION pgsodium.crypto_auth_hmacsha512(message bytea, key_id bigint, context bytea) FROM supabase_admin; +GRANT ALL ON FUNCTION pgsodium.crypto_auth_hmacsha512(message bytea, key_id bigint, context bytea) TO postgres; + + +-- +-- Name: FUNCTION crypto_auth_hmacsha512_verify(hash bytea, message bytea, secret bytea); Type: ACL; Schema: pgsodium; Owner: supabase_admin +-- + +REVOKE ALL ON FUNCTION pgsodium.crypto_auth_hmacsha512_verify(hash bytea, message bytea, secret bytea) FROM supabase_admin; +GRANT ALL ON FUNCTION pgsodium.crypto_auth_hmacsha512_verify(hash bytea, message bytea, secret bytea) TO postgres; + + +-- +-- Name: FUNCTION crypto_auth_hmacsha512_verify(hash bytea, message bytea, key_id bigint, context bytea); Type: ACL; Schema: pgsodium; Owner: supabase_admin +-- + +REVOKE ALL ON FUNCTION pgsodium.crypto_auth_hmacsha512_verify(hash bytea, message bytea, key_id bigint, context bytea) FROM supabase_admin; +GRANT ALL ON FUNCTION pgsodium.crypto_auth_hmacsha512_verify(hash bytea, message bytea, key_id bigint, context bytea) TO postgres; + + +-- +-- Name: FUNCTION crypto_auth_keygen(); Type: ACL; Schema: pgsodium; Owner: supabase_admin +-- + +REVOKE ALL ON FUNCTION pgsodium.crypto_auth_keygen() FROM supabase_admin; +GRANT ALL ON FUNCTION pgsodium.crypto_auth_keygen() TO postgres; + + +-- +-- Name: FUNCTION crypto_auth_verify(mac bytea, message bytea, key bytea); Type: ACL; Schema: pgsodium; Owner: supabase_admin +-- + +REVOKE ALL ON FUNCTION pgsodium.crypto_auth_verify(mac bytea, message bytea, key bytea) FROM supabase_admin; +GRANT ALL ON FUNCTION pgsodium.crypto_auth_verify(mac bytea, message bytea, key bytea) TO postgres; + + +-- +-- Name: FUNCTION crypto_auth_verify(mac bytea, message bytea, key_id bigint, context bytea); Type: ACL; Schema: pgsodium; Owner: supabase_admin +-- + +REVOKE ALL ON FUNCTION pgsodium.crypto_auth_verify(mac bytea, message bytea, key_id bigint, context bytea) FROM supabase_admin; +GRANT ALL ON FUNCTION pgsodium.crypto_auth_verify(mac bytea, message bytea, key_id bigint, context bytea) TO postgres; + + +-- +-- Name: FUNCTION crypto_box(message bytea, nonce bytea, public bytea, secret bytea); Type: ACL; Schema: pgsodium; Owner: supabase_admin +-- + +REVOKE ALL ON FUNCTION pgsodium.crypto_box(message bytea, nonce bytea, public bytea, secret bytea) FROM supabase_admin; +GRANT ALL ON FUNCTION pgsodium.crypto_box(message bytea, nonce bytea, public bytea, secret bytea) TO postgres; + + +-- +-- Name: FUNCTION crypto_box_new_keypair(); Type: ACL; Schema: pgsodium; Owner: supabase_admin +-- + +REVOKE ALL ON FUNCTION pgsodium.crypto_box_new_keypair() FROM supabase_admin; +GRANT ALL ON FUNCTION pgsodium.crypto_box_new_keypair() TO postgres; + + +-- +-- Name: FUNCTION crypto_box_noncegen(); Type: ACL; Schema: pgsodium; Owner: supabase_admin +-- + +REVOKE ALL ON FUNCTION pgsodium.crypto_box_noncegen() FROM supabase_admin; +GRANT ALL ON FUNCTION pgsodium.crypto_box_noncegen() TO postgres; + + +-- +-- Name: FUNCTION crypto_box_open(ciphertext bytea, nonce bytea, public bytea, secret bytea); Type: ACL; Schema: pgsodium; Owner: supabase_admin +-- + +REVOKE ALL ON FUNCTION pgsodium.crypto_box_open(ciphertext bytea, nonce bytea, public bytea, secret bytea) FROM supabase_admin; +GRANT ALL ON FUNCTION pgsodium.crypto_box_open(ciphertext bytea, nonce bytea, public bytea, secret bytea) TO postgres; + + +-- +-- Name: FUNCTION crypto_box_seed_new_keypair(seed bytea); Type: ACL; Schema: pgsodium; Owner: supabase_admin +-- + +REVOKE ALL ON FUNCTION pgsodium.crypto_box_seed_new_keypair(seed bytea) FROM supabase_admin; +GRANT ALL ON FUNCTION pgsodium.crypto_box_seed_new_keypair(seed bytea) TO postgres; + + +-- +-- Name: FUNCTION crypto_generichash(message bytea, key bytea); Type: ACL; Schema: pgsodium; Owner: supabase_admin +-- + +REVOKE ALL ON FUNCTION pgsodium.crypto_generichash(message bytea, key bytea) FROM supabase_admin; +GRANT ALL ON FUNCTION pgsodium.crypto_generichash(message bytea, key bytea) TO postgres; + + +-- +-- Name: FUNCTION crypto_generichash_keygen(); Type: ACL; Schema: pgsodium; Owner: supabase_admin +-- + +REVOKE ALL ON FUNCTION pgsodium.crypto_generichash_keygen() FROM supabase_admin; +GRANT ALL ON FUNCTION pgsodium.crypto_generichash_keygen() TO postgres; + + +-- +-- Name: FUNCTION crypto_kdf_derive_from_key(subkey_size bigint, subkey_id bigint, context bytea, primary_key bytea); Type: ACL; Schema: pgsodium; Owner: supabase_admin +-- + +REVOKE ALL ON FUNCTION pgsodium.crypto_kdf_derive_from_key(subkey_size bigint, subkey_id bigint, context bytea, primary_key bytea) FROM supabase_admin; +GRANT ALL ON FUNCTION pgsodium.crypto_kdf_derive_from_key(subkey_size bigint, subkey_id bigint, context bytea, primary_key bytea) TO postgres; + + +-- +-- Name: FUNCTION crypto_kdf_keygen(); Type: ACL; Schema: pgsodium; Owner: supabase_admin +-- + +REVOKE ALL ON FUNCTION pgsodium.crypto_kdf_keygen() FROM supabase_admin; +GRANT ALL ON FUNCTION pgsodium.crypto_kdf_keygen() TO postgres; + + +-- +-- Name: FUNCTION crypto_kx_new_keypair(); Type: ACL; Schema: pgsodium; Owner: supabase_admin +-- + +REVOKE ALL ON FUNCTION pgsodium.crypto_kx_new_keypair() FROM supabase_admin; +GRANT ALL ON FUNCTION pgsodium.crypto_kx_new_keypair() TO postgres; + + +-- +-- Name: FUNCTION crypto_kx_new_seed(); Type: ACL; Schema: pgsodium; Owner: supabase_admin +-- + +REVOKE ALL ON FUNCTION pgsodium.crypto_kx_new_seed() FROM supabase_admin; +GRANT ALL ON FUNCTION pgsodium.crypto_kx_new_seed() TO postgres; + + +-- +-- Name: FUNCTION crypto_kx_seed_new_keypair(seed bytea); Type: ACL; Schema: pgsodium; Owner: supabase_admin +-- + +REVOKE ALL ON FUNCTION pgsodium.crypto_kx_seed_new_keypair(seed bytea) FROM supabase_admin; +GRANT ALL ON FUNCTION pgsodium.crypto_kx_seed_new_keypair(seed bytea) TO postgres; + + +-- +-- Name: FUNCTION crypto_secretbox(message bytea, nonce bytea, key bytea); Type: ACL; Schema: pgsodium; Owner: supabase_admin +-- + +REVOKE ALL ON FUNCTION pgsodium.crypto_secretbox(message bytea, nonce bytea, key bytea) FROM supabase_admin; +GRANT ALL ON FUNCTION pgsodium.crypto_secretbox(message bytea, nonce bytea, key bytea) TO postgres; + + +-- +-- Name: FUNCTION crypto_secretbox(message bytea, nonce bytea, key_id bigint, context bytea); Type: ACL; Schema: pgsodium; Owner: supabase_admin +-- + +REVOKE ALL ON FUNCTION pgsodium.crypto_secretbox(message bytea, nonce bytea, key_id bigint, context bytea) FROM supabase_admin; +GRANT ALL ON FUNCTION pgsodium.crypto_secretbox(message bytea, nonce bytea, key_id bigint, context bytea) TO postgres; + + +-- +-- Name: FUNCTION crypto_secretbox_keygen(); Type: ACL; Schema: pgsodium; Owner: supabase_admin +-- + +REVOKE ALL ON FUNCTION pgsodium.crypto_secretbox_keygen() FROM supabase_admin; +GRANT ALL ON FUNCTION pgsodium.crypto_secretbox_keygen() TO postgres; + + +-- +-- Name: FUNCTION crypto_secretbox_noncegen(); Type: ACL; Schema: pgsodium; Owner: supabase_admin +-- + +REVOKE ALL ON FUNCTION pgsodium.crypto_secretbox_noncegen() FROM supabase_admin; +GRANT ALL ON FUNCTION pgsodium.crypto_secretbox_noncegen() TO postgres; + + +-- +-- Name: FUNCTION crypto_secretbox_open(ciphertext bytea, nonce bytea, key bytea); Type: ACL; Schema: pgsodium; Owner: supabase_admin +-- + +REVOKE ALL ON FUNCTION pgsodium.crypto_secretbox_open(ciphertext bytea, nonce bytea, key bytea) FROM supabase_admin; +GRANT ALL ON FUNCTION pgsodium.crypto_secretbox_open(ciphertext bytea, nonce bytea, key bytea) TO postgres; + + +-- +-- Name: FUNCTION crypto_secretbox_open(message bytea, nonce bytea, key_id bigint, context bytea); Type: ACL; Schema: pgsodium; Owner: supabase_admin +-- + +REVOKE ALL ON FUNCTION pgsodium.crypto_secretbox_open(message bytea, nonce bytea, key_id bigint, context bytea) FROM supabase_admin; +GRANT ALL ON FUNCTION pgsodium.crypto_secretbox_open(message bytea, nonce bytea, key_id bigint, context bytea) TO postgres; + + +-- +-- Name: FUNCTION crypto_shorthash(message bytea, key bytea); Type: ACL; Schema: pgsodium; Owner: supabase_admin +-- + +REVOKE ALL ON FUNCTION pgsodium.crypto_shorthash(message bytea, key bytea) FROM supabase_admin; +GRANT ALL ON FUNCTION pgsodium.crypto_shorthash(message bytea, key bytea) TO postgres; + + +-- +-- Name: FUNCTION crypto_shorthash_keygen(); Type: ACL; Schema: pgsodium; Owner: supabase_admin +-- + +REVOKE ALL ON FUNCTION pgsodium.crypto_shorthash_keygen() FROM supabase_admin; +GRANT ALL ON FUNCTION pgsodium.crypto_shorthash_keygen() TO postgres; + + +-- +-- Name: FUNCTION crypto_sign_final_create(state bytea, key bytea); Type: ACL; Schema: pgsodium; Owner: supabase_admin +-- + +REVOKE ALL ON FUNCTION pgsodium.crypto_sign_final_create(state bytea, key bytea) FROM supabase_admin; +GRANT ALL ON FUNCTION pgsodium.crypto_sign_final_create(state bytea, key bytea) TO postgres; + + +-- +-- Name: FUNCTION crypto_sign_final_verify(state bytea, signature bytea, key bytea); Type: ACL; Schema: pgsodium; Owner: supabase_admin +-- + +REVOKE ALL ON FUNCTION pgsodium.crypto_sign_final_verify(state bytea, signature bytea, key bytea) FROM supabase_admin; +GRANT ALL ON FUNCTION pgsodium.crypto_sign_final_verify(state bytea, signature bytea, key bytea) TO postgres; + + +-- +-- Name: FUNCTION crypto_sign_init(); Type: ACL; Schema: pgsodium; Owner: supabase_admin +-- + +REVOKE ALL ON FUNCTION pgsodium.crypto_sign_init() FROM supabase_admin; +GRANT ALL ON FUNCTION pgsodium.crypto_sign_init() TO postgres; + + +-- +-- Name: FUNCTION crypto_sign_new_keypair(); Type: ACL; Schema: pgsodium; Owner: supabase_admin +-- + +REVOKE ALL ON FUNCTION pgsodium.crypto_sign_new_keypair() FROM supabase_admin; +GRANT ALL ON FUNCTION pgsodium.crypto_sign_new_keypair() TO postgres; + + +-- +-- Name: FUNCTION crypto_sign_update(state bytea, message bytea); Type: ACL; Schema: pgsodium; Owner: supabase_admin +-- + +REVOKE ALL ON FUNCTION pgsodium.crypto_sign_update(state bytea, message bytea) FROM supabase_admin; +GRANT ALL ON FUNCTION pgsodium.crypto_sign_update(state bytea, message bytea) TO postgres; + + +-- +-- Name: FUNCTION crypto_sign_update_agg1(state bytea, message bytea); Type: ACL; Schema: pgsodium; Owner: supabase_admin +-- + +REVOKE ALL ON FUNCTION pgsodium.crypto_sign_update_agg1(state bytea, message bytea) FROM supabase_admin; +GRANT ALL ON FUNCTION pgsodium.crypto_sign_update_agg1(state bytea, message bytea) TO postgres; + + +-- +-- Name: FUNCTION crypto_sign_update_agg2(cur_state bytea, initial_state bytea, message bytea); Type: ACL; Schema: pgsodium; Owner: supabase_admin +-- + +REVOKE ALL ON FUNCTION pgsodium.crypto_sign_update_agg2(cur_state bytea, initial_state bytea, message bytea) FROM supabase_admin; +GRANT ALL ON FUNCTION pgsodium.crypto_sign_update_agg2(cur_state bytea, initial_state bytea, message bytea) TO postgres; + + +-- +-- Name: FUNCTION crypto_signcrypt_new_keypair(); Type: ACL; Schema: pgsodium; Owner: supabase_admin +-- + +REVOKE ALL ON FUNCTION pgsodium.crypto_signcrypt_new_keypair() FROM supabase_admin; +GRANT ALL ON FUNCTION pgsodium.crypto_signcrypt_new_keypair() TO postgres; + + +-- +-- Name: FUNCTION crypto_signcrypt_sign_after(state bytea, sender_sk bytea, ciphertext bytea); Type: ACL; Schema: pgsodium; Owner: supabase_admin +-- + +REVOKE ALL ON FUNCTION pgsodium.crypto_signcrypt_sign_after(state bytea, sender_sk bytea, ciphertext bytea) FROM supabase_admin; +GRANT ALL ON FUNCTION pgsodium.crypto_signcrypt_sign_after(state bytea, sender_sk bytea, ciphertext bytea) TO postgres; + + +-- +-- Name: FUNCTION crypto_signcrypt_sign_before(sender bytea, recipient bytea, sender_sk bytea, recipient_pk bytea, additional bytea); Type: ACL; Schema: pgsodium; Owner: supabase_admin +-- + +REVOKE ALL ON FUNCTION pgsodium.crypto_signcrypt_sign_before(sender bytea, recipient bytea, sender_sk bytea, recipient_pk bytea, additional bytea) FROM supabase_admin; +GRANT ALL ON FUNCTION pgsodium.crypto_signcrypt_sign_before(sender bytea, recipient bytea, sender_sk bytea, recipient_pk bytea, additional bytea) TO postgres; + + +-- +-- Name: FUNCTION crypto_signcrypt_verify_after(state bytea, signature bytea, sender_pk bytea, ciphertext bytea); Type: ACL; Schema: pgsodium; Owner: supabase_admin +-- + +REVOKE ALL ON FUNCTION pgsodium.crypto_signcrypt_verify_after(state bytea, signature bytea, sender_pk bytea, ciphertext bytea) FROM supabase_admin; +GRANT ALL ON FUNCTION pgsodium.crypto_signcrypt_verify_after(state bytea, signature bytea, sender_pk bytea, ciphertext bytea) TO postgres; + + +-- +-- Name: FUNCTION crypto_signcrypt_verify_before(signature bytea, sender bytea, recipient bytea, additional bytea, sender_pk bytea, recipient_sk bytea); Type: ACL; Schema: pgsodium; Owner: supabase_admin +-- + +REVOKE ALL ON FUNCTION pgsodium.crypto_signcrypt_verify_before(signature bytea, sender bytea, recipient bytea, additional bytea, sender_pk bytea, recipient_sk bytea) FROM supabase_admin; +GRANT ALL ON FUNCTION pgsodium.crypto_signcrypt_verify_before(signature bytea, sender bytea, recipient bytea, additional bytea, sender_pk bytea, recipient_sk bytea) TO postgres; + + +-- +-- Name: FUNCTION crypto_signcrypt_verify_public(signature bytea, sender bytea, recipient bytea, additional bytea, sender_pk bytea, ciphertext bytea); Type: ACL; Schema: pgsodium; Owner: supabase_admin +-- + +REVOKE ALL ON FUNCTION pgsodium.crypto_signcrypt_verify_public(signature bytea, sender bytea, recipient bytea, additional bytea, sender_pk bytea, ciphertext bytea) FROM supabase_admin; +GRANT ALL ON FUNCTION pgsodium.crypto_signcrypt_verify_public(signature bytea, sender bytea, recipient bytea, additional bytea, sender_pk bytea, ciphertext bytea) TO postgres; + + +-- +-- Name: FUNCTION derive_key(key_id bigint, key_len integer, context bytea); Type: ACL; Schema: pgsodium; Owner: supabase_admin +-- + +REVOKE ALL ON FUNCTION pgsodium.derive_key(key_id bigint, key_len integer, context bytea) FROM supabase_admin; +GRANT ALL ON FUNCTION pgsodium.derive_key(key_id bigint, key_len integer, context bytea) TO postgres; + + +-- +-- Name: FUNCTION pgsodium_derive(key_id bigint, key_len integer, context bytea); Type: ACL; Schema: pgsodium; Owner: supabase_admin +-- + +REVOKE ALL ON FUNCTION pgsodium.pgsodium_derive(key_id bigint, key_len integer, context bytea) FROM supabase_admin; +GRANT ALL ON FUNCTION pgsodium.pgsodium_derive(key_id bigint, key_len integer, context bytea) TO postgres; + + +-- +-- Name: FUNCTION randombytes_buf(size integer); Type: ACL; Schema: pgsodium; Owner: supabase_admin +-- + +REVOKE ALL ON FUNCTION pgsodium.randombytes_buf(size integer) FROM supabase_admin; +GRANT ALL ON FUNCTION pgsodium.randombytes_buf(size integer) TO postgres; + + +-- +-- Name: FUNCTION randombytes_buf_deterministic(size integer, seed bytea); Type: ACL; Schema: pgsodium; Owner: supabase_admin +-- + +REVOKE ALL ON FUNCTION pgsodium.randombytes_buf_deterministic(size integer, seed bytea) FROM supabase_admin; +GRANT ALL ON FUNCTION pgsodium.randombytes_buf_deterministic(size integer, seed bytea) TO postgres; + + +-- +-- Name: FUNCTION randombytes_new_seed(); Type: ACL; Schema: pgsodium; Owner: supabase_admin +-- + +REVOKE ALL ON FUNCTION pgsodium.randombytes_new_seed() FROM supabase_admin; +GRANT ALL ON FUNCTION pgsodium.randombytes_new_seed() TO postgres; + + +-- +-- Name: FUNCTION randombytes_random(); Type: ACL; Schema: pgsodium; Owner: supabase_admin +-- + +REVOKE ALL ON FUNCTION pgsodium.randombytes_random() FROM supabase_admin; +GRANT ALL ON FUNCTION pgsodium.randombytes_random() TO postgres; + + +-- +-- Name: FUNCTION randombytes_uniform(upper_bound integer); Type: ACL; Schema: pgsodium; Owner: supabase_admin +-- + +REVOKE ALL ON FUNCTION pgsodium.randombytes_uniform(upper_bound integer) FROM supabase_admin; +GRANT ALL ON FUNCTION pgsodium.randombytes_uniform(upper_bound integer) TO postgres; + + +-- +-- Name: FUNCTION can_insert_object(bucketid text, name text, owner uuid, metadata jsonb); Type: ACL; Schema: storage; Owner: supabase_storage_admin +-- + +GRANT ALL ON FUNCTION storage.can_insert_object(bucketid text, name text, owner uuid, metadata jsonb) TO postgres; + + +-- +-- Name: FUNCTION extension(name text); Type: ACL; Schema: storage; Owner: supabase_storage_admin +-- + +GRANT ALL ON FUNCTION storage.extension(name text) TO anon; +GRANT ALL ON FUNCTION storage.extension(name text) TO authenticated; +GRANT ALL ON FUNCTION storage.extension(name text) TO service_role; +GRANT ALL ON FUNCTION storage.extension(name text) TO dashboard_user; +GRANT ALL ON FUNCTION storage.extension(name text) TO postgres; + + +-- +-- Name: FUNCTION filename(name text); Type: ACL; Schema: storage; Owner: supabase_storage_admin +-- + +GRANT ALL ON FUNCTION storage.filename(name text) TO anon; +GRANT ALL ON FUNCTION storage.filename(name text) TO authenticated; +GRANT ALL ON FUNCTION storage.filename(name text) TO service_role; +GRANT ALL ON FUNCTION storage.filename(name text) TO dashboard_user; +GRANT ALL ON FUNCTION storage.filename(name text) TO postgres; + + +-- +-- Name: FUNCTION foldername(name text); Type: ACL; Schema: storage; Owner: supabase_storage_admin +-- + +GRANT ALL ON FUNCTION storage.foldername(name text) TO anon; +GRANT ALL ON FUNCTION storage.foldername(name text) TO authenticated; +GRANT ALL ON FUNCTION storage.foldername(name text) TO service_role; +GRANT ALL ON FUNCTION storage.foldername(name text) TO dashboard_user; +GRANT ALL ON FUNCTION storage.foldername(name text) TO postgres; + + +-- +-- Name: FUNCTION get_size_by_bucket(); Type: ACL; Schema: storage; Owner: supabase_storage_admin +-- + +GRANT ALL ON FUNCTION storage.get_size_by_bucket() TO postgres; + + +-- +-- Name: FUNCTION search(prefix text, bucketname text, limits integer, levels integer, offsets integer, search text, sortcolumn text, sortorder text); Type: ACL; Schema: storage; Owner: supabase_storage_admin +-- + +GRANT ALL ON FUNCTION storage.search(prefix text, bucketname text, limits integer, levels integer, offsets integer, search text, sortcolumn text, sortorder text) TO postgres; + + +-- +-- Name: FUNCTION update_updated_at_column(); Type: ACL; Schema: storage; Owner: supabase_storage_admin +-- + +GRANT ALL ON FUNCTION storage.update_updated_at_column() TO postgres; + + +-- +-- Name: TABLE audit_log_entries; Type: ACL; Schema: auth; Owner: supabase_auth_admin +-- + +GRANT ALL ON TABLE auth.audit_log_entries TO dashboard_user; +GRANT ALL ON TABLE auth.audit_log_entries TO postgres; + + +-- +-- Name: TABLE flow_state; Type: ACL; Schema: auth; Owner: supabase_auth_admin +-- + +GRANT ALL ON TABLE auth.flow_state TO postgres; +GRANT ALL ON TABLE auth.flow_state TO dashboard_user; + + +-- +-- Name: TABLE identities; Type: ACL; Schema: auth; Owner: supabase_auth_admin +-- + +GRANT ALL ON TABLE auth.identities TO postgres; +GRANT ALL ON TABLE auth.identities TO dashboard_user; + + +-- +-- Name: TABLE instances; Type: ACL; Schema: auth; Owner: supabase_auth_admin +-- + +GRANT ALL ON TABLE auth.instances TO dashboard_user; +GRANT ALL ON TABLE auth.instances TO postgres; + + +-- +-- Name: TABLE mfa_amr_claims; Type: ACL; Schema: auth; Owner: supabase_auth_admin +-- + +GRANT ALL ON TABLE auth.mfa_amr_claims TO postgres; +GRANT ALL ON TABLE auth.mfa_amr_claims TO dashboard_user; + + +-- +-- Name: TABLE mfa_challenges; Type: ACL; Schema: auth; Owner: supabase_auth_admin +-- + +GRANT ALL ON TABLE auth.mfa_challenges TO postgres; +GRANT ALL ON TABLE auth.mfa_challenges TO dashboard_user; + + +-- +-- Name: TABLE mfa_factors; Type: ACL; Schema: auth; Owner: supabase_auth_admin +-- + +GRANT ALL ON TABLE auth.mfa_factors TO postgres; +GRANT ALL ON TABLE auth.mfa_factors TO dashboard_user; + + +-- +-- Name: TABLE refresh_tokens; Type: ACL; Schema: auth; Owner: supabase_auth_admin +-- + +GRANT ALL ON TABLE auth.refresh_tokens TO dashboard_user; +GRANT ALL ON TABLE auth.refresh_tokens TO postgres; + + +-- +-- Name: SEQUENCE refresh_tokens_id_seq; Type: ACL; Schema: auth; Owner: supabase_auth_admin +-- + +GRANT ALL ON SEQUENCE auth.refresh_tokens_id_seq TO dashboard_user; +GRANT ALL ON SEQUENCE auth.refresh_tokens_id_seq TO postgres; + + +-- +-- Name: TABLE saml_providers; Type: ACL; Schema: auth; Owner: supabase_auth_admin +-- + +GRANT ALL ON TABLE auth.saml_providers TO postgres; +GRANT ALL ON TABLE auth.saml_providers TO dashboard_user; + + +-- +-- Name: TABLE saml_relay_states; Type: ACL; Schema: auth; Owner: supabase_auth_admin +-- + +GRANT ALL ON TABLE auth.saml_relay_states TO postgres; +GRANT ALL ON TABLE auth.saml_relay_states TO dashboard_user; + + +-- +-- Name: TABLE schema_migrations; Type: ACL; Schema: auth; Owner: supabase_auth_admin +-- + +GRANT ALL ON TABLE auth.schema_migrations TO dashboard_user; +GRANT ALL ON TABLE auth.schema_migrations TO postgres; + + +-- +-- Name: TABLE sessions; Type: ACL; Schema: auth; Owner: supabase_auth_admin +-- + +GRANT ALL ON TABLE auth.sessions TO postgres; +GRANT ALL ON TABLE auth.sessions TO dashboard_user; + + +-- +-- Name: TABLE sso_domains; Type: ACL; Schema: auth; Owner: supabase_auth_admin +-- + +GRANT ALL ON TABLE auth.sso_domains TO postgres; +GRANT ALL ON TABLE auth.sso_domains TO dashboard_user; + + +-- +-- Name: TABLE sso_providers; Type: ACL; Schema: auth; Owner: supabase_auth_admin +-- + +GRANT ALL ON TABLE auth.sso_providers TO postgres; +GRANT ALL ON TABLE auth.sso_providers TO dashboard_user; + + +-- +-- Name: TABLE users; Type: ACL; Schema: auth; Owner: supabase_auth_admin +-- + +GRANT ALL ON TABLE auth.users TO dashboard_user; +GRANT ALL ON TABLE auth.users TO postgres; + + +-- +-- Name: TABLE pg_stat_statements; Type: ACL; Schema: extensions; Owner: supabase_admin +-- + +GRANT ALL ON TABLE extensions.pg_stat_statements TO dashboard_user; +GRANT ALL ON TABLE extensions.pg_stat_statements TO postgres WITH GRANT OPTION; + + +-- +-- Name: TABLE pg_stat_statements_info; Type: ACL; Schema: extensions; Owner: supabase_admin +-- + +GRANT ALL ON TABLE extensions.pg_stat_statements_info TO dashboard_user; +GRANT ALL ON TABLE extensions.pg_stat_statements_info TO postgres WITH GRANT OPTION; + + +-- +-- Name: SEQUENCE seq_schema_version; Type: ACL; Schema: graphql; Owner: supabase_admin +-- + +GRANT ALL ON SEQUENCE graphql.seq_schema_version TO postgres; +GRANT ALL ON SEQUENCE graphql.seq_schema_version TO anon; +GRANT ALL ON SEQUENCE graphql.seq_schema_version TO authenticated; +GRANT ALL ON SEQUENCE graphql.seq_schema_version TO service_role; + + +-- +-- Name: TABLE decrypted_key; Type: ACL; Schema: pgsodium; Owner: supabase_admin +-- + +GRANT ALL ON TABLE pgsodium.decrypted_key TO pgsodium_keyholder; + + +-- +-- Name: SEQUENCE key_key_id_seq; Type: ACL; Schema: pgsodium; Owner: supabase_admin +-- + +REVOKE ALL ON SEQUENCE pgsodium.key_key_id_seq FROM supabase_admin; +GRANT ALL ON SEQUENCE pgsodium.key_key_id_seq TO postgres; +GRANT ALL ON SEQUENCE pgsodium.key_key_id_seq TO pgsodium_keyiduser; + + +-- +-- Name: TABLE masking_rule; Type: ACL; Schema: pgsodium; Owner: supabase_admin +-- + +GRANT ALL ON TABLE pgsodium.masking_rule TO pgsodium_keyholder; + + +-- +-- Name: TABLE mask_columns; Type: ACL; Schema: pgsodium; Owner: supabase_admin +-- + +GRANT ALL ON TABLE pgsodium.mask_columns TO pgsodium_keyholder; + + +-- +-- Name: TABLE test; Type: ACL; Schema: public; Owner: postgres +-- + +GRANT ALL ON TABLE public.test TO anon; +GRANT ALL ON TABLE public.test TO authenticated; +GRANT ALL ON TABLE public.test TO service_role; + + +-- +-- Name: TABLE test2; Type: ACL; Schema: public; Owner: postgres +-- + +GRANT ALL ON TABLE public.test2 TO anon; +GRANT ALL ON TABLE public.test2 TO authenticated; +GRANT ALL ON TABLE public.test2 TO service_role; + + +-- +-- Name: SEQUENCE test2_id_seq; Type: ACL; Schema: public; Owner: postgres +-- + +GRANT ALL ON SEQUENCE public.test2_id_seq TO anon; +GRANT ALL ON SEQUENCE public.test2_id_seq TO authenticated; +GRANT ALL ON SEQUENCE public.test2_id_seq TO service_role; + + +-- +-- Name: SEQUENCE test_id_seq; Type: ACL; Schema: public; Owner: postgres +-- + +GRANT ALL ON SEQUENCE public.test_id_seq TO anon; +GRANT ALL ON SEQUENCE public.test_id_seq TO authenticated; +GRANT ALL ON SEQUENCE public.test_id_seq TO service_role; + + +-- +-- Name: TABLE buckets; Type: ACL; Schema: storage; Owner: supabase_storage_admin +-- + +GRANT ALL ON TABLE storage.buckets TO anon; +GRANT ALL ON TABLE storage.buckets TO authenticated; +GRANT ALL ON TABLE storage.buckets TO service_role; +GRANT ALL ON TABLE storage.buckets TO postgres; + + +-- +-- Name: TABLE migrations; Type: ACL; Schema: storage; Owner: supabase_storage_admin +-- + +GRANT ALL ON TABLE storage.migrations TO anon; +GRANT ALL ON TABLE storage.migrations TO authenticated; +GRANT ALL ON TABLE storage.migrations TO service_role; +GRANT ALL ON TABLE storage.migrations TO postgres; + + +-- +-- Name: TABLE objects; Type: ACL; Schema: storage; Owner: supabase_storage_admin +-- + +GRANT ALL ON TABLE storage.objects TO anon; +GRANT ALL ON TABLE storage.objects TO authenticated; +GRANT ALL ON TABLE storage.objects TO service_role; +GRANT ALL ON TABLE storage.objects TO postgres; + + +-- +-- Name: TABLE decrypted_secrets; Type: ACL; Schema: vault; Owner: supabase_admin +-- + +GRANT ALL ON TABLE vault.decrypted_secrets TO pgsodium_keyiduser; + + +-- +-- Name: DEFAULT PRIVILEGES FOR SEQUENCES; Type: DEFAULT ACL; Schema: auth; Owner: supabase_auth_admin +-- + +ALTER DEFAULT PRIVILEGES FOR ROLE supabase_auth_admin IN SCHEMA auth GRANT ALL ON SEQUENCES TO postgres; +ALTER DEFAULT PRIVILEGES FOR ROLE supabase_auth_admin IN SCHEMA auth GRANT ALL ON SEQUENCES TO dashboard_user; + + +-- +-- Name: DEFAULT PRIVILEGES FOR FUNCTIONS; Type: DEFAULT ACL; Schema: auth; Owner: supabase_auth_admin +-- + +ALTER DEFAULT PRIVILEGES FOR ROLE supabase_auth_admin IN SCHEMA auth GRANT ALL ON FUNCTIONS TO postgres; +ALTER DEFAULT PRIVILEGES FOR ROLE supabase_auth_admin IN SCHEMA auth GRANT ALL ON FUNCTIONS TO dashboard_user; + + +-- +-- Name: DEFAULT PRIVILEGES FOR TABLES; Type: DEFAULT ACL; Schema: auth; Owner: supabase_auth_admin +-- + +ALTER DEFAULT PRIVILEGES FOR ROLE supabase_auth_admin IN SCHEMA auth GRANT ALL ON TABLES TO postgres; +ALTER DEFAULT PRIVILEGES FOR ROLE supabase_auth_admin IN SCHEMA auth GRANT ALL ON TABLES TO dashboard_user; + + +-- +-- Name: DEFAULT PRIVILEGES FOR SEQUENCES; Type: DEFAULT ACL; Schema: extensions; Owner: supabase_admin +-- + +ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA extensions GRANT ALL ON SEQUENCES TO postgres WITH GRANT OPTION; + + +-- +-- Name: DEFAULT PRIVILEGES FOR FUNCTIONS; Type: DEFAULT ACL; Schema: extensions; Owner: supabase_admin +-- + +ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA extensions GRANT ALL ON FUNCTIONS TO postgres WITH GRANT OPTION; + + +-- +-- Name: DEFAULT PRIVILEGES FOR TABLES; Type: DEFAULT ACL; Schema: extensions; Owner: supabase_admin +-- + +ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA extensions GRANT ALL ON TABLES TO postgres WITH GRANT OPTION; + + +-- +-- Name: DEFAULT PRIVILEGES FOR SEQUENCES; Type: DEFAULT ACL; Schema: graphql; Owner: supabase_admin +-- + +ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA graphql GRANT ALL ON SEQUENCES TO postgres; +ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA graphql GRANT ALL ON SEQUENCES TO anon; +ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA graphql GRANT ALL ON SEQUENCES TO authenticated; +ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA graphql GRANT ALL ON SEQUENCES TO service_role; + + +-- +-- Name: DEFAULT PRIVILEGES FOR FUNCTIONS; Type: DEFAULT ACL; Schema: graphql; Owner: supabase_admin +-- + +ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA graphql GRANT ALL ON FUNCTIONS TO postgres; +ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA graphql GRANT ALL ON FUNCTIONS TO anon; +ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA graphql GRANT ALL ON FUNCTIONS TO authenticated; +ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA graphql GRANT ALL ON FUNCTIONS TO service_role; + + +-- +-- Name: DEFAULT PRIVILEGES FOR TABLES; Type: DEFAULT ACL; Schema: graphql; Owner: supabase_admin +-- + +ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA graphql GRANT ALL ON TABLES TO postgres; +ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA graphql GRANT ALL ON TABLES TO anon; +ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA graphql GRANT ALL ON TABLES TO authenticated; +ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA graphql GRANT ALL ON TABLES TO service_role; + + +-- +-- Name: DEFAULT PRIVILEGES FOR SEQUENCES; Type: DEFAULT ACL; Schema: graphql_public; Owner: supabase_admin +-- + +ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA graphql_public GRANT ALL ON SEQUENCES TO postgres; +ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA graphql_public GRANT ALL ON SEQUENCES TO anon; +ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA graphql_public GRANT ALL ON SEQUENCES TO authenticated; +ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA graphql_public GRANT ALL ON SEQUENCES TO service_role; + + +-- +-- Name: DEFAULT PRIVILEGES FOR FUNCTIONS; Type: DEFAULT ACL; Schema: graphql_public; Owner: supabase_admin +-- + +ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA graphql_public GRANT ALL ON FUNCTIONS TO postgres; +ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA graphql_public GRANT ALL ON FUNCTIONS TO anon; +ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA graphql_public GRANT ALL ON FUNCTIONS TO authenticated; +ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA graphql_public GRANT ALL ON FUNCTIONS TO service_role; + + +-- +-- Name: DEFAULT PRIVILEGES FOR TABLES; Type: DEFAULT ACL; Schema: graphql_public; Owner: supabase_admin +-- + +ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA graphql_public GRANT ALL ON TABLES TO postgres; +ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA graphql_public GRANT ALL ON TABLES TO anon; +ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA graphql_public GRANT ALL ON TABLES TO authenticated; +ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA graphql_public GRANT ALL ON TABLES TO service_role; + + +-- +-- Name: DEFAULT PRIVILEGES FOR SEQUENCES; Type: DEFAULT ACL; Schema: pgsodium; Owner: supabase_admin +-- + +ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA pgsodium GRANT ALL ON SEQUENCES TO pgsodium_keyholder; + + +-- +-- Name: DEFAULT PRIVILEGES FOR SEQUENCES; Type: DEFAULT ACL; Schema: pgsodium; Owner: postgres +-- + +ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA pgsodium GRANT ALL ON SEQUENCES TO pgsodium_keyiduser; + + +-- +-- Name: DEFAULT PRIVILEGES FOR TABLES; Type: DEFAULT ACL; Schema: pgsodium; Owner: supabase_admin +-- + +ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA pgsodium GRANT ALL ON TABLES TO pgsodium_keyholder; + + +-- +-- Name: DEFAULT PRIVILEGES FOR TABLES; Type: DEFAULT ACL; Schema: pgsodium; Owner: postgres +-- + +ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA pgsodium GRANT ALL ON TABLES TO pgsodium_keyiduser; + + +-- +-- Name: DEFAULT PRIVILEGES FOR SEQUENCES; Type: DEFAULT ACL; Schema: pgsodium_masks; Owner: supabase_admin +-- + +ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA pgsodium_masks GRANT ALL ON SEQUENCES TO pgsodium_keyiduser; + + +-- +-- Name: DEFAULT PRIVILEGES FOR FUNCTIONS; Type: DEFAULT ACL; Schema: pgsodium_masks; Owner: supabase_admin +-- + +ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA pgsodium_masks GRANT ALL ON FUNCTIONS TO pgsodium_keyiduser; + + +-- +-- Name: DEFAULT PRIVILEGES FOR TABLES; Type: DEFAULT ACL; Schema: pgsodium_masks; Owner: supabase_admin +-- + +ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA pgsodium_masks GRANT ALL ON TABLES TO pgsodium_keyiduser; + + +-- +-- Name: DEFAULT PRIVILEGES FOR SEQUENCES; Type: DEFAULT ACL; Schema: public; Owner: postgres +-- + +ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON SEQUENCES TO postgres; +ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON SEQUENCES TO anon; +ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON SEQUENCES TO authenticated; +ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON SEQUENCES TO service_role; + + +-- +-- Name: DEFAULT PRIVILEGES FOR SEQUENCES; Type: DEFAULT ACL; Schema: public; Owner: supabase_admin +-- + +ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON SEQUENCES TO postgres; +ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON SEQUENCES TO anon; +ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON SEQUENCES TO authenticated; +ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON SEQUENCES TO service_role; + + +-- +-- Name: DEFAULT PRIVILEGES FOR FUNCTIONS; Type: DEFAULT ACL; Schema: public; Owner: postgres +-- + +ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON FUNCTIONS TO postgres; +ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON FUNCTIONS TO anon; +ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON FUNCTIONS TO authenticated; +ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON FUNCTIONS TO service_role; + + +-- +-- Name: DEFAULT PRIVILEGES FOR FUNCTIONS; Type: DEFAULT ACL; Schema: public; Owner: supabase_admin +-- + +ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON FUNCTIONS TO postgres; +ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON FUNCTIONS TO anon; +ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON FUNCTIONS TO authenticated; +ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON FUNCTIONS TO service_role; + + +-- +-- Name: DEFAULT PRIVILEGES FOR TABLES; Type: DEFAULT ACL; Schema: public; Owner: postgres +-- + +ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON TABLES TO postgres; +ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON TABLES TO anon; +ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON TABLES TO authenticated; +ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON TABLES TO service_role; + + +-- +-- Name: DEFAULT PRIVILEGES FOR TABLES; Type: DEFAULT ACL; Schema: public; Owner: supabase_admin +-- + +ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON TABLES TO postgres; +ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON TABLES TO anon; +ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON TABLES TO authenticated; +ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA public GRANT ALL ON TABLES TO service_role; + + +-- +-- Name: DEFAULT PRIVILEGES FOR SEQUENCES; Type: DEFAULT ACL; Schema: realtime; Owner: supabase_admin +-- + +ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA realtime GRANT ALL ON SEQUENCES TO postgres; +ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA realtime GRANT ALL ON SEQUENCES TO dashboard_user; + + +-- +-- Name: DEFAULT PRIVILEGES FOR FUNCTIONS; Type: DEFAULT ACL; Schema: realtime; Owner: supabase_admin +-- + +ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA realtime GRANT ALL ON FUNCTIONS TO postgres; +ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA realtime GRANT ALL ON FUNCTIONS TO dashboard_user; + + +-- +-- Name: DEFAULT PRIVILEGES FOR TABLES; Type: DEFAULT ACL; Schema: realtime; Owner: supabase_admin +-- + +ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA realtime GRANT ALL ON TABLES TO postgres; +ALTER DEFAULT PRIVILEGES FOR ROLE supabase_admin IN SCHEMA realtime GRANT ALL ON TABLES TO dashboard_user; + + +-- +-- Name: DEFAULT PRIVILEGES FOR SEQUENCES; Type: DEFAULT ACL; Schema: storage; Owner: postgres +-- + +ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA storage GRANT ALL ON SEQUENCES TO postgres; +ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA storage GRANT ALL ON SEQUENCES TO anon; +ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA storage GRANT ALL ON SEQUENCES TO authenticated; +ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA storage GRANT ALL ON SEQUENCES TO service_role; + + +-- +-- Name: DEFAULT PRIVILEGES FOR FUNCTIONS; Type: DEFAULT ACL; Schema: storage; Owner: postgres +-- + +ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA storage GRANT ALL ON FUNCTIONS TO postgres; +ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA storage GRANT ALL ON FUNCTIONS TO anon; +ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA storage GRANT ALL ON FUNCTIONS TO authenticated; +ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA storage GRANT ALL ON FUNCTIONS TO service_role; + + +-- +-- Name: DEFAULT PRIVILEGES FOR TABLES; Type: DEFAULT ACL; Schema: storage; Owner: postgres +-- + +ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA storage GRANT ALL ON TABLES TO postgres; +ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA storage GRANT ALL ON TABLES TO anon; +ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA storage GRANT ALL ON TABLES TO authenticated; +ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA storage GRANT ALL ON TABLES TO service_role; + + +-- +-- Name: issue_graphql_placeholder; Type: EVENT TRIGGER; Schema: -; Owner: supabase_admin +-- + +CREATE EVENT TRIGGER issue_graphql_placeholder ON sql_drop + WHEN TAG IN ('DROP EXTENSION') + EXECUTE FUNCTION extensions.set_graphql_placeholder(); + + +ALTER EVENT TRIGGER issue_graphql_placeholder OWNER TO supabase_admin; + +-- +-- Name: issue_pg_cron_access; Type: EVENT TRIGGER; Schema: -; Owner: supabase_admin +-- + +CREATE EVENT TRIGGER issue_pg_cron_access ON ddl_command_end + WHEN TAG IN ('CREATE SCHEMA') + EXECUTE FUNCTION extensions.grant_pg_cron_access(); + + +ALTER EVENT TRIGGER issue_pg_cron_access OWNER TO supabase_admin; + +-- +-- Name: issue_pg_graphql_access; Type: EVENT TRIGGER; Schema: -; Owner: supabase_admin +-- + +CREATE EVENT TRIGGER issue_pg_graphql_access ON ddl_command_end + WHEN TAG IN ('CREATE FUNCTION') + EXECUTE FUNCTION extensions.grant_pg_graphql_access(); + + +ALTER EVENT TRIGGER issue_pg_graphql_access OWNER TO supabase_admin; + +-- +-- Name: issue_pg_net_access; Type: EVENT TRIGGER; Schema: -; Owner: supabase_admin +-- + +CREATE EVENT TRIGGER issue_pg_net_access ON ddl_command_end + WHEN TAG IN ('CREATE EXTENSION') + EXECUTE FUNCTION extensions.grant_pg_net_access(); + + +ALTER EVENT TRIGGER issue_pg_net_access OWNER TO supabase_admin; + +-- +-- Name: pgrst_ddl_watch; Type: EVENT TRIGGER; Schema: -; Owner: supabase_admin +-- + +CREATE EVENT TRIGGER pgrst_ddl_watch ON ddl_command_end + EXECUTE FUNCTION extensions.pgrst_ddl_watch(); + + +ALTER EVENT TRIGGER pgrst_ddl_watch OWNER TO supabase_admin; + +-- +-- Name: pgrst_drop_watch; Type: EVENT TRIGGER; Schema: -; Owner: supabase_admin +-- + +CREATE EVENT TRIGGER pgrst_drop_watch ON sql_drop + EXECUTE FUNCTION extensions.pgrst_drop_watch(); + + +ALTER EVENT TRIGGER pgrst_drop_watch OWNER TO supabase_admin; + +-- +-- PostgreSQL database dump complete +-- + diff --git a/tests/resources/updateBackups.sh b/tests/resources/updateBackups.sh new file mode 100755 index 0000000..feabfeb --- /dev/null +++ b/tests/resources/updateBackups.sh @@ -0,0 +1,15 @@ +# This script uses your .env file and updates the backups.tar for NHost and Supabase. + +source ../../.env + +echo "Updating Supabase Backup..." +export PGPASSWORD=$SUPABASE_TEST_PASSWORD +pg_dump -U $SUPABASE_TEST_USERNAME -h $SUPABASE_TEST_HOST -p 5432 $SUPABASE_TEST_DATABASE > supabase/backup.tar +unset PGPASSWORD +echo "Done" + +echo "Updating NHost Backup..." +export PGPASSWORD=$NHOST_TEST_PASSWORD +pg_dump -U $NHOST_TEST_USERNAME -h $NHOST_TEST_SUBDOMAIN.db.$NHOST_TEST_REGION.nhost.run -p 5432 $NHOST_TEST_DATABASE > nhost/backup.tar +unset PGPASSWORD +echo "Done" diff --git a/tests/unit/ResourceCacheTest.php b/tests/unit/ResourceCacheTest.php index 9e4f39f..b56fc8e 100644 --- a/tests/unit/ResourceCacheTest.php +++ b/tests/unit/ResourceCacheTest.php @@ -3,9 +3,7 @@ namespace Utopia\Tests; use PHPUnit\Framework\TestCase; -use Utopia\Transfer\Resource; use Utopia\Transfer\ResourceCache; -use Utopia\Transfer\Transfer; class ResourceCacheTest extends TestCase { From 84c0be81876f9c6600fe80fa16a49327414e4126 Mon Sep 17 00:00:00 2001 From: Bradley Schofield Date: Mon, 5 Jun 2023 12:38:08 +0100 Subject: [PATCH 46/70] Rename ResourceCache to just Cache --- src/Transfer/Destinations/Appwrite.php | 2 +- src/Transfer/Destinations/Local.php | 2 +- src/Transfer/ResourceCache.php | 31 +++++++++++--------------- src/Transfer/Source.php | 2 +- src/Transfer/Sources/Appwrite.php | 14 ++++++------ src/Transfer/Sources/Firebase.php | 2 +- src/Transfer/Sources/NHost.php | 12 +++++----- src/Transfer/Sources/Supabase.php | 2 +- src/Transfer/Target.php | 10 ++++----- src/Transfer/Transfer.php | 20 ++++++++--------- tests/e2e/Sources/SourceTest.php | 4 ++-- tests/unit/ResourceCacheTest.php | 10 ++++----- 12 files changed, 53 insertions(+), 58 deletions(-) diff --git a/src/Transfer/Destinations/Appwrite.php b/src/Transfer/Destinations/Appwrite.php index 94430f7..1a8ee3d 100644 --- a/src/Transfer/Destinations/Appwrite.php +++ b/src/Transfer/Destinations/Appwrite.php @@ -233,7 +233,7 @@ public function importResources(array $resources, callable $callback): void break; } - $this->resourceCache->update($responseResource); + $this->cache->update($responseResource); } $callback($resources); diff --git a/src/Transfer/Destinations/Local.php b/src/Transfer/Destinations/Local.php index 800ecd1..5aff546 100644 --- a/src/Transfer/Destinations/Local.php +++ b/src/Transfer/Destinations/Local.php @@ -129,7 +129,7 @@ public function importResources(array $resources, callable $callback): void } $resource->setStatus(Resource::STATUS_SUCCESS); - $this->resourceCache->update($resource); + $this->cache->update($resource); $this->syncFile(); } diff --git a/src/Transfer/ResourceCache.php b/src/Transfer/ResourceCache.php index b611ef1..4ec416f 100644 --- a/src/Transfer/ResourceCache.php +++ b/src/Transfer/ResourceCache.php @@ -2,30 +2,25 @@ namespace Utopia\Transfer; -class ResourceCache +class Cache { - /** - * Resource Cache - * - * @var array - */ - protected $resourceCache = []; + protected $cache = []; public function __construct() { - $this->resourceCache = []; + $this->cache = []; } public function add($resource) { if (! $resource->getInternalId()) { $resourceId = uniqid(); - if (isset($this->resourceCache[$resource->getName()][$resourceId])) { + if (isset($this->cache[$resource->getName()][$resourceId])) { $resourceId = uniqid(); } $resource->setInternalId(uniqid()); } - $this->resourceCache[$resource->getName()][$resource->getInternalId()] = $resource; + $this->cache[$resource->getName()][$resource->getInternalId()] = $resource; } public function addAll(array $resources) @@ -37,11 +32,11 @@ public function addAll(array $resources) public function update($resource) { - if (! in_array($resource, $this->resourceCache[$resource->getName()])) { + if (! in_array($resource, $this->cache[$resource->getName()])) { throw new \Exception('Resource does not exist in cache'); } - $this->resourceCache[$resource->getName()][$resource->getInternalId()] = $resource; + $this->cache[$resource->getName()][$resource->getInternalId()] = $resource; } public function updateAll($resources) @@ -53,11 +48,11 @@ public function updateAll($resources) public function remove($resource) { - if (! in_array($resource, $this->resourceCache[$resource->getName()])) { + if (! in_array($resource, $this->cache[$resource->getName()])) { throw new \Exception('Resource does not exist in cache'); } - unset($this->resourceCache[$resource->getName()][$resource->getInternalId()]); + unset($this->cache[$resource->getName()][$resource->getInternalId()]); } /** @@ -69,19 +64,19 @@ public function remove($resource) public function get($resource) { if (is_string($resource)) { - return $this->resourceCache[$resource] ?? []; + return $this->cache[$resource] ?? []; } else { - return $this->resourceCache[$resource->getName()] ?? []; + return $this->cache[$resource->getName()] ?? []; } } public function getAll() { - return $this->resourceCache; + return $this->cache; } public function wipe() { - $this->resourceCache = []; + $this->cache = []; } } diff --git a/src/Transfer/Source.php b/src/Transfer/Source.php index b250ad4..38f7326 100644 --- a/src/Transfer/Source.php +++ b/src/Transfer/Source.php @@ -27,7 +27,7 @@ public function run(array $resources, callable $callback): void } } - $this->resourceCache->addAll($returnedResources); + $this->cache->addAll($returnedResources); $callback($prunedResurces); }; diff --git a/src/Transfer/Sources/Appwrite.php b/src/Transfer/Sources/Appwrite.php index ac8d5c3..382dcf6 100644 --- a/src/Transfer/Sources/Appwrite.php +++ b/src/Transfer/Sources/Appwrite.php @@ -354,7 +354,7 @@ private function exportMemberships(int $batchSize) $lastDocument = null; // Export Memberships - $cacheTeams = $this->resourceCache->get(Team::getName()); + $cacheTeams = $this->cache->get(Team::getName()); foreach ($cacheTeams as $team) { /** @param Team $team */ @@ -419,7 +419,7 @@ public function exportDatabasesGroup(int $batchSize, array $resources) private function exportDocuments(int $batchSize) { $databaseClient = new Databases($this->client); - $collections = $this->resourceCache->get(Collection::getName()); + $collections = $this->cache->get(Collection::getName()); foreach ($collections as $collection) { /** @var Collection $collection */ @@ -623,7 +623,7 @@ private function exportCollections(int $batchSize) // Transfer Collections $lastDocument = null; - $databases = $this->resourceCache->get(Database::getName()); + $databases = $this->cache->get(Database::getName()); foreach ($databases as $database) { while (true) { $queries = [Query::limit($batchSize)]; @@ -665,7 +665,7 @@ private function exportAttributes(int $batchSize) // Transfer Attributes $lastDocument = null; - $collections = $this->resourceCache->get(Collection::getName()); + $collections = $this->cache->get(Collection::getName()); /** @var Collection[] $collections */ foreach ($collections as $collection) { while (true) { @@ -699,7 +699,7 @@ private function exportIndexes(int $batchSize) { $databaseClient = new Databases($this->client); - $collections = $this->resourceCache->get(Resource::TYPE_COLLECTION); + $collections = $this->cache->get(Resource::TYPE_COLLECTION); // Transfer Indexes $lastDocument = null; @@ -804,7 +804,7 @@ private function exportFiles(int $batchSize) { $storageClient = new Storage($this->client); - $buckets = $this->resourceCache->get(Bucket::getName()); + $buckets = $this->cache->get(Bucket::getName()); foreach ($buckets as $bucket) { $lastDocument = null; @@ -933,7 +933,7 @@ public function exportDeployments(int $batchSize) { $functionsClient = new Functions($this->client); - $functions = $this->resourceCache->get(Func::getName()); + $functions = $this->cache->get(Func::getName()); foreach ($functions as $func) { /** @var Func $func */ diff --git a/src/Transfer/Sources/Firebase.php b/src/Transfer/Sources/Firebase.php index 96f5341..f91cbf5 100644 --- a/src/Transfer/Sources/Firebase.php +++ b/src/Transfer/Sources/Firebase.php @@ -448,7 +448,7 @@ public function exportBuckets(int $batchsize) public function exportFiles(int $batchsize) { - $buckets = $this->resourceCache->get(Bucket::getName()); + $buckets = $this->cache->get(Bucket::getName()); foreach ($buckets as $bucket) { $endpoint = 'https://storage.googleapis.com/storage/v1/b/'.$bucket->getId().'/o'; diff --git a/src/Transfer/Sources/NHost.php b/src/Transfer/Sources/NHost.php index 042de99..5621dd9 100644 --- a/src/Transfer/Sources/NHost.php +++ b/src/Transfer/Sources/NHost.php @@ -268,7 +268,7 @@ private function exportDatabases(int $batchSize): void private function exportCollections(int $batchSize) { - $databases = $this->resourceCache->get(Database::getName()); + $databases = $this->cache->get(Database::getName()); foreach ($databases as $database) { $statement = $this->pdo->prepare('SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = :database'); @@ -298,7 +298,7 @@ private function exportCollections(int $batchSize) private function exportAttributes(int $batchSize) { - $collections = $this->resourceCache->get(Collection::getName()); + $collections = $this->cache->get(Collection::getName()); foreach ($collections as $collection) { /** @var Collection $collection */ @@ -319,7 +319,7 @@ private function exportAttributes(int $batchSize) private function exportIndexes(int $batchSize) { - $collections = $this->resourceCache->get(Collection::getName()); + $collections = $this->cache->get(Collection::getName()); foreach ($collections as $collection) { /** @var Collection $collection */ @@ -341,7 +341,7 @@ private function exportIndexes(int $batchSize) private function exportDocuments(int $batchSize) { - $databases = $this->resourceCache->get(Database::getName()); + $databases = $this->cache->get(Database::getName()); foreach ($databases as $database) { /** @var Database $database */ @@ -364,7 +364,7 @@ private function exportDocuments(int $batchSize) $transferDocuments = []; - $attributes = $this->resourceCache->get(Attribute::getName()); + $attributes = $this->cache->get(Attribute::getName()); $collectionAttributes = array_filter($attributes, function (Attribute $attribute) use ($collection) { return $attribute->getId() === $collection->getId(); }); @@ -577,7 +577,7 @@ public function exportBuckets(int $batchSize) public function exportFiles(int $batchSize) { - $buckets = $this->resourceCache->get(Bucket::getName()); + $buckets = $this->cache->get(Bucket::getName()); foreach ($buckets as $bucket) { $totalStatement = $this->pdo->prepare('SELECT COUNT(*) FROM storage.files WHERE bucket_id=:bucketId'); diff --git a/src/Transfer/Sources/Supabase.php b/src/Transfer/Sources/Supabase.php index f02a850..e90df58 100644 --- a/src/Transfer/Sources/Supabase.php +++ b/src/Transfer/Sources/Supabase.php @@ -268,7 +268,7 @@ private function exportFiles(int $batchSize) * TODO: Supabase has folders, with enough folders within folders this could cause us to hit the max name length * Need to figure out a solution to this. */ - $buckets = $this->resourceCache->get(Bucket::getName()); + $buckets = $this->cache->get(Bucket::getName()); foreach ($buckets as $bucket) { /** @var Bucket $bucket */ diff --git a/src/Transfer/Target.php b/src/Transfer/Target.php index 7fe5155..d6204e9 100644 --- a/src/Transfer/Target.php +++ b/src/Transfer/Target.php @@ -14,11 +14,11 @@ abstract class Target ]; /** - * Resource Cache + * Cache * - * @var ResourceCache + * @var Cache */ - public $resourceCache; + public $cache; /** * Endpoint @@ -40,9 +40,9 @@ abstract public function getSupportedResources(): array; /** * Register Transfer Cache */ - public function registerTransferCache(ResourceCache &$cache): void + public function registerTransferCache(Cache &$cache): void { - $this->resourceCache = &$cache; + $this->cache = &$cache; } /** diff --git a/src/Transfer/Transfer.php b/src/Transfer/Transfer.php index da0a61a..3e1336e 100644 --- a/src/Transfer/Transfer.php +++ b/src/Transfer/Transfer.php @@ -45,10 +45,10 @@ public function __construct(Source $source, Destination $destination) { $this->source = $source; $this->destination = $destination; - $this->resourceCache = new ResourceCache(); + $this->cache = new Cache(); - $this->source->registerTransferCache($this->resourceCache); - $this->destination->registerTransferCache($this->resourceCache); + $this->source->registerTransferCache($this->cache); + $this->destination->registerTransferCache($this->cache); $this->destination->setSource($source); return $this; @@ -63,7 +63,7 @@ public function __construct(Source $source, Destination $destination) /** * A local cache of resources that were transferred. */ - protected ResourceCache $resourceCache; + protected Cache $cache; protected array $options = []; @@ -75,7 +75,7 @@ public function getStatusCounters() { $status = []; - foreach ($this->resourceCache->getAll() as $resources) { + foreach ($this->cache->getAll() as $resources) { foreach ($resources as $resource) { /** @var resource $resource */ if (! array_key_exists($resource->getName(), $status)) { @@ -117,11 +117,11 @@ public function run(array $resources, callable $callback): void } /** - * Get Resource Cache + * Get Cache */ - public function getResourceCache(): ResourceCache + public function getCache(): Cache { - return $this->resourceCache; + return $this->cache; } /** @@ -143,9 +143,9 @@ public function getReport(string $statusLevel = ''): array { $report = []; - $resourceCache = $this->resourceCache->getAll(); + $cache = $this->cache->getAll(); - foreach ($resourceCache as $type => $resources) { + foreach ($cache as $type => $resources) { foreach ($resources as $resource) { if ($statusLevel && $resource->getStatus() !== $statusLevel) { continue; diff --git a/tests/e2e/Sources/SourceTest.php b/tests/e2e/Sources/SourceTest.php index f65ecc8..35942d1 100644 --- a/tests/e2e/Sources/SourceTest.php +++ b/tests/e2e/Sources/SourceTest.php @@ -26,9 +26,9 @@ public function testGetSupportedResources(): void public function testTransferCache(): void { - $this->source->registerTransferCache($this->createMock(\Utopia\Transfer\ResourceCache::class)); + $this->source->registerTransferCache($this->createMock(\Utopia\Transfer\Cache::class)); - $this->assertNotNull($this->source->resourceCache); + $this->assertNotNull($this->source->cache); } abstract public function testReport(): void; diff --git a/tests/unit/ResourceCacheTest.php b/tests/unit/ResourceCacheTest.php index b56fc8e..717aade 100644 --- a/tests/unit/ResourceCacheTest.php +++ b/tests/unit/ResourceCacheTest.php @@ -3,13 +3,13 @@ namespace Utopia\Tests; use PHPUnit\Framework\TestCase; -use Utopia\Transfer\ResourceCache; +use Utopia\Transfer\Cache; -class ResourceCacheTest extends TestCase +class CacheTest extends TestCase { public function testAdd() { - $cache = new ResourceCache(); + $cache = new Cache(); $resource = new ConcreteResource(); $cache->add($resource); @@ -24,7 +24,7 @@ public function testAdd() /** * @depends testAdd */ - public function testRemove(ResourceCache $cache) + public function testRemove(Cache $cache) { $resources = $cache->get(ConcreteResource::getName()); $resource = $resources[array_keys($resources)[0]]; @@ -39,7 +39,7 @@ public function testRemove(ResourceCache $cache) /** * @depends testRemove */ - public function testWipe(ResourceCache $cache) + public function testWipe(Cache $cache) { $resource = new ConcreteResource(); $cache->add($resource); From 8ee17a1414369a2baf4af7251a1dfe65508b9c82 Mon Sep 17 00:00:00 2001 From: Bradley Schofield Date: Mon, 5 Jun 2023 12:45:32 +0100 Subject: [PATCH 47/70] Continue work on PR updates --- src/Transfer/Destinations/Local.php | 4 ++-- src/Transfer/Source.php | 25 +++++++++++++------------ src/Transfer/Sources/Appwrite.php | 12 ++++++------ src/Transfer/Sources/Firebase.php | 14 +++++++------- src/Transfer/Sources/NHost.php | 9 ++++----- src/Transfer/Sources/Supabase.php | 8 ++++---- src/Transfer/Target.php | 2 +- 7 files changed, 37 insertions(+), 37 deletions(-) diff --git a/src/Transfer/Destinations/Local.php b/src/Transfer/Destinations/Local.php index 5aff546..6c5d3cf 100644 --- a/src/Transfer/Destinations/Local.php +++ b/src/Transfer/Destinations/Local.php @@ -79,7 +79,7 @@ public function report(array $resources = []): array return $report; } - public function syncFile(): void + public function sync(): void { $jsonEncodedData = \json_encode($this->data, JSON_PRETTY_PRINT); @@ -130,7 +130,7 @@ public function importResources(array $resources, callable $callback): void $resource->setStatus(Resource::STATUS_SUCCESS); $this->cache->update($resource); - $this->syncFile(); + $this->sync(); } $callback($resources); diff --git a/src/Transfer/Source.php b/src/Transfer/Source.php index 38f7326..4c3f7fb 100644 --- a/src/Transfer/Source.php +++ b/src/Transfer/Source.php @@ -37,12 +37,13 @@ public function run(array $resources, callable $callback): void /** * Export Resources * - * @param string[] $resources + * @param string[] $resources * @return void */ public function exportResources(array $resources, int $batchSize) { // Convert Resources back into their relevant groups + $groups = []; foreach ($resources as $resource) { if (in_array($resource, Transfer::GROUP_AUTH_RESOURCES)) { @@ -82,35 +83,35 @@ public function exportResources(array $resources, int $batchSize) /** * Export Auth Group * - * @param array $resources Resources to export + * @param array $resources Resources to export * @return void */ - abstract public function exportAuthGroup(int $batchSize, array $resources); + abstract protected function exportAuthGroup(int $batchSize, array $resources); /** * Export Databases Group * - * @param int $batchSize Max 100 - * @param array $resources Resources to export + * @param int $batchSize Max 100 + * @param array $resources Resources to export * @return void */ - abstract public function exportDatabasesGroup(int $batchSize, array $resources); + abstract protected function exportDatabasesGroup(int $batchSize, array $resources); /** * Export Storage Group * - * @param int $batchSize Max 5 - * @param array $resources Resources to export + * @param int $batchSize Max 5 + * @param array $resources Resources to export * @return void */ - abstract public function exportStorageGroup(int $batchSize, array $resources); + abstract protected function exportStorageGroup(int $batchSize, array $resources); /** * Export Functions Group * - * @param int $batchSize Max 100 - * @param array $resources Resources to export + * @param int $batchSize Max 100 + * @param array $resources Resources to export * @return void */ - abstract public function exportFunctionsGroup(int $batchSize, array $resources); + abstract protected function exportFunctionsGroup(int $batchSize, array $resources); } diff --git a/src/Transfer/Sources/Appwrite.php b/src/Transfer/Sources/Appwrite.php index 382dcf6..17df4ff 100644 --- a/src/Transfer/Sources/Appwrite.php +++ b/src/Transfer/Sources/Appwrite.php @@ -246,7 +246,7 @@ public function report(array $resources = []): array * @param int $batchSize Max 100 * @return void */ - public function exportAuthGroup(int $batchSize, array $resources) + protected function exportAuthGroup(int $batchSize, array $resources) { if (in_array(Resource::TYPE_USER, $resources)) { $this->exportUsers($batchSize); @@ -473,7 +473,7 @@ private function convertAttribute(array $value, Collection $collection): Attribu { switch ($value['type']) { case 'string': - if (! isset($value['format'])) { + if (!isset($value['format'])) { return new StringAttribute( $value['key'], $collection, @@ -821,7 +821,7 @@ private function exportFiles(int $batchSize) ); foreach ($response['files'] as $file) { - $this->handleDataTransfer(new File( + $this->exportFileData(new File( $file['$id'], $bucket, $file['name'], @@ -841,7 +841,7 @@ private function exportFiles(int $batchSize) } } - private function handleDataTransfer(File $file) + private function exportFileData(File $file) { // Set the chunk size (5MB) $start = 0; @@ -951,7 +951,7 @@ public function exportDeployments(int $batchSize) ); foreach ($response['deployments'] as $deployment) { - $this->handleDeploymentData($func, $deployment); + $this->exportDeploymentData($func, $deployment); $lastDocument = $deployment['$id']; } @@ -963,7 +963,7 @@ public function exportDeployments(int $batchSize) } } - public function handleDeploymentData(Func $func, array $deployment) + public function exportDeploymentData(Func $func, array $deployment) { // Set the chunk size (5MB) $start = 0; diff --git a/src/Transfer/Sources/Firebase.php b/src/Transfer/Sources/Firebase.php index f91cbf5..6e9ab42 100644 --- a/src/Transfer/Sources/Firebase.php +++ b/src/Transfer/Sources/Firebase.php @@ -127,7 +127,7 @@ public function report(array $resources = []): array throw new \Exception('Not implemented'); } - public function exportAuthGroup(int $batchSize, array $resources) + protected function exportAuthGroup(int $batchSize, array $resources) { if (in_array(Resource::TYPE_USER, $resources)) { $this->exportUsers($batchSize); @@ -217,11 +217,11 @@ public function exportDatabasesGroup(int $batchSize, array $resources) } if (in_array(Resource::TYPE_COLLECTION, $resources)) { - $this->handleDBData($batchSize, in_array(Resource::TYPE_DOCUMENT, $resources), $database); + $this->exportDB($batchSize, in_array(Resource::TYPE_DOCUMENT, $resources), $database); } } - private function handleDBData(int $batchSize, bool $pushDocuments, Database $database) + private function exportDB(int $batchSize, bool $pushDocuments, Database $database) { $baseURL = "https://firestore.googleapis.com/v1/{$this->projectID}/databases/(default)"; @@ -251,7 +251,7 @@ private function handleDBData(int $batchSize, bool $pushDocuments, Database $dat // Transfer Documents and Calculate Schema foreach ($collections as $collection) { - $this->handleCollection($collection, $batchSize, $pushDocuments); + $this->exportCollection($collection, $batchSize, $pushDocuments); } if (count($result['collectionIds']) < $batchSize) { @@ -314,7 +314,7 @@ private function calculateArrayType(Collection $collection, string $key, array $ } } - private function handleCollection(Collection $collection, int $batchSize, bool $transferDocuments) + private function exportCollection(Collection $collection, int $batchSize, bool $transferDocuments) { $resourceURL = 'https://firestore.googleapis.com/v1/projects/'.$this->projectID.'/databases/'.$collection->getDatabase()->getId().'/documents/'.$collection->getId(); @@ -472,7 +472,7 @@ public function exportFiles(int $batchsize) } foreach ($result['items'] as $item) { - $this->handleDataTransfer(new File($item['name'], $bucket, $item['name'])); + $this->exportFile(new File($item['name'], $bucket, $item['name'])); } if (count($result['items']) < $batchsize) { @@ -484,7 +484,7 @@ public function exportFiles(int $batchsize) } } - public function handleDataTransfer(File $file) + public function exportFile(File $file) { $endpoint = 'https://storage.googleapis.com/storage/v1/b/'.$file->getBucket()->getId().'/o/'.$file->getId().'?alt=media'; $start = 0; diff --git a/src/Transfer/Sources/NHost.php b/src/Transfer/Sources/NHost.php index 5621dd9..317800a 100644 --- a/src/Transfer/Sources/NHost.php +++ b/src/Transfer/Sources/NHost.php @@ -190,7 +190,7 @@ public function report(array $resources = []): array return $report; } - public function exportAuthGroup(int $batchSize, array $resources) + protected function exportAuthGroup(int $batchSize, array $resources) { if (in_array(Resource::TYPE_USER, $resources)) { $this->exportUsers($batchSize); @@ -448,7 +448,6 @@ private function convertAttribute(array $column, Collection $collection): Attrib ); break; default: - // $this->logs[Log::WARNING][] = new Log('Unknown data type: ' . $column['data_type'] . ' for column: ' . $column['column_name'] . ' Falling back to string.', \time()); TODO: Figure out how to deal with warnings return new StringAttribute( $column['column_name'], $collection, @@ -538,7 +537,7 @@ public function exportStorageGroup(int $batchSize, array $resources) } } - public function exportBuckets(int $batchSize) + protected function exportBuckets(int $batchSize) { $total = $this->pdo->query('SELECT COUNT(*) FROM storage.buckets')->fetchColumn(); @@ -597,7 +596,7 @@ public function exportFiles(int $batchSize) $offset += $batchSize; foreach ($files as $file) { - $this->handleDataTransfer(new File( + $this->exportFile(new File( $file['id'], $bucket, $file['name'], @@ -611,7 +610,7 @@ public function exportFiles(int $batchSize) } } - public function handleDataTransfer(File $file) + public function exportFile(File $file) { $url = "https://{$this->subdomain}.storage.{$this->region}.nhost.run"; $start = 0; diff --git a/src/Transfer/Sources/Supabase.php b/src/Transfer/Sources/Supabase.php index e90df58..53a3a70 100644 --- a/src/Transfer/Sources/Supabase.php +++ b/src/Transfer/Sources/Supabase.php @@ -152,7 +152,7 @@ public function report(array $resources = []): array return $report; } - public function exportAuthGroup(int $batchSize, array $resources) + protected function exportAuthGroup(int $batchSize, array $resources) { if (in_array(Resource::TYPE_USER, $resources)) { $this->exportUsers($batchSize); @@ -238,7 +238,7 @@ public function exportStorageGroup(int $batchSize, array $resources) } } - private function exportBuckets(int $batchSize) + protected function exportBuckets(int $batchSize) { $statement = $this->pdo->prepare('SELECT * FROM storage.buckets order by created_at'); $statement->execute(); @@ -292,7 +292,7 @@ private function exportFiles(int $batchSize) foreach ($files as $file) { $metadata = json_decode($file['metadata'], true); - $this->handleDataTransfer(new File( + $this->exportFile(new File( $file['id'], $bucket, $file['name'], @@ -306,7 +306,7 @@ private function exportFiles(int $batchSize) } } - private function handleDataTransfer(File $file) + private function exportFile(File $file) { $start = 0; $end = Transfer::STORAGE_MAX_CHUNK_SIZE - 1; diff --git a/src/Transfer/Target.php b/src/Transfer/Target.php index d6204e9..a4bb0d7 100644 --- a/src/Transfer/Target.php +++ b/src/Transfer/Target.php @@ -157,7 +157,7 @@ protected function flatten(array $data, string $prefix = ''): array $finalKey = $prefix ? "{$prefix}[{$key}]" : $key; if (is_array($value)) { - $output += $this->flatten($value, $finalKey); // @todo: handle name collision here if needed + $output += $this->flatten($value, $finalKey); } else { $output[$finalKey] = $value; } From e45e1ae8706cac99b52a1566b87e50331407a069 Mon Sep 17 00:00:00 2001 From: Bradley Schofield Date: Mon, 5 Jun 2023 13:19:01 +0100 Subject: [PATCH 48/70] Fix Visibility --- src/Transfer/Sources/Appwrite.php | 12 ++++++------ src/Transfer/Sources/Firebase.php | 14 +++++++------- src/Transfer/Sources/NHost.php | 10 +++++----- src/Transfer/Sources/Supabase.php | 2 +- src/Transfer/Target.php | 6 +++--- src/Transfer/Transfer.php | 4 ++-- .../e2e/Sources/{Appwrite.php => AppwriteTest.php} | 2 +- tests/e2e/Sources/SourceTest.php | 4 ++-- tests/e2e/adapters/MockSource.php | 8 ++------ 9 files changed, 29 insertions(+), 33 deletions(-) rename tests/e2e/Sources/{Appwrite.php => AppwriteTest.php} (87%) diff --git a/src/Transfer/Sources/Appwrite.php b/src/Transfer/Sources/Appwrite.php index 17df4ff..10592f6 100644 --- a/src/Transfer/Sources/Appwrite.php +++ b/src/Transfer/Sources/Appwrite.php @@ -393,7 +393,7 @@ private function exportMemberships(int $batchSize) } } - public function exportDatabasesGroup(int $batchSize, array $resources) + protected function exportDatabasesGroup(int $batchSize, array $resources) { if (in_array(Resource::TYPE_DATABASE, $resources)) { $this->exportDatabases($batchSize); @@ -758,7 +758,7 @@ private function calculateTypes(array $user): array return $types; } - public function exportStorageGroup(int $batchSize, array $resources) + protected function exportStorageGroup(int $batchSize, array $resources) { if (in_array(Resource::TYPE_BUCKET, $resources)) { $this->exportBuckets($batchSize); @@ -879,7 +879,7 @@ private function exportFileData(File $file) } } - public function exportFunctionsGroup(int $batchSize, array $resources) + protected function exportFunctionsGroup(int $batchSize, array $resources) { if (in_array(Resource::TYPE_FUNCTION, $resources)) { $this->exportFunctions($batchSize); @@ -890,7 +890,7 @@ public function exportFunctionsGroup(int $batchSize, array $resources) } } - public function exportFunctions(int $batchSize) + private function exportFunctions(int $batchSize) { //TODO: Implement batching $functionsClient = new Functions($this->client); @@ -929,7 +929,7 @@ public function exportFunctions(int $batchSize) $this->callback($convertedResources); } - public function exportDeployments(int $batchSize) + private function exportDeployments(int $batchSize) { $functionsClient = new Functions($this->client); @@ -963,7 +963,7 @@ public function exportDeployments(int $batchSize) } } - public function exportDeploymentData(Func $func, array $deployment) + private function exportDeploymentData(Func $func, array $deployment) { // Set the chunk size (5MB) $start = 0; diff --git a/src/Transfer/Sources/Firebase.php b/src/Transfer/Sources/Firebase.php index 6e9ab42..430e1e9 100644 --- a/src/Transfer/Sources/Firebase.php +++ b/src/Transfer/Sources/Firebase.php @@ -94,7 +94,7 @@ private function authenticate() } } - public function call(string $method, string $path = '', array $headers = [], array $params = []): array|string + protected function call(string $method, string $path = '', array $headers = [], array $params = []): array|string { $this->authenticate(); @@ -209,7 +209,7 @@ private function calculateUserType(array $providerData): array return $types; } - public function exportDatabasesGroup(int $batchSize, array $resources) + protected function exportDatabasesGroup(int $batchSize, array $resources) { if (in_array(Resource::TYPE_DATABASE, $resources)) { $database = new Database('default', '(default)'); @@ -405,7 +405,7 @@ private function convertDocument(Collection $collection, array $document): Docum return new Document($document['name'], $collection->getDatabase(), $collection, $data, []); } - public function exportStorageGroup(int $batchSize, array $resources) + protected function exportStorageGroup(int $batchSize, array $resources) { if (in_array(Resource::TYPE_BUCKET, $resources)) { $this->exportBuckets($batchSize); @@ -416,7 +416,7 @@ public function exportStorageGroup(int $batchSize, array $resources) } } - public function exportBuckets(int $batchsize) + private function exportBuckets(int $batchsize) { $endpoint = 'https://storage.googleapis.com/storage/v1/b'; @@ -446,7 +446,7 @@ public function exportBuckets(int $batchsize) } } - public function exportFiles(int $batchsize) + private function exportFiles(int $batchsize) { $buckets = $this->cache->get(Bucket::getName()); @@ -484,7 +484,7 @@ public function exportFiles(int $batchsize) } } - public function exportFile(File $file) + private function exportFile(File $file) { $endpoint = 'https://storage.googleapis.com/storage/v1/b/'.$file->getBucket()->getId().'/o/'.$file->getId().'?alt=media'; $start = 0; @@ -514,7 +514,7 @@ public function exportFile(File $file) } } - public function exportFunctionsGroup(int $batchSize, array $resources) + protected function exportFunctionsGroup(int $batchSize, array $resources) { throw new \Exception('Not implemented'); } diff --git a/src/Transfer/Sources/NHost.php b/src/Transfer/Sources/NHost.php index 317800a..a87361e 100644 --- a/src/Transfer/Sources/NHost.php +++ b/src/Transfer/Sources/NHost.php @@ -235,7 +235,7 @@ private function exportUsers(int $batchSize) } } - public function exportDatabasesGroup(int $batchSize, array $resources) + protected function exportDatabasesGroup(int $batchSize, array $resources) { if (in_array(Resource::TYPE_DATABASE, $resources)) { $this->exportDatabases($batchSize); @@ -526,7 +526,7 @@ private function calculateUserTypes(array $user): array return $types; } - public function exportStorageGroup(int $batchSize, array $resources) + protected function exportStorageGroup(int $batchSize, array $resources) { if (in_array(Resource::TYPE_BUCKET, $resources)) { $this->exportBuckets($batchSize); @@ -574,7 +574,7 @@ protected function exportBuckets(int $batchSize) } } - public function exportFiles(int $batchSize) + private function exportFiles(int $batchSize) { $buckets = $this->cache->get(Bucket::getName()); @@ -610,7 +610,7 @@ public function exportFiles(int $batchSize) } } - public function exportFile(File $file) + private function exportFile(File $file) { $url = "https://{$this->subdomain}.storage.{$this->region}.nhost.run"; $start = 0; @@ -659,7 +659,7 @@ public function exportFile(File $file) } } - public function exportFunctionsGroup(int $batchSize, array $resources) + protected function exportFunctionsGroup(int $batchSize, array $resources) { throw new \Exception('Not Implemented'); } diff --git a/src/Transfer/Sources/Supabase.php b/src/Transfer/Sources/Supabase.php index 53a3a70..c8b1559 100644 --- a/src/Transfer/Sources/Supabase.php +++ b/src/Transfer/Sources/Supabase.php @@ -227,7 +227,7 @@ private function calculateAuthTypes(array $user): array return $types; } - public function exportStorageGroup(int $batchSize, array $resources) + protected function exportStorageGroup(int $batchSize, array $resources) { if (in_array(Resource::TYPE_BUCKET, $resources)) { $this->exportBuckets($batchSize); diff --git a/src/Transfer/Target.php b/src/Transfer/Target.php index a4bb0d7..df5d577 100644 --- a/src/Transfer/Target.php +++ b/src/Transfer/Target.php @@ -38,9 +38,9 @@ abstract public static function getName(): string; abstract public function getSupportedResources(): array; /** - * Register Transfer Cache + * Register Cache */ - public function registerTransferCache(Cache &$cache): void + public function registerCache(Cache &$cache): void { $this->cache = &$cache; } @@ -68,7 +68,7 @@ abstract public function report(array $resources = []): array; * * @throws \Exception */ - public function call(string $method, string $path = '', array $headers = [], array $params = []): array|string + protected function call(string $method, string $path = '', array $headers = [], array $params = []): array|string { $headers = array_merge($this->headers, $headers); $ch = curl_init((str_contains($path, 'http') ? $path.(($method == 'GET' && ! empty($params)) ? '?'.http_build_query($params) : '') : $this->endpoint.$path.(($method == 'GET' && ! empty($params)) ? '?'.http_build_query($params) : ''))); diff --git a/src/Transfer/Transfer.php b/src/Transfer/Transfer.php index 3e1336e..3ef4237 100644 --- a/src/Transfer/Transfer.php +++ b/src/Transfer/Transfer.php @@ -47,8 +47,8 @@ public function __construct(Source $source, Destination $destination) $this->destination = $destination; $this->cache = new Cache(); - $this->source->registerTransferCache($this->cache); - $this->destination->registerTransferCache($this->cache); + $this->source->registerCache($this->cache); + $this->destination->registerCache($this->cache); $this->destination->setSource($source); return $this; diff --git a/tests/e2e/Sources/Appwrite.php b/tests/e2e/Sources/AppwriteTest.php similarity index 87% rename from tests/e2e/Sources/Appwrite.php rename to tests/e2e/Sources/AppwriteTest.php index c58e8d8..35210d3 100644 --- a/tests/e2e/Sources/Appwrite.php +++ b/tests/e2e/Sources/AppwriteTest.php @@ -2,7 +2,7 @@ namespace Tests\E2E\Sources; -class Appwrite extends SourceTest +class AppwriteTest extends SourceTest { public function testExportResources(): void { diff --git a/tests/e2e/Sources/SourceTest.php b/tests/e2e/Sources/SourceTest.php index 35942d1..1c7daaa 100644 --- a/tests/e2e/Sources/SourceTest.php +++ b/tests/e2e/Sources/SourceTest.php @@ -24,9 +24,9 @@ public function testGetSupportedResources(): void } } - public function testTransferCache(): void + public function testCache(): void { - $this->source->registerTransferCache($this->createMock(\Utopia\Transfer\Cache::class)); + $this->source->registerCache($this->createMock(\Utopia\Transfer\Cache::class)); $this->assertNotNull($this->source->cache); } diff --git a/tests/e2e/adapters/MockSource.php b/tests/e2e/adapters/MockSource.php index 5609c8c..5ea6e7c 100644 --- a/tests/e2e/adapters/MockSource.php +++ b/tests/e2e/adapters/MockSource.php @@ -2,10 +2,10 @@ namespace Utopia\Tests; -use Utopia\Transfer\Destination; +use Utopia\Transfer\Source; use Utopia\Transfer\Transfer; -class MockSource extends Destination +class MockSource extends Source { public static function getName(): string { @@ -23,10 +23,6 @@ public function getSupportedResources(): array ]; } - public function importResources(array $resources, callable $callback): void - { - } - public function report(array $groups = []): array { return []; From 53ee401d9db731e4b4300ae96228c481b129d06e Mon Sep 17 00:00:00 2001 From: Bradley Schofield Date: Mon, 5 Jun 2023 13:19:31 +0100 Subject: [PATCH 49/70] Update NHost.php --- src/Transfer/Sources/NHost.php | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/Transfer/Sources/NHost.php b/src/Transfer/Sources/NHost.php index a87361e..19cc6d4 100644 --- a/src/Transfer/Sources/NHost.php +++ b/src/Transfer/Sources/NHost.php @@ -500,9 +500,6 @@ private function convertIndex(array $index, Collection $collection): Index|false return new Index($matches['name'], $matches['name'], $collection, $type, $attributes, $order); } else { - // $this->logs[Log::ERROR][] = new Log('Skipping index due to unsupported format: ' . $index['indexdef'] . ' for index: ' . $index['indexname'] . '. Transfers only support BTree.', \time()); - // Add error here for unsupported index format - return false; } } From 9f1c522dca189d322386dead2444e1bbb9d5b0e9 Mon Sep 17 00:00:00 2001 From: Bradley Schofield Date: Mon, 5 Jun 2023 13:25:25 +0100 Subject: [PATCH 50/70] Continue Addressing PR issues --- src/Transfer/{ResourceCache.php => Cache.php} | 0 src/Transfer/Destination.php | 4 ++-- src/Transfer/Destinations/Appwrite.php | 4 ++-- src/Transfer/Destinations/Local.php | 6 +++--- src/Transfer/Source.php | 2 +- src/Transfer/Sources/Appwrite.php | 4 +++- src/Transfer/Sources/Firebase.php | 1 + src/Transfer/Sources/NHost.php | 2 ++ src/Transfer/Transfer.php | 2 +- 9 files changed, 15 insertions(+), 10 deletions(-) rename src/Transfer/{ResourceCache.php => Cache.php} (100%) diff --git a/src/Transfer/ResourceCache.php b/src/Transfer/Cache.php similarity index 100% rename from src/Transfer/ResourceCache.php rename to src/Transfer/Cache.php diff --git a/src/Transfer/Destination.php b/src/Transfer/Destination.php index 466b400..dc0bc15 100644 --- a/src/Transfer/Destination.php +++ b/src/Transfer/Destination.php @@ -33,7 +33,7 @@ public function setSource(Source $source): self public function run(array $resources, callable $callback): void { $this->source->run($resources, function (array $resources) use ($callback) { - $this->importResources($resources, $callback); + $this->import($resources, $callback); }); } @@ -42,5 +42,5 @@ public function run(array $resources, callable $callback): void * * @param callable $callback (array $resources) */ - abstract public function importResources(array $resources, callable $callback): void; + abstract protected function import(array $resources, callable $callback): void; } diff --git a/src/Transfer/Destinations/Appwrite.php b/src/Transfer/Destinations/Appwrite.php index 1a8ee3d..dfd7642 100644 --- a/src/Transfer/Destinations/Appwrite.php +++ b/src/Transfer/Destinations/Appwrite.php @@ -214,10 +214,10 @@ public function report(array $resources = []): array } } - public function importResources(array $resources, callable $callback): void + protected function import(array $resources, callable $callback): void { foreach ($resources as $resource) { - /** @var resource $resource */ + /** @var Resource $resource */ switch ($resource->getGroup()) { case Transfer::GROUP_DATABASES: $responseResource = $this->importDatabaseResource($resource); diff --git a/src/Transfer/Destinations/Local.php b/src/Transfer/Destinations/Local.php index 6c5d3cf..656e248 100644 --- a/src/Transfer/Destinations/Local.php +++ b/src/Transfer/Destinations/Local.php @@ -79,7 +79,7 @@ public function report(array $resources = []): array return $report; } - public function sync(): void + private function sync(): void { $jsonEncodedData = \json_encode($this->data, JSON_PRETTY_PRINT); @@ -90,10 +90,10 @@ public function sync(): void \file_put_contents($this->path . '/backup.json', \json_encode($this->data, JSON_PRETTY_PRINT)); } - public function importResources(array $resources, callable $callback): void + protected function import(array $resources, callable $callback): void { foreach ($resources as $resource) { - /** @var resource $resource */ + /** @var Resource $resource */ switch ($resource->getName()) { case 'Deployment': /** @var Deployment $resource */ diff --git a/src/Transfer/Source.php b/src/Transfer/Source.php index 4c3f7fb..9598263 100644 --- a/src/Transfer/Source.php +++ b/src/Transfer/Source.php @@ -19,7 +19,7 @@ public function run(array $resources, callable $callback): void $this->transferCallback = function (array $returnedResources) use ($callback, $resources) { $prunedResurces = []; foreach ($returnedResources as $resource) { - /** @var resource $resource */ + /** @var Resource $resource */ if (! in_array($resource->getName(), $resources)) { $resource->setStatus(Resource::STATUS_SKIPPED); } else { diff --git a/src/Transfer/Sources/Appwrite.php b/src/Transfer/Sources/Appwrite.php index 10592f6..109c616 100644 --- a/src/Transfer/Sources/Appwrite.php +++ b/src/Transfer/Sources/Appwrite.php @@ -357,7 +357,7 @@ private function exportMemberships(int $batchSize) $cacheTeams = $this->cache->get(Team::getName()); foreach ($cacheTeams as $team) { - /** @param Team $team */ + /** @var Team $team */ while (true) { $memberships = []; @@ -625,6 +625,7 @@ private function exportCollections(int $batchSize) $databases = $this->cache->get(Database::getName()); foreach ($databases as $database) { + /** @var Database $database */ while (true) { $queries = [Query::limit($batchSize)]; $collections = []; @@ -806,6 +807,7 @@ private function exportFiles(int $batchSize) $buckets = $this->cache->get(Bucket::getName()); foreach ($buckets as $bucket) { + /** @var Bucket $bucket */ $lastDocument = null; while (true) { diff --git a/src/Transfer/Sources/Firebase.php b/src/Transfer/Sources/Firebase.php index 430e1e9..9305643 100644 --- a/src/Transfer/Sources/Firebase.php +++ b/src/Transfer/Sources/Firebase.php @@ -451,6 +451,7 @@ private function exportFiles(int $batchsize) $buckets = $this->cache->get(Bucket::getName()); foreach ($buckets as $bucket) { + /** @var Bucket $bucket */ $endpoint = 'https://storage.googleapis.com/storage/v1/b/'.$bucket->getId().'/o'; $nextPageToken = null; diff --git a/src/Transfer/Sources/NHost.php b/src/Transfer/Sources/NHost.php index 19cc6d4..7183376 100644 --- a/src/Transfer/Sources/NHost.php +++ b/src/Transfer/Sources/NHost.php @@ -271,6 +271,7 @@ private function exportCollections(int $batchSize) $databases = $this->cache->get(Database::getName()); foreach ($databases as $database) { + /** @var Database $database */ $statement = $this->pdo->prepare('SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = :database'); $statement->execute([':database' => $database->getName()]); $total = $statement->fetchColumn(); @@ -576,6 +577,7 @@ private function exportFiles(int $batchSize) $buckets = $this->cache->get(Bucket::getName()); foreach ($buckets as $bucket) { + /** @var Bucket $bucket */ $totalStatement = $this->pdo->prepare('SELECT COUNT(*) FROM storage.files WHERE bucket_id=:bucketId'); $totalStatement->execute([':bucketId' => $bucket->getId()]); $total = $totalStatement->fetchColumn(); diff --git a/src/Transfer/Transfer.php b/src/Transfer/Transfer.php index 3ef4237..ed5936d 100644 --- a/src/Transfer/Transfer.php +++ b/src/Transfer/Transfer.php @@ -77,7 +77,7 @@ public function getStatusCounters() foreach ($this->cache->getAll() as $resources) { foreach ($resources as $resource) { - /** @var resource $resource */ + /** @var Resource $resource */ if (! array_key_exists($resource->getName(), $status)) { $status[$resource->getName()] = [ Resource::STATUS_PENDING => 0, From 2e0cd4d4f1cde1311f9676b015cca750b284f19f Mon Sep 17 00:00:00 2001 From: Bradley Schofield Date: Wed, 14 Jun 2023 14:19:22 +0100 Subject: [PATCH 51/70] Continue work on tests --- .env.example | 20 ++-- .gitignore | 3 +- Dockerfile | 25 ++++- composer.json | 3 + docker-compose.yml | 11 ++- phpunit.xml | 4 +- src/Transfer/Resource.php | 19 ++-- src/Transfer/Sources/NHost.php | 96 +++++++++++-------- .../Transfer/E2E/Adapters/MockDestination.php | 86 +++++++++++++++++ tests/Transfer/E2E/Sources/Appwrite.php | 0 tests/Transfer/E2E/Sources/NHostTest.php | 49 ++++++++++ .../E2E/Sources/SourceCore.php} | 30 +++--- .../{ => Transfer}/resources/nhost/backup.tar | 0 tests/{ => Transfer}/resources/restore.sh | 0 .../resources/supabase/backup.tar | 0 .../{ => Transfer}/resources/updateBackups.sh | 0 tests/Unit/ConcreteResource.php | 24 ----- tests/e2e/Sources/Appwrite.php | 18 ---- tests/e2e/adapters/MockDestination.php | 43 --------- tests/e2e/adapters/MockSource.php | 51 +++++++++- tests/unit/ResourceCacheTest.php | 56 ----------- 21 files changed, 318 insertions(+), 220 deletions(-) create mode 100644 tests/Transfer/E2E/Adapters/MockDestination.php create mode 100644 tests/Transfer/E2E/Sources/Appwrite.php create mode 100644 tests/Transfer/E2E/Sources/NHostTest.php rename tests/{e2e/Sources/SourceTest.php => Transfer/E2E/Sources/SourceCore.php} (59%) rename tests/{ => Transfer}/resources/nhost/backup.tar (100%) rename tests/{ => Transfer}/resources/restore.sh (100%) rename tests/{ => Transfer}/resources/supabase/backup.tar (100%) rename tests/{ => Transfer}/resources/updateBackups.sh (100%) delete mode 100644 tests/Unit/ConcreteResource.php delete mode 100644 tests/e2e/Sources/Appwrite.php delete mode 100644 tests/e2e/adapters/MockDestination.php delete mode 100644 tests/unit/ResourceCacheTest.php diff --git a/.env.example b/.env.example index 6916e59..1723737 100644 --- a/.env.example +++ b/.env.example @@ -9,12 +9,18 @@ SOURCE_APPWRITE_TEST_KEY=xxxxxxxxxxxxxxxxxx FIREBASE_TEST_PROJECT=testProject FIREBASE_TEST_ACCOUNT='{type: "service_account", ...}' -SUPABASE_TEST_HOST=db.xxxxxxxxx.supabase.co +SSUPABASE_TEST_ENDPOINT=https://xxxxxxxxxxxx.supabase.co +SUPABASE_TEST_KEY=xxxxxxxxxxxxxxxxxxxxxxxxxxxxx +SUPABASE_TEST_HOST=db.xxxxxxxxxxxxxxxx.supabase.co SUPABASE_TEST_DATABASE=postgres -SUPABASE_TEST_USERNAME=xxxxxxxxxxxxxxxxxx -SUPABASE_TEST_PASSWORD=xxxxxxxxxxxxxxxxxx +SUPABASE_TEST_USERNAME=postgres +SUPABASE_TEST_PASSWORD=xxxxxxxxxxxxxxxxxxxxx + +NHOST_TEST_SUBDOMAIN=xxxxxxxxxxx +NHOST_TEST_REGION=eu-central-1 +NHOST_TEST_SECRET=xxxxxxxxxxxxxxxxx +NHOST_TEST_DATABASE=xxxxxxxxxxxxxxx +NHOST_TEST_USERNAME=postgres +NHOST_TEST_PASSWORD=xxxxxxxxxxxxxxx + -NHOST_TEST_HOST=db.xxxxxxxxx.nhost.run -NHOST_TEST_DATABASE=xxxxxxxxxxxxxxxxxx -NHOST_TEST_USERNAME=xxxxxxxxxxxxxxxxxx -NHOST_TEST_PASSWORD=xxxxxxxxxxxxxxxxxx diff --git a/.gitignore b/.gitignore index ab409cd..9c0bf12 100644 --- a/.gitignore +++ b/.gitignore @@ -6,4 +6,5 @@ composer.lock test-service-account.json .DS_Store localBackup/ -.vscode/ \ No newline at end of file +.vscode/ +.env.prod \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index ddd53d1..173bd98 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,10 +1,10 @@ FROM postgres:alpine3.18 as supabase-db -COPY ./tests/resources/supabase/backup.tar /docker-entrypoint-initdb.d/backup.tar -COPY ./tests/resources/restore.sh /docker-entrypoint-initdb.d/restore.sh +COPY ./tests/Transfer/resources/supabase/backup.tar /docker-entrypoint-initdb.d/backup.tar +COPY ./tests/Transfer/resources/restore.sh /docker-entrypoint-initdb.d/restore.sh FROM postgres:alpine3.18 as nhost-db -COPY ./tests/resources/nhost/backup.tar /docker-entrypoint-initdb.d/backup.tar -COPY ./tests/resources/restore.sh /docker-entrypoint-initdb.d/restore.sh +COPY ./tests/Transfer/resources/nhost/backup.tar /docker-entrypoint-initdb.d/backup.tar +COPY ./tests/Transfer/resources/restore.sh /docker-entrypoint-initdb.d/restore.sh # Use my fork of mockoon while waiting for range headers to be merged FROM node:14-alpine3.14 as mock-api @@ -15,4 +15,19 @@ RUN npm run build:libs RUN npm run build:cli RUN mv ./packages/cli/dist/run /usr/local/bin/mockoon -FROM php:8.0-fpm-alpine3.14 as tests \ No newline at end of file +FROM composer:2.0 as composer + +WORKDIR /usr/local/src/ + +COPY composer.lock /usr/local/src/ +COPY composer.json /usr/local/src/ + +RUN composer install --ignore-platform-reqs + +FROM php:8.0-fpm-alpine3.14 as tests +RUN set -ex && apk --no-cache add postgresql-dev +RUN docker-php-ext-install pdo pdo_pgsql +COPY ./src /usr/local/src +COPY ./tests /usr/local/src/tests +COPY --from=composer /usr/local/src/vendor /usr/local/src/vendor +CMD php ./vendor/bin/phpunit \ No newline at end of file diff --git a/composer.json b/composer.json index 710bce6..cf830d9 100644 --- a/composer.json +++ b/composer.json @@ -19,6 +19,9 @@ "Utopia\\Transfer\\": "src/Transfer" } }, + "autoload-dev": { + "psr-4": {"Utopia\\Tests\\": "tests/Transfer"} + }, "require": { "php": ">=8.0", "utopia-php/cli": "^0.13.0", diff --git a/docker-compose.yml b/docker-compose.yml index 45f865f..1dbd99a 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -7,6 +7,8 @@ services: target: supabase-db ports: - "5432:5432" + networks: + - tests environment: POSTGRES_USER: postgres POSTGRES_PASSWORD: postgres @@ -16,6 +18,8 @@ services: build: context: . target: nhost-db + networks: + - tests ports: - "5433:5432" environment: @@ -27,6 +31,8 @@ services: build: context: . target: tests + networks: + - tests volumes: - ./:/app working_dir: /app @@ -35,4 +41,7 @@ services: - nhost-db environment: - NHOST_DB_URL=postgres://postgres:postgres@nhost-db:5432/postgres - - SUPABASE_DB_URL=postgres://postgres:postgres@supabase-db:5432/postgres \ No newline at end of file + - SUPABASE_DB_URL=postgres://postgres:postgres@supabase-db:5432/postgres + +networks: + tests: \ No newline at end of file diff --git a/phpunit.xml b/phpunit.xml index 0658ab9..b4ec372 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -9,8 +9,8 @@ stopOnFailure="false" > - - ./tests/Unit + + ./tests/Transfer/E2E \ No newline at end of file diff --git a/src/Transfer/Resource.php b/src/Transfer/Resource.php index 971360a..9860c04 100644 --- a/src/Transfer/Resource.php +++ b/src/Transfer/Resource.php @@ -23,8 +23,7 @@ abstract class Resource */ public const STATUS_DISREGARDED = 'DISREGARDED'; - public const TYPE_ATTRIBUTE = 'Attribute'; - + // Master Resources public const TYPE_BUCKET = 'Bucket'; public const TYPE_COLLECTION = 'Collection'; @@ -35,22 +34,26 @@ abstract class Resource public const TYPE_FILE = 'File'; + public const TYPE_USER = 'User'; + + public const TYPE_TEAM = 'Team'; + + public const TYPE_MEMBERSHIP = 'Membership'; + public const TYPE_FUNCTION = 'Function'; + // Children (Resources that are created by other resources) + + public const TYPE_ATTRIBUTE = 'Attribute'; + public const TYPE_DEPLOYMENT = 'Deployment'; public const TYPE_HASH = 'Hash'; public const TYPE_INDEX = 'Index'; - public const TYPE_USER = 'User'; - public const TYPE_ENVVAR = 'EnvVar'; - public const TYPE_TEAM = 'Team'; - - public const TYPE_MEMBERSHIP = 'Membership'; - public const ALL_RESOURCES = [ self::TYPE_ATTRIBUTE, self::TYPE_BUCKET, diff --git a/src/Transfer/Sources/NHost.php b/src/Transfer/Sources/NHost.php index 317800a..02e41fa 100644 --- a/src/Transfer/Sources/NHost.php +++ b/src/Transfer/Sources/NHost.php @@ -2,6 +2,7 @@ namespace Utopia\Transfer\Sources; +use PDO; use Utopia\Transfer\Resource; use Utopia\Transfer\Resources\Auth\Hash; use Utopia\Transfer\Resources\Auth\User; @@ -50,12 +51,19 @@ public function __construct(string $subdomain, string $region, string $adminSecr $this->username = $username; $this->password = $password; $this->port = $port; + } - try { - $this->pdo = new \PDO('pgsql:host='.$this->subdomain.'.db.'.$this->region.'.nhost.run'.';port='.$this->port.';dbname='.$this->databaseName, $this->username, $this->password); - } catch (\PDOException $e) { - throw new \Exception('Failed to connect to database: '.$e->getMessage()); + public function getDatabase(): PDO + { + if (!$this->pdo) { + try { + $this->pdo = new \PDO('pgsql:host=' . $this->subdomain . '.db.' . $this->region . '.nhost.run' . ';port=' . $this->port . ';dbname=' . $this->databaseName, $this->username, $this->password); + } catch (\PDOException $e) { + throw new \Exception('Failed to connect to database: ' . $e->getMessage()); + } } + + return $this->pdo; } public static function getName(): string @@ -94,22 +102,22 @@ public function report(array $resources = []): array } try { - $this->pdo = new \PDO('pgsql:host='.$this->subdomain.'.db.'.$this->region.'.nhost.run'.';port='.$this->port.';dbname='.$this->databaseName, $this->username, $this->password); + $db = $this->getDatabase(); } catch (\PDOException $e) { - throw new \Exception('Failed to connect to database. PDO Code: '.$e->getCode().' Error: '.$e->getMessage()); + throw new \Exception('Failed to connect to database. PDO Code: ' . $e->getCode() . ' Error: ' . $e->getMessage()); } - if (! empty($this->pdo->errorCode())) { - throw new \Exception('Failed to connect to database. PDO Code: '.$this->pdo->errorCode().(empty($this->pdo->errorInfo()[2]) ? '' : ' Error: '.$this->pdo->errorInfo()[2])); + if (!empty($db->errorCode())) { + throw new \Exception('Failed to connect to database. PDO Code: ' . $db->errorCode() . (empty($db->errorInfo()[2]) ? '' : ' Error: ' . $db->errorInfo()[2])); } // Auth if (in_array(Resource::TYPE_USER, $resources)) { - $statement = $this->pdo->prepare('SELECT COUNT(*) FROM auth.users'); + $statement = $db->prepare('SELECT COUNT(*) FROM auth.users'); $statement->execute(); if ($statement->errorCode() !== '00000') { - throw new \Exception('Failed to access users table. Error: '.$statement->errorInfo()[2]); + throw new \Exception('Failed to access users table. Error: ' . $statement->errorInfo()[2]); } $report[Resource::TYPE_USER] = $statement->fetchColumn(); @@ -121,44 +129,44 @@ public function report(array $resources = []): array } if (in_array(Resource::TYPE_COLLECTION, $resources)) { - $statement = $this->pdo->prepare('SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = \'public\''); + $statement = $db->prepare('SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = \'public\''); $statement->execute(); if ($statement->errorCode() !== '00000') { - throw new \Exception('Failed to access tables table. Error: '.$statement->errorInfo()[2]); + throw new \Exception('Failed to access tables table. Error: ' . $statement->errorInfo()[2]); } $report[Resource::TYPE_COLLECTION] = $statement->fetchColumn(); } if (in_array(Resource::TYPE_ATTRIBUTE, $resources)) { - $statement = $this->pdo->prepare('SELECT COUNT(*) FROM information_schema.columns WHERE table_schema = \'public\''); + $statement = $db->prepare('SELECT COUNT(*) FROM information_schema.columns WHERE table_schema = \'public\''); $statement->execute(); if ($statement->errorCode() !== '00000') { - throw new \Exception('Failed to access columns table. Error: '.$statement->errorInfo()[2]); + throw new \Exception('Failed to access columns table. Error: ' . $statement->errorInfo()[2]); } $report[Resource::TYPE_ATTRIBUTE] = $statement->fetchColumn(); } if (in_array(Resource::TYPE_INDEX, $resources)) { - $statement = $this->pdo->prepare('SELECT COUNT(*) FROM pg_indexes WHERE schemaname = \'public\''); + $statement = $db->prepare('SELECT COUNT(*) FROM pg_indexes WHERE schemaname = \'public\''); $statement->execute(); if ($statement->errorCode() !== '00000') { - throw new \Exception('Failed to access indexes table. Error: '.$statement->errorInfo()[2]); + throw new \Exception('Failed to access indexes table. Error: ' . $statement->errorInfo()[2]); } $report[Resource::TYPE_INDEX] = $statement->fetchColumn(); } if (in_array(Resource::TYPE_DOCUMENT, $resources)) { - $statement = $this->pdo->prepare('SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = \'public\''); + $statement = $db->prepare('SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = \'public\''); $statement->execute(); if ($statement->errorCode() !== '00000') { - throw new \Exception('Failed to access tables table. Error: '.$statement->errorInfo()[2]); + throw new \Exception('Failed to access tables table. Error: ' . $statement->errorInfo()[2]); } $report[Resource::TYPE_DOCUMENT] = $statement->fetchColumn(); @@ -166,22 +174,22 @@ public function report(array $resources = []): array // Storage if (in_array(Resource::TYPE_BUCKET, $resources)) { - $statement = $this->pdo->prepare('SELECT COUNT(*) FROM storage.buckets'); + $statement = $db->prepare('SELECT COUNT(*) FROM storage.buckets'); $statement->execute(); if ($statement->errorCode() !== '00000') { - throw new \Exception('Failed to access buckets table. Error: '.$statement->errorInfo()[2]); + throw new \Exception('Failed to access buckets table. Error: ' . $statement->errorInfo()[2]); } $report[Resource::TYPE_BUCKET] = $statement->fetchColumn(); } if (in_array(Resource::TYPE_FILE, $resources)) { - $statement = $this->pdo->prepare('SELECT COUNT(*) FROM storage.files'); + $statement = $db->prepare('SELECT COUNT(*) FROM storage.files'); $statement->execute(); if ($statement->errorCode() !== '00000') { - throw new \Exception('Failed to access files table. Error: '.$statement->errorInfo()[2]); + throw new \Exception('Failed to access files table. Error: ' . $statement->errorInfo()[2]); } $report[Resource::TYPE_FILE] = $statement->fetchColumn(); @@ -199,12 +207,14 @@ protected function exportAuthGroup(int $batchSize, array $resources) private function exportUsers(int $batchSize) { - $total = $this->pdo->query('SELECT COUNT(*) FROM auth.users')->fetchColumn(); + $db = $this->getDatabase(); + + $total = $db->query('SELECT COUNT(*) FROM auth.users')->fetchColumn(); $offset = 0; while ($offset < $total) { - $statement = $this->pdo->prepare('SELECT * FROM auth.users order by created_at LIMIT :limit OFFSET :offset'); + $statement = $db->prepare('SELECT * FROM auth.users order by created_at LIMIT :limit OFFSET :offset'); $statement->bindValue(':limit', $batchSize, \PDO::PARAM_INT); $statement->bindValue(':offset', $offset, \PDO::PARAM_INT); $statement->execute(); @@ -269,16 +279,18 @@ private function exportDatabases(int $batchSize): void private function exportCollections(int $batchSize) { $databases = $this->cache->get(Database::getName()); + $db = $this->getDatabase(); foreach ($databases as $database) { - $statement = $this->pdo->prepare('SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = :database'); + /** @var Database $database */ + $statement = $db->prepare('SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = :database'); $statement->execute([':database' => $database->getName()]); $total = $statement->fetchColumn(); $offset = 0; while ($offset < $total) { - $statement = $this->pdo->prepare('SELECT table_name FROM information_schema.tables WHERE table_schema = \'public\' order by table_name LIMIT :limit OFFSET :offset'); + $statement = $db->prepare('SELECT table_name FROM information_schema.tables WHERE table_schema = \'public\' order by table_name LIMIT :limit OFFSET :offset'); $statement->execute([':limit' => $batchSize, ':offset' => $offset]); $tables = $statement->fetchAll(\PDO::FETCH_ASSOC); @@ -299,10 +311,11 @@ private function exportCollections(int $batchSize) private function exportAttributes(int $batchSize) { $collections = $this->cache->get(Collection::getName()); + $db = $this->getDatabase(); foreach ($collections as $collection) { /** @var Collection $collection */ - $statement = $this->pdo->prepare('SELECT * FROM information_schema."columns" where "table_name" = :tableName'); + $statement = $db->prepare('SELECT * FROM information_schema."columns" where "table_name" = :tableName'); $statement->bindValue(':tableName', $collection->getCollectionName(), \PDO::PARAM_STR); $statement->execute(); $databaseCollection = $statement->fetchAll(\PDO::FETCH_ASSOC); @@ -320,10 +333,11 @@ private function exportAttributes(int $batchSize) private function exportIndexes(int $batchSize) { $collections = $this->cache->get(Collection::getName()); + $db = $this->getDatabase(); foreach ($collections as $collection) { /** @var Collection $collection */ - $indexStatement = $this->pdo->prepare('SELECT indexname, indexdef FROM pg_indexes WHERE tablename = :tableName'); + $indexStatement = $db->prepare('SELECT indexname, indexdef FROM pg_indexes WHERE tablename = :tableName'); $indexStatement->bindValue(':tableName', $collection->getCollectionName(), \PDO::PARAM_STR); $indexStatement->execute(); @@ -342,18 +356,19 @@ private function exportIndexes(int $batchSize) private function exportDocuments(int $batchSize) { $databases = $this->cache->get(Database::getName()); + $db = $this->getDatabase(); foreach ($databases as $database) { /** @var Database $database */ $collections = $database->getCollections(); foreach ($collections as $collection) { - $total = $this->pdo->query('SELECT COUNT(*) FROM '.$collection->getCollectionName())->fetchColumn(); + $total = $db->query('SELECT COUNT(*) FROM ' . $collection->getCollectionName())->fetchColumn(); $offset = 0; while ($offset < $total) { - $statement = $this->pdo->prepare('SELECT row_to_json(t) FROM (SELECT * FROM '.$collection->getCollectionName().' LIMIT :limit OFFSET :offset) t;'); + $statement = $db->prepare('SELECT row_to_json(t) FROM (SELECT * FROM ' . $collection->getCollectionName() . ' LIMIT :limit OFFSET :offset) t;'); $statement->bindValue(':limit', $batchSize, \PDO::PARAM_INT); $statement->bindValue(':offset', $offset, \PDO::PARAM_INT); $statement->execute(); @@ -375,7 +390,7 @@ private function exportDocuments(int $batchSize) $processedData = []; foreach ($collectionAttributes as $attribute) { /** @var Attribute $attribute */ - if (! $attribute->getArray() && \is_array($data[$attribute->getKey()])) { + if (!$attribute->getArray() && \is_array($data[$attribute->getKey()])) { $processedData[$attribute->getKey()] = json_encode($data[$attribute->getKey()]); } else { $processedData[$attribute->getKey()] = $data[$attribute->getKey()]; @@ -396,7 +411,7 @@ private function convertAttribute(array $column, Collection $collection): Attrib $isArray = $column['data_type'] === 'ARRAY'; switch ($isArray ? str_replace('_', '', $column['udt_name']) : $column['data_type']) { - // Numbers + // Numbers case 'boolean': case 'bool': return new BoolAttribute($column['column_name'], $collection, $column['is_nullable'] === 'NO', $isArray, $column['column_default']); @@ -515,11 +530,11 @@ private function calculateUserTypes(array $user): array $types = []; - if (! empty($user['password_hash'])) { + if (!empty($user['password_hash'])) { $types[] = User::TYPE_EMAIL; } - if (! empty($user['phone_number'])) { + if (!empty($user['phone_number'])) { $types[] = User::TYPE_PHONE; } @@ -539,12 +554,13 @@ public function exportStorageGroup(int $batchSize, array $resources) protected function exportBuckets(int $batchSize) { - $total = $this->pdo->query('SELECT COUNT(*) FROM storage.buckets')->fetchColumn(); + $db = $this->getDatabase(); + $total = $db->query('SELECT COUNT(*) FROM storage.buckets')->fetchColumn(); $offset = 0; while ($offset < $total) { - $statement = $this->pdo->prepare('SELECT * FROM storage.buckets order by created_at LIMIT :limit OFFSET :offset'); + $statement = $db->prepare('SELECT * FROM storage.buckets order by created_at LIMIT :limit OFFSET :offset'); $statement->bindValue(':limit', $batchSize, \PDO::PARAM_INT); $statement->bindValue(':offset', $offset, \PDO::PARAM_INT); $statement->execute(); @@ -577,15 +593,17 @@ protected function exportBuckets(int $batchSize) public function exportFiles(int $batchSize) { $buckets = $this->cache->get(Bucket::getName()); + $db = $this->getDatabase(); foreach ($buckets as $bucket) { - $totalStatement = $this->pdo->prepare('SELECT COUNT(*) FROM storage.files WHERE bucket_id=:bucketId'); + /** @var Bucket $bucket */ + $totalStatement = $db->prepare('SELECT COUNT(*) FROM storage.files WHERE bucket_id=:bucketId'); $totalStatement->execute([':bucketId' => $bucket->getId()]); $total = $totalStatement->fetchColumn(); $offset = 0; while ($offset < $total) { - $statement = $this->pdo->prepare('SELECT * FROM storage.files WHERE bucket_id=:bucketId order by created_at LIMIT :limit OFFSET :offset'); + $statement = $db->prepare('SELECT * FROM storage.files WHERE bucket_id=:bucketId order by created_at LIMIT :limit OFFSET :offset'); $statement->bindValue(':limit', $batchSize, \PDO::PARAM_INT); $statement->bindValue(':offset', $offset, \PDO::PARAM_INT); $statement->bindValue(':bucketId', $bucket->getId(), \PDO::PARAM_STR); @@ -617,7 +635,7 @@ public function exportFile(File $file) $end = Transfer::STORAGE_MAX_CHUNK_SIZE - 1; $fileSize = $file->getSize(); - $response = $this->call('GET', $url."/v1/files/{$file->getId()}/presignedurl", [ + $response = $this->call('GET', $url . "/v1/files/{$file->getId()}/presignedurl", [ 'X-Hasura-Admin-Secret' => $this->adminSecret, ]); diff --git a/tests/Transfer/E2E/Adapters/MockDestination.php b/tests/Transfer/E2E/Adapters/MockDestination.php new file mode 100644 index 0000000..67d0ebf --- /dev/null +++ b/tests/Transfer/E2E/Adapters/MockDestination.php @@ -0,0 +1,86 @@ +getName()) { + case 'Deployment': + /** @var Deployment $resource */ + if ($resource->getStart() === 0) { + $this->data[$resource->getGroup()][$resource->getName()][$resource->getInternalId()] = $resource->asArray(); + } + + // file_put_contents($this->path . 'deployments/' . $resource->getId() . '.tar.gz', $resource->getData(), FILE_APPEND); + break; + case 'File': + //TODO: Handle Files and Deployments + // /** @var File $resource */ + + // // Handle folders + // if (str_contains($resource->getFileName(), '/')) { + // $folders = explode('/', $resource->getFileName()); + // $folderPath = $this->path . '/files'; + + // foreach ($folders as $folder) { + // $folderPath .= '/' . $folder; + + // if (!\file_exists($folderPath) && str_contains($folder, '.') === false) { + // mkdir($folderPath, 0777, true); + // } + // } + // } + + // if ($resource->getStart() === 0 && \file_exists($this->path . '/files/' . $resource->getFileName())) { + // unlink($this->path . '/files/' . $resource->getFileName()); + // } + + // file_put_contents($this->path . '/files/' . $resource->getFileName(), $resource->getData(), FILE_APPEND); + // break; + } + + $resource->setStatus(Resource::STATUS_SUCCESS); + $this->cache->update($resource); + } + + $callback($resources); + } + + public function report(array $groups = []): array + { + return []; + } +} diff --git a/tests/Transfer/E2E/Sources/Appwrite.php b/tests/Transfer/E2E/Sources/Appwrite.php new file mode 100644 index 0000000..e69de29 diff --git a/tests/Transfer/E2E/Sources/NHostTest.php b/tests/Transfer/E2E/Sources/NHostTest.php new file mode 100644 index 0000000..2856b70 --- /dev/null +++ b/tests/Transfer/E2E/Sources/NHostTest.php @@ -0,0 +1,49 @@ +source = new NHost( + 'xxxxxxxxxxxx', + 'eu-central-1', + 'xxxxxxxxxxxxxxxxxx', + 'xxxxxxxxxxxxxxx', + 'xxxxxxxxx', + 'xxxxxxxxxxxxxxxx' + ); + + $this->destination = new MockDestination(); + $this->transfer = new Transfer($this->source, $this->destination); + + // $this->source->pdo = new \PDO('pgsql:host=nhost-db' . ';port=5432;dbname=postgres', 'postgres', 'postgres'); + } + + public function testSourceReport(): void + { + // Test report all + $report = $this->source->report(); + + $this->assertNotEmpty($report); + } + + public function testRunTransfer(): void + { + $this->transfer->run( + $this->source->getSupportedResources(), + function ($data) { + $this->assertNotEmpty($data); + } + ); + } +} diff --git a/tests/e2e/Sources/SourceTest.php b/tests/Transfer/E2E/Sources/SourceCore.php similarity index 59% rename from tests/e2e/Sources/SourceTest.php rename to tests/Transfer/E2E/Sources/SourceCore.php index 35942d1..68e4cfa 100644 --- a/tests/e2e/Sources/SourceTest.php +++ b/tests/Transfer/E2E/Sources/SourceCore.php @@ -1,14 +1,28 @@ source) + throw new \Exception('Source not set'); + + $this->destination = new MockDestination(); + $this->transfer = new Transfer($this->source, $this->destination); + } public function testGetName(): void { @@ -30,16 +44,4 @@ public function testTransferCache(): void $this->assertNotNull($this->source->cache); } - - abstract public function testReport(): void; - - public function validateReport(array $report) - { - foreach ($report as $resource => $amount) { - $this->assertContains($resource, Resource::ALL_RESOURCES); - $this->assertIsInt($amount); - } - } - - abstract public function testExportResources(): void; } diff --git a/tests/resources/nhost/backup.tar b/tests/Transfer/resources/nhost/backup.tar similarity index 100% rename from tests/resources/nhost/backup.tar rename to tests/Transfer/resources/nhost/backup.tar diff --git a/tests/resources/restore.sh b/tests/Transfer/resources/restore.sh similarity index 100% rename from tests/resources/restore.sh rename to tests/Transfer/resources/restore.sh diff --git a/tests/resources/supabase/backup.tar b/tests/Transfer/resources/supabase/backup.tar similarity index 100% rename from tests/resources/supabase/backup.tar rename to tests/Transfer/resources/supabase/backup.tar diff --git a/tests/resources/updateBackups.sh b/tests/Transfer/resources/updateBackups.sh similarity index 100% rename from tests/resources/updateBackups.sh rename to tests/Transfer/resources/updateBackups.sh diff --git a/tests/Unit/ConcreteResource.php b/tests/Unit/ConcreteResource.php deleted file mode 100644 index 4ac245d..0000000 --- a/tests/Unit/ConcreteResource.php +++ /dev/null @@ -1,24 +0,0 @@ -source->report(); - - $this->assertIsArray($report); - $this->validateReport($report); - } -} diff --git a/tests/e2e/adapters/MockDestination.php b/tests/e2e/adapters/MockDestination.php deleted file mode 100644 index 80bab3b..0000000 --- a/tests/e2e/adapters/MockDestination.php +++ /dev/null @@ -1,43 +0,0 @@ -add($resource); - - $this->assertArrayHasKey($resource->getName(), $cache->getAll()); - $this->assertArrayHasKey($resource->getInternalId(), $cache->getAll()[$resource->getName()]); - $this->assertEquals($resource, $cache->getAll()[$resource->getName()][$resource->getInternalId()]); - - return $cache; - } - - /** - * @depends testAdd - */ - public function testRemove(Cache $cache) - { - $resources = $cache->get(ConcreteResource::getName()); - $resource = $resources[array_keys($resources)[0]]; - - $cache->remove($resource); - - $this->assertArrayNotHasKey($resource->getInternalId(), $cache->getAll()[$resource->getName()]); - - return $cache; - } - - /** - * @depends testRemove - */ - public function testWipe(Cache $cache) - { - $resource = new ConcreteResource(); - $cache->add($resource); - - $resource2 = new ConcreteResource(); - $cache->add($resource2); - - $cache->wipe(); - - $this->assertEmpty($cache->getAll()); - - return $cache; - } -} From 560bb93b0e977b624e69cc99a68067b22e6c42eb Mon Sep 17 00:00:00 2001 From: Bradley Schofield Date: Sat, 17 Jun 2023 16:44:03 +0100 Subject: [PATCH 52/70] Continue work on types --- src/Transfer/Destinations/Appwrite.php | 4 ++-- src/Transfer/Destinations/Local.php | 4 ++-- src/Transfer/Source.php | 4 ++-- src/Transfer/Sources/Appwrite.php | 2 +- src/Transfer/Sources/Firebase.php | 5 +++-- src/Transfer/Sources/NHost.php | 2 +- src/Transfer/Sources/Supabase.php | 4 ++-- src/Transfer/Target.php | 2 +- src/Transfer/Transfer.php | 2 +- 9 files changed, 15 insertions(+), 14 deletions(-) diff --git a/src/Transfer/Destinations/Appwrite.php b/src/Transfer/Destinations/Appwrite.php index 1a8ee3d..1ad3860 100644 --- a/src/Transfer/Destinations/Appwrite.php +++ b/src/Transfer/Destinations/Appwrite.php @@ -67,7 +67,7 @@ public static function getName(): string /** * Get Supported Resources */ - public function getSupportedResources(): array + static function getSupportedResources(): array { return [ // Auth @@ -217,7 +217,7 @@ public function report(array $resources = []): array public function importResources(array $resources, callable $callback): void { foreach ($resources as $resource) { - /** @var resource $resource */ + /** @var Resource $resource */ switch ($resource->getGroup()) { case Transfer::GROUP_DATABASES: $responseResource = $this->importDatabaseResource($resource); diff --git a/src/Transfer/Destinations/Local.php b/src/Transfer/Destinations/Local.php index 6c5d3cf..7308b56 100644 --- a/src/Transfer/Destinations/Local.php +++ b/src/Transfer/Destinations/Local.php @@ -42,7 +42,7 @@ public static function getName(): string /** * Get Supported Resources */ - public function getSupportedResources(): array + static function getSupportedResources(): array { return [ Resource::TYPE_ATTRIBUTE, @@ -93,7 +93,7 @@ public function sync(): void public function importResources(array $resources, callable $callback): void { foreach ($resources as $resource) { - /** @var resource $resource */ + /** @var Resource $resource */ switch ($resource->getName()) { case 'Deployment': /** @var Deployment $resource */ diff --git a/src/Transfer/Source.php b/src/Transfer/Source.php index 4c3f7fb..fae4f3d 100644 --- a/src/Transfer/Source.php +++ b/src/Transfer/Source.php @@ -19,8 +19,8 @@ public function run(array $resources, callable $callback): void $this->transferCallback = function (array $returnedResources) use ($callback, $resources) { $prunedResurces = []; foreach ($returnedResources as $resource) { - /** @var resource $resource */ - if (! in_array($resource->getName(), $resources)) { + /** @var Resource $resource */ + if (!in_array($resource->getName(), $resources)) { $resource->setStatus(Resource::STATUS_SKIPPED); } else { $prunedResurces[] = $resource; diff --git a/src/Transfer/Sources/Appwrite.php b/src/Transfer/Sources/Appwrite.php index 17df4ff..d5673f1 100644 --- a/src/Transfer/Sources/Appwrite.php +++ b/src/Transfer/Sources/Appwrite.php @@ -79,7 +79,7 @@ public static function getName(): string /** * Get Supported Resources */ - public function getSupportedResources(): array + static function getSupportedResources(): array { return [ // Auth diff --git a/src/Transfer/Sources/Firebase.php b/src/Transfer/Sources/Firebase.php index 6e9ab42..23abe07 100644 --- a/src/Transfer/Sources/Firebase.php +++ b/src/Transfer/Sources/Firebase.php @@ -104,7 +104,7 @@ public function call(string $method, string $path = '', array $headers = [], arr /** * Get Supported Resources */ - public function getSupportedResources(): array + static function getSupportedResources(): array { return [ // Auth @@ -451,6 +451,7 @@ public function exportFiles(int $batchsize) $buckets = $this->cache->get(Bucket::getName()); foreach ($buckets as $bucket) { + /** @var Bucket $bucket */ $endpoint = 'https://storage.googleapis.com/storage/v1/b/'.$bucket->getId().'/o'; $nextPageToken = null; @@ -467,7 +468,7 @@ public function exportFiles(int $batchsize) break; } - if (! isset($result['items'])) { + if (!isset($result['items'])) { break; } diff --git a/src/Transfer/Sources/NHost.php b/src/Transfer/Sources/NHost.php index 02e41fa..78c118c 100644 --- a/src/Transfer/Sources/NHost.php +++ b/src/Transfer/Sources/NHost.php @@ -74,7 +74,7 @@ public static function getName(): string /** * Get Supported Resources */ - public function getSupportedResources(): array + static function getSupportedResources(): array { return [ // Auth diff --git a/src/Transfer/Sources/Supabase.php b/src/Transfer/Sources/Supabase.php index 53a3a70..2663201 100644 --- a/src/Transfer/Sources/Supabase.php +++ b/src/Transfer/Sources/Supabase.php @@ -262,7 +262,7 @@ protected function exportBuckets(int $batchSize) $this->callback($transferBuckets); } - private function exportFiles(int $batchSize) + public function exportFiles(int $batchSize) { /** * TODO: Supabase has folders, with enough folders within folders this could cause us to hit the max name length @@ -306,7 +306,7 @@ private function exportFiles(int $batchSize) } } - private function exportFile(File $file) + public function exportFile(File $file) { $start = 0; $end = Transfer::STORAGE_MAX_CHUNK_SIZE - 1; diff --git a/src/Transfer/Target.php b/src/Transfer/Target.php index a4bb0d7..3adb8ce 100644 --- a/src/Transfer/Target.php +++ b/src/Transfer/Target.php @@ -35,7 +35,7 @@ abstract public static function getName(): string; /** * Get Supported Resources */ - abstract public function getSupportedResources(): array; + abstract static function getSupportedResources(): array; /** * Register Transfer Cache diff --git a/src/Transfer/Transfer.php b/src/Transfer/Transfer.php index 3e1336e..a7b7883 100644 --- a/src/Transfer/Transfer.php +++ b/src/Transfer/Transfer.php @@ -77,7 +77,7 @@ public function getStatusCounters() foreach ($this->cache->getAll() as $resources) { foreach ($resources as $resource) { - /** @var resource $resource */ + /** @var Resource $resource */ if (! array_key_exists($resource->getName(), $status)) { $status[$resource->getName()] = [ Resource::STATUS_PENDING => 0, From 6116d8d9308b5066ad5bb9b93d0e22c7527b5922 Mon Sep 17 00:00:00 2001 From: Bradley Schofield Date: Sat, 17 Jun 2023 17:02:15 +0100 Subject: [PATCH 53/70] Improve Null Username handling --- src/Transfer/Destinations/Appwrite.php | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Transfer/Destinations/Appwrite.php b/src/Transfer/Destinations/Appwrite.php index 2a4da67..0ed4439 100644 --- a/src/Transfer/Destinations/Appwrite.php +++ b/src/Transfer/Destinations/Appwrite.php @@ -543,7 +543,7 @@ public function importPasswordUser(User $user): array|null $hash->getSalt(), $hash->getSeparator(), $hash->getSigningKey(), - $user->getUsername() + empty($user->getUsername()) ? null : $user->getUsername() ); break; case Hash::BCRYPT: @@ -551,7 +551,7 @@ public function importPasswordUser(User $user): array|null $user->getId(), $user->getEmail(), $hash->getHash(), - $user->getUsername() + empty($user->getUsername()) ? null : $user->getUsername() ); break; case Hash::ARGON2: @@ -559,7 +559,7 @@ public function importPasswordUser(User $user): array|null $user->getId(), $user->getEmail(), $hash->getHash(), - $user->getUsername() + empty($user->getUsername()) ? null : $user->getUsername() ); break; case Hash::SHA256: @@ -568,7 +568,7 @@ public function importPasswordUser(User $user): array|null $user->getEmail(), $hash->getHash(), 'sha256', - $user->getUsername() + empty($user->getUsername()) ? null : $user->getUsername() ); break; case Hash::PHPASS: @@ -576,7 +576,7 @@ public function importPasswordUser(User $user): array|null $user->getId(), $user->getEmail(), $hash->getHash(), - $user->getUsername() + empty($user->getUsername()) ? null : $user->getUsername() ); break; case Hash::SCRYPT: @@ -589,7 +589,7 @@ public function importPasswordUser(User $user): array|null $hash->getPasswordMemory(), $hash->getPasswordParallel(), $hash->getPasswordLength(), - $user->getUsername() + empty($user->getUsername()) ? null : $user->getUsername() ); break; case Hash::PLAINTEXT: @@ -598,7 +598,7 @@ public function importPasswordUser(User $user): array|null $user->getEmail(), $user->getPhone(), $hash->getHash(), - $user->getUsername() + empty($user->getUsername()) ? null : $user->getUsername() ); break; } From c1861d279d4c5a0d11604fdc58dcae982627cc04 Mon Sep 17 00:00:00 2001 From: Bradley Schofield Date: Mon, 19 Jun 2023 12:10:05 +0100 Subject: [PATCH 54/70] Continue Work --- README.md | 2 +- composer.json | 1 - playground.php | 113 ++++++++++++++++----- src/Transfer/Cache.php | 48 ++++++++- src/Transfer/Destinations/Appwrite.php | 4 +- src/Transfer/Destinations/Local.php | 6 ++ src/Transfer/Resources/Auth/Membership.php | 3 + src/Transfer/Sources/Appwrite.php | 6 +- src/Transfer/Sources/Firebase.php | 8 +- src/Transfer/Sources/Supabase.php | 12 +-- src/Transfer/Target.php | 2 +- src/Transfer/Transfer.php | 2 +- 12 files changed, 163 insertions(+), 44 deletions(-) diff --git a/README.md b/README.md index 0874903..b8ed83b 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ ![Total Downloads](https://img.shields.io/packagist/dt/utopia-php/transfer.svg) [![Discord](https://img.shields.io/discord/564160730845151244?label=discord)](https://appwrite.io/discord) -Utopia Transfer is a simple and lite library to transfer and translate resources inbetween services. This library is aiming to be as simple and easy to learn and use. This library is maintained by the [Appwrite team](https://appwrite.io). +Utopia Transfer is a simple and lite library to transfer and transform resources inbetween services. This library is aiming to be as simple and easy to learn and use. This library is maintained by the [Appwrite team](https://appwrite.io). Although this library is part of the [Utopia Framework](https://github.com/utopia-php/framework) project it is dependency free and can be used as standalone with any other PHP project or framework. diff --git a/composer.json b/composer.json index cf830d9..72a150f 100644 --- a/composer.json +++ b/composer.json @@ -29,7 +29,6 @@ }, "require-dev": { "phpunit/phpunit": "^9.3", - "vimeo/psalm": "^5.6", "vlucas/phpdotenv": "^5.5", "laravel/pint": "^1.10" } diff --git a/playground.php b/playground.php index d99978a..9ec23c3 100644 --- a/playground.php +++ b/playground.php @@ -5,8 +5,9 @@ * * A place to test and debug the Transfer Library stuff */ -require_once __DIR__.'/vendor/autoload.php'; +require_once __DIR__ . '/vendor/autoload.php'; +use Appwrite\Query; use Dotenv\Dotenv; use Utopia\Transfer\Destinations\Appwrite as AppwriteDestination; use Utopia\Transfer\Destinations\Local; @@ -19,30 +20,32 @@ $dotenv = Dotenv::createImmutable(__DIR__); $dotenv->load(); +cleanupAppwrite(); + /** * Initialise All Source Adapters */ -$sourceAppwrite = new Appwrite( - $_ENV['SOURCE_APPWRITE_TEST_PROJECT'], - $_ENV['SOURCE_APPWRITE_TEST_ENDPOINT'], - $_ENV['SOURCE_APPWRITE_TEST_KEY'] -); +// $sourceAppwrite = new Appwrite( +// $_ENV['SOURCE_APPWRITE_TEST_PROJECT'], +// $_ENV['SOURCE_APPWRITE_TEST_ENDPOINT'], +// $_ENV['SOURCE_APPWRITE_TEST_KEY'] +// ); -$firebase = json_decode($_ENV['FIREBASE_TEST_ACCOUNT'], true); +// $firebase = json_decode($_ENV['FIREBASE_TEST_ACCOUNT'], true); -$sourceFirebase = new Firebase( - $firebase, - $firebase['project_id'] ?? '', -); +// $sourceFirebase = new Firebase( +// $firebase, +// $firebase['project_id'] ?? '', +// ); -$sourceNHost = new NHost( - $_ENV['NHOST_TEST_SUBDOMAIN'] ?? '', - $_ENV['NHOST_TEST_REGION'] ?? '', - $_ENV['NHOST_TEST_SECRET'] ?? '', - $_ENV['NHOST_TEST_DATABASE'] ?? '', - $_ENV['NHOST_TEST_USERNAME'] ?? '', - $_ENV['NHOST_TEST_PASSWORD'] ?? '', -); +// $sourceNHost = new NHost( +// $_ENV['NHOST_TEST_SUBDOMAIN'] ?? '', +// $_ENV['NHOST_TEST_REGION'] ?? '', +// $_ENV['NHOST_TEST_SECRET'] ?? '', +// $_ENV['NHOST_TEST_DATABASE'] ?? '', +// $_ENV['NHOST_TEST_USERNAME'] ?? '', +// $_ENV['NHOST_TEST_PASSWORD'] ?? '', +// ); $sourceSupabase = new Supabase( $_ENV['SUPABASE_TEST_ENDPOINT'] ?? '', @@ -62,21 +65,85 @@ $_ENV['DESTINATION_APPWRITE_TEST_KEY'] ); -$destinationLocal = new Local(__DIR__.'/localBackup/'); +$destinationLocal = new Local(__DIR__ . '/localBackup/'); /** * Initialise Transfer Class */ $transfer = new Transfer( - $sourceFirebase, - $destinationLocal + $sourceSupabase, + $destinationAppwrite ); /** * Run Transfer */ $transfer->run( - [Transfer::GROUP_STORAGE_RESOURCES, Transfer::GROUP_DATABASES_RESOURCES], + Supabase::getSupportedResources(), function (array $resources) { } ); + +function cleanupAppwrite() +{ + $client = new \Appwrite\Client(); + + $client + ->setEndpoint($_ENV['DESTINATION_APPWRITE_TEST_ENDPOINT']) + ->setProject($_ENV['DESTINATION_APPWRITE_TEST_PROJECT']) + ->setKey($_ENV['DESTINATION_APPWRITE_TEST_KEY']); + + $databaseService = new \Appwrite\Services\Databases($client); + $listDatabases = $databaseService->list(); + foreach ($listDatabases['databases'] as $database) { + $databaseId = $database['$id']; + $listCollections = $databaseService->listCollections($databaseId); + foreach ($listCollections['collections'] as $collection) { + $collectionId = $collection['$id']; + $listDocuments = $databaseService->listDocuments($databaseId, $collectionId); + foreach ($listDocuments['documents'] as $document) { + $documentId = $document['$id']; + $databaseService->deleteDocument($databaseId, $collectionId, $documentId); + } + } + + $databaseService->delete($databaseId); + } + + $usersService = new \Appwrite\Services\Users($client); + $listUsers = $usersService->list(); + if ($listUsers['total'] > count($listUsers['users'])) { + while ($listUsers['total'] > count($listUsers['users'])) { + $listUsers['users'] = array_merge($listUsers['users'], $usersService->list( + [Query::cursorAfter( + $listUsers['users'][count($listUsers['users']) - 1]['$id'] + )] + )['users']); + } + } + + foreach ($listUsers['users'] as $user) { + $userId = $user['$id']; + $usersService->delete($userId); + } + + $teamsService = new \Appwrite\Services\Teams($client); + $listTeams = $teamsService->list(); + foreach ($listTeams['teams'] as $team) { + $teamId = $team['$id']; + $teamsService->delete($teamId); + } + + $storageService = new \Appwrite\Services\Storage($client); + $listBuckets = $storageService->listBuckets(); + foreach ($listBuckets['buckets'] as $bucket) { + $bucketId = $bucket['$id']; + $listFiles = $storageService->listFiles($bucketId); + foreach ($listFiles['files'] as $file) { + $fileId = $file['$id']; + $storageService->deleteFile($bucketId, $fileId); + } + } +} + +var_dump($transfer->getStatusCounters()); diff --git a/src/Transfer/Cache.php b/src/Transfer/Cache.php index 4ec416f..f9de689 100644 --- a/src/Transfer/Cache.php +++ b/src/Transfer/Cache.php @@ -2,6 +2,11 @@ namespace Utopia\Transfer; +/** + * Cache stores a local version of all data copied over from the source, This can be used as reference point for + * previous transfers and also help the destination to determine what needs to be updated, modified, + * added or removed. It is also used for debugging and validation purposes. + */ class Cache { protected $cache = []; @@ -11,9 +16,17 @@ public function __construct() $this->cache = []; } + /** + * Add Resource + * + * Places the resource in the cache, in the cache backend this also gets assigned a unique ID. + * + * @param Resource $resource + * @return void + */ public function add($resource) { - if (! $resource->getInternalId()) { + if (!$resource->getInternalId()) { $resourceId = uniqid(); if (isset($this->cache[$resource->getName()][$resourceId])) { $resourceId = uniqid(); @@ -30,9 +43,18 @@ public function addAll(array $resources) } } + /** + * Update Resource + * + * Updates the resource in the cache, if the resource does not exist in the cache an exception is thrown. + * Use Add to add a new resource to the cache. + * + * @param Resource $resource + * @return void + */ public function update($resource) { - if (! in_array($resource, $this->cache[$resource->getName()])) { + if (!in_array($resource, $this->cache[$resource->getName()])) { throw new \Exception('Resource does not exist in cache'); } @@ -46,9 +68,17 @@ public function updateAll($resources) } } + /** + * Remove Resource + * + * Removes the resource from the cache, if the resource does not exist in the cache an exception is thrown. + * + * @param Resource $resource + * @return void + */ public function remove($resource) { - if (! in_array($resource, $this->cache[$resource->getName()])) { + if (!in_array($resource, $this->cache[$resource->getName()])) { throw new \Exception('Resource does not exist in cache'); } @@ -70,11 +100,23 @@ public function get($resource) } } + /** + * Get All Resources + * + * @return array + */ public function getAll() { return $this->cache; } + /** + * Wipe Cache + * + * Removes all resources from the cache. + * + * @return void + */ public function wipe() { $this->cache = []; diff --git a/src/Transfer/Destinations/Appwrite.php b/src/Transfer/Destinations/Appwrite.php index 0ed4439..6917893 100644 --- a/src/Transfer/Destinations/Appwrite.php +++ b/src/Transfer/Destinations/Appwrite.php @@ -502,7 +502,7 @@ public function importAuthResource(Resource $resource): Resource } if ($resource->getDisabled()) { - $userService->updateStatus($resource->getId(), ! $resource->getDisabled()); + $userService->updateStatus($resource->getId(), !$resource->getDisabled()); } break; @@ -517,6 +517,8 @@ public function importAuthResource(Resource $resource): Resource // $teamService->createMembership($resource->getTeam()->getId(), $resource->getRoles(), ) // break; } + + $resource->setStatus(Resource::STATUS_SUCCESS); } catch (\Exception $e) { $resource->setStatus(Resource::STATUS_ERROR, $e->getMessage()); } finally { diff --git a/src/Transfer/Destinations/Local.php b/src/Transfer/Destinations/Local.php index 5d8ddb3..73268ad 100644 --- a/src/Transfer/Destinations/Local.php +++ b/src/Transfer/Destinations/Local.php @@ -62,6 +62,9 @@ static function getSupportedResources(): array ]; } + /** + * Report checks if all resources are accessible and ready for writing. + */ public function report(array $resources = []): array { $report = []; @@ -79,6 +82,9 @@ public function report(array $resources = []): array return $report; } + /** + * Write all data to file + */ private function sync(): void { $jsonEncodedData = \json_encode($this->data, JSON_PRETTY_PRINT); diff --git a/src/Transfer/Resources/Auth/Membership.php b/src/Transfer/Resources/Auth/Membership.php index f582e51..971141f 100644 --- a/src/Transfer/Resources/Auth/Membership.php +++ b/src/Transfer/Resources/Auth/Membership.php @@ -5,6 +5,9 @@ use Utopia\Transfer\Resource; use Utopia\Transfer\Transfer; +/** + * Represents a membership of a user in a team + */ class Membership extends Resource { protected Team $team; diff --git a/src/Transfer/Sources/Appwrite.php b/src/Transfer/Sources/Appwrite.php index 270b7e8..73bd129 100644 --- a/src/Transfer/Sources/Appwrite.php +++ b/src/Transfer/Sources/Appwrite.php @@ -293,7 +293,7 @@ private function exportUsers(int $batchSize) '', $user['emailVerification'], $user['phoneVerification'], - ! $user['status'], + !$user['status'], $user['prefs'] ); @@ -748,11 +748,11 @@ private function calculateTypes(array $user): array $types = []; - if (! empty($user['email'])) { + if (!empty($user['email'])) { $types[] = User::TYPE_EMAIL; } - if (! empty($user['phone'])) { + if (!empty($user['phone'])) { $types[] = User::TYPE_PHONE; } diff --git a/src/Transfer/Sources/Firebase.php b/src/Transfer/Sources/Firebase.php index 3e92c26..ce3666a 100644 --- a/src/Transfer/Sources/Firebase.php +++ b/src/Transfer/Sources/Firebase.php @@ -297,7 +297,7 @@ private function calculateArrayType(Collection $collection, string $key, array $ $previousType = null; foreach ($data['values'] as $field) { - if (! $previousType) { + if (!$previousType) { $previousType = $this->convertAttribute($collection, $key, $field); } elseif ($previousType->getName() != ($this->convertAttribute($collection, $key, $field))->getName()) { $isSameType = false; @@ -340,12 +340,12 @@ private function exportCollection(Collection $collection, int $batchSize, bool $ // Calculate Schema and handle subcollections $documentSchema = []; foreach ($result['documents'] as $document) { - if (! isset($document['fields'])) { + if (!isset($document['fields'])) { continue; //TODO: Transfer Empty Documents } foreach ($document['fields'] as $key => $field) { - if (! isset($documentSchema[$key])) { + if (!isset($documentSchema[$key])) { $documentSchema[$key] = $this->convertAttribute($collection, $key, $field); } } @@ -438,7 +438,7 @@ private function exportBuckets(int $batchsize) $this->callback([new Bucket($bucket['id'], [], false, $bucket['name'])]); } - if (! isset($result['nextPageToken'])) { + if (!isset($result['nextPageToken'])) { break; } diff --git a/src/Transfer/Sources/Supabase.php b/src/Transfer/Sources/Supabase.php index 26c9354..7f03a96 100644 --- a/src/Transfer/Sources/Supabase.php +++ b/src/Transfer/Sources/Supabase.php @@ -61,7 +61,7 @@ public function report(array $resources = []): array throw new \Exception('Failed to connect to database. PDO Code: '.$e->getCode().' Error: '.$e->getMessage()); } - if (! empty($this->pdo->errorCode())) { + if (!empty($this->pdo->errorCode())) { throw new \Exception('Failed to connect to database. PDO Code: '.$this->pdo->errorCode().(empty($this->pdo->errorInfo()[2]) ? '' : ' Error: '.$this->pdo->errorInfo()[2])); } @@ -186,8 +186,8 @@ private function exportUsers(int $batchSize) $user['phone'] ?? '', $this->calculateAuthTypes($user), '', - ! empty($user['email_confirmed_at']), - ! empty($user['phone_confirmed_at']), + !empty($user['email_confirmed_at']), + !empty($user['phone_confirmed_at']), false, [] ); @@ -216,11 +216,11 @@ private function calculateAuthTypes(array $user): array $types = []; - if (! empty($user['encrypted_password'])) { + if (!empty($user['encrypted_password'])) { $types[] = User::TYPE_EMAIL; } - if (! empty($user['phone'])) { + if (!empty($user['phone'])) { $types[] = User::TYPE_PHONE; } @@ -249,7 +249,7 @@ protected function exportBuckets(int $batchSize) foreach ($buckets as $bucket) { $transferBuckets[] = new Bucket( - $bucket['id'], + '', [], false, $bucket['name'], diff --git a/src/Transfer/Target.php b/src/Transfer/Target.php index 0af0338..a6ac944 100644 --- a/src/Transfer/Target.php +++ b/src/Transfer/Target.php @@ -71,7 +71,7 @@ abstract public function report(array $resources = []): array; protected function call(string $method, string $path = '', array $headers = [], array $params = []): array|string { $headers = array_merge($this->headers, $headers); - $ch = curl_init((str_contains($path, 'http') ? $path.(($method == 'GET' && ! empty($params)) ? '?'.http_build_query($params) : '') : $this->endpoint.$path.(($method == 'GET' && ! empty($params)) ? '?'.http_build_query($params) : ''))); + $ch = curl_init((str_contains($path, 'http') ? $path.(($method == 'GET' && !empty($params)) ? '?'.http_build_query($params) : '') : $this->endpoint.$path.(($method == 'GET' && !empty($params)) ? '?'.http_build_query($params) : ''))); $responseHeaders = []; $responseStatus = -1; $responseType = ''; diff --git a/src/Transfer/Transfer.php b/src/Transfer/Transfer.php index ed5936d..d3bfa6e 100644 --- a/src/Transfer/Transfer.php +++ b/src/Transfer/Transfer.php @@ -78,7 +78,7 @@ public function getStatusCounters() foreach ($this->cache->getAll() as $resources) { foreach ($resources as $resource) { /** @var Resource $resource */ - if (! array_key_exists($resource->getName(), $status)) { + if (!array_key_exists($resource->getName(), $status)) { $status[$resource->getName()] = [ Resource::STATUS_PENDING => 0, Resource::STATUS_SUCCESS => 0, From 75c4371238c232175e2109fe4c7578cec174e4c9 Mon Sep 17 00:00:00 2001 From: Bradley Schofield Date: Wed, 21 Jun 2023 15:53:30 +0100 Subject: [PATCH 55/70] Update .env.example Co-authored-by: Christy Jacob --- .env.example | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.env.example b/.env.example index 1723737..9349b00 100644 --- a/.env.example +++ b/.env.example @@ -9,7 +9,7 @@ SOURCE_APPWRITE_TEST_KEY=xxxxxxxxxxxxxxxxxx FIREBASE_TEST_PROJECT=testProject FIREBASE_TEST_ACCOUNT='{type: "service_account", ...}' -SSUPABASE_TEST_ENDPOINT=https://xxxxxxxxxxxx.supabase.co +SUPABASE_TEST_ENDPOINT=https://xxxxxxxxxxxx.supabase.co SUPABASE_TEST_KEY=xxxxxxxxxxxxxxxxxxxxxxxxxxxxx SUPABASE_TEST_HOST=db.xxxxxxxxxxxxxxxx.supabase.co SUPABASE_TEST_DATABASE=postgres From e3af0cb3332f5a68c8afba93b817565536151057 Mon Sep 17 00:00:00 2001 From: Bradley Schofield Date: Wed, 21 Jun 2023 15:53:38 +0100 Subject: [PATCH 56/70] Update tests/e2e/adapters/MockSource.php Co-authored-by: Christy Jacob --- tests/e2e/adapters/MockSource.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/e2e/adapters/MockSource.php b/tests/e2e/adapters/MockSource.php index 2e1a463..c9b6bb7 100644 --- a/tests/e2e/adapters/MockSource.php +++ b/tests/e2e/adapters/MockSource.php @@ -5,7 +5,7 @@ use Utopia\Transfer\Source; use Utopia\Transfer\Transfer; -class MockSource extends Source +class Mock extends Source { public static function getName(): string { From 4aa03e92a51090d3837897511839f32b05b2319f Mon Sep 17 00:00:00 2001 From: Bradley Schofield Date: Wed, 21 Jun 2023 15:55:00 +0100 Subject: [PATCH 57/70] Continue work on PR --- src/Transfer/Resource.php | 24 +++++++++++++------ .../e2e/adapters/{MockSource.php => Mock.php} | 2 +- 2 files changed, 18 insertions(+), 8 deletions(-) rename tests/e2e/adapters/{MockSource.php => Mock.php} (96%) diff --git a/src/Transfer/Resource.php b/src/Transfer/Resource.php index 9860c04..80fa444 100644 --- a/src/Transfer/Resource.php +++ b/src/Transfer/Resource.php @@ -87,9 +87,9 @@ abstract class Resource protected string $status = self::STATUS_PENDING; /** - * Reason for the status + * message for the status */ - protected string $reason = ''; + protected string $message = ''; /** * Gets the name of the adapter. @@ -148,20 +148,30 @@ public function getStatus(): string /** * Set Status */ - public function setStatus(string $status, string $reason = ''): self + public function setStatus(string $status, string $message = ''): self { $this->status = $status; - $this->reason = $reason; + $this->message = $message; return $this; } /** - * Get Reason + * Get message */ - public function getReason(): string + public function getMessage(): string { - return $this->reason; + return $this->message; + } + + /** + * Set message + */ + public function setMessage(string $message): self + { + $this->message = $message; + + return $this; } /** diff --git a/tests/e2e/adapters/MockSource.php b/tests/e2e/adapters/Mock.php similarity index 96% rename from tests/e2e/adapters/MockSource.php rename to tests/e2e/adapters/Mock.php index c9b6bb7..b80a318 100644 --- a/tests/e2e/adapters/MockSource.php +++ b/tests/e2e/adapters/Mock.php @@ -12,7 +12,7 @@ public static function getName(): string return 'MockSource'; } - public function getSupportedResources(): array + static function getSupportedResources(): array { return [ Transfer::GROUP_AUTH, From b92c71125d8338edcd2c1066b8164efee6113b57 Mon Sep 17 00:00:00 2001 From: Bradley Schofield Date: Wed, 21 Jun 2023 15:55:36 +0100 Subject: [PATCH 58/70] Update src/Transfer/Resources/Database/Attribute.php Co-authored-by: Christy Jacob --- src/Transfer/Resources/Database/Attribute.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Transfer/Resources/Database/Attribute.php b/src/Transfer/Resources/Database/Attribute.php index 4081669..9dcb0cd 100644 --- a/src/Transfer/Resources/Database/Attribute.php +++ b/src/Transfer/Resources/Database/Attribute.php @@ -7,7 +7,7 @@ abstract class Attribute extends Resource { - public const TYPE_STRING = 'stringAttribute'; + public const TYPE_STRING = 'string'; public const TYPE_INTEGER = 'intAttribute'; From 209cb69efb0ebcdc29637356cf6b9483eeedbb67 Mon Sep 17 00:00:00 2001 From: Bradley Schofield Date: Wed, 21 Jun 2023 15:56:05 +0100 Subject: [PATCH 59/70] Update Attribute.php --- src/Transfer/Resources/Database/Attribute.php | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/Transfer/Resources/Database/Attribute.php b/src/Transfer/Resources/Database/Attribute.php index 4081669..9a3b0d8 100644 --- a/src/Transfer/Resources/Database/Attribute.php +++ b/src/Transfer/Resources/Database/Attribute.php @@ -7,25 +7,25 @@ abstract class Attribute extends Resource { - public const TYPE_STRING = 'stringAttribute'; + public const TYPE_STRING = 'string'; - public const TYPE_INTEGER = 'intAttribute'; + public const TYPE_INTEGER = 'int'; - public const TYPE_FLOAT = 'floatAttribute'; + public const TYPE_FLOAT = 'float'; - public const TYPE_BOOLEAN = 'boolAttribute'; + public const TYPE_BOOLEAN = 'bool'; - public const TYPE_DATETIME = 'dateTimeAttribute'; + public const TYPE_DATETIME = 'dateTime'; - public const TYPE_EMAIL = 'emailAttribute'; + public const TYPE_EMAIL = 'email'; - public const TYPE_ENUM = 'enumAttribute'; + public const TYPE_ENUM = 'enum'; - public const TYPE_IP = 'IPAttribute'; + public const TYPE_IP = 'IP'; - public const TYPE_URL = 'URLAttribute'; + public const TYPE_URL = 'URL'; - public const TYPE_RELATIONSHIP = 'relationshipAttribute'; + public const TYPE_RELATIONSHIP = 'relationship'; protected string $key; From 2119dfbb3e7276ac0083e0a7499f1a1296935445 Mon Sep 17 00:00:00 2001 From: Bradley Schofield Date: Wed, 21 Jun 2023 16:07:24 +0100 Subject: [PATCH 60/70] Continue work on attributes --- src/Transfer/Destinations/Appwrite.php | 42 +++++++++--------- .../{BoolAttribute.php => Boolean.php} | 4 +- .../{DateTimeAttribute.php => DateTime.php} | 4 +- .../{FloatAttribute.php => Decimal.php} | 4 +- .../{URLAttribute.php => Email.php} | 4 +- .../{EnumAttribute.php => Enum.php} | 4 +- .../Attributes/{IPAttribute.php => IP.php} | 4 +- .../{IntAttribute.php => Integer.php} | 4 +- ...tionshipAttribute.php => Relationship.php} | 4 +- .../{StringAttribute.php => Text.php} | 4 +- .../{EmailAttribute.php => URL.php} | 4 +- src/Transfer/Sources/Appwrite.php | 44 ++++++++++--------- src/Transfer/Sources/Firebase.php | 32 +++++++------- src/Transfer/Sources/NHost.php | 26 +++++------ tests/Transfer/E2E/Sources/Appwrite.php | 0 15 files changed, 93 insertions(+), 91 deletions(-) rename src/Transfer/Resources/Database/Attributes/{BoolAttribute.php => Boolean.php} (93%) rename src/Transfer/Resources/Database/Attributes/{DateTimeAttribute.php => DateTime.php} (90%) rename src/Transfer/Resources/Database/Attributes/{FloatAttribute.php => Decimal.php} (95%) rename src/Transfer/Resources/Database/Attributes/{URLAttribute.php => Email.php} (91%) rename src/Transfer/Resources/Database/Attributes/{EnumAttribute.php => Enum.php} (94%) rename src/Transfer/Resources/Database/Attributes/{IPAttribute.php => IP.php} (91%) rename src/Transfer/Resources/Database/Attributes/{IntAttribute.php => Integer.php} (95%) rename src/Transfer/Resources/Database/Attributes/{RelationshipAttribute.php => Relationship.php} (96%) rename src/Transfer/Resources/Database/Attributes/{StringAttribute.php => Text.php} (93%) rename src/Transfer/Resources/Database/Attributes/{EmailAttribute.php => URL.php} (91%) delete mode 100644 tests/Transfer/E2E/Sources/Appwrite.php diff --git a/src/Transfer/Destinations/Appwrite.php b/src/Transfer/Destinations/Appwrite.php index 6917893..46b7d3d 100644 --- a/src/Transfer/Destinations/Appwrite.php +++ b/src/Transfer/Destinations/Appwrite.php @@ -15,16 +15,16 @@ use Utopia\Transfer\Resources\Auth\Membership; use Utopia\Transfer\Resources\Auth\User; use Utopia\Transfer\Resources\Database\Attribute; -use Utopia\Transfer\Resources\Database\Attributes\BoolAttribute; -use Utopia\Transfer\Resources\Database\Attributes\DateTimeAttribute; -use Utopia\Transfer\Resources\Database\Attributes\EmailAttribute; -use Utopia\Transfer\Resources\Database\Attributes\EnumAttribute; -use Utopia\Transfer\Resources\Database\Attributes\FloatAttribute; -use Utopia\Transfer\Resources\Database\Attributes\IntAttribute; -use Utopia\Transfer\Resources\Database\Attributes\IPAttribute; -use Utopia\Transfer\Resources\Database\Attributes\RelationshipAttribute; -use Utopia\Transfer\Resources\Database\Attributes\StringAttribute; -use Utopia\Transfer\Resources\Database\Attributes\URLAttribute; +use Utopia\Transfer\Resources\Database\Attributes\Boolean; +use Utopia\Transfer\Resources\Database\Attributes\DateTime; +use Utopia\Transfer\Resources\Database\Attributes\Email; +use Utopia\Transfer\Resources\Database\Attributes\Enum; +use Utopia\Transfer\Resources\Database\Attributes\Decimal; +use Utopia\Transfer\Resources\Database\Attributes\Integer; +use Utopia\Transfer\Resources\Database\Attributes\IP; +use Utopia\Transfer\Resources\Database\Attributes\Relationship; +use Utopia\Transfer\Resources\Database\Attributes\Text; +use Utopia\Transfer\Resources\Database\Attributes\URL; use Utopia\Transfer\Resources\Database\Collection; use Utopia\Transfer\Resources\Database\Database; use Utopia\Transfer\Resources\Database\Document; @@ -304,43 +304,43 @@ public function createAttribute(Attribute $attribute): void switch ($attribute->getTypeName()) { case Attribute::TYPE_STRING: - /** @var StringAttribute $attribute */ + /** @var Text $attribute */ $databaseService->createStringAttribute($attribute->getCollection()->getDatabase()->getId(), $attribute->getCollection()->getId(), $attribute->getKey(), $attribute->getSize(), $attribute->getRequired(), $attribute->getDefault(), $attribute->getArray()); break; case Attribute::TYPE_INTEGER: - /** @var IntAttribute $attribute */ + /** @var Integer $attribute */ $databaseService->createIntegerAttribute($attribute->getCollection()->getDatabase()->getId(), $attribute->getCollection()->getId(), $attribute->getKey(), $attribute->getRequired(), $attribute->getMin(), $attribute->getMax() ?? null, $attribute->getDefault(), $attribute->getArray()); break; case Attribute::TYPE_FLOAT: - /** @var FloatAttribute $attribute */ + /** @var Decimal $attribute */ $databaseService->createFloatAttribute($attribute->getCollection()->getDatabase()->getId(), $attribute->getCollection()->getId(), $attribute->getKey(), $attribute->getRequired(), null, null, $attribute->getDefault(), $attribute->getArray()); break; case Attribute::TYPE_BOOLEAN: - /** @var BoolAttribute $attribute */ + /** @var Boolean $attribute */ $databaseService->createBooleanAttribute($attribute->getCollection()->getDatabase()->getId(), $attribute->getCollection()->getId(), $attribute->getKey(), $attribute->getRequired(), $attribute->getDefault(), $attribute->getArray()); break; case Attribute::TYPE_DATETIME: - /** @var DateTimeAttribute $attribute */ - $databaseService->createDateTimeAttribute($attribute->getCollection()->getDatabase()->getId(), $attribute->getCollection()->getId(), $attribute->getKey(), $attribute->getRequired(), $attribute->getDefault(), $attribute->getArray()); + /** @var DateTime $attribute */ + $databaseService->createDatetimeAttribute($attribute->getCollection()->getDatabase()->getId(), $attribute->getCollection()->getId(), $attribute->getKey(), $attribute->getRequired(), $attribute->getDefault(), $attribute->getArray()); break; case Attribute::TYPE_EMAIL: - /** @var EmailAttribute $attribute */ + /** @var Email $attribute */ $databaseService->createEmailAttribute($attribute->getCollection()->getDatabase()->getId(), $attribute->getCollection()->getId(), $attribute->getKey(), $attribute->getRequired(), $attribute->getDefault(), $attribute->getArray()); break; case Attribute::TYPE_IP: - /** @var IPAttribute $attribute */ - $databaseService->createIPAttribute($attribute->getCollection()->getDatabase()->getId(), $attribute->getCollection()->getId(), $attribute->getKey(), $attribute->getRequired(), $attribute->getDefault(), $attribute->getArray()); + /** @var IP $attribute */ + $databaseService->createIpAttribute($attribute->getCollection()->getDatabase()->getId(), $attribute->getCollection()->getId(), $attribute->getKey(), $attribute->getRequired(), $attribute->getDefault(), $attribute->getArray()); break; case Attribute::TYPE_URL: /** @var URLAttribute $attribute */ $databaseService->createUrlAttribute($attribute->getCollection()->getDatabase()->getId(), $attribute->getCollection()->getId(), $attribute->getKey(), $attribute->getRequired(), $attribute->getDefault(), $attribute->getArray()); break; case Attribute::TYPE_ENUM: - /** @var EnumAttribute $attribute */ + /** @var Enum $attribute */ $databaseService->createEnumAttribute($attribute->getCollection()->getDatabase()->getId(), $attribute->getCollection()->getId(), $attribute->getKey(), $attribute->getElements(), $attribute->getRequired(), $attribute->getDefault(), $attribute->getArray()); break; case Attribute::TYPE_RELATIONSHIP: - /** @var RelationshipAttribute $attribute */ + /** @var Relationship $attribute */ $databaseService->createRelationshipAttribute($attribute->getCollection()->getDatabase()->getId(), $attribute->getCollection()->getId(), $attribute->getRelatedCollection(), $attribute->getRelationType(), $attribute->getTwoWay(), $attribute->getKey(), $attribute->getTwoWayKey(), $attribute->getOnDelete()); break; default: diff --git a/src/Transfer/Resources/Database/Attributes/BoolAttribute.php b/src/Transfer/Resources/Database/Attributes/Boolean.php similarity index 93% rename from src/Transfer/Resources/Database/Attributes/BoolAttribute.php rename to src/Transfer/Resources/Database/Attributes/Boolean.php index c5db96f..b72f531 100644 --- a/src/Transfer/Resources/Database/Attributes/BoolAttribute.php +++ b/src/Transfer/Resources/Database/Attributes/Boolean.php @@ -5,7 +5,7 @@ use Utopia\Transfer\Resources\Database\Attribute; use Utopia\Transfer\Resources\Database\Collection; -class BoolAttribute extends Attribute +class Boolean extends Attribute { protected string $key; @@ -26,7 +26,7 @@ public function __construct(string $key, Collection $collection, bool $required public function getTypeName(): string { - return 'boolAttribute'; + return Attribute::TYPE_BOOLEAN; } public function getDefault(): ?bool diff --git a/src/Transfer/Resources/Database/Attributes/DateTimeAttribute.php b/src/Transfer/Resources/Database/Attributes/DateTime.php similarity index 90% rename from src/Transfer/Resources/Database/Attributes/DateTimeAttribute.php rename to src/Transfer/Resources/Database/Attributes/DateTime.php index e2e5680..a8b4d5a 100644 --- a/src/Transfer/Resources/Database/Attributes/DateTimeAttribute.php +++ b/src/Transfer/Resources/Database/Attributes/DateTime.php @@ -5,7 +5,7 @@ use Utopia\Transfer\Resources\Database\Attribute; use Utopia\Transfer\Resources\Database\Collection; -class DateTimeAttribute extends Attribute +class DateTime extends Attribute { protected ?string $default; @@ -30,6 +30,6 @@ public function setDefault(string $default): void public function getTypeName(): string { - return 'dateTimeAttribute'; + return Attribute::TYPE_DATETIME; } } diff --git a/src/Transfer/Resources/Database/Attributes/FloatAttribute.php b/src/Transfer/Resources/Database/Attributes/Decimal.php similarity index 95% rename from src/Transfer/Resources/Database/Attributes/FloatAttribute.php rename to src/Transfer/Resources/Database/Attributes/Decimal.php index f33b275..9222646 100644 --- a/src/Transfer/Resources/Database/Attributes/FloatAttribute.php +++ b/src/Transfer/Resources/Database/Attributes/Decimal.php @@ -5,7 +5,7 @@ use Utopia\Transfer\Resources\Database\Attribute; use Utopia\Transfer\Resources\Database\Collection; -class FloatAttribute extends Attribute +class Decimal extends Attribute { protected ?float $default; @@ -28,7 +28,7 @@ public function __construct(string $key, Collection $collection, bool $required public function getTypeName(): string { - return 'floatAttribute'; + return Attribute::TYPE_FLOAT; } public function getMin(): float|null diff --git a/src/Transfer/Resources/Database/Attributes/URLAttribute.php b/src/Transfer/Resources/Database/Attributes/Email.php similarity index 91% rename from src/Transfer/Resources/Database/Attributes/URLAttribute.php rename to src/Transfer/Resources/Database/Attributes/Email.php index 3834c74..64b2df3 100644 --- a/src/Transfer/Resources/Database/Attributes/URLAttribute.php +++ b/src/Transfer/Resources/Database/Attributes/Email.php @@ -5,7 +5,7 @@ use Utopia\Transfer\Resources\Database\Attribute; use Utopia\Transfer\Resources\Database\Collection; -class URLAttribute extends Attribute +class Email extends Attribute { protected ?string $default; @@ -30,6 +30,6 @@ public function setDefault(string $default): void public function getTypeName(): string { - return 'URLAttribute'; + return Attribute::TYPE_EMAIL; } } diff --git a/src/Transfer/Resources/Database/Attributes/EnumAttribute.php b/src/Transfer/Resources/Database/Attributes/Enum.php similarity index 94% rename from src/Transfer/Resources/Database/Attributes/EnumAttribute.php rename to src/Transfer/Resources/Database/Attributes/Enum.php index 5c8a85f..6880c54 100644 --- a/src/Transfer/Resources/Database/Attributes/EnumAttribute.php +++ b/src/Transfer/Resources/Database/Attributes/Enum.php @@ -5,7 +5,7 @@ use Utopia\Transfer\Resources\Database\Attribute; use Utopia\Transfer\Resources\Database\Collection; -class EnumAttribute extends Attribute +class Enum extends Attribute { protected ?string $default; @@ -24,7 +24,7 @@ public function __construct(string $key, Collection $collection, array $elements public function getTypeName(): string { - return 'enumAttribute'; + return Attribute::TYPE_ENUM; } public function getElements(): array diff --git a/src/Transfer/Resources/Database/Attributes/IPAttribute.php b/src/Transfer/Resources/Database/Attributes/IP.php similarity index 91% rename from src/Transfer/Resources/Database/Attributes/IPAttribute.php rename to src/Transfer/Resources/Database/Attributes/IP.php index ae46b0e..a204918 100644 --- a/src/Transfer/Resources/Database/Attributes/IPAttribute.php +++ b/src/Transfer/Resources/Database/Attributes/IP.php @@ -5,7 +5,7 @@ use Utopia\Transfer\Resources\Database\Attribute; use Utopia\Transfer\Resources\Database\Collection; -class IPAttribute extends Attribute +class IP extends Attribute { protected ?string $default; @@ -30,6 +30,6 @@ public function setDefault(string $default): void public function getTypeName(): string { - return 'IPAttribute'; + return Attribute::TYPE_IP; } } diff --git a/src/Transfer/Resources/Database/Attributes/IntAttribute.php b/src/Transfer/Resources/Database/Attributes/Integer.php similarity index 95% rename from src/Transfer/Resources/Database/Attributes/IntAttribute.php rename to src/Transfer/Resources/Database/Attributes/Integer.php index 5d46c1c..f84e2f5 100644 --- a/src/Transfer/Resources/Database/Attributes/IntAttribute.php +++ b/src/Transfer/Resources/Database/Attributes/Integer.php @@ -5,7 +5,7 @@ use Utopia\Transfer\Resources\Database\Attribute; use Utopia\Transfer\Resources\Database\Collection; -class IntAttribute extends Attribute +class Integer extends Attribute { protected ?int $default; @@ -28,7 +28,7 @@ public function __construct(string $key, Collection $collection, bool $required public function getTypeName(): string { - return 'intAttribute'; + return Attribute::TYPE_INTEGER; } public function getMin(): int|null diff --git a/src/Transfer/Resources/Database/Attributes/RelationshipAttribute.php b/src/Transfer/Resources/Database/Attributes/Relationship.php similarity index 96% rename from src/Transfer/Resources/Database/Attributes/RelationshipAttribute.php rename to src/Transfer/Resources/Database/Attributes/Relationship.php index 24e9c1d..59190c0 100644 --- a/src/Transfer/Resources/Database/Attributes/RelationshipAttribute.php +++ b/src/Transfer/Resources/Database/Attributes/Relationship.php @@ -5,7 +5,7 @@ use Utopia\Transfer\Resources\Database\Attribute; use Utopia\Transfer\Resources\Database\Collection; -class RelationshipAttribute extends Attribute +class Relationship extends Attribute { protected string $relatedCollection; @@ -32,7 +32,7 @@ public function __construct(string $key, Collection $collection, bool $required public function getTypeName(): string { - return 'relationshipAttribute'; + return Attribute::TYPE_RELATIONSHIP; } public function getRelatedCollection(): string diff --git a/src/Transfer/Resources/Database/Attributes/StringAttribute.php b/src/Transfer/Resources/Database/Attributes/Text.php similarity index 93% rename from src/Transfer/Resources/Database/Attributes/StringAttribute.php rename to src/Transfer/Resources/Database/Attributes/Text.php index 46abde5..1b97d40 100644 --- a/src/Transfer/Resources/Database/Attributes/StringAttribute.php +++ b/src/Transfer/Resources/Database/Attributes/Text.php @@ -5,7 +5,7 @@ use Utopia\Transfer\Resources\Database\Attribute; use Utopia\Transfer\Resources\Database\Collection; -class StringAttribute extends Attribute +class Text extends Attribute { protected ?string $default; @@ -23,7 +23,7 @@ public function __construct(string $key, Collection $collection, bool $required public function getTypeName(): string { - return 'stringAttribute'; + return Attribute::TYPE_STRING; } public function getSize(): int diff --git a/src/Transfer/Resources/Database/Attributes/EmailAttribute.php b/src/Transfer/Resources/Database/Attributes/URL.php similarity index 91% rename from src/Transfer/Resources/Database/Attributes/EmailAttribute.php rename to src/Transfer/Resources/Database/Attributes/URL.php index 3203735..9e6ec41 100644 --- a/src/Transfer/Resources/Database/Attributes/EmailAttribute.php +++ b/src/Transfer/Resources/Database/Attributes/URL.php @@ -5,7 +5,7 @@ use Utopia\Transfer\Resources\Database\Attribute; use Utopia\Transfer\Resources\Database\Collection; -class EmailAttribute extends Attribute +class URL extends Attribute { protected ?string $default; @@ -30,6 +30,6 @@ public function setDefault(string $default): void public function getTypeName(): string { - return 'emailAttribute'; + return Attribute::TYPE_URL;; } } diff --git a/src/Transfer/Sources/Appwrite.php b/src/Transfer/Sources/Appwrite.php index 73bd129..0113bce 100644 --- a/src/Transfer/Sources/Appwrite.php +++ b/src/Transfer/Sources/Appwrite.php @@ -15,16 +15,18 @@ use Utopia\Transfer\Resources\Auth\Membership; use Utopia\Transfer\Resources\Auth\User; use Utopia\Transfer\Resources\Database\Attribute; -use Utopia\Transfer\Resources\Database\Attributes\BoolAttribute; -use Utopia\Transfer\Resources\Database\Attributes\DateTimeAttribute; -use Utopia\Transfer\Resources\Database\Attributes\EmailAttribute; -use Utopia\Transfer\Resources\Database\Attributes\EnumAttribute; -use Utopia\Transfer\Resources\Database\Attributes\FloatAttribute; -use Utopia\Transfer\Resources\Database\Attributes\IntAttribute; -use Utopia\Transfer\Resources\Database\Attributes\IPAttribute; -use Utopia\Transfer\Resources\Database\Attributes\RelationshipAttribute; -use Utopia\Transfer\Resources\Database\Attributes\StringAttribute; -use Utopia\Transfer\Resources\Database\Attributes\URLAttribute; +use Utopia\Transfer\Resources\Database\Attributes\Bool; +use Utopia\Transfer\Resources\Database\Attributes\Boolean; +use Utopia\Transfer\Resources\Database\Attributes\DateTime; +use Utopia\Transfer\Resources\Database\Attributes\Decimal; +use Utopia\Transfer\Resources\Database\Attributes\Email; +use Utopia\Transfer\Resources\Database\Attributes\Enum; +use Utopia\Transfer\Resources\Database\Attributes\Float; +use Utopia\Transfer\Resources\Database\Attributes\Integer; +use Utopia\Transfer\Resources\Database\Attributes\IP; +use Utopia\Transfer\Resources\Database\Attributes\Relationship; +use Utopia\Transfer\Resources\Database\Attributes\Text; +use Utopia\Transfer\Resources\Database\Attributes\URL; use Utopia\Transfer\Resources\Database\Collection; use Utopia\Transfer\Resources\Database\Database; use Utopia\Transfer\Resources\Database\Document; @@ -474,7 +476,7 @@ private function convertAttribute(array $value, Collection $collection): Attribu switch ($value['type']) { case 'string': if (!isset($value['format'])) { - return new StringAttribute( + return new Text( $value['key'], $collection, $value['required'], @@ -486,7 +488,7 @@ private function convertAttribute(array $value, Collection $collection): Attribu switch ($value['format']) { case 'email': - return new EmailAttribute( + return new Email( $value['key'], $collection, $value['required'], @@ -494,7 +496,7 @@ private function convertAttribute(array $value, Collection $collection): Attribu $value['default'] ); case 'enum': - return new EnumAttribute( + return new Enum( $value['key'], $collection, $value['elements'], @@ -503,7 +505,7 @@ private function convertAttribute(array $value, Collection $collection): Attribu $value['default'] ); case 'url': - return new URLAttribute( + return new URL( $value['key'], $collection, $value['required'], @@ -511,7 +513,7 @@ private function convertAttribute(array $value, Collection $collection): Attribu $value['default'] ); case 'ip': - return new IPAttribute( + return new IP( $value['key'], $collection, $value['required'], @@ -519,7 +521,7 @@ private function convertAttribute(array $value, Collection $collection): Attribu $value['default'] ); case 'datetime': - return new DateTimeAttribute( + return new DateTime( $value['key'], $collection, $value['required'], @@ -527,7 +529,7 @@ private function convertAttribute(array $value, Collection $collection): Attribu $value['default'] ); default: - return new StringAttribute( + return new Text( $value['key'], $collection, $value['required'], @@ -537,7 +539,7 @@ private function convertAttribute(array $value, Collection $collection): Attribu ); } case 'boolean': - return new BoolAttribute( + return new Boolean( $value['key'], $collection, $value['required'], @@ -545,7 +547,7 @@ private function convertAttribute(array $value, Collection $collection): Attribu $value['default'] ); case 'integer': - return new IntAttribute( + return new Integer( $value['key'], $collection, $value['required'], @@ -555,7 +557,7 @@ private function convertAttribute(array $value, Collection $collection): Attribu $value['max'] ?? 0 ); case 'double': - return new FloatAttribute( + return new Decimal( $value['key'], $collection, $value['required'], @@ -565,7 +567,7 @@ private function convertAttribute(array $value, Collection $collection): Attribu $value['max'] ?? 0 ); case 'relationship': - return new RelationshipAttribute( + return new Relationship( $value['key'], $collection, $value['required'], diff --git a/src/Transfer/Sources/Firebase.php b/src/Transfer/Sources/Firebase.php index ce3666a..db395d6 100644 --- a/src/Transfer/Sources/Firebase.php +++ b/src/Transfer/Sources/Firebase.php @@ -6,11 +6,11 @@ use Utopia\Transfer\Resources\Auth\Hash; use Utopia\Transfer\Resources\Auth\User; use Utopia\Transfer\Resources\Database\Attribute; -use Utopia\Transfer\Resources\Database\Attributes\BoolAttribute; -use Utopia\Transfer\Resources\Database\Attributes\DateTimeAttribute; -use Utopia\Transfer\Resources\Database\Attributes\FloatAttribute; -use Utopia\Transfer\Resources\Database\Attributes\IntAttribute; -use Utopia\Transfer\Resources\Database\Attributes\StringAttribute; +use Utopia\Transfer\Resources\Database\Attributes\Boolean; +use Utopia\Transfer\Resources\Database\Attributes\DateTime; +use Utopia\Transfer\Resources\Database\Attributes\Decimal; +use Utopia\Transfer\Resources\Database\Attributes\Integer; +use Utopia\Transfer\Resources\Database\Attributes\Text; use Utopia\Transfer\Resources\Database\Collection; use Utopia\Transfer\Resources\Database\Database; use Utopia\Transfer\Resources\Database\Document; @@ -265,25 +265,25 @@ private function exportDB(int $batchSize, bool $pushDocuments, Database $databas private function convertAttribute(Collection $collection, string $key, array $field): Attribute { if (array_key_exists('booleanValue', $field)) { - return new BoolAttribute($key, $collection, false, false, null); + return new Boolean($key, $collection, false, false, null); } elseif (array_key_exists('bytesValue', $field)) { - return new StringAttribute($key, $collection, false, false, null, 1000000); + return new Text($key, $collection, false, false, null, 1000000); } elseif (array_key_exists('doubleValue', $field)) { - return new FloatAttribute($key, $collection, false, false, null); + return new Decimal($key, $collection, false, false, null); } elseif (array_key_exists('integerValue', $field)) { - return new IntAttribute($key, $collection, false, false, null); + return new Integer($key, $collection, false, false, null); } elseif (array_key_exists('mapValue', $field)) { - return new StringAttribute($key, $collection, false, false, null, 1000000); + return new Text($key, $collection, false, false, null, 1000000); } elseif (array_key_exists('nullValue', $field)) { - return new StringAttribute($key, $collection, false, false, null, 1000000); + return new Text($key, $collection, false, false, null, 1000000); } elseif (array_key_exists('referenceValue', $field)) { - return new StringAttribute($key, $collection, false, false, null, 1000000); //TODO: This should be a reference attribute + return new Text($key, $collection, false, false, null, 1000000); //TODO: This should be a reference attribute } elseif (array_key_exists('stringValue', $field)) { - return new StringAttribute($key, $collection, false, false, null, 1000000); + return new Text($key, $collection, false, false, null, 1000000); } elseif (array_key_exists('timestampValue', $field)) { - return new DateTimeAttribute($key, $collection, false, false, null); + return new DateTime($key, $collection, false, false, null); } elseif (array_key_exists('geoPointValue', $field)) { - return new StringAttribute($key, $collection, false, false, null, 1000000); + return new Text($key, $collection, false, false, null, 1000000); } elseif (array_key_exists('arrayValue', $field)) { return $this->calculateArrayType($collection, $key, $field['arrayValue']); } else { @@ -310,7 +310,7 @@ private function calculateArrayType(Collection $collection, string $key, array $ return $previousType; } else { - return new StringAttribute($key, $collection, false, true, null, 1000000); + return new Text($key, $collection, false, true, null, 1000000); } } diff --git a/src/Transfer/Sources/NHost.php b/src/Transfer/Sources/NHost.php index 46c10ac..fafeb58 100644 --- a/src/Transfer/Sources/NHost.php +++ b/src/Transfer/Sources/NHost.php @@ -7,11 +7,11 @@ use Utopia\Transfer\Resources\Auth\Hash; use Utopia\Transfer\Resources\Auth\User; use Utopia\Transfer\Resources\Database\Attribute; -use Utopia\Transfer\Resources\Database\Attributes\BoolAttribute; -use Utopia\Transfer\Resources\Database\Attributes\DateTimeAttribute; -use Utopia\Transfer\Resources\Database\Attributes\FloatAttribute; -use Utopia\Transfer\Resources\Database\Attributes\IntAttribute; -use Utopia\Transfer\Resources\Database\Attributes\StringAttribute; +use Utopia\Transfer\Resources\Database\Attributes\Boolean; +use Utopia\Transfer\Resources\Database\Attributes\DateTime; +use Utopia\Transfer\Resources\Database\Attributes\Decimal; +use Utopia\Transfer\Resources\Database\Attributes\Integer; +use Utopia\Transfer\Resources\Database\Attributes\Text; use Utopia\Transfer\Resources\Database\Collection; use Utopia\Transfer\Resources\Database\Database; use Utopia\Transfer\Resources\Database\Document; @@ -414,24 +414,24 @@ private function convertAttribute(array $column, Collection $collection): Attrib // Numbers case 'boolean': case 'bool': - return new BoolAttribute($column['column_name'], $collection, $column['is_nullable'] === 'NO', $isArray, $column['column_default']); + return new Boolean($column['column_name'], $collection, $column['is_nullable'] === 'NO', $isArray, $column['column_default']); case 'smallint': case 'int2': - return new IntAttribute($column['column_name'], $collection, $column['is_nullable'] === 'NO', $isArray, $column['column_default'], -32768, 32767); + return new Integer($column['column_name'], $collection, $column['is_nullable'] === 'NO', $isArray, $column['column_default'], -32768, 32767); case 'integer': case 'int4': - return new IntAttribute($column['column_name'], $collection, $column['is_nullable'] === 'NO', $isArray, $column['column_default'], -2147483648, 2147483647); + return new Integer($column['column_name'], $collection, $column['is_nullable'] === 'NO', $isArray, $column['column_default'], -2147483648, 2147483647); case 'bigint': case 'int8': case 'numeric': - return new IntAttribute($column['column_name'], $collection, $column['is_nullable'] === 'NO', $isArray, $column['column_default']); + return new Integer($column['column_name'], $collection, $column['is_nullable'] === 'NO', $isArray, $column['column_default']); case 'decimal': case 'real': case 'double precision': case 'float4': case 'float8': case 'money': - return new FloatAttribute($column['column_name'], $collection, $column['is_nullable'] === 'NO', $isArray, $column['column_default']); + return new Decimal($column['column_name'], $collection, $column['is_nullable'] === 'NO', $isArray, $column['column_default']); // Time (Conversion happens with documents) case 'timestamp with time zone': case 'date': @@ -442,7 +442,7 @@ private function convertAttribute(array $column, Collection $collection): Attrib case 'time': case 'timetz': case 'interval': - return new DateTimeAttribute($column['column_name'], $collection, $column['is_nullable'] === 'NO', $isArray, null); + return new DateTime($column['column_name'], $collection, $column['is_nullable'] === 'NO', $isArray, null); break; // Strings and Objects case 'uuid': @@ -453,7 +453,7 @@ private function convertAttribute(array $column, Collection $collection): Attrib case 'jsonb': case 'varchar': case 'bytea': - return new StringAttribute( + return new Text( $column['column_name'], $collection, $column['is_nullable'] === 'NO', @@ -463,7 +463,7 @@ private function convertAttribute(array $column, Collection $collection): Attrib ); break; default: - return new StringAttribute( + return new Text( $column['column_name'], $collection, $column['is_nullable'] === 'NO', diff --git a/tests/Transfer/E2E/Sources/Appwrite.php b/tests/Transfer/E2E/Sources/Appwrite.php deleted file mode 100644 index e69de29..0000000 From 28730fad75c3f402972de4c063b289a47163bfb7 Mon Sep 17 00:00:00 2001 From: Bradley Schofield Date: Wed, 21 Jun 2023 16:07:48 +0100 Subject: [PATCH 61/70] Update tests/Transfer/E2E/Sources/SourceCore.php Co-authored-by: Christy Jacob --- tests/Transfer/E2E/Sources/SourceCore.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Transfer/E2E/Sources/SourceCore.php b/tests/Transfer/E2E/Sources/SourceCore.php index 2c73ad2..4bb5973 100644 --- a/tests/Transfer/E2E/Sources/SourceCore.php +++ b/tests/Transfer/E2E/Sources/SourceCore.php @@ -9,7 +9,7 @@ use Utopia\Transfer\Source; use Utopia\Transfer\Transfer; -abstract class SourceCore extends TestCase +abstract class Base extends TestCase { protected ?Transfer $transfer = null; protected ?Source $source = null; From 99ea59d7415d862448ee82ba161e31b2c290ea60 Mon Sep 17 00:00:00 2001 From: Bradley Schofield Date: Wed, 21 Jun 2023 16:08:53 +0100 Subject: [PATCH 62/70] Update src/Transfer/Source.php Co-authored-by: Christy Jacob --- src/Transfer/Source.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Transfer/Source.php b/src/Transfer/Source.php index fae4f3d..e2e6d2c 100644 --- a/src/Transfer/Source.php +++ b/src/Transfer/Source.php @@ -86,7 +86,7 @@ public function exportResources(array $resources, int $batchSize) * @param array $resources Resources to export * @return void */ - abstract protected function exportAuthGroup(int $batchSize, array $resources); + abstract protected function exportGroupAuth(int $batchSize, array $resources); /** * Export Databases Group From 38d12350fe0fc7578e0663a7ec997caa0ed51ccf Mon Sep 17 00:00:00 2001 From: Bradley Schofield Date: Wed, 21 Jun 2023 16:11:15 +0100 Subject: [PATCH 63/70] Continue Work --- src/Transfer/Source.php | 14 +++++++------- .../E2E/Adapters/{MockDestination.php => Mock.php} | 6 +++--- tests/Transfer/E2E/Sources/NHostTest.php | 6 +++--- 3 files changed, 13 insertions(+), 13 deletions(-) rename tests/Transfer/E2E/Adapters/{MockDestination.php => Mock.php} (95%) diff --git a/src/Transfer/Source.php b/src/Transfer/Source.php index e2e6d2c..e35a536 100644 --- a/src/Transfer/Source.php +++ b/src/Transfer/Source.php @@ -65,16 +65,16 @@ public function exportResources(array $resources, int $batchSize) foreach ($groups as $group => $resources) { switch ($group) { case Transfer::GROUP_AUTH: - $this->exportAuthGroup($batchSize, $resources); + $this->exportGroupAuth($batchSize, $resources); break; case Transfer::GROUP_DATABASES: - $this->exportDatabasesGroup($batchSize, $resources); + $this->exportGroupDatabases($batchSize, $resources); break; case Transfer::GROUP_STORAGE: - $this->exportStorageGroup($batchSize, $resources); + $this->exportGroupStorage($batchSize, $resources); break; case Transfer::GROUP_FUNCTIONS: - $this->exportFunctionsGroup($batchSize, $resources); + $this->exportGroupFunctions($batchSize, $resources); break; } } @@ -95,7 +95,7 @@ abstract protected function exportGroupAuth(int $batchSize, array $resources); * @param array $resources Resources to export * @return void */ - abstract protected function exportDatabasesGroup(int $batchSize, array $resources); + abstract protected function exportGroupDatabases(int $batchSize, array $resources); /** * Export Storage Group @@ -104,7 +104,7 @@ abstract protected function exportDatabasesGroup(int $batchSize, array $resource * @param array $resources Resources to export * @return void */ - abstract protected function exportStorageGroup(int $batchSize, array $resources); + abstract protected function exportGroupStorage(int $batchSize, array $resources); /** * Export Functions Group @@ -113,5 +113,5 @@ abstract protected function exportStorageGroup(int $batchSize, array $resources) * @param array $resources Resources to export * @return void */ - abstract protected function exportFunctionsGroup(int $batchSize, array $resources); + abstract protected function exportGroupFunctions(int $batchSize, array $resources); } diff --git a/tests/Transfer/E2E/Adapters/MockDestination.php b/tests/Transfer/E2E/Adapters/Mock.php similarity index 95% rename from tests/Transfer/E2E/Adapters/MockDestination.php rename to tests/Transfer/E2E/Adapters/Mock.php index 67d0ebf..b0de6bf 100644 --- a/tests/Transfer/E2E/Adapters/MockDestination.php +++ b/tests/Transfer/E2E/Adapters/Mock.php @@ -4,16 +4,16 @@ use Utopia\Transfer\Destination; use Utopia\Transfer\Resource; -class MockDestination extends Destination +class Mock extends Destination { public array $data = []; public static function getName(): string { - return 'MockDestination'; + return 'Mock'; } - public function getSupportedResources(): array + static function getSupportedResources(): array { return [ Resource::TYPE_ATTRIBUTE, diff --git a/tests/Transfer/E2E/Sources/NHostTest.php b/tests/Transfer/E2E/Sources/NHostTest.php index 2856b70..68af962 100644 --- a/tests/Transfer/E2E/Sources/NHostTest.php +++ b/tests/Transfer/E2E/Sources/NHostTest.php @@ -4,13 +4,13 @@ use Utopia\Transfer\Sources\NHost; use Utopia\Transfer\Transfer; -use Utopia\Tests\E2E\Adapters\MockDestination; +use Utopia\Tests\E2E\Adapters\Mock; class NHostTest extends SourceCore { protected ?NHost $source = null; protected ?Transfer $transfer = null; - protected ?MockDestination $destination = null; + protected ?Mock $destination = null; public function __construct() { @@ -23,7 +23,7 @@ public function __construct() 'xxxxxxxxxxxxxxxx' ); - $this->destination = new MockDestination(); + $this->destination = new Mock(); $this->transfer = new Transfer($this->source, $this->destination); // $this->source->pdo = new \PDO('pgsql:host=nhost-db' . ';port=5432;dbname=postgres', 'postgres', 'postgres'); From 16c6c50b67784f19cafd021671984340ecc3c844 Mon Sep 17 00:00:00 2001 From: Bradley Schofield Date: Wed, 21 Jun 2023 18:15:45 +0100 Subject: [PATCH 64/70] Normalise const convention --- src/Transfer/Resource.php | 42 ++++++++++++++-------------- src/Transfer/Resources/Auth/Hash.php | 18 ++++++------ src/Transfer/Transfer.php | 12 ++++---- 3 files changed, 36 insertions(+), 36 deletions(-) diff --git a/src/Transfer/Resource.php b/src/Transfer/Resource.php index 80fa444..a35c0a2 100644 --- a/src/Transfer/Resource.php +++ b/src/Transfer/Resource.php @@ -4,55 +4,55 @@ abstract class Resource { - public const STATUS_PENDING = 'PENDING'; + public const STATUS_PENDING = 'pending'; - public const STATUS_SUCCESS = 'SUCCESS'; + public const STATUS_SUCCESS = 'success'; - public const STATUS_ERROR = 'ERROR'; + public const STATUS_ERROR = 'error'; - public const STATUS_SKIPPED = 'SKIP'; + public const STATUS_SKIPPED = 'skip'; - public const STATUS_PROCESSING = 'PROCESSING'; + public const STATUS_PROCESSING = 'processing'; - public const STATUS_WARNING = 'WARNING'; + public const STATUS_WARNING = 'warning'; /** * For some transfers (namely Firebase) we have to keep resources in cache that do not necessarily need to be Transferred * This status is used to mark resources that are not going to be transferred but are still needed for the transfer to work * e.g Documents are required for Database transfers because of schema tracing in firebase */ - public const STATUS_DISREGARDED = 'DISREGARDED'; + public const STATUS_DISREGARDED = 'disregarded'; // Master Resources - public const TYPE_BUCKET = 'Bucket'; + public const TYPE_BUCKET = 'bucket'; - public const TYPE_COLLECTION = 'Collection'; + public const TYPE_COLLECTION = 'collection'; - public const TYPE_DATABASE = 'Database'; + public const TYPE_DATABASE = 'database'; - public const TYPE_DOCUMENT = 'Document'; + public const TYPE_DOCUMENT = 'document'; - public const TYPE_FILE = 'File'; + public const TYPE_FILE = 'file'; - public const TYPE_USER = 'User'; + public const TYPE_USER = 'user'; - public const TYPE_TEAM = 'Team'; + public const TYPE_TEAM = 'team'; - public const TYPE_MEMBERSHIP = 'Membership'; + public const TYPE_MEMBERSHIP = 'membership'; - public const TYPE_FUNCTION = 'Function'; + public const TYPE_FUNCTION = 'function'; // Children (Resources that are created by other resources) - public const TYPE_ATTRIBUTE = 'Attribute'; + public const TYPE_ATTRIBUTE = 'attribute'; - public const TYPE_DEPLOYMENT = 'Deployment'; + public const TYPE_DEPLOYMENT = 'deployment'; - public const TYPE_HASH = 'Hash'; + public const TYPE_HASH = 'hash'; - public const TYPE_INDEX = 'Index'; + public const TYPE_INDEX = 'index'; - public const TYPE_ENVVAR = 'EnvVar'; + public const TYPE_ENVVAR = 'envvar'; public const ALL_RESOURCES = [ self::TYPE_ATTRIBUTE, diff --git a/src/Transfer/Resources/Auth/Hash.php b/src/Transfer/Resources/Auth/Hash.php index 0c76b0d..bd84a72 100644 --- a/src/Transfer/Resources/Auth/Hash.php +++ b/src/Transfer/Resources/Auth/Hash.php @@ -10,27 +10,27 @@ */ class Hash extends Resource { - public const SCRYPT_MODIFIED = 'ScryptModified'; + public const ALGORITHM_SCRYPT_MODIFIED = 'scryptModified'; - public const BCRYPT = 'Bcrypt'; + public const ALGORITHM_BCRYPT = 'bcrypt'; - public const MD5 = 'MD5'; + public const ALGORITHM_MD5 = 'md5'; - public const ARGON2 = 'Argon2'; + public const ALGORITHM_ARGON2 = 'argon2'; - public const SHA256 = 'SHA256'; + public const ALGORITHM_SHA256 = 'sha256'; - public const PHPASS = 'PHPass'; + public const ALGORITHM_PHPASS = 'phpass'; - public const SCRYPT = 'Scrypt'; + public const ALGORITHM_SCRYPT = 'scrypt'; - public const PLAINTEXT = 'PlainText'; + public const ALGORITHM_PLAINTEXT = 'plainText'; private string $hash; private string $salt = ''; - private string $algorithm = self::SHA256; + private string $algorithm = self::ALGORITHM_SHA256; private string $separator = ''; diff --git a/src/Transfer/Transfer.php b/src/Transfer/Transfer.php index d3bfa6e..16f091b 100644 --- a/src/Transfer/Transfer.php +++ b/src/Transfer/Transfer.php @@ -4,17 +4,17 @@ class Transfer { - public const GROUP_GENERAL = 'General'; + public const GROUP_GENERAL = 'general'; - public const GROUP_AUTH = 'Auth'; + public const GROUP_AUTH = 'auth'; - public const GROUP_STORAGE = 'Storage'; + public const GROUP_STORAGE = 'storage'; - public const GROUP_FUNCTIONS = 'Functions'; + public const GROUP_FUNCTIONS = 'functions'; - public const GROUP_DATABASES = 'Databases'; + public const GROUP_DATABASES = 'databases'; - public const GROUP_SETTINGS = 'Settings'; + public const GROUP_SETTINGS = 'settings'; public const GROUP_AUTH_RESOURCES = [Resource::TYPE_USER, Resource::TYPE_TEAM, Resource::TYPE_MEMBERSHIP, Resource::TYPE_HASH]; From 15eea1c3a062658c428525a7312a2b6f333f10f6 Mon Sep 17 00:00:00 2001 From: Bradley Schofield Date: Wed, 21 Jun 2023 18:17:33 +0100 Subject: [PATCH 65/70] Update hash vars --- src/Transfer/Destinations/Appwrite.php | 14 +++++++------- src/Transfer/Sources/Firebase.php | 2 +- src/Transfer/Sources/NHost.php | 2 +- src/Transfer/Sources/Supabase.php | 2 +- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/Transfer/Destinations/Appwrite.php b/src/Transfer/Destinations/Appwrite.php index 46b7d3d..ccbdcef 100644 --- a/src/Transfer/Destinations/Appwrite.php +++ b/src/Transfer/Destinations/Appwrite.php @@ -537,7 +537,7 @@ public function importPasswordUser(User $user): array|null } switch ($hash->getAlgorithm()) { - case Hash::SCRYPT_MODIFIED: + case Hash::ALGORITHM_SCRYPT_MODIFIED: $result = $auth->createScryptModifiedUser( $user->getId(), $user->getEmail(), @@ -548,7 +548,7 @@ public function importPasswordUser(User $user): array|null empty($user->getUsername()) ? null : $user->getUsername() ); break; - case Hash::BCRYPT: + case Hash::ALGORITHM_BCRYPT: $result = $auth->createBcryptUser( $user->getId(), $user->getEmail(), @@ -556,7 +556,7 @@ public function importPasswordUser(User $user): array|null empty($user->getUsername()) ? null : $user->getUsername() ); break; - case Hash::ARGON2: + case Hash::ALGORITHM_ARGON2: $result = $auth->createArgon2User( $user->getId(), $user->getEmail(), @@ -564,7 +564,7 @@ public function importPasswordUser(User $user): array|null empty($user->getUsername()) ? null : $user->getUsername() ); break; - case Hash::SHA256: + case Hash::ALGORITHM_SHA256: $result = $auth->createShaUser( $user->getId(), $user->getEmail(), @@ -573,7 +573,7 @@ public function importPasswordUser(User $user): array|null empty($user->getUsername()) ? null : $user->getUsername() ); break; - case Hash::PHPASS: + case Hash::ALGORITHM_PHPASS: $result = $auth->createPHPassUser( $user->getId(), $user->getEmail(), @@ -581,7 +581,7 @@ public function importPasswordUser(User $user): array|null empty($user->getUsername()) ? null : $user->getUsername() ); break; - case Hash::SCRYPT: + case Hash::ALGORITHM_SCRYPT: $result = $auth->createScryptUser( $user->getId(), $user->getEmail(), @@ -594,7 +594,7 @@ public function importPasswordUser(User $user): array|null empty($user->getUsername()) ? null : $user->getUsername() ); break; - case Hash::PLAINTEXT: + case Hash::ALGORITHM_PLAINTEXT: $result = $auth->create( $user->getId(), $user->getEmail(), diff --git a/src/Transfer/Sources/Firebase.php b/src/Transfer/Sources/Firebase.php index db395d6..c74d41a 100644 --- a/src/Transfer/Sources/Firebase.php +++ b/src/Transfer/Sources/Firebase.php @@ -166,7 +166,7 @@ private function exportUsers(int $batchSize) $user['localId'] ?? '', $user['email'] ?? '', $user['displayName'] ?? $user['email'] ?? '', - new Hash($user['passwordHash'] ?? '', $user['salt'] ?? '', Hash::SCRYPT_MODIFIED, $hashConfig['saltSeparator'], $hashConfig['signerKey']), + new Hash($user['passwordHash'] ?? '', $user['salt'] ?? '', Hash::ALGORITHM_SCRYPT_MODIFIED, $hashConfig['saltSeparator'], $hashConfig['signerKey']), $user['phoneNumber'] ?? '', $this->calculateUserType($user['providerUserInfo'] ?? []), '', diff --git a/src/Transfer/Sources/NHost.php b/src/Transfer/Sources/NHost.php index fafeb58..fbbe7c7 100644 --- a/src/Transfer/Sources/NHost.php +++ b/src/Transfer/Sources/NHost.php @@ -230,7 +230,7 @@ private function exportUsers(int $batchSize) $user['id'], $user['email'] ?? '', $user['display_name'] ?? '', - new Hash($user['password_hash'], '', Hash::BCRYPT), + new Hash($user['password_hash'], '', Hash::ALGORITHM_BCRYPT), $user['phone_number'] ?? '', $this->calculateUserTypes($user), '', diff --git a/src/Transfer/Sources/Supabase.php b/src/Transfer/Sources/Supabase.php index 7f03a96..229d9b9 100644 --- a/src/Transfer/Sources/Supabase.php +++ b/src/Transfer/Sources/Supabase.php @@ -182,7 +182,7 @@ private function exportUsers(int $batchSize) $user['id'], $user['email'] ?? '', '', - new Hash($user['encrypted_password'], '', Hash::BCRYPT), + new Hash($user['encrypted_password'], '', Hash::ALGORITHM_BCRYPT), $user['phone'] ?? '', $this->calculateAuthTypes($user), '', From 464ac5c93a4bdfe6be6e4c5c7849f0c943281012 Mon Sep 17 00:00:00 2001 From: Bradley Schofield Date: Wed, 21 Jun 2023 18:25:28 +0100 Subject: [PATCH 66/70] Fix more namessssss --- src/Transfer/Resources/Auth/Hash.php | 2 +- src/Transfer/Sources/Appwrite.php | 8 ++++---- src/Transfer/Sources/Firebase.php | 8 ++++---- src/Transfer/Sources/NHost.php | 8 ++++---- tests/Transfer/E2E/Sources/{SourceCore.php => Base.php} | 3 ++- tests/Transfer/E2E/Sources/NHostTest.php | 8 +++++--- tests/e2e/adapters/Mock.php | 8 ++++---- 7 files changed, 24 insertions(+), 21 deletions(-) rename tests/Transfer/E2E/Sources/{SourceCore.php => Base.php} (94%) diff --git a/src/Transfer/Resources/Auth/Hash.php b/src/Transfer/Resources/Auth/Hash.php index bd84a72..d903b91 100644 --- a/src/Transfer/Resources/Auth/Hash.php +++ b/src/Transfer/Resources/Auth/Hash.php @@ -44,7 +44,7 @@ class Hash extends Resource private int $passwordLength = 0; - public function __construct(string $hash, string $salt = '', string $algorithm = self::SHA256, string $separator = '', string $signingKey = '', int $passwordCpu = 0, int $passwordMemory = 0, int $passwordParallel = 0, int $passwordLength = 0) + public function __construct(string $hash, string $salt = '', string $algorithm = self::ALGORITHM_SHA256, string $separator = '', string $signingKey = '', int $passwordCpu = 0, int $passwordMemory = 0, int $passwordParallel = 0, int $passwordLength = 0) { $this->hash = $hash; $this->salt = $salt; diff --git a/src/Transfer/Sources/Appwrite.php b/src/Transfer/Sources/Appwrite.php index 0113bce..14babb6 100644 --- a/src/Transfer/Sources/Appwrite.php +++ b/src/Transfer/Sources/Appwrite.php @@ -248,7 +248,7 @@ public function report(array $resources = []): array * @param int $batchSize Max 100 * @return void */ - protected function exportAuthGroup(int $batchSize, array $resources) + protected function exportGroupAuth(int $batchSize, array $resources) { if (in_array(Resource::TYPE_USER, $resources)) { $this->exportUsers($batchSize); @@ -395,7 +395,7 @@ private function exportMemberships(int $batchSize) } } - protected function exportDatabasesGroup(int $batchSize, array $resources) + protected function exportGroupDatabases(int $batchSize, array $resources) { if (in_array(Resource::TYPE_DATABASE, $resources)) { $this->exportDatabases($batchSize); @@ -761,7 +761,7 @@ private function calculateTypes(array $user): array return $types; } - protected function exportStorageGroup(int $batchSize, array $resources) + protected function exportGroupStorage(int $batchSize, array $resources) { if (in_array(Resource::TYPE_BUCKET, $resources)) { $this->exportBuckets($batchSize); @@ -883,7 +883,7 @@ private function exportFileData(File $file) } } - protected function exportFunctionsGroup(int $batchSize, array $resources) + protected function exportGroupFunctions(int $batchSize, array $resources) { if (in_array(Resource::TYPE_FUNCTION, $resources)) { $this->exportFunctions($batchSize); diff --git a/src/Transfer/Sources/Firebase.php b/src/Transfer/Sources/Firebase.php index c74d41a..6469d01 100644 --- a/src/Transfer/Sources/Firebase.php +++ b/src/Transfer/Sources/Firebase.php @@ -127,7 +127,7 @@ public function report(array $resources = []): array throw new \Exception('Not implemented'); } - protected function exportAuthGroup(int $batchSize, array $resources) + protected function exportGroupAuth(int $batchSize, array $resources) { if (in_array(Resource::TYPE_USER, $resources)) { $this->exportUsers($batchSize); @@ -209,7 +209,7 @@ private function calculateUserType(array $providerData): array return $types; } - protected function exportDatabasesGroup(int $batchSize, array $resources) + protected function exportGroupDatabases(int $batchSize, array $resources) { if (in_array(Resource::TYPE_DATABASE, $resources)) { $database = new Database('default', '(default)'); @@ -405,7 +405,7 @@ private function convertDocument(Collection $collection, array $document): Docum return new Document($document['name'], $collection->getDatabase(), $collection, $data, []); } - protected function exportStorageGroup(int $batchSize, array $resources) + protected function exportGroupStorage(int $batchSize, array $resources) { if (in_array(Resource::TYPE_BUCKET, $resources)) { $this->exportBuckets($batchSize); @@ -515,7 +515,7 @@ private function exportFile(File $file) } } - protected function exportFunctionsGroup(int $batchSize, array $resources) + protected function exportGroupFunctions(int $batchSize, array $resources) { throw new \Exception('Not implemented'); } diff --git a/src/Transfer/Sources/NHost.php b/src/Transfer/Sources/NHost.php index fbbe7c7..e3f9b5e 100644 --- a/src/Transfer/Sources/NHost.php +++ b/src/Transfer/Sources/NHost.php @@ -198,7 +198,7 @@ public function report(array $resources = []): array return $report; } - protected function exportAuthGroup(int $batchSize, array $resources) + protected function exportGroupAuth(int $batchSize, array $resources) { if (in_array(Resource::TYPE_USER, $resources)) { $this->exportUsers($batchSize); @@ -245,7 +245,7 @@ private function exportUsers(int $batchSize) } } - protected function exportDatabasesGroup(int $batchSize, array $resources) + protected function exportGroupDatabases(int $batchSize, array $resources) { if (in_array(Resource::TYPE_DATABASE, $resources)) { $this->exportDatabases($batchSize); @@ -538,7 +538,7 @@ private function calculateUserTypes(array $user): array return $types; } - protected function exportStorageGroup(int $batchSize, array $resources) + protected function exportGroupStorage(int $batchSize, array $resources) { if (in_array(Resource::TYPE_BUCKET, $resources)) { $this->exportBuckets($batchSize); @@ -674,7 +674,7 @@ private function exportFile(File $file) } } - protected function exportFunctionsGroup(int $batchSize, array $resources) + protected function exportGroupFunctions(int $batchSize, array $resources) { throw new \Exception('Not Implemented'); } diff --git a/tests/Transfer/E2E/Sources/SourceCore.php b/tests/Transfer/E2E/Sources/Base.php similarity index 94% rename from tests/Transfer/E2E/Sources/SourceCore.php rename to tests/Transfer/E2E/Sources/Base.php index 4bb5973..e1d06ef 100644 --- a/tests/Transfer/E2E/Sources/SourceCore.php +++ b/tests/Transfer/E2E/Sources/Base.php @@ -3,6 +3,7 @@ namespace Utopia\Tests\E2E\Sources; use PHPUnit\Framework\TestCase; +use Utopia\Tests\E2E\Adapters\Mock; use Utopia\Tests\E2E\Adapters\MockDestination; use Utopia\Transfer\Destination; use Utopia\Transfer\Resource; @@ -20,7 +21,7 @@ public function __construct() if (!$this->source) throw new \Exception('Source not set'); - $this->destination = new MockDestination(); + $this->destination = new Mock(); $this->transfer = new Transfer($this->source, $this->destination); } diff --git a/tests/Transfer/E2E/Sources/NHostTest.php b/tests/Transfer/E2E/Sources/NHostTest.php index 68af962..700478f 100644 --- a/tests/Transfer/E2E/Sources/NHostTest.php +++ b/tests/Transfer/E2E/Sources/NHostTest.php @@ -5,12 +5,14 @@ use Utopia\Transfer\Sources\NHost; use Utopia\Transfer\Transfer; use Utopia\Tests\E2E\Adapters\Mock; +use Utopia\Transfer\Destination; +use Utopia\Transfer\Source; -class NHostTest extends SourceCore +class NHostTest extends Base { - protected ?NHost $source = null; + protected ?Source $source = null; protected ?Transfer $transfer = null; - protected ?Mock $destination = null; + protected ?Destination $destination = null; public function __construct() { diff --git a/tests/e2e/adapters/Mock.php b/tests/e2e/adapters/Mock.php index b80a318..3c6c554 100644 --- a/tests/e2e/adapters/Mock.php +++ b/tests/e2e/adapters/Mock.php @@ -34,7 +34,7 @@ public function report(array $groups = []): array * @param array $resources Resources to export * @return void */ - protected function exportAuthGroup(int $batchSize, array $resources) + protected function exportGroupAuth(int $batchSize, array $resources) { } @@ -46,7 +46,7 @@ protected function exportAuthGroup(int $batchSize, array $resources) * @param array $resources Resources to export * @return void */ - protected function exportDatabasesGroup(int $batchSize, array $resources) + protected function exportGroupDatabases(int $batchSize, array $resources) { } @@ -58,7 +58,7 @@ protected function exportDatabasesGroup(int $batchSize, array $resources) * @param array $resources Resources to export * @return void */ - protected function exportStorageGroup(int $batchSize, array $resources) + protected function exportGroupStorage(int $batchSize, array $resources) { } @@ -70,7 +70,7 @@ protected function exportStorageGroup(int $batchSize, array $resources) * @param array $resources Resources to export * @return void */ - protected function exportFunctionsGroup(int $batchSize, array $resources) + protected function exportGroupFunctions(int $batchSize, array $resources) { } From 9b73954b67ce34e47919bcf865356cdc5e1faf30 Mon Sep 17 00:00:00 2001 From: Bradley Schofield Date: Wed, 21 Jun 2023 18:35:39 +0100 Subject: [PATCH 67/70] Fix Supabase --- playground.php | 12 ++++++------ src/Transfer/Sources/Supabase.php | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/playground.php b/playground.php index 9ec23c3..4b676c1 100644 --- a/playground.php +++ b/playground.php @@ -59,11 +59,11 @@ /** * Initialise All Destination Adapters */ -$destinationAppwrite = new AppwriteDestination( - $_ENV['DESTINATION_APPWRITE_TEST_PROJECT'], - $_ENV['DESTINATION_APPWRITE_TEST_ENDPOINT'], - $_ENV['DESTINATION_APPWRITE_TEST_KEY'] -); +// $destinationAppwrite = new AppwriteDestination( +// $_ENV['DESTINATION_APPWRITE_TEST_PROJECT'], +// $_ENV['DESTINATION_APPWRITE_TEST_ENDPOINT'], +// $_ENV['DESTINATION_APPWRITE_TEST_KEY'] +// ); $destinationLocal = new Local(__DIR__ . '/localBackup/'); @@ -72,7 +72,7 @@ */ $transfer = new Transfer( $sourceSupabase, - $destinationAppwrite + $destinationLocal ); /** diff --git a/src/Transfer/Sources/Supabase.php b/src/Transfer/Sources/Supabase.php index 229d9b9..e22dc46 100644 --- a/src/Transfer/Sources/Supabase.php +++ b/src/Transfer/Sources/Supabase.php @@ -152,7 +152,7 @@ public function report(array $resources = []): array return $report; } - protected function exportAuthGroup(int $batchSize, array $resources) + protected function exportGroupAuth(int $batchSize, array $resources) { if (in_array(Resource::TYPE_USER, $resources)) { $this->exportUsers($batchSize); @@ -227,7 +227,7 @@ private function calculateAuthTypes(array $user): array return $types; } - protected function exportStorageGroup(int $batchSize, array $resources) + protected function exportGroupStorage(int $batchSize, array $resources) { if (in_array(Resource::TYPE_BUCKET, $resources)) { $this->exportBuckets($batchSize); From c1af62d1ff93214a0cb4fdc46c349ccfb8b1b537 Mon Sep 17 00:00:00 2001 From: Bradley Schofield Date: Wed, 21 Jun 2023 21:14:49 +0100 Subject: [PATCH 68/70] Update Supabase.php --- src/Transfer/Sources/Supabase.php | 190 +++++++++++++++++++++++++++++- 1 file changed, 188 insertions(+), 2 deletions(-) diff --git a/src/Transfer/Sources/Supabase.php b/src/Transfer/Sources/Supabase.php index e22dc46..4e0efc6 100644 --- a/src/Transfer/Sources/Supabase.php +++ b/src/Transfer/Sources/Supabase.php @@ -9,7 +9,192 @@ use Utopia\Transfer\Resources\Storage\File; use Utopia\Transfer\Transfer; -const MIME_MAP = ['video/3gpp2' => '3g2', 'video/3gp' => '3gp', 'video/3gpp' => '3gp', 'application/x-compressed' => '7zip', 'audio/x-acc' => 'aac', 'audio/ac3' => 'ac3', 'application/postscript' => 'ai', 'audio/x-aiff' => 'aif', 'audio/aiff' => 'aif', 'audio/x-au' => 'au', 'video/x-msvideo' => 'avi', 'video/msvideo' => 'avi', 'video/avi' => 'avi', 'application/x-troff-msvideo' => 'avi', 'application/macbinary' => 'bin', 'application/mac-binary' => 'bin', 'application/x-binary' => 'bin', 'application/x-macbinary' => 'bin', 'image/bmp' => 'bmp', 'image/x-bmp' => 'bmp', 'image/x-bitmap' => 'bmp', 'image/x-xbitmap' => 'bmp', 'image/x-win-bitmap' => 'bmp', 'image/x-windows-bmp' => 'bmp', 'image/ms-bmp' => 'bmp', 'image/x-ms-bmp' => 'bmp', 'application/bmp' => 'bmp', 'application/x-bmp' => 'bmp', 'application/x-win-bitmap' => 'bmp', 'application/cdr' => 'cdr', 'application/coreldraw' => 'cdr', 'application/x-cdr' => 'cdr', 'application/x-coreldraw' => 'cdr', 'image/cdr' => 'cdr', 'image/x-cdr' => 'cdr', 'zz-application/zz-winassoc-cdr' => 'cdr', 'application/mac-compactpro' => 'cpt', 'application/pkix-crl' => 'crl', 'application/pkcs-crl' => 'crl', 'application/x-x509-ca-cert' => 'crt', 'application/pkix-cert' => 'crt', 'text/css' => 'css', 'text/x-comma-separated-values' => 'csv', 'text/comma-separated-values' => 'csv', 'application/vnd.msexcel' => 'csv', 'application/x-director' => 'dcr', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' => 'docx', 'application/x-dvi' => 'dvi', 'message/rfc822' => 'eml', 'application/x-msdownload' => 'exe', 'video/x-f4v' => 'f4v', 'audio/x-flac' => 'flac', 'video/x-flv' => 'flv', 'image/gif' => 'gif', 'application/gpg-keys' => 'gpg', 'application/x-gtar' => 'gtar', 'application/x-gzip' => 'gzip', 'application/mac-binhex40' => 'hqx', 'application/mac-binhex' => 'hqx', 'application/x-binhex40' => 'hqx', 'application/x-mac-binhex40' => 'hqx', 'text/html' => 'html', 'image/x-icon' => 'ico', 'image/x-ico' => 'ico', 'image/vnd.microsoft.icon' => 'ico', 'text/calendar' => 'ics', 'application/java-archive' => 'jar', 'application/x-java-application' => 'jar', 'application/x-jar' => 'jar', 'image/jp2' => 'jp2', 'video/mj2' => 'jp2', 'image/jpx' => 'jp2', 'image/jpm' => 'jp2', 'image/jpeg' => 'jpeg', 'image/jpg' => 'jpeg', 'image/pjpeg' => 'jpeg', 'application/x-javascript' => 'js', 'application/json' => 'json', 'text/json' => 'json', 'application/vnd.google-earth.kml+xml' => 'kml', 'application/vnd.google-earth.kmz' => 'kmz', 'text/x-log' => 'log', 'audio/x-m4a' => 'm4a', 'audio/mp4' => 'm4a', 'application/vnd.mpegurl' => 'm4u', 'audio/midi' => 'mid', 'application/vnd.mif' => 'mif', 'video/quicktime' => 'mov', 'video/x-sgi-movie' => 'movie', 'audio/mpeg' => 'mp3', 'audio/mpg' => 'mp3', 'audio/mpeg3' => 'mp3', 'audio/mp3' => 'mp3', 'video/mp4' => 'mp4', 'video/mpeg' => 'mpeg', 'application/oda' => 'oda', 'audio/ogg' => 'ogg', 'video/ogg' => 'ogg', 'application/ogg' => 'ogg', 'font/otf' => 'otf', 'application/x-pkcs10' => 'p10', 'application/pkcs10' => 'p10', 'application/x-pkcs12' => 'p12', 'application/x-pkcs7-signature' => 'p7a', 'application/pkcs7-mime' => 'p7c', 'application/x-pkcs7-mime' => 'p7c', 'application/x-pkcs7-certreqresp' => 'p7r', 'application/pkcs7-signature' => 'p7s', 'application/pdf' => 'pdf', 'application/octet-stream' => 'pdf', 'application/x-x509-user-cert' => 'pem', 'application/x-pem-file' => 'pem', 'application/pgp' => 'pgp', 'application/x-httpd-php' => 'php', 'application/php' => 'php', 'application/x-php' => 'php', 'text/php' => 'php', 'text/x-php' => 'php', 'application/x-httpd-php-source' => 'php', 'image/png' => 'png', 'image/x-png' => 'png', 'application/powerpoint' => 'ppt', 'application/vnd.ms-powerpoint' => 'ppt', 'application/vnd.ms-office' => 'ppt', 'application/msword' => 'doc', 'application/vnd.openxmlformats-officedocument.presentationml.presentation' => 'pptx', 'application/x-photoshop' => 'psd', 'image/vnd.adobe.photoshop' => 'psd', 'audio/x-realaudio' => 'ra', 'audio/x-pn-realaudio' => 'ram', 'application/x-rar' => 'rar', 'application/rar' => 'rar', 'application/x-rar-compressed' => 'rar', 'audio/x-pn-realaudio-plugin' => 'rpm', 'application/x-pkcs7' => 'rsa', 'text/rtf' => 'rtf', 'text/richtext' => 'rtx', 'video/vnd.rn-realvideo' => 'rv', 'application/x-stuffit' => 'sit', 'application/smil' => 'smil', 'text/srt' => 'srt', 'image/svg+xml' => 'svg', 'application/x-shockwave-flash' => 'swf', 'application/x-tar' => 'tar', 'application/x-gzip-compressed' => 'tgz', 'image/tiff' => 'tiff', 'font/ttf' => 'ttf', 'text/plain' => 'txt', 'text/x-vcard' => 'vcf', 'application/videolan' => 'vlc', 'text/vtt' => 'vtt', 'audio/x-wav' => 'wav', 'audio/wave' => 'wav', 'audio/wav' => 'wav', 'application/wbxml' => 'wbxml', 'video/webm' => 'webm', 'image/webp' => 'webp', 'audio/x-ms-wma' => 'wma', 'application/wmlc' => 'wmlc', 'video/x-ms-wmv' => 'wmv', 'video/x-ms-asf' => 'wmv', 'font/woff' => 'woff', 'font/woff2' => 'woff2', 'application/xhtml+xml' => 'xhtml', 'application/excel' => 'xl', 'application/msexcel' => 'xls', 'application/x-msexcel' => 'xls', 'application/x-ms-excel' => 'xls', 'application/x-excel' => 'xls', 'application/x-dos_ms_excel' => 'xls', 'application/xls' => 'xls', 'application/x-xls' => 'xls', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' => 'xlsx', 'application/vnd.ms-excel' => 'xlsx', 'application/xml' => 'xml', 'text/xml' => 'xml', 'text/xsl' => 'xsl', 'application/xspf+xml' => 'xspf', 'application/x-compress' => 'z', 'application/x-zip' => 'zip', 'application/zip' => 'zip', 'application/x-zip-compressed' => 'zip', 'application/s-compressed' => 'zip', 'multipart/x-zip' => 'zip', 'text/x-scriptzsh' => 'zsh']; +const MIME_MAP = ['video/3gpp2' => '3g2', + 'video/3gp' => '3gp', + 'video/3gpp' => '3gp', + 'application/x-compressed' => '7zip', + 'audio/x-acc' => 'aac', + 'audio/ac3' => 'ac3', + 'application/postscript' => 'ai', + 'audio/x-aiff' => 'aif', + 'audio/aiff' => 'aif', + 'audio/x-au' => 'au', + 'video/x-msvideo' => 'avi', + 'video/msvideo' => 'avi', + 'video/avi' => 'avi', + 'application/x-troff-msvideo' => 'avi', + 'application/macbinary' => 'bin', + 'application/mac-binary' => 'bin', + 'application/x-binary' => 'bin', + 'application/x-macbinary' => 'bin', + 'image/bmp' => 'bmp', + 'image/x-bmp' => 'bmp', + 'image/x-bitmap' => 'bmp', + 'image/x-xbitmap' => 'bmp', + 'image/x-win-bitmap' => 'bmp', + 'image/x-windows-bmp' => 'bmp', + 'image/ms-bmp' => 'bmp', + 'image/x-ms-bmp' => 'bmp', + 'application/bmp' => 'bmp', + 'application/x-bmp' => 'bmp', + 'application/x-win-bitmap' => 'bmp', + 'application/cdr' => 'cdr', + 'application/coreldraw' => 'cdr', + 'application/x-cdr' => 'cdr', + 'application/x-coreldraw' => 'cdr', + 'image/cdr' => 'cdr', + 'image/x-cdr' => 'cdr', + 'zz-application/zz-winassoc-cdr' => 'cdr', + 'application/mac-compactpro' => 'cpt', + 'application/pkix-crl' => 'crl', + 'application/pkcs-crl' => 'crl', + 'application/x-x509-ca-cert' => 'crt', + 'application/pkix-cert' => 'crt', + 'text/css' => 'css', + 'text/x-comma-separated-values' => 'csv', + 'text/comma-separated-values' => 'csv', + 'application/vnd.msexcel' => 'csv', + 'application/x-director' => 'dcr', + 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' => 'docx', + 'application/x-dvi' => 'dvi', + 'message/rfc822' => 'eml', + 'application/x-msdownload' => 'exe', + 'video/x-f4v' => 'f4v', + 'audio/x-flac' => 'flac', + 'video/x-flv' => 'flv', + 'image/gif' => 'gif', + 'application/gpg-keys' => 'gpg', + 'application/x-gtar' => 'gtar', + 'application/x-gzip' => 'gzip', + 'application/mac-binhex40' => 'hqx', + 'application/mac-binhex' => 'hqx', + 'application/x-binhex40' => 'hqx', + 'application/x-mac-binhex40' => 'hqx', + 'text/html' => 'html', + 'image/x-icon' => 'ico', + 'image/x-ico' => 'ico', + 'image/vnd.microsoft.icon' => 'ico', + 'text/calendar' => 'ics', + 'application/java-archive' => 'jar', + 'application/x-java-application' => 'jar', + 'application/x-jar' => 'jar', + 'image/jp2' => 'jp2', + 'video/mj2' => 'jp2', + 'image/jpx' => 'jp2', + 'image/jpm' => 'jp2', + 'image/jpeg' => 'jpeg', + 'image/jpg' => 'jpeg', + 'image/pjpeg' => 'jpeg', + 'application/x-javascript' => 'js', + 'application/json' => 'json', + 'text/json' => 'json', + 'application/vnd.google-earth.kml+xml' => 'kml', + 'application/vnd.google-earth.kmz' => 'kmz', + 'text/x-log' => 'log', + 'audio/x-m4a' => 'm4a', + 'audio/mp4' => 'm4a', + 'application/vnd.mpegurl' => 'm4u', + 'audio/midi' => 'mid', + 'application/vnd.mif' => 'mif', + 'video/quicktime' => 'mov', + 'video/x-sgi-movie' => 'movie', + 'audio/mpeg' => 'mp3', + 'audio/mpg' => 'mp3', + 'audio/mpeg3' => 'mp3', + 'audio/mp3' => 'mp3', + 'video/mp4' => 'mp4', + 'video/mpeg' => 'mpeg', + 'application/oda' => 'oda', + 'audio/ogg' => 'ogg', + 'video/ogg' => 'ogg', + 'application/ogg' => 'ogg', + 'font/otf' => 'otf', + 'application/x-pkcs10' => 'p10', + 'application/pkcs10' => 'p10', + 'application/x-pkcs12' => 'p12', + 'application/x-pkcs7-signature' => 'p7a', + 'application/pkcs7-mime' => 'p7c', + 'application/x-pkcs7-mime' => 'p7c', + 'application/x-pkcs7-certreqresp' => 'p7r', + 'application/pkcs7-signature' => 'p7s', + 'application/pdf' => 'pdf', + 'application/octet-stream' => 'pdf', + 'application/x-x509-user-cert' => 'pem', + 'application/x-pem-file' => 'pem', + 'application/pgp' => 'pgp', + 'application/x-httpd-php' => 'php', + 'application/php' => 'php', + 'application/x-php' => 'php', + 'text/php' => 'php', + 'text/x-php' => 'php', + 'application/x-httpd-php-source' => 'php', + 'image/png' => 'png', + 'image/x-png' => 'png', + 'application/powerpoint' => 'ppt', + 'application/vnd.ms-powerpoint' => 'ppt', + 'application/vnd.ms-office' => 'ppt', + 'application/msword' => 'doc', + 'application/vnd.openxmlformats-officedocument.presentationml.presentation' => 'pptx', + 'application/x-photoshop' => 'psd', + 'image/vnd.adobe.photoshop' => 'psd', + 'audio/x-realaudio' => 'ra', + 'audio/x-pn-realaudio' => 'ram', + 'application/x-rar' => 'rar', + 'application/rar' => 'rar', + 'application/x-rar-compressed' => 'rar', + 'audio/x-pn-realaudio-plugin' => 'rpm', + 'application/x-pkcs7' => 'rsa', + 'text/rtf' => 'rtf', + 'text/richtext' => 'rtx', + 'video/vnd.rn-realvideo' => 'rv', + 'application/x-stuffit' => 'sit', + 'application/smil' => 'smil', + 'text/srt' => 'srt', + 'image/svg+xml' => 'svg', + 'application/x-shockwave-flash' => 'swf', + 'application/x-tar' => 'tar', + 'application/x-gzip-compressed' => 'tgz', + 'image/tiff' => 'tiff', + 'font/ttf' => 'ttf', + 'text/plain' => 'txt', + 'text/x-vcard' => 'vcf', + 'application/videolan' => 'vlc', + 'text/vtt' => 'vtt', + 'audio/x-wav' => 'wav', + 'audio/wave' => 'wav', + 'audio/wav' => 'wav', + 'application/wbxml' => 'wbxml', + 'video/webm' => 'webm', + 'image/webp' => 'webp', + 'audio/x-ms-wma' => 'wma', + 'application/wmlc' => 'wmlc', + 'video/x-ms-wmv' => 'wmv', + 'video/x-ms-asf' => 'wmv', + 'font/woff' => 'woff', + 'font/woff2' => 'woff2', + 'application/xhtml+xml' => 'xhtml', + 'application/excel' => 'xl', + 'application/msexcel' => 'xls', + 'application/x-msexcel' => 'xls', + 'application/x-ms-excel' => 'xls', + 'application/x-excel' => 'xls', + 'application/x-dos_ms_excel' => 'xls', + 'application/xls' => 'xls', + 'application/x-xls' => 'xls', + 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' => 'xlsx', + 'application/vnd.ms-excel' => 'xlsx', + 'application/xml' => 'xml', + 'text/xml' => 'xml', + 'text/xsl' => 'xsl', + 'application/xspf+xml' => 'xspf', + 'application/x-compress' => 'z', + 'application/x-zip' => 'zip', + 'application/zip' => 'zip', + 'application/x-zip-compressed' => 'zip', + 'application/s-compressed' => 'zip', + 'multipart/x-zip' => 'zip', + 'text/x-scriptzsh' => 'zsh' +]; class Supabase extends NHost { @@ -28,7 +213,8 @@ public static function getName(): string * * @return self */ - public function __construct(string $endpoint, string $key, string $host, string $databaseName, string $username, string $password, string $port = '5432') + public function __construct(string $endpoint + , string $key, string $host, string $databaseName, string $username, string $password, string $port = '5432') { $this->endpoint = $endpoint; $this->key = $key; From 9c8ccd8edb39faa6cb9ec8248878aa398a7c7cb7 Mon Sep 17 00:00:00 2001 From: Bradley Schofield Date: Wed, 21 Jun 2023 21:15:39 +0100 Subject: [PATCH 69/70] Update Firebase.php --- src/Transfer/Sources/Firebase.php | 49 ++++++++++++++++--------------- 1 file changed, 25 insertions(+), 24 deletions(-) diff --git a/src/Transfer/Sources/Firebase.php b/src/Transfer/Sources/Firebase.php index 6469d01..c29bda7 100644 --- a/src/Transfer/Sources/Firebase.php +++ b/src/Transfer/Sources/Firebase.php @@ -264,30 +264,31 @@ private function exportDB(int $batchSize, bool $pushDocuments, Database $databas private function convertAttribute(Collection $collection, string $key, array $field): Attribute { - if (array_key_exists('booleanValue', $field)) { - return new Boolean($key, $collection, false, false, null); - } elseif (array_key_exists('bytesValue', $field)) { - return new Text($key, $collection, false, false, null, 1000000); - } elseif (array_key_exists('doubleValue', $field)) { - return new Decimal($key, $collection, false, false, null); - } elseif (array_key_exists('integerValue', $field)) { - return new Integer($key, $collection, false, false, null); - } elseif (array_key_exists('mapValue', $field)) { - return new Text($key, $collection, false, false, null, 1000000); - } elseif (array_key_exists('nullValue', $field)) { - return new Text($key, $collection, false, false, null, 1000000); - } elseif (array_key_exists('referenceValue', $field)) { - return new Text($key, $collection, false, false, null, 1000000); //TODO: This should be a reference attribute - } elseif (array_key_exists('stringValue', $field)) { - return new Text($key, $collection, false, false, null, 1000000); - } elseif (array_key_exists('timestampValue', $field)) { - return new DateTime($key, $collection, false, false, null); - } elseif (array_key_exists('geoPointValue', $field)) { - return new Text($key, $collection, false, false, null, 1000000); - } elseif (array_key_exists('arrayValue', $field)) { - return $this->calculateArrayType($collection, $key, $field['arrayValue']); - } else { - throw new \Exception('Unknown field type'); + switch (true) { + case array_key_exists('booleanValue', $field): + return new Boolean($key, $collection, false, false, null); + case array_key_exists('bytesValue', $field): + return new Text($key, $collection, false, false, null, 1000000); + case array_key_exists('doubleValue', $field): + return new Decimal($key, $collection, false, false, null); + case array_key_exists('integerValue', $field): + return new Integer($key, $collection, false, false, null); + case array_key_exists('mapValue', $field): + return new Text($key, $collection, false, false, null, 1000000); + case array_key_exists('nullValue', $field): + return new Text($key, $collection, false, false, null, 1000000); + case array_key_exists('referenceValue', $field): + return new Text($key, $collection, false, false, null, 1000000); //TODO: This should be a reference attribute + case array_key_exists('stringValue', $field): + return new Text($key, $collection, false, false, null, 1000000); + case array_key_exists('timestampValue', $field): + return new DateTime($key, $collection, false, false, null); + case array_key_exists('geoPointValue', $field): + return new Text($key, $collection, false, false, null, 1000000); + case array_key_exists('arrayValue', $field): + return $this->calculateArrayType($collection, $key, $field['arrayValue']); + default: + throw new \Exception('Unknown field type'); } } From 736a8f89e41c7b275ec3f69a45eee16a345746e6 Mon Sep 17 00:00:00 2001 From: Bradley Schofield Date: Wed, 21 Jun 2023 21:19:49 +0100 Subject: [PATCH 70/70] Fix Firebase --- playground.php | 12 ++++++------ src/Transfer/Sources/Firebase.php | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/playground.php b/playground.php index 4b676c1..1576e61 100644 --- a/playground.php +++ b/playground.php @@ -31,12 +31,12 @@ // $_ENV['SOURCE_APPWRITE_TEST_KEY'] // ); -// $firebase = json_decode($_ENV['FIREBASE_TEST_ACCOUNT'], true); +$firebase = json_decode($_ENV['FIREBASE_TEST_ACCOUNT'], true); -// $sourceFirebase = new Firebase( -// $firebase, -// $firebase['project_id'] ?? '', -// ); +$sourceFirebase = new Firebase( + $firebase, + $firebase['project_id'] ?? '', +); // $sourceNHost = new NHost( // $_ENV['NHOST_TEST_SUBDOMAIN'] ?? '', @@ -71,7 +71,7 @@ * Initialise Transfer Class */ $transfer = new Transfer( - $sourceSupabase, + $sourceFirebase, $destinationLocal ); diff --git a/src/Transfer/Sources/Firebase.php b/src/Transfer/Sources/Firebase.php index c29bda7..c5a3f50 100644 --- a/src/Transfer/Sources/Firebase.php +++ b/src/Transfer/Sources/Firebase.php @@ -223,7 +223,7 @@ protected function exportGroupDatabases(int $batchSize, array $resources) private function exportDB(int $batchSize, bool $pushDocuments, Database $database) { - $baseURL = "https://firestore.googleapis.com/v1/{$this->projectID}/databases/(default)"; + $baseURL = "https://firestore.googleapis.com/v1/projects/{$this->projectID}/databases/(default)/documents"; $nextPageToken = null; $allCollections = [];