diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 00000000..b51d5e71 --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,19 @@ +name: Tests +on: + pull_request: + push: { branches: [main] } + +jobs: + tests: + name: Run Test Suite + runs-on: ubuntu-latest + env: + COMPOSE_FILE: docker-compose.yml + + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Run Tests + run: | + docker-compose up -d --build && sleep 5 && docker compose exec tests php ./vendor/bin/phpunit \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 173bd98e..dc80ae10 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,33 +1,37 @@ -FROM postgres:alpine3.18 as supabase-db -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 supabase/postgres:15.1.0.96 as supabase-db +COPY ./tests/Transfer/resources/supabase/1_globals.sql /docker-entrypoint-initdb.d/1_globals.sql +COPY ./tests/Transfer/resources/supabase/2_main.sql /docker-entrypoint-initdb.d/2_main.sql +RUN rm -rf /docker-entrypoint-initdb.d/migrate.sh FROM postgres:alpine3.18 as nhost-db -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 +COPY ./tests/Transfer/resources/nhost/1_globals.sql /docker-entrypoint-initdb.d/1_globals.sql +COPY ./tests/Transfer/resources/nhost/2_main.sql /docker-entrypoint-initdb.d/2_main.sql # Use my fork of mockoon while waiting for range headers to be merged -FROM node:14-alpine3.14 as mock-api +FROM node:20.4-alpine3.17 as mock-api WORKDIR /app -RUN git clone https://github.com/PineappleIOnic/mockoon.git . +RUN apk add --no-cache git +RUN git clone https://github.com/PineappleIOnic/mockoon.git +WORKDIR /app/mockoon +RUN git checkout origin/feat-implement-range +RUN apk add python3 make gcc g++ RUN npm run bootstrap RUN npm run build:libs RUN npm run build:cli -RUN mv ./packages/cli/dist/run /usr/local/bin/mockoon +RUN cd packages/cli && npm install -g . +RUN adduser --shell /bin/sh --disabled-password --gecos "" mockoon +USER mockoon +CMD mockoon-cli start --data /mockoon/api.json --port 80 --disable-log-to-file && tail -f /dev/null 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 +FROM php:8.1.21-fpm-alpine3.18 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 +COPY ./src /app/src +COPY ./tests /app/src/tests +COPY --from=composer /usr/local/src/vendor /app/vendor +CMD tail -f /dev/null \ No newline at end of file diff --git a/composer.json b/composer.json index a3595876..3bbe651f 100644 --- a/composer.json +++ b/composer.json @@ -28,7 +28,7 @@ }, "require": { "php": ">=8.0", - "utopia-php/cli": "^0.13.0", + "utopia-php/cli": "^0.15.0", "appwrite/appwrite": "^8.0" }, "require-dev": { diff --git a/docker-compose.yml b/docker-compose.yml index 1dbd99ac..e084efa7 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -26,6 +26,24 @@ services: POSTGRES_USER: postgres POSTGRES_PASSWORD: postgres POSTGRES_DB: postgres + + nhost-storage: + build: + context: . + target: mock-api + networks: + - tests + volumes: + - ./tests/Transfer/resources/nhost:/mockoon + + supabase-api: + build: + context: . + target: mock-api + networks: + - tests + volumes: + - ./tests/Transfer/resources/supabase:/mockoon tests: build: @@ -34,11 +52,14 @@ services: networks: - tests volumes: - - ./:/app + - ./tests:/app/tests + - ./src:/app/src + - ./phpunit.xml:/app/phpunit.xml working_dir: /app depends_on: - supabase-db - nhost-db + - nhost-storage environment: - NHOST_DB_URL=postgres://postgres:postgres@nhost-db:5432/postgres - SUPABASE_DB_URL=postgres://postgres:postgres@supabase-db:5432/postgres diff --git a/playground.php b/playground.php index 4aade024..0f533188 100644 --- a/playground.php +++ b/playground.php @@ -7,10 +7,10 @@ */ require_once __DIR__.'/vendor/autoload.php'; -use Appwrite\Query; use Dotenv\Dotenv; use Utopia\Transfer\Destinations\Appwrite as AppwriteDestination; use Utopia\Transfer\Destinations\Local; +use Utopia\Transfer\Resource; use Utopia\Transfer\Sources\Appwrite; use Utopia\Transfer\Sources\Firebase; use Utopia\Transfer\Sources\NHost; @@ -20,16 +20,14 @@ $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); @@ -38,14 +36,14 @@ $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'] ?? '', @@ -59,11 +57,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/'); @@ -71,79 +69,30 @@ * Initialise Transfer Class */ $transfer = new Transfer( - $sourceFirebase, + $sourceSupabase, $destinationLocal ); -/** - * Run Transfer - */ -$transfer->run( - Supabase::getSupportedResources(), - function (array $resources) { - } -); +$sourceFirebase->report(); + +// $sourceSupabase->report(); + +// /** +// * Run Transfer +// */ +// $transfer->run($sourceAppwrite->getSupportedResources(), +// function (array $resources) { +// } +// ); + +// $report = []; + +// $cache = $transfer->getCache()->getAll(); -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()); +// foreach ($cache as $type => $resources) { +// foreach ($resources as $resource) { +// if ($resource->getStatus() !== Resource::STATUS_ERROR) { +// continue; +// } +// } +// } diff --git a/src/Transfer/Cache.php b/src/Transfer/Cache.php index f927f896..4347a514 100644 --- a/src/Transfer/Cache.php +++ b/src/Transfer/Cache.php @@ -2,6 +2,9 @@ namespace Utopia\Transfer; +use Utopia\Transfer\Resources\Functions\Func; +use Utopia\Transfer\Resources\Storage\File; + /** * 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, @@ -33,6 +36,12 @@ public function add($resource) } $resource->setInternalId(uniqid()); } + + if ($resource->getName() == Resource::TYPE_FILE || $resource->getName() == Resource::TYPE_FUNCTION) { + /** @var File|Func $resource */ + $resource->setData(''); // Prevent Memory Leak + } + $this->cache[$resource->getName()][$resource->getInternalId()] = $resource; } @@ -54,8 +63,8 @@ public function addAll(array $resources) */ public function update($resource) { - if (! in_array($resource, $this->cache[$resource->getName()])) { - throw new \Exception('Resource does not exist in cache'); + if (! in_array($resource->getName(), $this->cache)) { + $this->add($resource); } $this->cache[$resource->getName()][$resource->getInternalId()] = $resource; diff --git a/src/Transfer/Destinations/Appwrite.php b/src/Transfer/Destinations/Appwrite.php index daa1b054..a0a44d74 100644 --- a/src/Transfer/Destinations/Appwrite.php +++ b/src/Transfer/Destinations/Appwrite.php @@ -13,6 +13,7 @@ use Utopia\Transfer\Resource; use Utopia\Transfer\Resources\Auth\Hash; use Utopia\Transfer\Resources\Auth\Membership; +use Utopia\Transfer\Resources\Auth\Team; use Utopia\Transfer\Resources\Auth\User; use Utopia\Transfer\Resources\Database\Attribute; use Utopia\Transfer\Resources\Database\Attributes\DateTime; @@ -277,13 +278,26 @@ public function importDatabaseResource(Resource $resource): Resource break; case Resource::TYPE_DOCUMENT: /** @var Document $resource */ - $databaseService->createDocument( - $resource->getDatabase()->getId(), - $resource->getCollection()->getId(), - $resource->getId(), - $resource->getData(), - $resource->getPermissions() - ); + // Check if document has already been created by subcollection + $docExists = array_key_exists($resource->getId(), $this->cache->get(Resource::TYPE_DOCUMENT)); + + if ($docExists) { + $resource->setStatus(Resource::STATUS_SKIPPED, 'Document has been already created by relationship'); + + return $resource; + } + + try { + $databaseService->createDocument( + $resource->getDatabase()->getId(), + $resource->getCollection()->getId(), + $resource->getId(), + $resource->getData(), + $resource->getPermissions() + ); + } catch (\Exception $e) { + $resource->setStatus(Resource::STATUS_ERROR, $e->getMessage()); + } break; } @@ -338,7 +352,16 @@ public function createAttribute(Attribute $attribute): void break; case Attribute::TYPE_RELATIONSHIP: /** @var Relationship $attribute */ - $databaseService->createRelationshipAttribute($attribute->getCollection()->getDatabase()->getId(), $attribute->getCollection()->getId(), $attribute->getRelatedCollection(), $attribute->getRelationType(), $attribute->getTwoWay(), $attribute->getKey(), $attribute->getTwoWayKey(), $attribute->getOnDelete()); + $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'); @@ -370,7 +393,7 @@ public function awaitAttributeCreation(Attribute $attribute, int $timeout): bool throw new \Exception('Attribute creation timeout'); } - public function importFileResource(Resource $resource): Resource + public function importFileResource(File|Bucket $resource): Resource { $storageService = new Storage($this->client); @@ -384,18 +407,33 @@ public function importFileResource(Resource $resource): 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() - ); + if (! $resource->getUpdateLimits()) { + $response = $storageService->createBucket( + $resource->getId() ?? 'unique()', + $resource->getBucketName(), + $resource->getPermissions(), + $resource->getFileSecurity(), + true, // Set to true for now, we'll come back later. + null, + null, + $resource->getCompression() ?? 'none', + $resource->getEncryption() ?? null, + $resource->getAntiVirus() ?? null + ); + } else { + $response = $storageService->updateBucket( + $resource->getId(), + $resource->getBucketName(), + $resource->getPermissions(), + $resource->getFileSecurity(), + $resource->getEnabled(), + $resource->getMaxFileSize() ?? null, + $resource->getAllowedFileExtensions() ?? null, + $resource->getCompression() ?? 'none', + $resource->getEncryption() ?? null, + $resource->getAntiVirus() ?? null + ); + } $resource->setId($response['$id']); } @@ -422,9 +460,11 @@ public function importFile(File $file): File if ($file->getSize() <= Transfer::STORAGE_MAX_CHUNK_SIZE) { $response = $this->client->call( 'POST', - "/v1/storage/buckets/{$bucketId}/files", + "/storage/buckets/{$bucketId}/files", [ 'content-type' => 'multipart/form-data', + 'X-Appwrite-Project' => $this->project, + 'X-Appwrite-Key' => $this->key, ], [ 'bucketId' => $bucketId, @@ -435,16 +475,19 @@ public function importFile(File $file): File ); $file->setStatus(Resource::STATUS_SUCCESS); + $file->setData(''); return $file; } $response = $this->client->call( 'POST', - "/v1/storage/buckets/{$bucketId}/files", + "/storage/buckets/{$bucketId}/files", [ 'content-type' => 'multipart/form-data', 'content-range' => 'bytes '.($file->getStart()).'-'.($file->getEnd() == ($file->getSize() - 1) ? $file->getSize() : $file->getEnd()).'/'.$file->getSize(), + 'X-Appwrite-Project' => $this->project, + 'x-Appwrite-Key' => $this->key, ], [ 'bucketId' => $bucketId, @@ -477,8 +520,18 @@ public function importAuthResource(Resource $resource): Resource /** @var User $resource */ if (in_array(User::TYPE_EMAIL, $resource->getTypes())) { $this->importPasswordUser($resource); + } elseif (in_array(User::TYPE_ANONYMOUS, $resource->getTypes()) || in_array(User::TYPE_OAUTH, $resource->getTypes())) { + $resource->setStatus(Resource::STATUS_WARNING, 'Anonymous and OAuth users cannot be imported.'); + + return $resource; } else { - $userService->create($resource->getId(), $resource->getEmail(), $resource->getPhone(), null, $resource->getName()); + $userService->create( + $resource->getId(), + null, + in_array(User::TYPE_PHONE, $resource->getTypes()) ? $resource->getPhone() : null, + null, + $resource->getName() + ); } if ($resource->getUsername()) { @@ -504,18 +557,26 @@ public function importAuthResource(Resource $resource): Resource break; case Resource::TYPE_TEAM: /** @var Team $resource */ - $teamService->create($resource->getId(), $resource->getName()); - $teamService->updatePrefs($resource->getId(), $resource->getPrefs()); + $teamService->create($resource->getId(), $resource->getTeamName()); + $teamService->updatePrefs($resource->getId(), $resource->getPreferences()); break; case Resource::TYPE_MEMBERSHIP: /** @var Membership $resource */ - //TODO: Discuss in meeting. - // $teamService->createMembership($resource->getTeam()->getId(), $resource->getRoles(), ) - // break; + $user = $resource->getUser(); + $teamService->createMembership( + $resource->getTeam()->getId(), + $resource->getRoles(), + '', + $user->getEmail(), + $user->getId(), + $user->getPhone(), + $user->getName() + ); + break; } $resource->setStatus(Resource::STATUS_SUCCESS); - } catch (\Exception $e) { + } catch (\Throwable $e) { $resource->setStatus(Resource::STATUS_ERROR, $e->getMessage()); } finally { return $resource; @@ -528,8 +589,8 @@ public function importPasswordUser(User $user): ?array $hash = $user->getPasswordHash(); $result = null; - if (empty($hash->getHash())) { - throw new \Exception('User password hash is empty'); + if (! $hash) { + throw new \Exception('Password hash is missing'); } switch ($hash->getAlgorithm()) { @@ -655,7 +716,7 @@ private function importDeployment(Deployment $deployment): Resource if ($deployment->getSize() <= Transfer::STORAGE_MAX_CHUNK_SIZE) { $response = $this->client->call( 'POST', - "/v1/functions/{$functionId}/deployments", + "/functions/{$functionId}/deployments", [ 'content-type' => 'multipart/form-data', ], diff --git a/src/Transfer/Destinations/Local.php b/src/Transfer/Destinations/Local.php index 76a14285..2a9cca43 100644 --- a/src/Transfer/Destinations/Local.php +++ b/src/Transfer/Destinations/Local.php @@ -101,15 +101,16 @@ protected function import(array $resources, callable $callback): void foreach ($resources as $resource) { /** @var resource $resource */ switch ($resource->getName()) { - case 'Deployment': + case Resource::TYPE_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); + $resource->setData(''); break; - case 'File': + case Resource::TYPE_FILE: /** @var File $resource */ // Handle folders @@ -131,6 +132,10 @@ protected function import(array $resources, callable $callback): void } file_put_contents($this->path.'/files/'.$resource->getFileName(), $resource->getData(), FILE_APPEND); + $resource->setData(''); + break; + default: + $this->data[$resource->getGroup()][$resource->getName()][$resource->getInternalId()] = $resource->asArray(); break; } diff --git a/src/Transfer/Resource.php b/src/Transfer/Resource.php index a35c0a2a..64bea336 100644 --- a/src/Transfer/Resource.php +++ b/src/Transfer/Resource.php @@ -42,6 +42,8 @@ abstract class Resource public const TYPE_FUNCTION = 'function'; + public const TYPE_INDEX = 'index'; + // Children (Resources that are created by other resources) public const TYPE_ATTRIBUTE = 'attribute'; @@ -50,8 +52,6 @@ abstract class Resource public const TYPE_HASH = 'hash'; - public const TYPE_INDEX = 'index'; - public const TYPE_ENVVAR = 'envvar'; public const ALL_RESOURCES = [ @@ -76,6 +76,11 @@ abstract class Resource */ protected string $id = ''; + /** + * Original ID of the resource + */ + protected string $originalId = ''; + /** * Internal ID */ @@ -91,6 +96,11 @@ abstract class Resource */ protected string $message = ''; + /** + * Permissions + */ + protected array $permissions = []; + /** * Gets the name of the adapter. */ @@ -119,6 +129,24 @@ public function setId(string $id): self return $this; } + /** + * Get Original ID + */ + public function getOriginalId(): string + { + return $this->originalId; + } + + /** + * Set Original ID + */ + public function setOriginalId(string $originalId): self + { + $this->originalId = $originalId; + + return $this; + } + /** * Get Internal ID */ @@ -174,6 +202,28 @@ public function setMessage(string $message): self return $this; } + /** + * Get Permissions + * + * @return array + */ + public function getPermissions(): array + { + return $this->permissions; + } + + /** + * Set Permissions + * + * @param array $permissions + */ + public function setPermissions(array $permissions): self + { + $this->permissions = $permissions; + + return $this; + } + /** * As Array */ diff --git a/src/Transfer/Resources/Auth/Membership.php b/src/Transfer/Resources/Auth/Membership.php index 971141ff..35c0ab1e 100644 --- a/src/Transfer/Resources/Auth/Membership.php +++ b/src/Transfer/Resources/Auth/Membership.php @@ -12,16 +12,16 @@ class Membership extends Resource { protected Team $team; - protected string $userId; + protected User $user; protected array $roles; protected bool $active = true; - public function __construct(Team $team, string $userId, array $roles = [], bool $active = true) + public function __construct(Team $team, User $user, array $roles = [], bool $active = true) { $this->team = $team; - $this->userId = $userId; + $this->user = $user; $this->roles = $roles; $this->active = $active; } @@ -48,14 +48,14 @@ public function setTeam(Team $team): self return $this; } - public function getUserId(): string + public function getUser(): User { - return $this->userId; + return $this->user; } - public function setUserId(string $userId): self + public function setUser(User $user): self { - $this->userId = $userId; + $this->user = $user; return $this; } @@ -87,7 +87,7 @@ public function setActive(bool $active): self public function asArray(): array { return [ - 'userId' => $this->userId, + 'userId' => $this->user->getId(), 'roles' => $this->roles, 'active' => $this->active, ]; diff --git a/src/Transfer/Resources/Auth/Team.php b/src/Transfer/Resources/Auth/Team.php index 78afcb64..85267f65 100644 --- a/src/Transfer/Resources/Auth/Team.php +++ b/src/Transfer/Resources/Auth/Team.php @@ -8,8 +8,6 @@ class Team extends Resource { - protected string $id; - protected string $name; protected array $preferences = []; @@ -46,18 +44,6 @@ public function setTeamName(string $name): self return $this; } - public function getId(): string - { - return $this->id; - } - - public function setId(string $id): self - { - $this->id = $id; - - return $this; - } - public function getPreferences(): array { return $this->preferences; diff --git a/src/Transfer/Resources/Auth/User.php b/src/Transfer/Resources/Auth/User.php index 14a2a2f9..d34d4591 100644 --- a/src/Transfer/Resources/Auth/User.php +++ b/src/Transfer/Resources/Auth/User.php @@ -17,8 +17,6 @@ class User extends Resource public const TYPE_OAUTH = 'oauth'; - protected string $id = ''; - protected string $email = ''; protected string $username = ''; @@ -40,7 +38,7 @@ class User extends Resource protected array $preferences = []; public function __construct( - string $id = '', + string $id, string $email = '', string $username = '', Hash $passwordHash = null, @@ -73,24 +71,6 @@ public static function getName(): string return Resource::TYPE_USER; } - /** - * Get ID - */ - public function getId(): string - { - return $this->id; - } - - /** - * Set ID - */ - public function setId(string $id): self - { - $this->id = $id; - - return $this; - } - /** * Get Email */ @@ -130,7 +110,7 @@ public function setUsername(string $username): self /** * Get Password Hash */ - public function getPasswordHash(): Hash + public function getPasswordHash(): ?Hash { return $this->passwordHash; } diff --git a/src/Transfer/Resources/Database/Collection.php b/src/Transfer/Resources/Database/Collection.php index 2e09a74b..ac8d0080 100644 --- a/src/Transfer/Resources/Database/Collection.php +++ b/src/Transfer/Resources/Database/Collection.php @@ -19,14 +19,10 @@ class Collection extends Resource private Database $database; - protected array $permissions = []; - protected bool $documentSecurity = false; protected string $name; - protected string $id; - public function __construct(Database $database, string $name, string $id, bool $documentSecurity = false, array $permissions = []) { $this->database = $database; @@ -70,18 +66,6 @@ public function setCollectionName(string $name): self return $this; } - public function getId(): string - { - return $this->id; - } - - public function setId(string $id): self - { - $this->id = $id; - - return $this; - } - public function getDocumentSecurity(): bool { return $this->documentSecurity; @@ -94,18 +78,6 @@ public function setDocumentSecurity(bool $documentSecurity): self return $this; } - public function getPermissions(): array - { - return $this->permissions; - } - - public function setPermissions(array $permissions): self - { - $this->permissions = $permissions; - - return $this; - } - public function asArray(): array { return [ diff --git a/src/Transfer/Resources/Database/Database.php b/src/Transfer/Resources/Database/Database.php index 33bc69fc..cee3d6a4 100644 --- a/src/Transfer/Resources/Database/Database.php +++ b/src/Transfer/Resources/Database/Database.php @@ -22,8 +22,6 @@ class Database extends Resource protected string $name; - protected string $id; - public function __construct(string $name = '', string $id = '') { $this->name = $name; @@ -45,18 +43,6 @@ 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; - } - /** * @return list */ diff --git a/src/Transfer/Resources/Database/Document.php b/src/Transfer/Resources/Database/Document.php index 676eb2bb..7f846d92 100644 --- a/src/Transfer/Resources/Database/Document.php +++ b/src/Transfer/Resources/Database/Document.php @@ -7,16 +7,12 @@ 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 = []) { $this->id = $id; @@ -36,18 +32,6 @@ public function getGroup(): string return Transfer::GROUP_DATABASES; } - public function getId(): string - { - return $this->id; - } - - public function setId(string $id): self - { - $this->id = $id; - - return $this; - } - public function getDatabase(): Database { return $this->database; @@ -89,23 +73,6 @@ public function setData(array $data): self return $this; } - public function getPermissions(): array - { - return $this->permissions; - } - - /** - * Set Permissions - * - * @param array $permissions - */ - public function setPermissions(array $permissions): self - { - $this->permissions = $permissions; - - return $this; - } - public function asArray(): array { return [ diff --git a/src/Transfer/Resources/Functions/Deployment.php b/src/Transfer/Resources/Functions/Deployment.php index 2f6fe407..bc4fc699 100644 --- a/src/Transfer/Resources/Functions/Deployment.php +++ b/src/Transfer/Resources/Functions/Deployment.php @@ -7,8 +7,6 @@ class Deployment extends Resource { - protected string $id; - protected Func $func; protected string $entrypoint; @@ -45,18 +43,6 @@ 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; diff --git a/src/Transfer/Resources/Functions/Func.php b/src/Transfer/Resources/Functions/Func.php index ef65d3dc..7a978a2e 100644 --- a/src/Transfer/Resources/Functions/Func.php +++ b/src/Transfer/Resources/Functions/Func.php @@ -9,8 +9,6 @@ class Func extends Resource { protected string $name; - protected string $id; - protected array $execute; protected bool $enabled; @@ -50,18 +48,6 @@ 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; diff --git a/src/Transfer/Resources/Storage/Bucket.php b/src/Transfer/Resources/Storage/Bucket.php index 6dfc0030..401a3042 100644 --- a/src/Transfer/Resources/Storage/Bucket.php +++ b/src/Transfer/Resources/Storage/Bucket.php @@ -7,32 +7,30 @@ class Bucket extends Resource { - protected string $id; - - protected array $permissions; - - protected bool $fileSecurity; + protected ?bool $fileSecurity; protected string $name; - protected bool $enabled; + protected ?bool $enabled; - protected int $maxFileSize; + protected ?int $maxFileSize; - protected array $allowedFileExtensions; + protected ?array $allowedFileExtensions; - protected string $compression; + protected ?string $compression; - protected bool $encryption; + protected ?bool $encryption; - protected bool $antiVirus; + 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) + protected bool $updateLimits = false; + + public function __construct(string $id = '', string $name = '', array $permissions = [], bool $fileSecurity = false, bool $enabled = false, int $maxFileSize = null, array $allowedFileExtensions = [], string $compression = 'none', bool $encryption = false, bool $antiVirus = false, bool $updateLimits = false) { $this->id = $id; + $this->name = $name; $this->permissions = $permissions; $this->fileSecurity = $fileSecurity; - $this->name = $name; $this->enabled = $enabled; $this->maxFileSize = $maxFileSize; $this->allowedFileExtensions = $allowedFileExtensions; @@ -51,30 +49,6 @@ public function getGroup(): string return Transfer::GROUP_STORAGE; } - 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; @@ -111,12 +85,12 @@ public function setEnabled(bool $enabled): self return $this; } - public function getMaxFileSize(): int + public function getMaxFileSize(): ?int { return $this->maxFileSize; } - public function setMaxFileSize(int $maxFileSize): self + public function setMaxFileSize(?int $maxFileSize): self { $this->maxFileSize = $maxFileSize; @@ -171,6 +145,18 @@ public function setAntiVirus(bool $antiVirus): self return $this; } + public function getUpdateLimits(): bool + { + return $this->updateLimits; + } + + public function setUpdateLimits(bool $updateLimits): self + { + $this->updateLimits = $updateLimits; + + return $this; + } + public function asArray(): array { return [ diff --git a/src/Transfer/Resources/Storage/File.php b/src/Transfer/Resources/Storage/File.php index 4c09743e..dd00624f 100644 --- a/src/Transfer/Resources/Storage/File.php +++ b/src/Transfer/Resources/Storage/File.php @@ -7,8 +7,6 @@ class File extends Resource { - protected string $id; - protected Bucket $bucket; protected string $name; @@ -17,8 +15,6 @@ class File extends Resource protected string $mimeType; - protected array $permissions; - protected int $size; protected string $data; @@ -51,18 +47,6 @@ public function getGroup(): string return Transfer::GROUP_STORAGE; } - 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; @@ -116,18 +100,6 @@ public function setMimeType(string $mimeType): self return $this; } - public function getPermissions(): array - { - return $this->permissions; - } - - public function setPermissions(array $permissions): self - { - $this->permissions = $permissions; - - return $this; - } - public function getData(): string { return $this->data; diff --git a/src/Transfer/Source.php b/src/Transfer/Source.php index 4a747aa2..28967787 100644 --- a/src/Transfer/Source.php +++ b/src/Transfer/Source.php @@ -6,6 +6,11 @@ abstract class Source extends Target { protected $transferCallback; + /** + * @var array + */ + public array $previousReport = []; + public function callback(array $resources): void { ($this->transferCallback)($resources); @@ -27,8 +32,8 @@ public function run(array $resources, callable $callback): void } } - $this->cache->addAll($returnedResources); - $callback($prunedResurces); + $callback($returnedResources); + $this->cache->addAll($prunedResurces); }; $this->exportResources($resources, 100); diff --git a/src/Transfer/Sources/Appwrite.php b/src/Transfer/Sources/Appwrite.php index 2de8dde9..e8987550 100644 --- a/src/Transfer/Sources/Appwrite.php +++ b/src/Transfer/Sources/Appwrite.php @@ -138,7 +138,7 @@ public function report(array $resources = []): array $report[Resource::TYPE_MEMBERSHIP] = 0; $teams = $teamsClient->list()['teams']; foreach ($teams as $team) { - $report[Resource::TYPE_MEMBERSHIP] += $teamsClient->listMemberships($team['$id'])['total']; + $report[Resource::TYPE_MEMBERSHIP] += $teamsClient->listMemberships($team['$id'], [Query::limit(1)])['total']; } } @@ -153,7 +153,7 @@ public function report(array $resources = []): array $report[Resource::TYPE_COLLECTION] = 0; $databases = $databaseClient->list()['databases']; foreach ($databases as $database) { - $report[Resource::TYPE_COLLECTION] += $databaseClient->listCollections($database['$id'])['total']; + $report[Resource::TYPE_COLLECTION] += $databaseClient->listCollections($database['$id'], [Query::limit(1)])['total']; } } @@ -164,7 +164,7 @@ public function report(array $resources = []): array 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']; + $report[Resource::TYPE_DOCUMENT] += $databaseClient->listDocuments($database['$id'], $collection['$id'], [Query::limit(1)])['total']; } } } @@ -202,10 +202,16 @@ public function report(array $resources = []): array $currentPermission = 'files.read'; if (in_array(Resource::TYPE_FILE, $resources)) { $report[Resource::TYPE_FILE] = 0; + $report['size'] = 0; $buckets = $storageClient->listBuckets()['buckets']; foreach ($buckets as $bucket) { - $report[Resource::TYPE_FILE] += $storageClient->listFiles($bucket['$id'])['total']; + $files = $storageClient->listFiles($bucket['$id']); + $report[Resource::TYPE_FILE] += $files['total']; + foreach ($files['files'] as $file) { + $report['size'] += $storageClient->getFile($bucket['$id'], $file['$id'])['sizeOriginal']; + } } + $report['size'] = $report['size'] / 1024 / 1024; // MB } // Functions @@ -218,7 +224,7 @@ public function report(array $resources = []): array $report[Resource::TYPE_DEPLOYMENT] = 0; $functions = $functionsClient->list()['functions']; foreach ($functions as $function) { - $report[Resource::TYPE_DEPLOYMENT] += $functionsClient->listDeployments($function['$id'])['total']; + $report[Resource::TYPE_DEPLOYMENT] += $functionsClient->listDeployments($function['$id'], [Query::limit(1)])['total']; } } @@ -230,6 +236,10 @@ public function report(array $resources = []): array } } + $report['version'] = $this->call('GET', '/health/version', ['X-Appwrite-Key' => '', 'X-Appwrite-Project' => ''])['version']; + + $this->previousReport = $report; + return $report; } catch (\Exception $e) { if ($e->getCode() === 403) { @@ -355,6 +365,7 @@ private function exportMemberships(int $batchSize) // Export Memberships $cacheTeams = $this->cache->get(Team::getName()); + $cacheUsers = $this->cache->get(User::getName()); foreach ($cacheTeams as $team) { /** @var Team $team */ @@ -373,10 +384,23 @@ private function exportMemberships(int $batchSize) break; } + $user = null; + foreach ($cacheUsers as $cacheUser) { + /** @var User $cacheUser */ + if ($cacheUser->getId() === $response['memberships'][0]['userId']) { + $user = $cacheUser; + break; + } + } + + if (! $user) { + throw new \Exception('User not found'); + } + foreach ($response['memberships'] as $membership) { $memberships[] = new Membership( $team, - $membership['userId'], + $user, $membership['roles'], $membership['confirm'] ); @@ -416,6 +440,27 @@ protected function exportGroupDatabases(int $batchSize, array $resources) } } + public function stripMetadata(array $document, bool $root = true) + { + if ($root) { + unset($document['$id']); + } + + unset($document['$permissions']); + unset($document['$collectionId']); + unset($document['$updatedAt']); + unset($document['$createdAt']); + unset($document['$databaseId']); + + foreach ($document as $key => $value) { + if (is_array($value)) { + $document[$key] = $this->stripMetadata($value, false); + } + } + + return $document; + } + private function exportDocuments(int $batchSize) { $databaseClient = new Databases($this->client); @@ -443,18 +488,49 @@ private function exportDocuments(int $batchSize) 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']); + + $document = $this->stripMetadata($document); + + // Certain Appwrite versions allowed for data to be required but null + // This isn't allowed in modern versions so we need to remove it by comparing their attributes and replacing it with default value. + $attributes = $this->cache->get(Attribute::getName()); + foreach ($attributes as $attribute) { + /** @var Attribute $attribute */ + if ($attribute->getCollection()->getId() !== $collection->getId()) { + continue; + } + + if ($attribute->getRequired() && ! isset($document[$attribute->getKey()])) { + switch ($attribute->getTypeName()) { + case Attribute::TYPE_BOOLEAN: + $document[$attribute->getKey()] = false; + break; + case Attribute::TYPE_STRING: + $document[$attribute->getKey()] = ''; + break; + case Attribute::TYPE_INTEGER: + $document[$attribute->getKey()] = 0; + break; + case Attribute::TYPE_FLOAT: + $document[$attribute->getKey()] = 0.0; + break; + case Attribute::TYPE_DATETIME: + $document[$attribute->getKey()] = 0; + break; + case Attribute::TYPE_URL: + $document[$attribute->getKey()] = 'http://null'; + break; + } + } + } + + $cleanData = $this->stripMetadata($document); $documents[] = new Document( $id, $collection->getDatabase(), $collection, - $document, + $cleanData, $permissions ); $lastDocument = $id; @@ -683,7 +759,27 @@ private function exportAttributes(int $batchSize) $queries ); + // Remove two way relationship attributes + $this->cache->get(Resource::TYPE_ATTRIBUTE); + + $knownTwoways = []; + + foreach ($this->cache->get(Resource::TYPE_ATTRIBUTE) as $attribute) { + /** @var Attribute|Relationship $attribute */ + if ($attribute->getTypeName() == Attribute::TYPE_RELATIONSHIP && $attribute->getTwoWay()) { + $knownTwoways[] = $attribute->getTwoWayKey(); + } + } + foreach ($response['attributes'] as $attribute) { + if (in_array($attribute['key'], $knownTwoways)) { + continue; + } + + if ($attribute['type'] === 'relationship') { + $knownTwoways[] = $attribute['twoWayKey']; + } + $attributes[] = $this->convertAttribute($attribute, $collection); } @@ -762,17 +858,20 @@ private function calculateTypes(array $user): array protected function exportGroupStorage(int $batchSize, array $resources) { if (in_array(Resource::TYPE_BUCKET, $resources)) { - $this->exportBuckets($batchSize); + $this->exportBuckets($batchSize, false); } if (in_array(Resource::TYPE_FILE, $resources)) { $this->exportFiles($batchSize); } + + if (in_array(Resource::TYPE_BUCKET, $resources)) { + $this->exportBuckets($batchSize, true); + } } - private function exportBuckets(int $batchSize) + private function exportBuckets(int $batchSize, bool $updateLimits) { - //TODO: Impl batching $storageClient = new Storage($this->client); $buckets = $storageClient->listBuckets(); @@ -780,18 +879,22 @@ private function exportBuckets(int $batchSize) $convertedBuckets = []; foreach ($buckets['buckets'] as $bucket) { - $convertedBuckets[] = new Bucket( + $bucket = new Bucket( $bucket['$id'], + $bucket['name'], $bucket['$permissions'], $bucket['fileSecurity'], - $bucket['name'], $bucket['enabled'], $bucket['maximumFileSize'], $bucket['allowedFileExtensions'], $bucket['compression'], $bucket['encryption'], $bucket['antivirus'], + $updateLimits ); + + $bucket->setUpdateLimits($updateLimits); + $convertedBuckets[] = $bucket; } if (empty($convertedBuckets)) { @@ -894,7 +997,6 @@ protected function exportGroupFunctions(int $batchSize, array $resources) private function exportFunctions(int $batchSize) { - //TODO: Implement batching $functionsClient = new Functions($this->client); $functions = $functionsClient->list(); @@ -934,9 +1036,15 @@ private function exportFunctions(int $batchSize) private function exportDeployments(int $batchSize) { $functionsClient = new Functions($this->client); - $functions = $this->cache->get(Func::getName()); + // exportDeploymentData doesn't exist on Appwrite versions prior to 1.4 + $appwriteVersion = $this->call('GET', '/health/version', ['X-Appwrite-Key' => '', 'X-Appwrite-Project' => ''])['version']; + + if (version_compare($appwriteVersion, '1.4.0', '<')) { + return; + } + foreach ($functions as $func) { /** @var Func $func */ $lastDocument = null; diff --git a/src/Transfer/Sources/Firebase.php b/src/Transfer/Sources/Firebase.php index 7f3597c6..d19a3c12 100644 --- a/src/Transfer/Sources/Firebase.php +++ b/src/Transfer/Sources/Firebase.php @@ -124,7 +124,30 @@ public static function getSupportedResources(): array public function report(array $resources = []): array { - throw new \Exception('Not implemented'); + // Check our service account is valid + if (! isset($this->serviceAccount['project_id'])) { + throw new \Exception('Invalid Firebase Service Account'); + } + + $this->authenticate(); + + $scopes = $this->call('GET', 'https://www.googleapis.com/oauth2/v1/tokeninfo?access_token='.$this->currentToken)['scope']; + + $scopes = explode(' ', $scopes); + + if (! in_array('https://www.googleapis.com/auth/firebase', $scopes)) { + throw new \Exception('Firebase Scope Missing'); + } + + if (! in_array('https://www.googleapis.com/auth/cloud-platform', $scopes)) { + throw new \Exception('Cloud Platform Scope Missing'); + } + + if (! in_array('https://www.googleapis.com/auth/datastore', $scopes)) { + throw new \Exception('Datastore Scope Missing'); + } + + return []; } protected function exportGroupAuth(int $batchSize, array $resources) @@ -212,7 +235,8 @@ private function calculateUserType(array $providerData): array protected function exportGroupDatabases(int $batchSize, array $resources) { if (in_array(Resource::TYPE_DATABASE, $resources)) { - $database = new Database('default', '(default)'); + $database = new Database('default', 'default'); + $database->setOriginalId('(default)'); $this->callback([$database]); } @@ -264,31 +288,30 @@ private function exportDB(int $batchSize, bool $pushDocuments, Database $databas private function convertAttribute(Collection $collection, string $key, array $field): Attribute { - 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'); + 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'); } } @@ -317,7 +340,7 @@ private function calculateArrayType(Collection $collection, string $key, array $ 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(); + $resourceURL = 'https://firestore.googleapis.com/v1/projects/'.$this->projectID.'/databases/'.$collection->getDatabase()->getOriginalId().'/documents/'.$collection->getId(); $nextPageToken = null; @@ -356,6 +379,7 @@ private function exportCollection(Collection $collection, int $batchSize, bool $ // Transfer Documents if ($transferDocuments) { + $this->callback(array_values($documentSchema)); $this->callback($documents); } @@ -378,7 +402,7 @@ private function calculateValue(array $field) } elseif (array_key_exists('integerValue', $field)) { return $field['integerValue']; } elseif (array_key_exists('mapValue', $field)) { - return $field['mapValue']; + return json_encode($field['mapValue']); } elseif (array_key_exists('nullValue', $field)) { return $field['nullValue']; } elseif (array_key_exists('referenceValue', $field)) { @@ -388,9 +412,11 @@ private function calculateValue(array $field) } elseif (array_key_exists('timestampValue', $field)) { return $field['timestampValue']; } elseif (array_key_exists('geoPointValue', $field)) { - return $field['geoPointValue']; + return [$field['geoPointValue']['latitude'], $field['geoPointValue']['longitude']]; } elseif (array_key_exists('arrayValue', $field)) { //TODO: + } elseif (array_key_exists('referenceValue', $field)) { + //TODO: } else { throw new \Exception('Unknown field type'); } @@ -403,7 +429,10 @@ private function convertDocument(Collection $collection, array $document): Docum $data[$key] = $this->calculateValue($field); } - return new Document($document['name'], $collection->getDatabase(), $collection, $data, []); + $documentId = explode('/', $document['name']); + $documentId = end($documentId); + + return new Document($documentId, $collection->getDatabase(), $collection, $data, []); } protected function exportGroupStorage(int $batchSize, array $resources) @@ -436,7 +465,7 @@ private function exportBuckets(int $batchsize) } foreach ($result['items'] as $bucket) { - $this->callback([new Bucket($bucket['id'], [], false, $bucket['name'])]); + $this->callback([new Bucket($bucket['id'], $bucket['name'], [], false)]); } if (! isset($result['nextPageToken'])) { diff --git a/src/Transfer/Sources/NHost.php b/src/Transfer/Sources/NHost.php index d0ca19b2..c956b242 100644 --- a/src/Transfer/Sources/NHost.php +++ b/src/Transfer/Sources/NHost.php @@ -42,6 +42,8 @@ class NHost extends Source public string $adminSecret; + public string $storageURL; + public function __construct(string $subdomain, string $region, string $adminSecret, string $databaseName, string $username, string $password, string $port = '5432') { $this->subdomain = $subdomain; @@ -51,6 +53,7 @@ public function __construct(string $subdomain, string $region, string $adminSecr $this->username = $username; $this->password = $password; $this->port = $port; + $this->storageURL = "https://{$this->subdomain}.storage.{$this->region}.nhost.run"; } public function getDatabase(): PDO @@ -193,8 +196,19 @@ public function report(array $resources = []): array } $report[Resource::TYPE_FILE] = $statement->fetchColumn(); + + $statement = $db->prepare('SELECT SUM(storage.files."size") from storage.files;'); + $statement->execute(); + + if ($statement->errorCode() !== '00000') { + throw new \Exception('Failed to access files table. Error: '.$statement->errorInfo()[2]); + } + + $report['size'] = ($statement->fetchColumn()) / 1024 / 1024; // MB; } + $this->previousReport = $report; + return $report; } @@ -284,14 +298,14 @@ private function exportCollections(int $batchSize) foreach ($databases as $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(); + $statement->execute([':database' => $database->getId()]); + $total = $statement->fetchColumn(0); $offset = 0; while ($offset < $total) { - $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]); + $statement = $db->prepare('SELECT table_name FROM information_schema.tables WHERE table_schema = :database order by table_name LIMIT :limit OFFSET :offset'); + $statement->execute([':limit' => $batchSize, ':offset' => $offset, ':database' => $database->getId()]); $tables = $statement->fetchAll(\PDO::FETCH_ASSOC); @@ -346,7 +360,9 @@ private function exportIndexes(int $batchSize) foreach ($databaseIndexes as $index) { $result = $this->convertIndex($index, $collection); - $indexes[] = $result; + if ($result) { + $indexes[] = $result; + } } $this->callback($indexes); @@ -356,19 +372,23 @@ private function exportIndexes(int $batchSize) private function exportDocuments(int $batchSize) { $databases = $this->cache->get(Database::getName()); + $collections = $this->cache->get(Collection::getName()); $db = $this->getDatabase(); foreach ($databases as $database) { /** @var Database $database */ - $collections = $database->getCollections(); + $collections = array_filter($collections, function (Collection $collection) use ($database) { + return $collection->getDatabase()->getId() === $database->getId(); + }); foreach ($collections as $collection) { - $total = $db->query('SELECT COUNT(*) FROM '.$collection->getCollectionName())->fetchColumn(); + /** @var Collection $collection */ + $total = $db->query('SELECT COUNT(*) FROM '.$collection->getDatabase()->getDBName().'."'.$collection->getCollectionName().'"')->fetchColumn(); $offset = 0; while ($offset < $total) { - $statement = $db->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->getDatabase()->getDBName().'."'.$collection->getCollectionName().'" LIMIT :limit OFFSET :offset) t;'); $statement->bindValue(':limit', $batchSize, \PDO::PARAM_INT); $statement->bindValue(':offset', $offset, \PDO::PARAM_INT); $statement->execute(); @@ -381,7 +401,7 @@ private function exportDocuments(int $batchSize) $attributes = $this->cache->get(Attribute::getName()); $collectionAttributes = array_filter($attributes, function (Attribute $attribute) use ($collection) { - return $attribute->getId() === $collection->getId(); + return $attribute->getCollection()->getId() === $collection->getId(); }); foreach ($documents as $document) { @@ -571,16 +591,8 @@ protected function exportBuckets(int $batchSize) foreach ($buckets as $bucket) { $transferBuckets[] = new Bucket( $bucket['id'], - [], - false, - $bucket['id'], - true, - $bucket['max_upload_file_size'], - [], - '', - false, - false - ); + $bucket['id'] + ); //TODO: To add file_size transfer then we need to be able to see the destination's limit on files. } $this->callback($transferBuckets); @@ -627,12 +639,11 @@ private function exportFiles(int $batchSize) private function exportFile(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", [ + $response = $this->call('GET', $this->storageURL."/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 94833e63..675269b7 100644 --- a/src/Transfer/Sources/Supabase.php +++ b/src/Transfer/Sources/Supabase.php @@ -224,6 +224,7 @@ public function __construct(string $endpoint, string $key, string $host, string $this->port = $port; $this->headers['Authorization'] = 'Bearer '.$this->key; + $this->headers['apiKey'] = $this->key; try { $this->pdo = new \PDO('pgsql:host='.$this->host.';port='.$this->port.';dbname='.$this->databaseName, $this->username, $this->password); @@ -332,8 +333,20 @@ public function report(array $resources = []): array } $report[Resource::TYPE_FILE] = $statement->fetchColumn(); + + $statementFileSize = $this->pdo->prepare('SELECT objects.metadata FROM storage.objects;'); + $statementFileSize->execute(); + + $report['size'] = 0; + foreach ($statementFileSize->fetchAll(\PDO::FETCH_ASSOC) as $file) { + $metadata = json_decode($file['metadata'], true); + + $report['size'] += ($metadata['size'] / 1024 / 1024); // MB + } } + $this->previousReport = $report; + return $report; } @@ -433,15 +446,17 @@ protected function exportBuckets(int $batchSize) $transferBuckets = []; foreach ($buckets as $bucket) { - $transferBuckets[] = new Bucket( - '', + $convertedBucket = new Bucket( + 'unique()', + $bucket['name'], [], false, - $bucket['name'], true, - $bucket['file_size_limit'] ?? 0, + $bucket['file_size_limit'] ?? null, $bucket['allowed_mime_types'] ? $this->convertMimes($bucket['allowed_mime_types']) : [], ); + $convertedBucket->setOriginalId($bucket['id']); + $transferBuckets[] = $convertedBucket; } $this->callback($transferBuckets); @@ -458,14 +473,14 @@ public function exportFiles(int $batchSize) 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()]); + $totalStatement->execute([':bucketId' => $bucket->getOriginalId()]); $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(), + ':bucketId' => $bucket->getOriginalId(), ':limit' => $batchSize, ':offset' => $offset, ]); @@ -507,7 +522,7 @@ public function exportFile(File $file) $chunkData = $this->call( 'GET', '/storage/v1/object/'. - rawurlencode($file->getBucket()->getId()).'/'.rawurlencode($file->getFileName()), + rawurlencode($file->getBucket()->getOriginalId()).'/'.rawurlencode($file->getFileName()), ['range' => "bytes=$start-$end"] ); diff --git a/src/Transfer/Transfer.php b/src/Transfer/Transfer.php index 715fb021..c7216647 100644 --- a/src/Transfer/Transfer.php +++ b/src/Transfer/Transfer.php @@ -71,25 +71,36 @@ public function __construct(Source $source, Destination $destination) protected array $events = []; + protected array $resources = []; + public function getStatusCounters() { $status = []; + foreach ($this->resources as $resource) { + $status[$resource] = [ + Resource::STATUS_PENDING => 0, + Resource::STATUS_SUCCESS => 0, + Resource::STATUS_ERROR => 0, + Resource::STATUS_SKIPPED => 0, + Resource::STATUS_PROCESSING => 0, + Resource::STATUS_WARNING => 0, + ]; + } + + if ($this->source->previousReport) { + foreach ($this->source->previousReport as $resource => $data) { + if ($resource != 'size' && $resource != 'version') { + $status[$resource]['pending'] = $data; + } + } + } + foreach ($this->cache->getAll() as $resources) { foreach ($resources as $resource) { /** @var resource $resource */ - if (! array_key_exists($resource->getName(), $status)) { - $status[$resource->getName()] = [ - 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->getName()][$resource->getStatus()]++; + $status[$resource->getName()]['pending']--; } } @@ -113,6 +124,9 @@ public function run(array $resources, callable $callback): void } } + $computedResources = array_map('strtolower', $computedResources); + + $this->resources = $computedResources; $this->destination->run($computedResources, $callback, $this->source); } @@ -155,7 +169,7 @@ public function getReport(string $statusLevel = ''): array 'resource' => $type, 'id' => $resource->getId(), 'status' => $resource->getStatus(), - 'message' => $resource->getReason(), + 'message' => $resource->getMessage(), ]; } } diff --git a/tests/Transfer/E2E/Adapters/Mock.php b/tests/Transfer/E2E/Adapters/Mock.php index caa7bfab..9e7cb33f 100644 --- a/tests/Transfer/E2E/Adapters/Mock.php +++ b/tests/Transfer/E2E/Adapters/Mock.php @@ -47,30 +47,9 @@ public function import(array $resources, callable $callback): void // 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; + case Resource::TYPE_FILE: + /** @var File $resource */ + break; } $resource->setStatus(Resource::STATUS_SUCCESS); diff --git a/tests/Transfer/E2E/Sources/Base.php b/tests/Transfer/E2E/Sources/Base.php index e7605e79..36b397d8 100644 --- a/tests/Transfer/E2E/Sources/Base.php +++ b/tests/Transfer/E2E/Sources/Base.php @@ -17,7 +17,7 @@ abstract class Base extends TestCase protected ?Destination $destination = null; - public function __construct() + protected function setUp(): void { if (! $this->source) { throw new \Exception('Source not set'); @@ -43,8 +43,97 @@ public function testGetSupportedResources(): void public function testCache(): void { - $this->source->registerCache($this->createMock(\Utopia\Transfer\Cache::class)); + $cache = $this->createMock(\Utopia\Transfer\Cache::class); + $this->source->registerCache($cache); $this->assertNotNull($this->source->cache); } + + /** + * Call + * + * Make an API call + * + * @throws \Exception + */ + protected function call(string $method, string $path = '', array $headers = [], array $params = []): array|string + { + $queryString = ''; + if ($method == 'GET' && ! empty($params)) { + $queryString = '?'.http_build_query($params); + } + + $url = $path.$queryString; + + $ch = curl_init($url); + + $responseHeaders = []; + $responseStatus = -1; + $responseType = ''; + $responseBody = ''; + + switch ($headers['Content-Type']) { + case 'application/json': + $query = json_encode($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)) { + 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; + } } diff --git a/tests/Transfer/E2E/Sources/NHostTest.php b/tests/Transfer/E2E/Sources/NHostTest.php index 0b6586a4..62aa586b 100644 --- a/tests/Transfer/E2E/Sources/NHostTest.php +++ b/tests/Transfer/E2E/Sources/NHostTest.php @@ -4,6 +4,7 @@ use Utopia\Tests\E2E\Adapters\Mock; use Utopia\Transfer\Destination; +use Utopia\Transfer\Resource; use Utopia\Transfer\Source; use Utopia\Transfer\Sources\NHost; use Utopia\Transfer\Transfer; @@ -16,38 +17,253 @@ class NHostTest extends Base protected ?Destination $destination = null; - public function __construct() + protected function setUp(): void { + // Check DB is online and ready + $pdo = null; + $tries = 5; + + while ($tries > 0) { + try { + $pdo = new \PDO('pgsql:host=nhost-db'.';port=5432;dbname=postgres', 'postgres', 'postgres'); + $pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION); + + if ($pdo && $pdo->query('SELECT 1')->fetchColumn() === 1) { + break; + } else { + var_dump('DB was offline, waiting 1s then retrying.'); + } + } catch (\PDOException $e) { + } + + sleep(1); + $tries--; + } + + if (! $pdo || $tries === 0) { + throw new \Exception('DB was offline after 5 tries'); + } + + // Check Storage is online and ready + $tries = 5; + while ($tries > 0) { + try { + $this->call('GET', 'http://nhost-storage/', ['Content-Type' => 'text/plain']); + + break; + } catch (\Exception $e) { + } + + sleep(5); + $tries--; + } + + if ($tries === 0) { + throw new \Exception('Storage was offline after 5 tries'); + } + $this->source = new NHost( 'xxxxxxxxxxxx', 'eu-central-1', - 'xxxxxxxxxxxxxxxxxx', - 'xxxxxxxxxxxxxxx', - 'xxxxxxxxx', - 'xxxxxxxxxxxxxxxx' + 'hasuraSecret', + 'postgres', + 'postgres', + 'password' ); + $this->source->pdo = new \PDO('pgsql:host=nhost-db'.';port=5432;dbname=postgres', 'postgres', 'postgres'); + $this->source->storageURL = 'http://nhost-storage'; $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'); } - public function testSourceReport(): void + public function testSourceReport() { // Test report all $report = $this->source->report(); $this->assertNotEmpty($report); + + return [ + 'report' => $report, + ]; } - public function testRunTransfer(): void + /** + * @depends testSourceReport + */ + public function testRunTransfer($state) { $this->transfer->run( $this->source->getSupportedResources(), - function ($data) { - $this->assertNotEmpty($data); + function () { } ); + + $this->assertEquals(0, count($this->transfer->getReport('error'))); + + return array_merge($state, [ + 'transfer' => $this->transfer, + 'source' => $this->source, + ]); + } + + /** + * @depends testRunTransfer + */ + public function testValidateTransfer($state) + { + $statusCounters = $state['transfer']->getStatusCounters(); + $this->assertNotEmpty($statusCounters); + + foreach ($statusCounters as $resource => $counters) { + $this->assertNotEmpty($counters); + + if ($counters[Resource::STATUS_ERROR] > 0) { + $this->fail('Resource '.$resource.' has '.$counters[Resource::STATUS_ERROR].' errors'); + + return; + } + } + + return $state; + } + + /** + * @depends testValidateTransfer + */ + public function testValidateUserTransfer($state): void + { + // Find known user + $users = $state['source']->cache->get(Resource::TYPE_USER); + $foundUser = null; + + foreach ($users as $user) { + /** @var \Utopia\Transfer\Resources\Auth\User $user */ + if ($user->getEmail() === 'test@test.com') { + $foundUser = $user; + } + + break; + } + + if (! $foundUser) { + $this->fail('User "test@test.com" not found'); + + return; + } + + $this->assertEquals('success', $foundUser->getStatus()); + $this->assertEquals('$2a$10$ARQ/f.K6OmCjZ8XF0U.6fezPMlxDqsmcl0Rs6xQVkvj62u7gcSzOW', $foundUser->getPasswordHash()->getHash()); + $this->assertEquals('bcrypt', $foundUser->getPasswordHash()->getAlgorithm()); + $this->assertEquals('test@test.com', $foundUser->getUsername()); + $this->assertEquals(['email'], $foundUser->getTypes()); + } + + /** + * @depends testValidateTransfer + */ + public function testValidateDatabaseTransfer($state): void + { + // Find known database + $databases = $state['source']->cache->get(Resource::TYPE_DATABASE); + $foundDatabase = null; + + foreach ($databases as $database) { + /** @var \Utopia\Transfer\Resources\Database $database */ + if ($database->getDBName() === 'public') { + $foundDatabase = $database; + } + + break; + } + + if (! $foundDatabase) { + $this->fail('Database "public" not found'); + + return; + } + + $this->assertEquals('success', $foundDatabase->getStatus()); + $this->assertEquals('public', $foundDatabase->getDBName()); + $this->assertEquals('public', $foundDatabase->getId()); + + // Find known collection + $collections = $state['source']->cache->get(Resource::TYPE_COLLECTION); + $foundCollection = null; + + foreach ($collections as $collection) { + /** @var \Utopia\Transfer\Resources\Database\Collection $collection */ + if ($collection->getCollectionName() === 'TestTable') { + $foundCollection = $collection; + } + + break; + } + + if (! $foundCollection) { + $this->fail('Collection "TestTable" not found'); + + return; + } + + $this->assertEquals('success', $foundCollection->getStatus()); + $this->assertEquals('TestTable', $foundCollection->getCollectionName()); + $this->assertEquals('TestTable', $foundCollection->getId()); + $this->assertEquals('public', $foundCollection->getDatabase()->getId()); + } + + /** + * @depends testValidateTransfer + */ + public function testValidateStorageTransfer($state): void + { + // Find known bucket + $buckets = $state['source']->cache->get(Resource::TYPE_BUCKET); + $foundBucket = null; + + foreach ($buckets as $bucket) { + /** @var \Utopia\Transfer\Resources\Bucket $bucket */ + if ($bucket->getId() === 'default') { + $foundBucket = $bucket; + } + + break; + } + + if (! $foundBucket) { + $this->fail('Bucket "default" not found'); + + return; + } + + $this->assertEquals('success', $foundBucket->getStatus()); + $this->assertEquals('default', $foundBucket->getId()); + + // Find known file + $files = $state['source']->cache->get(Resource::TYPE_FILE); + $foundFile = null; + + foreach ($files as $file) { + /** @var \Utopia\Transfer\Resources\File $file */ + if ($file->getFileName() === 'tulips.png') { + $foundFile = $file; + } + + break; + } + + if (! $foundFile) { + $this->fail('File "tulips.png" not found'); + + return; + } + /** @var \Utopia\Transfer\Resources\Storage\File $foundFile */ + $this->assertEquals('success', $foundFile->getStatus()); + $this->assertEquals('tulips.png', $foundFile->getFileName()); + $this->assertEquals('default', $foundFile->getBucket()->getId()); + $this->assertEquals('image/png', $foundFile->getMimeType()); + $this->assertEquals(679233, $foundFile->getSize()); + $this->assertEquals('', $foundFile->getData()); // Memory Leak Check } } diff --git a/tests/Transfer/E2E/Sources/SupabaseTest.php b/tests/Transfer/E2E/Sources/SupabaseTest.php new file mode 100644 index 00000000..62807f81 --- /dev/null +++ b/tests/Transfer/E2E/Sources/SupabaseTest.php @@ -0,0 +1,276 @@ + 0) { + try { + $pdo = new \PDO('pgsql:host=supabase-db'.';port=5432;dbname=postgres', 'postgres', 'postgres'); + $pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION); + + if ($pdo && $pdo->query('SELECT 1')->fetchColumn() === 1) { + break; + } else { + var_dump('DB was offline, waiting 1s then retrying.'); + } + } catch (\PDOException $e) { + } + + sleep(1); + $tries--; + } + + if (! $pdo || $tries === 0) { + throw new \Exception('DB was offline after 5 tries'); + } + + $this->source = new Supabase( + 'http://supabase-api', + 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx', + 'supabase-db', + 'postgres', + 'postgres', + 'postgres' + ); + + $this->destination = new Mock(); + $this->transfer = new Transfer($this->source, $this->destination); + } + + public function testSourceReport() + { + // Test report all + $report = $this->source->report(); + + $this->assertNotEmpty($report); + + return [ + 'report' => $report, + ]; + } + + /** + * @depends testSourceReport + */ + public function testRunTransfer($state) + { + $this->transfer->run($this->source->getSupportedResources(), + function () { + } + ); + + $this->assertEquals(0, count($this->transfer->getReport('error'))); + + return array_merge($state, [ + 'transfer' => $this->transfer, + 'source' => $this->source, + ]); + } + + /** + * @depends testRunTransfer + */ + public function testValidateTransfer($state) + { + $statusCounters = $state['transfer']->getStatusCounters(); + $this->assertNotEmpty($statusCounters); + + foreach ($statusCounters as $resource => $counters) { + $this->assertNotEmpty($counters); + + if ($counters[Resource::STATUS_ERROR] > 0) { + $this->fail('Resource '.$resource.' has '.$counters[Resource::STATUS_ERROR].' errors'); + + return; + } + } + + return $state; + } + + /** + * @depends testValidateTransfer + */ + public function testValidateUserTransfer($state): void + { + // Find known user + $users = $state['source']->cache->get(Resource::TYPE_USER); + $this->assertGreaterThan(0, count($users)); + $foundUser = null; + + foreach ($users as $user) { + /** @var \Utopia\Transfer\Resources\Auth\User $user */ + if ($user->getEmail() == 'albert.kihn95@yahoo.com') { + $foundUser = $user; + + break; + } + } + + if (! $foundUser) { + $this->fail('User "albert.kihn95@yahoo.com" not found'); + + return; + } + + $this->assertEquals('success', $foundUser->getStatus()); + $this->assertEquals('$2a$10$NGZAAOfXeheUoH9V3dnRoeR.r3J5ynnSZ6KjvHxOUlV8XUrulJzQa', $foundUser->getPasswordHash()->getHash()); + $this->assertEquals('bcrypt', $foundUser->getPasswordHash()->getAlgorithm()); + $this->assertEquals(['email'], $foundUser->getTypes()); + } + + /** + * @depends testValidateTransfer + */ + public function testValidateDatabaseTransfer($state): void + { + // Find known database + $databases = $state['source']->cache->get(Resource::TYPE_DATABASE); + $this->assertGreaterThan(0, count($databases)); + $foundDatabase = null; + + foreach ($databases as $database) { + /** @var \Utopia\Transfer\Resources\Database $database */ + if ($database->getDBName() === 'public') { + $foundDatabase = $database; + } + + break; + } + + if (! $foundDatabase) { + $this->fail('Database "public" not found'); + + return; + } + + $this->assertEquals('success', $foundDatabase->getStatus()); + $this->assertEquals('public', $foundDatabase->getDBName()); + $this->assertEquals('public', $foundDatabase->getId()); + + // Find Known Collections + $collections = $state['source']->cache->get(Resource::TYPE_COLLECTION); + $this->assertGreaterThan(0, count($collections)); + + $foundCollection = null; + + foreach ($collections as $collection) { + /** @var \Utopia\Transfer\Resources\Database\Collection $collection */ + if ($collection->getDatabase()->getDBName() === 'public' && $collection->getCollectionName() === 'test') { + $foundCollection = $collection; + + break; + } + } + + if (! $foundCollection) { + $this->fail('Collection "test" not found'); + + return; + } + + $this->assertEquals('success', $foundCollection->getStatus()); + $this->assertEquals('test', $foundCollection->getCollectionName()); + $this->assertEquals('public', $foundCollection->getDatabase()->getDBName()); + $this->assertEquals('public', $foundCollection->getDatabase()->getId()); + + // Find Known Documents + $documents = $state['source']->cache->get(Resource::TYPE_DOCUMENT); + $this->assertGreaterThan(0, count($documents)); + + $foundDocument = null; + + foreach ($documents as $document) { + /** @var \Utopia\Transfer\Resources\Database\Document $document */ + if ($document->getCollection()->getDatabase()->getDBName() === 'public' && $document->getCollection()->getCollectionName() === 'test') { + $foundDocument = $document; + } + + break; + } + + if (! $foundDocument) { + $this->fail('Document "1" not found'); + + return; + } + + $this->assertEquals('success', $foundDocument->getStatus()); + } + + /** + * @depends testValidateTransfer + */ + public function testValidateStorageTransfer($state): void + { + // Find known bucket + $buckets = $state['source']->cache->get(Resource::TYPE_BUCKET); + $this->assertGreaterThan(0, count($buckets)); + + $foundBucket = null; + + foreach ($buckets as $bucket) { + /** @var \Utopia\Transfer\Resources\Storage\Bucket $bucket */ + if ($bucket->getBucketName() === 'Test Bucket 1') { + $foundBucket = $bucket; + } + + break; + } + + if (! $foundBucket) { + $this->fail('Bucket "Test Bucket 1" not found'); + + return; + } + + $this->assertEquals('success', $foundBucket->getStatus()); + + // Find known file + $files = $state['source']->cache->get(Resource::TYPE_FILE); + $this->assertGreaterThan(0, count($files)); + + $foundFile = null; + + foreach ($files as $file) { + /** @var \Utopia\Transfer\Resources\File $file */ + if ($file->getFileName() === 'tulips.png') { + $foundFile = $file; + } + + break; + } + + if (! $foundFile) { + $this->fail('File "tulips.png" not found'); + + return; + } + /** @var \Utopia\Transfer\Resources\Storage\File $foundFile */ + $this->assertEquals('success', $foundFile->getStatus()); + $this->assertEquals('tulips.png', $foundFile->getFileName()); + $this->assertEquals('image/png', $foundFile->getMimeType()); + $this->assertEquals(679233, $foundFile->getSize()); + $this->assertEquals('', $foundFile->getData()); // Memory Leak Check + } +} diff --git a/tests/Transfer/resources/nhost/1_globals.sql b/tests/Transfer/resources/nhost/1_globals.sql new file mode 100644 index 00000000..68237a78 --- /dev/null +++ b/tests/Transfer/resources/nhost/1_globals.sql @@ -0,0 +1,44 @@ +-- +-- PostgreSQL database cluster dump +-- + +SET default_transaction_read_only = off; + +SET client_encoding = 'UTF8'; +SET standard_conforming_strings = on; + +-- +-- Roles +-- + +CREATE ROLE nhost_admin; +ALTER ROLE nhost_admin WITH SUPERUSER INHERIT CREATEROLE CREATEDB LOGIN REPLICATION BYPASSRLS; +CREATE ROLE nhost_auth_admin; +ALTER ROLE nhost_auth_admin WITH NOSUPERUSER NOINHERIT CREATEROLE NOCREATEDB LOGIN NOREPLICATION NOBYPASSRLS; +CREATE ROLE nhost_hasura; +ALTER ROLE nhost_hasura WITH NOSUPERUSER INHERIT NOCREATEROLE NOCREATEDB LOGIN NOREPLICATION NOBYPASSRLS; +CREATE ROLE nhost_storage_admin; +ALTER ROLE nhost_storage_admin WITH NOSUPERUSER NOINHERIT CREATEROLE NOCREATEDB LOGIN NOREPLICATION NOBYPASSRLS; +CREATE ROLE pgbouncer; +ALTER ROLE pgbouncer WITH NOSUPERUSER INHERIT NOCREATEROLE NOCREATEDB LOGIN NOREPLICATION NOBYPASSRLS; + +-- +-- User Configurations +-- + +-- +-- User Config "nhost_auth_admin" +-- + +ALTER ROLE nhost_auth_admin SET search_path TO 'auth'; + +-- +-- User Config "nhost_storage_admin" +-- + +ALTER ROLE nhost_storage_admin SET search_path TO 'storage'; + +-- +-- PostgreSQL database cluster dump complete +-- + diff --git a/tests/Transfer/resources/nhost/backup.tar b/tests/Transfer/resources/nhost/2_main.sql similarity index 80% rename from tests/Transfer/resources/nhost/backup.tar rename to tests/Transfer/resources/nhost/2_main.sql index 6657eca1..1546377e 100644 --- a/tests/Transfer/resources/nhost/backup.tar +++ b/tests/Transfer/resources/nhost/2_main.sql @@ -2,8 +2,8 @@ -- PostgreSQL database dump -- --- Dumped from database version 14.5 (Debian 14.5-2.pgdg110+2) --- Dumped by pg_dump version 15.2 +-- Dumped from database version 14.6 (Debian 14.6-1.pgdg110+1) +-- Dumped by pg_dump version 15.3 SET statement_timeout = 0; SET lock_timeout = 0; @@ -16,10 +16,6 @@ SET xmloption = content; SET client_min_messages = warning; SET row_security = off; --- --- Name: auth; Type: SCHEMA; Schema: -; Owner: nhost_admin --- - CREATE SCHEMA auth; @@ -263,16 +259,30 @@ ALTER TABLE auth.providers OWNER TO 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_token_types; Type: TABLE; Schema: auth; Owner: nhost_auth_admin +-- + +CREATE TABLE auth.refresh_token_types ( + value text NOT NULL, + comment text +); + + +ALTER TABLE auth.refresh_token_types OWNER TO nhost_auth_admin; + -- -- Name: refresh_tokens; Type: TABLE; Schema: auth; Owner: nhost_auth_admin -- CREATE TABLE auth.refresh_tokens ( - refresh_token uuid NOT NULL, + id uuid DEFAULT gen_random_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 + metadata jsonb, + type text DEFAULT 'regular'::text NOT NULL, + refresh_token_hash character varying(255) ); @@ -285,13 +295,6 @@ ALTER TABLE auth.refresh_tokens OWNER TO 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 -- @@ -554,57 +557,27 @@ CREATE TABLE hdb_catalog.hdb_version ( 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 + console_state jsonb DEFAULT '{}'::jsonb NOT NULL, + ee_client_id text, + ee_client_secret text ); 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 +-- Name: TestTable; 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 +CREATE TABLE public."TestTable" ( + string text NOT NULL, + "integer" bigint NOT NULL, + "boolean" boolean NOT NULL, + date date NOT NULL ); -ALTER TABLE public.test_table_1 OWNER TO nhost_hasura; +ALTER TABLE public."TestTable" OWNER TO nhost_hasura; -- -- Name: buckets; Type: TABLE; Schema: storage; Owner: nhost_storage_admin @@ -662,18 +635,22 @@ ALTER TABLE storage.schema_migrations OWNER TO nhost_storage_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 +0 create-migrations-table 9c0c864e0ccb0f8d1c77ab0576ef9f2841ec1b68 2023-07-25 11:03:00.726234 +1 create-initial-tables c16083c88329c867581a9c73c3f140783a1a5df4 2023-07-25 11:03:00.800607 +2 custom-user-fields 78236c9c2b50da88786bcf50099dd290f820e000 2023-07-25 11:03:00.805145 +3 discord-twitch-providers 857db1e92c7a8034e61a3d88ea672aec9b424036 2023-07-25 11:03:00.809131 +4 provider-request-options 42428265112b904903d9ad7833d8acf2812a00ed 2023-07-25 11:03:00.813437 +5 table-comments 78f76f88eff3b11ebab9be4f2469020dae017110 2023-07-25 11:03:00.815186 +6 setup-webauthn 87ba279363f8ecf8b450a681938a74b788cf536c 2023-07-25 11:03:00.829301 +7 add_authenticator_nickname d32fd62bb7a441eea48c5434f5f3744f2e334288 2023-07-25 11:03:00.832627 +8 workos-provider 0727238a633ff119bedcbebfec6a9ea83b2bd01d 2023-07-25 11:03:00.83641 +9 rename-authenticator-to-security-key fd7e00bef4d141a6193cf9642afd88fb6fe2b283 2023-07-25 11:03:00.840136 +10 azuread-provider f492ff4780f8210016e1c12fa0ed83eb4278a780 2023-07-25 11:03:00.84337 +11 add_refresh_token_hash_column 62a2cd295f63153dd9f16f3159d1ab2a49b01c2f 2023-07-25 11:03:00.852059 +12 add_refresh_token_metadata 3daa907e813d1e8b72107112a89916909702897c 2023-07-25 11:03:00.858989 +13 add_refresh_token_type 5f2472c56df4c4735f6add046782680eb27484e5 2023-07-25 11:03:00.862626 +14 alter_refresh_token_type a059cb9fda67f286e6bd2765f8aa7ea1e4a7fd6c 2023-07-25 11:03:00.879516 +15 rename_refresh_token_column 71e1d7fa6e6056fa193b4ff4d6f8e61cf3f5cd9f 2023-07-25 11:03:00.883585 \. @@ -708,11 +685,21 @@ azuread \. +-- +-- Data for Name: refresh_token_types; Type: TABLE DATA; Schema: auth; Owner: nhost_auth_admin +-- + +COPY auth.refresh_token_types (value, comment) FROM stdin; +regular Regular refresh token +pat Personal access token +\. + + -- -- 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; +COPY auth.refresh_tokens (id, created_at, expires_at, user_id, metadata, type, refresh_token_hash) FROM stdin; \. @@ -740,8 +727,8 @@ COPY auth.user_providers (id, created_at, updated_at, user_id, access_token, ref -- 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 +1503b765-f2a4-45de-956d-05c42a7e48de 2023-07-25 11:04:13.96605+00 8ff692dc-3f4f-4be1-879c-aafa46eadf10 me +9c231460-8803-4269-b689-212e56ee7e72 2023-07-25 11:04:13.96605+00 8ff692dc-3f4f-4be1-879c-aafa46eadf10 user \. @@ -758,7 +745,7 @@ COPY auth.user_security_keys (id, user_id, credential_id, credential_public_key, -- 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 +8ff692dc-3f4f-4be1-879c-aafa46eadf10 2023-07-25 11:04:13.96605+00 2023-07-25 11:04:13.96605+00 \N f test@test.com https://s.gravatar.com/avatar/b642b4217b34b1e8d3bd915fc65c4452?r=g&default=blank en test@test.com \N $2a$10$ARQ/f.K6OmCjZ8XF0U.6fezPMlxDqsmcl0Rs6xQVkvj62u7gcSzOW f f \N \N \N 2023-07-25 11:04:13.96605+00 user f \N \N verifyEmail:d8eddf37-8a59-4a63-a67f-7a2baf5d2fbb 2023-08-24 11:04:13.95+00 {} \N \. @@ -791,7 +778,7 @@ COPY hdb_catalog.hdb_cron_events (id, trigger_name, scheduled_time, status, trie -- 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 +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"}},{"array_relationships":[{"name":"refreshTokens","using":{"foreign_key_constraint_on":{"column":"type","table":{"name":"refresh_tokens","schema":"auth"}}}}],"configuration":{"column_config":{},"custom_column_names":{},"custom_name":"authRefreshTokenTypes","custom_root_fields":{"delete":"deleteAuthRefreshTokenTypes","delete_by_pk":"deleteAuthRefreshTokenType","insert":"insertAuthRefreshTokenTypes","insert_one":"insertAuthRefreshTokenType","select":"authRefreshTokenTypes","select_aggregate":"authRefreshTokenTypesAggregate","select_by_pk":"authRefreshTokenType","update":"updateAuthRefreshTokenTypes","update_by_pk":"updateAuthRefreshTokenType"}},"is_enum":true,"table":{"name":"refresh_token_types","schema":"auth"}},{"configuration":{"column_config":{"created_at":{"custom_name":"createdAt"},"expires_at":{"custom_name":"expiresAt"},"refresh_token_hash":{"custom_name":"refreshTokenHash"},"user_id":{"custom_name":"userId"}},"custom_column_names":{"created_at":"createdAt","expires_at":"expiresAt","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"}},"delete_permissions":[{"permission":{"filter":{"_and":[{"user_id":{"_eq":"X-Hasura-User-Id"}},{"type":{"_eq":"pat"}}]}},"role":"user"}],"object_relationships":[{"name":"user","using":{"foreign_key_constraint_on":"user_id"}}],"select_permissions":[{"permission":{"columns":["id","created_at","expires_at","metadata","type","user_id"],"filter":{"user_id":{"_eq":"X-Hasura-User-Id"}}},"role":"user"}],"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":"TestTable","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} 7 \. @@ -816,7 +803,7 @@ COPY hdb_catalog.hdb_scheduled_events (id, webhook_conf, scheduled_time, retry_c -- 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 +1 {"metadata":false,"remote_schemas":[],"sources":[],"data_connectors":[]} 7 3e840c3c-50b3-4860-9c05-dca4f48ff1ba 2023-07-25 11:03:01.079463+00 \. @@ -824,26 +811,17 @@ COPY hdb_catalog.hdb_schema_notifications (id, notification, resource_version, i -- 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} +COPY hdb_catalog.hdb_version (hasura_uuid, version, upgraded_on, cli_state, console_state, ee_client_id, ee_client_secret) FROM stdin; +1ca70c2d-0475-468f-ae8c-7b2a1ae60887 48 2023-07-25 11:02:37.40342+00 {} {} \N \N \. -- --- Data for Name: data_test; Type: TABLE DATA; Schema: public; Owner: nhost_hasura +-- Data for Name: TestTable; 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!"} +COPY public."TestTable" (string, "integer", "boolean", date) FROM stdin; +Hello World 42 f 2004-03-30 \. @@ -852,7 +830,7 @@ Sarah 22598e7c-3f7d-4d94-9690-67ad925e17ce { "name": "Sarah", "data": { "test": -- 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 +default 2023-07-25 11:02:53.50777+00 2023-07-25 11:02:53.50777+00 30 1 50000000 max-age=3600 t \. @@ -861,7 +839,7 @@ default 2023-04-18 03:07:31.49138+00 2023-04-18 03:07:31.49138+00 30 1 50000000 -- 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 +ea86457d-594b-4ea0-bb63-3b3fbc08ff47 2023-07-25 12:13:07.057526+00 2023-07-25 12:13:07.221984+00 default tulips.png 679233 image/png "2e57bf7a8a9bc49b3eacca90c921a4ae" t \N \. @@ -906,12 +884,20 @@ ALTER TABLE ONLY auth.providers ADD CONSTRAINT providers_pkey PRIMARY KEY (id); +-- +-- Name: refresh_token_types refresh_token_types_pkey; Type: CONSTRAINT; Schema: auth; Owner: nhost_auth_admin +-- + +ALTER TABLE ONLY auth.refresh_token_types + ADD CONSTRAINT refresh_token_types_pkey PRIMARY KEY (value); + + -- -- 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); + ADD CONSTRAINT refresh_tokens_pkey PRIMARY KEY (id); -- @@ -1075,27 +1061,11 @@ ALTER TABLE ONLY hdb_catalog.hdb_version -- --- Name: data_test data_test_pkey; Type: CONSTRAINT; Schema: public; Owner: nhost_hasura +-- Name: TestTable TestTable_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); +ALTER TABLE ONLY public."TestTable" + ADD CONSTRAINT "TestTable_pkey" PRIMARY KEY (string); -- @@ -1255,6 +1225,14 @@ 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: refresh_tokens refresh_tokens_types_fkey; Type: FK CONSTRAINT; Schema: auth; Owner: nhost_auth_admin +-- + +ALTER TABLE ONLY auth.refresh_tokens + ADD CONSTRAINT refresh_tokens_types_fkey FOREIGN KEY (type) REFERENCES auth.refresh_token_types(value) ON UPDATE RESTRICT ON DELETE RESTRICT; + + -- -- Name: hdb_cron_event_invocation_logs hdb_cron_event_invocation_logs_event_id_fkey; Type: FK CONSTRAINT; Schema: hdb_catalog; Owner: nhost_hasura -- @@ -1340,6 +1318,13 @@ GRANT ALL ON TABLE auth.provider_requests TO nhost_hasura; GRANT ALL ON TABLE auth.providers TO nhost_hasura; +-- +-- Name: TABLE refresh_token_types; Type: ACL; Schema: auth; Owner: nhost_auth_admin +-- + +GRANT ALL ON TABLE auth.refresh_token_types TO nhost_hasura; + + -- -- Name: TABLE refresh_tokens; Type: ACL; Schema: auth; Owner: nhost_auth_admin -- diff --git a/tests/Transfer/resources/nhost/api.json b/tests/Transfer/resources/nhost/api.json new file mode 100644 index 00000000..1be108c6 --- /dev/null +++ b/tests/Transfer/resources/nhost/api.json @@ -0,0 +1,266 @@ +{ + "uuid": "906a95b0-f861-4ab4-9b11-b3c6372852e8", + "lastMigration": 27, + "name": "NHost", + "endpointPrefix": "", + "latency": 0, + "port": 80, + "hostname": "", + "folders": [], + "routes": [ + { + "uuid": "d2ef5c52-336a-420f-a962-15a7974ca7a5", + "type": "http", + "documentation": "Get File URL", + "method": "get", + "endpoint": "v1/files/:id/presignedurl", + "responses": [ + { + "uuid": "e7d4239d-63aa-4111-a86a-304b7278817f", + "body": "{\n \"error\": {\n \"message\": \"you are not authorized\"\n }\n}", + "latency": 0, + "statusCode": 403, + "label": "No Hasura Key", + "headers": [], + "bodyType": "INLINE", + "filePath": "", + "databucketID": "", + "sendFileAsBody": false, + "rules": [], + "rulesOperator": "OR", + "disableTemplating": false, + "fallbackTo404": false, + "default": true + }, + { + "uuid": "38bdd9f1-5786-46a0-8165-1c830dc05b6f", + "body": "{\n \"url\": \"http://nhost-storage/v1/files/{{urlParam 'id'}}/presignedurl/content?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=XXXXXXXXXXXXXXXXXXXXXXXXXXXX&X-Amz-Date=2023XXXXXXXXXXXXXXX&X-Amz-Expires=30&X-Amz-SignedHeaders=host&X-Amz-Signature=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\",\n \"expiration\": 30\n}", + "latency": 0, + "statusCode": 200, + "label": "Success", + "headers": [], + "bodyType": "INLINE", + "filePath": "", + "databucketID": "", + "sendFileAsBody": false, + "rules": [ + { + "target": "header", + "modifier": "X-Hasura-Admin-Secret", + "value": "hasuraSecret", + "invert": false, + "operator": "equals" + } + ], + "rulesOperator": "OR", + "disableTemplating": false, + "fallbackTo404": false, + "default": false + } + ], + "enabled": true, + "responseMode": null + }, + { + "uuid": "c9d0a861-f15d-4017-9092-3341deabf43b", + "type": "http", + "documentation": "Get File", + "method": "get", + "endpoint": "v1/files/:id/presignedurl/content", + "responses": [ + { + "uuid": "5e7364e6-d39c-47d0-8a37-c8506273b1f2", + "body": "{}", + "latency": 0, + "statusCode": 200, + "label": "", + "headers": [], + "bodyType": "FILE", + "filePath": "./tulips.png", + "databucketID": "", + "sendFileAsBody": false, + "rules": [ + { + "target": "header", + "modifier": "X-Amz-Algorithm", + "value": "", + "invert": true, + "operator": "null" + }, + { + "target": "header", + "modifier": "X-Amz-Credential", + "value": "", + "invert": true, + "operator": "null" + }, + { + "target": "header", + "modifier": "X-Amz-Date", + "value": "", + "invert": true, + "operator": "null" + }, + { + "target": "header", + "modifier": "X-Amz-Expires", + "value": "", + "invert": true, + "operator": "null" + }, + { + "target": "header", + "modifier": "X-Amz-SignedHeaders", + "value": "", + "invert": true, + "operator": "null" + }, + { + "target": "header", + "modifier": "X-Amz-Signature", + "value": "", + "invert": true, + "operator": "null" + } + ], + "rulesOperator": "AND", + "disableTemplating": true, + "fallbackTo404": false, + "default": true + }, + { + "uuid": "3c6335b4-cbfc-43ad-9a45-b2cc22d3f423", + "body": "{\n \"error\": {\n \"message\": \"signature already expired\"\n }\n}", + "latency": 0, + "statusCode": 403, + "label": "", + "headers": [], + "bodyType": "INLINE", + "filePath": "", + "databucketID": "", + "sendFileAsBody": false, + "rules": [ + { + "target": "header", + "modifier": "X-Amz-Algorithm", + "value": "AWS4-HMAC-SHA256", + "invert": true, + "operator": "null" + }, + { + "target": "header", + "modifier": "X-Amz-Date", + "value": "", + "invert": true, + "operator": "null" + }, + { + "target": "header", + "modifier": "X-Amz-Expires", + "value": "", + "invert": true, + "operator": "null" + } + ], + "rulesOperator": "AND", + "disableTemplating": false, + "fallbackTo404": false, + "default": false + }, + { + "uuid": "04f4b226-9933-4286-b086-7f28f05657de", + "body": "{\n \"error\": {\n \"message\": \"problem parsing X-Amz-Expires: strconv.Atoi: parsing \\\"\\\": invalid syntax\"\n }\n}", + "latency": 0, + "statusCode": 400, + "label": "", + "headers": [], + "bodyType": "INLINE", + "filePath": "", + "databucketID": "", + "sendFileAsBody": false, + "rules": [], + "rulesOperator": "OR", + "disableTemplating": false, + "fallbackTo404": false, + "default": false + } + ], + "enabled": true, + "responseMode": null + }, + { + "uuid": "de59d199-83fc-469a-8af6-f230f12cec79", + "type": "http", + "documentation": "Sanity Check", + "method": "get", + "endpoint": "", + "responses": [ + { + "uuid": "3c3c843e-0784-4273-9cce-982acf3da07c", + "body": "{\"Hello\": \"World!\"}", + "latency": 0, + "statusCode": 200, + "label": "", + "headers": [], + "bodyType": "INLINE", + "filePath": "", + "databucketID": "", + "sendFileAsBody": false, + "rules": [], + "rulesOperator": "OR", + "disableTemplating": false, + "fallbackTo404": false, + "default": true + } + ], + "enabled": true, + "responseMode": null + } + ], + "rootChildren": [ + { + "type": "route", + "uuid": "d2ef5c52-336a-420f-a962-15a7974ca7a5" + }, + { + "type": "route", + "uuid": "c9d0a861-f15d-4017-9092-3341deabf43b" + }, + { + "type": "route", + "uuid": "de59d199-83fc-469a-8af6-f230f12cec79" + } + ], + "proxyMode": false, + "proxyHost": "", + "proxyRemovePrefix": false, + "tlsOptions": { + "enabled": false, + "type": "CERT", + "pfxPath": "", + "certPath": "", + "keyPath": "", + "caPath": "", + "passphrase": "" + }, + "cors": true, + "headers": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "proxyReqHeaders": [ + { + "key": "", + "value": "" + } + ], + "proxyResHeaders": [ + { + "key": "", + "value": "" + } + ], + "data": [] +} \ No newline at end of file diff --git a/tests/Transfer/resources/nhost/tulips.png b/tests/Transfer/resources/nhost/tulips.png new file mode 100644 index 00000000..c8bfa4b5 Binary files /dev/null and b/tests/Transfer/resources/nhost/tulips.png differ diff --git a/tests/Transfer/resources/restore.sh b/tests/Transfer/resources/restore.sh deleted file mode 100644 index 45c8b7f6..00000000 --- a/tests/Transfer/resources/restore.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/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/Transfer/resources/supabase/1_globals.sql b/tests/Transfer/resources/supabase/1_globals.sql new file mode 100644 index 00000000..62b4d5cf --- /dev/null +++ b/tests/Transfer/resources/supabase/1_globals.sql @@ -0,0 +1,128 @@ +-- +-- PostgreSQL database cluster dump +-- + +SET default_transaction_read_only = off; + +SET client_encoding = 'UTF8'; +SET standard_conforming_strings = on; + +-- +-- Roles +-- + +CREATE ROLE anon; +ALTER ROLE anon WITH NOSUPERUSER INHERIT NOCREATEROLE NOCREATEDB NOLOGIN NOREPLICATION NOBYPASSRLS; +CREATE ROLE authenticated; +ALTER ROLE authenticated WITH NOSUPERUSER INHERIT NOCREATEROLE NOCREATEDB NOLOGIN NOREPLICATION NOBYPASSRLS; +CREATE ROLE authenticator; +ALTER ROLE authenticator WITH NOSUPERUSER NOINHERIT NOCREATEROLE NOCREATEDB LOGIN NOREPLICATION NOBYPASSRLS PASSWORD 'SCRAM-SHA-256$4096:szx/7w+4m/hR2n9MfVITFQ==$GGxciglaM3HdLL1ahHSCqmxHm7Qz4vKrte4Lnb7N0jo=:XoV9G2pR9vra3HWwv1amgqqPS6jAmq6DnSlN6NN+oP8='; +CREATE ROLE dashboard_user; +ALTER ROLE dashboard_user WITH NOSUPERUSER INHERIT CREATEROLE CREATEDB NOLOGIN REPLICATION NOBYPASSRLS; +CREATE ROLE pgbouncer; +ALTER ROLE pgbouncer WITH NOSUPERUSER INHERIT NOCREATEROLE NOCREATEDB LOGIN NOREPLICATION NOBYPASSRLS PASSWORD 'SCRAM-SHA-256$4096:G45ht6arMDEwvdkoYVf0nA==$Zmxb/SeY3OQGehZwKl1Z+cPItFhUxAAfXmnD3pYxWns=:qV03RcTGmWGNI15aucpO2gnS0B/VORXIPlNKI762z3Q='; +CREATE ROLE pgsodium_keyholder; +ALTER ROLE pgsodium_keyholder WITH NOSUPERUSER INHERIT NOCREATEROLE NOCREATEDB NOLOGIN NOREPLICATION NOBYPASSRLS; +CREATE ROLE pgsodium_keyiduser; +ALTER ROLE pgsodium_keyiduser WITH NOSUPERUSER INHERIT NOCREATEROLE NOCREATEDB NOLOGIN NOREPLICATION NOBYPASSRLS; +CREATE ROLE pgsodium_keymaker; +ALTER ROLE pgsodium_keymaker WITH NOSUPERUSER INHERIT NOCREATEROLE NOCREATEDB NOLOGIN NOREPLICATION NOBYPASSRLS; +CREATE ROLE pgtle_admin; +ALTER ROLE pgtle_admin WITH NOSUPERUSER INHERIT NOCREATEROLE NOCREATEDB NOLOGIN NOREPLICATION NOBYPASSRLS; +CREATE ROLE service_role; +ALTER ROLE service_role WITH NOSUPERUSER INHERIT NOCREATEROLE NOCREATEDB NOLOGIN NOREPLICATION BYPASSRLS; +CREATE ROLE supabase_admin; +ALTER ROLE supabase_admin WITH SUPERUSER INHERIT CREATEROLE CREATEDB LOGIN REPLICATION BYPASSRLS PASSWORD 'SCRAM-SHA-256$4096:c+sBcrIXDNbQrZzLMsu1IQ==$hfowwoASBk25DiYF0qJOEg7w1gI+ymoJx8a5+vwO0eE=:L/CN+wuruMEejJ/LuAOS3We5kW3fQ4y8NGd28t7FaL4='; +CREATE ROLE supabase_auth_admin; +ALTER ROLE supabase_auth_admin WITH NOSUPERUSER NOINHERIT CREATEROLE NOCREATEDB LOGIN NOREPLICATION NOBYPASSRLS PASSWORD 'SCRAM-SHA-256$4096:9Qqu8Ll/V1X12FSamb0QZw==$/ZtigsHl2PkOSEZOkipQt+rEAItTXxO8qaG93mIB36Q=:8EsySWfNFoTAlu60O3L/PS3XX2GQBGfitZwli2yL6JI='; +CREATE ROLE supabase_read_only_user; +ALTER ROLE supabase_read_only_user WITH NOSUPERUSER INHERIT NOCREATEROLE NOCREATEDB LOGIN NOREPLICATION BYPASSRLS PASSWORD 'SCRAM-SHA-256$4096:HDZLOG6Y5BTRO9bZ86fhMg==$LpcVbWDXFn9PzSV6dRoAwi/C0ze8cTOErUmIw1f5qps=:eB7dElN0V1+DSDK5TcngUXcGAu1n8wT+d/tkTcsuJyg='; +CREATE ROLE supabase_replication_admin; +ALTER ROLE supabase_replication_admin WITH NOSUPERUSER INHERIT NOCREATEROLE NOCREATEDB LOGIN REPLICATION NOBYPASSRLS PASSWORD 'SCRAM-SHA-256$4096:A5RAYfnsNlNpFjDzHTB/8g==$rnnZHZ/77r8r37RglM56Br/25jLoUDFVDwgu2/fOATI=:0wLlbnAC7hzLhYwFSQVVfFBHiugU9+e25+IWkonLDeg='; +CREATE ROLE supabase_storage_admin; +ALTER ROLE supabase_storage_admin WITH NOSUPERUSER NOINHERIT CREATEROLE NOCREATEDB LOGIN NOREPLICATION NOBYPASSRLS PASSWORD 'SCRAM-SHA-256$4096:xf0jcoRsFxNrSY7GdGxW6Q==$nuEuY584hOb0eZHfRfXa9Z7mB8rNnn3AB/X+Kd2jXOE=:cByY2VNQTEILEjw3qtwK0Xydk/4n+Te7uEOdMl8LQk8='; + +-- +-- User Configurations +-- + +-- +-- User Config "anon" +-- + +ALTER ROLE anon SET statement_timeout TO '3s'; + +-- +-- User Config "authenticated" +-- + +ALTER ROLE authenticated SET statement_timeout TO '8s'; + +-- +-- User Config "authenticator" +-- + +ALTER ROLE authenticator SET session_preload_libraries TO 'supautils', 'safeupdate'; +ALTER ROLE authenticator SET statement_timeout TO '8s'; + +-- +-- User Config "postgres" +-- + +ALTER ROLE postgres SET search_path TO E'\\$user', 'public', 'extensions'; + +-- +-- User Config "supabase_admin" +-- + +ALTER ROLE supabase_admin SET search_path TO '$user', 'public', 'auth', 'extensions'; + +-- +-- User Config "supabase_auth_admin" +-- + +ALTER ROLE supabase_auth_admin SET search_path TO 'auth'; +ALTER ROLE supabase_auth_admin SET idle_in_transaction_session_timeout TO '60000'; + +-- +-- User Config "supabase_storage_admin" +-- + +ALTER ROLE supabase_storage_admin SET search_path TO 'storage'; + + +-- +-- Role memberships +-- + +GRANT anon TO authenticator GRANTED BY postgres; +GRANT anon TO postgres GRANTED BY supabase_admin; +GRANT anon TO supabase_storage_admin GRANTED BY supabase_admin; +GRANT authenticated TO authenticator GRANTED BY postgres; +GRANT authenticated TO postgres GRANTED BY supabase_admin; +GRANT authenticated TO supabase_storage_admin GRANTED BY supabase_admin; +GRANT pg_monitor TO postgres GRANTED BY supabase_admin; +GRANT pg_read_all_data TO supabase_read_only_user GRANTED BY postgres; +GRANT pgsodium_keyholder TO pgsodium_keymaker GRANTED BY supabase_admin; +GRANT pgsodium_keyholder TO postgres WITH ADMIN OPTION GRANTED BY supabase_admin; +GRANT pgsodium_keyholder TO service_role GRANTED BY supabase_admin; +GRANT pgsodium_keyiduser TO pgsodium_keyholder GRANTED BY supabase_admin; +GRANT pgsodium_keyiduser TO pgsodium_keymaker GRANTED BY supabase_admin; +GRANT pgsodium_keyiduser TO postgres WITH ADMIN OPTION GRANTED BY supabase_admin; +GRANT pgsodium_keymaker TO postgres WITH ADMIN OPTION GRANTED BY supabase_admin; +GRANT pgtle_admin TO postgres GRANTED BY supabase_admin; +GRANT service_role TO authenticator GRANTED BY postgres; +GRANT service_role TO postgres GRANTED BY supabase_admin; +GRANT service_role TO supabase_storage_admin GRANTED BY supabase_admin; +GRANT supabase_auth_admin TO postgres GRANTED BY supabase_admin; +GRANT supabase_storage_admin TO postgres GRANTED BY supabase_admin; + + + + + + +-- +-- PostgreSQL database cluster dump complete +-- + diff --git a/tests/Transfer/resources/supabase/backup.tar b/tests/Transfer/resources/supabase/2_main.sql similarity index 98% rename from tests/Transfer/resources/supabase/backup.tar rename to tests/Transfer/resources/supabase/2_main.sql index ac30a06d..7e25991a 100644 --- a/tests/Transfer/resources/supabase/backup.tar +++ b/tests/Transfer/resources/supabase/2_main.sql @@ -2,8 +2,8 @@ -- PostgreSQL database dump -- --- Dumped from database version 15.1 --- Dumped by pg_dump version 15.2 +-- Dumped from database version 15.1 (Ubuntu 15.1-1.pgdg20.04+1) +-- Dumped by pg_dump version 15.3 SET statement_timeout = 0; SET lock_timeout = 0; @@ -16,10 +16,6 @@ SET xmloption = content; SET client_min_messages = warning; SET row_security = off; --- --- Name: auth; Type: SCHEMA; Schema: -; Owner: supabase_admin --- - CREATE SCHEMA auth; @@ -52,15 +48,6 @@ 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 -- @@ -659,26 +646,6 @@ ALTER FUNCTION extensions.set_graphql_placeholder() OWNER TO 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 -- @@ -864,24 +831,6 @@ ALTER FUNCTION storage.update_updated_at_column() OWNER TO supabase_storage_admi -- 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 = ''; @@ -1467,30 +1416,6 @@ CREATE TABLE storage.objects ( 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; -- @@ -1768,6 +1693,8 @@ COPY auth.schema_migrations (version) FROM stdin; 20230322519590 20230402418590 20230411005111 +20230508135423 +20230523124323 \. @@ -1935,9 +1862,7 @@ COPY storage.migrations (id, name, hash, executed_at) FROM stdin; -- 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 \. @@ -2212,6 +2137,13 @@ CREATE UNIQUE INDEX email_change_token_new_idx ON auth.users USING btree (email_ CREATE INDEX factor_id_created_at_idx ON auth.mfa_factors USING btree (user_id, created_at); +-- +-- Name: flow_state_created_at_idx; Type: INDEX; Schema: auth; Owner: supabase_auth_admin +-- + +CREATE INDEX flow_state_created_at_idx ON auth.flow_state USING btree (created_at DESC); + + -- -- Name: identities_email_idx; Type: INDEX; Schema: auth; Owner: supabase_auth_admin -- @@ -2247,6 +2179,13 @@ CREATE INDEX idx_auth_code ON auth.flow_state USING btree (auth_code); CREATE INDEX idx_user_id_auth_method ON auth.flow_state USING btree (user_id, authentication_method); +-- +-- Name: mfa_challenge_created_at_idx; Type: INDEX; Schema: auth; Owner: supabase_auth_admin +-- + +CREATE INDEX mfa_challenge_created_at_idx ON auth.mfa_challenges USING btree (created_at DESC); + + -- -- Name: mfa_factors_user_friendly_name_unique; Type: INDEX; Schema: auth; Owner: supabase_auth_admin -- @@ -2296,6 +2235,13 @@ CREATE INDEX refresh_tokens_parent_idx ON auth.refresh_tokens USING btree (paren CREATE INDEX refresh_tokens_session_id_revoked_idx ON auth.refresh_tokens USING btree (session_id, revoked); +-- +-- Name: refresh_tokens_updated_at_idx; Type: INDEX; Schema: auth; Owner: supabase_auth_admin +-- + +CREATE INDEX refresh_tokens_updated_at_idx ON auth.refresh_tokens USING btree (updated_at DESC); + + -- -- Name: saml_providers_sso_provider_id_idx; Type: INDEX; Schema: auth; Owner: supabase_auth_admin -- @@ -2303,6 +2249,13 @@ CREATE INDEX refresh_tokens_session_id_revoked_idx ON auth.refresh_tokens USING CREATE INDEX saml_providers_sso_provider_id_idx ON auth.saml_providers USING btree (sso_provider_id); +-- +-- Name: saml_relay_states_created_at_idx; Type: INDEX; Schema: auth; Owner: supabase_auth_admin +-- + +CREATE INDEX saml_relay_states_created_at_idx ON auth.saml_relay_states USING btree (created_at DESC); + + -- -- Name: saml_relay_states_for_email_idx; Type: INDEX; Schema: auth; Owner: supabase_auth_admin -- @@ -2317,6 +2270,13 @@ CREATE INDEX saml_relay_states_for_email_idx ON auth.saml_relay_states USING btr CREATE INDEX saml_relay_states_sso_provider_id_idx ON auth.saml_relay_states USING btree (sso_provider_id); +-- +-- Name: sessions_not_after_idx; Type: INDEX; Schema: auth; Owner: supabase_auth_admin +-- + +CREATE INDEX sessions_not_after_idx ON auth.sessions USING btree (not_after DESC); + + -- -- Name: sessions_user_id_idx; Type: INDEX; Schema: auth; Owner: supabase_auth_admin -- @@ -3168,26 +3128,6 @@ 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 -- diff --git a/tests/Transfer/resources/supabase/api.json b/tests/Transfer/resources/supabase/api.json new file mode 100644 index 00000000..60ffed5d --- /dev/null +++ b/tests/Transfer/resources/supabase/api.json @@ -0,0 +1,1339 @@ +{ + "uuid": "d9c54163-f7fd-499a-b67f-1243dd8bbd23", + "lastMigration": 27, + "name": "Supabase Storage API", + "endpointPrefix": "storage/v1", + "latency": 0, + "port": 80, + "hostname": "", + "folders": [], + "routes": [ + { + "uuid": "63e28c16-b61e-443c-9635-c83b5b039229", + "type": "http", + "documentation": "", + "method": "get", + "endpoint": "metrics", + "responses": [ + { + "uuid": "75f611d2-2de9-4b30-aa30-92dadc8903c2", + "body": "", + "latency": 0, + "statusCode": 200, + "label": "Default Response", + "headers": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "bodyType": "INLINE", + "filePath": "", + "databucketID": "", + "sendFileAsBody": false, + "rules": [], + "rulesOperator": "OR", + "disableTemplating": false, + "fallbackTo404": false, + "default": true + } + ], + "enabled": true, + "responseMode": null + }, + { + "uuid": "d1a447eb-af06-4268-8f0f-31befa77b499", + "type": "http", + "documentation": "Handle POST request for TUS Resumable uploads", + "method": "post", + "endpoint": "upload/resumable/", + "responses": [ + { + "uuid": "bb1333cd-4a6c-41a3-9005-18fa01d096fa", + "body": "", + "latency": 0, + "statusCode": 200, + "label": "Default Response", + "headers": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "bodyType": "INLINE", + "filePath": "", + "databucketID": "", + "sendFileAsBody": false, + "rules": [], + "rulesOperator": "OR", + "disableTemplating": false, + "fallbackTo404": false, + "default": true + } + ], + "enabled": true, + "responseMode": null + }, + { + "uuid": "9540d8f5-0b06-4c7c-af8d-f91035357a48", + "type": "http", + "documentation": "Handle OPTIONS request for TUS Resumable uploads", + "method": "options", + "endpoint": "upload/resumable/", + "responses": [ + { + "uuid": "870e0286-fdf7-4fc5-8592-61c747cfbd3e", + "body": "", + "latency": 0, + "statusCode": 200, + "label": "Default Response", + "headers": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "bodyType": "INLINE", + "filePath": "", + "databucketID": "", + "sendFileAsBody": false, + "rules": [], + "rulesOperator": "OR", + "disableTemplating": false, + "fallbackTo404": false, + "default": true + } + ], + "enabled": true, + "responseMode": null + }, + { + "uuid": "4411e477-06ea-4715-a114-e3e121176dbd", + "type": "http", + "documentation": "Handle POST request for TUS Resumable uploads", + "method": "post", + "endpoint": "upload/resumable/:wildcard", + "responses": [ + { + "uuid": "70fe0830-4cb0-4270-88d4-058c1263c987", + "body": "", + "latency": 0, + "statusCode": 200, + "label": "Default Response", + "headers": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "bodyType": "INLINE", + "filePath": "", + "databucketID": "", + "sendFileAsBody": false, + "rules": [], + "rulesOperator": "OR", + "disableTemplating": false, + "fallbackTo404": false, + "default": true + } + ], + "enabled": true, + "responseMode": null + }, + { + "uuid": "0e8bffeb-d499-4d37-8b92-a41fcedc4c62", + "type": "http", + "documentation": "Handle PUT request for TUS Resumable uploads", + "method": "put", + "endpoint": "upload/resumable/:wildcard", + "responses": [ + { + "uuid": "99efdb5f-ce5f-42f0-9f30-7fa4893e4e51", + "body": "", + "latency": 0, + "statusCode": 200, + "label": "Default Response", + "headers": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "bodyType": "INLINE", + "filePath": "", + "databucketID": "", + "sendFileAsBody": false, + "rules": [], + "rulesOperator": "OR", + "disableTemplating": false, + "fallbackTo404": false, + "default": true + } + ], + "enabled": true, + "responseMode": null + }, + { + "uuid": "8102ef27-9c16-41d2-8232-44cdaa612da7", + "type": "http", + "documentation": "Handle PATCH request for TUS Resumable uploads", + "method": "patch", + "endpoint": "upload/resumable/:wildcard", + "responses": [ + { + "uuid": "ee9029a6-b705-4ba7-890f-0bdba5f33995", + "body": "", + "latency": 0, + "statusCode": 200, + "label": "Default Response", + "headers": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "bodyType": "INLINE", + "filePath": "", + "databucketID": "", + "sendFileAsBody": false, + "rules": [], + "rulesOperator": "OR", + "disableTemplating": false, + "fallbackTo404": false, + "default": true + } + ], + "enabled": true, + "responseMode": null + }, + { + "uuid": "e9d260ac-dbc4-4fcd-b972-61aa98347b90", + "type": "http", + "documentation": "Handle OPTIONS request for TUS Resumable uploads", + "method": "options", + "endpoint": "upload/resumable/:wildcard", + "responses": [ + { + "uuid": "daadb125-8a69-40b0-95ba-8a5ece5ca5b4", + "body": "", + "latency": 0, + "statusCode": 200, + "label": "Default Response", + "headers": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "bodyType": "INLINE", + "filePath": "", + "databucketID": "", + "sendFileAsBody": false, + "rules": [], + "rulesOperator": "OR", + "disableTemplating": false, + "fallbackTo404": false, + "default": true + } + ], + "enabled": true, + "responseMode": null + }, + { + "uuid": "13271d9c-ca44-408d-8f84-f2335620b7e1", + "type": "http", + "documentation": "Create a bucket", + "method": "post", + "endpoint": "bucket/", + "responses": [ + { + "uuid": "fb255b1e-ef2a-46a2-8587-0e3ee5c9721f", + "body": "{\n \"name\": \"\"\n}", + "latency": 0, + "statusCode": 200, + "label": "Successful response", + "headers": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "bodyType": "INLINE", + "filePath": "", + "databucketID": "", + "sendFileAsBody": false, + "rules": [], + "rulesOperator": "OR", + "disableTemplating": false, + "fallbackTo404": false, + "default": true + } + ], + "enabled": true, + "responseMode": null + }, + { + "uuid": "7cec2368-22a0-488c-aee6-ab0b48a62d66", + "type": "http", + "documentation": "Gets all buckets", + "method": "get", + "endpoint": "bucket/", + "responses": [ + { + "uuid": "8601306d-88b2-46fc-a5f3-bbdb8eec5716", + "body": "[\n {\n \"id\": \"\",\n \"name\": \"\",\n \"owner\": \"\",\n \"public\": {{faker 'datatype.boolean'}},\n \"file_size_limit\": \"\",\n \"allowed_mime_types\": \"\",\n \"created_at\": \"\",\n \"updated_at\": \"\"\n }\n]", + "latency": 0, + "statusCode": 200, + "label": "Successful response", + "headers": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "bodyType": "INLINE", + "filePath": "", + "databucketID": "", + "sendFileAsBody": false, + "rules": [], + "rulesOperator": "OR", + "disableTemplating": false, + "fallbackTo404": false, + "default": true + } + ], + "enabled": true, + "responseMode": null + }, + { + "uuid": "61e76e46-b390-4acc-8731-dc3233cae4a2", + "type": "http", + "documentation": "Empty a bucket", + "method": "post", + "endpoint": "bucket/:bucketId/empty", + "responses": [ + { + "uuid": "41b4663e-31d9-47ea-8d73-6ce272c49252", + "body": "{\n \"message\": \"\"\n}", + "latency": 0, + "statusCode": 200, + "label": "Successful response", + "headers": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "bodyType": "INLINE", + "filePath": "", + "databucketID": "", + "sendFileAsBody": false, + "rules": [], + "rulesOperator": "OR", + "disableTemplating": false, + "fallbackTo404": false, + "default": true + } + ], + "enabled": true, + "responseMode": null + }, + { + "uuid": "a9cda983-2620-48ad-8ddb-eb6fbb9d2435", + "type": "http", + "documentation": "Get details of a bucket", + "method": "get", + "endpoint": "bucket/:bucketId", + "responses": [ + { + "uuid": "37305d4e-2365-481e-99b8-6808c61b20a7", + "body": "{\n \"id\": \"\",\n \"name\": \"\",\n \"owner\": \"\",\n \"public\": {{faker 'datatype.boolean'}},\n \"file_size_limit\": \"\",\n \"allowed_mime_types\": \"\",\n \"created_at\": \"\",\n \"updated_at\": \"\"\n}", + "latency": 0, + "statusCode": 200, + "label": "Successful response", + "headers": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "bodyType": "INLINE", + "filePath": "", + "databucketID": "", + "sendFileAsBody": false, + "rules": [], + "rulesOperator": "OR", + "disableTemplating": false, + "fallbackTo404": false, + "default": true + } + ], + "enabled": true, + "responseMode": null + }, + { + "uuid": "21848d44-988b-4ff7-953e-eb162cfd5bc0", + "type": "http", + "documentation": "Update properties of a bucket", + "method": "put", + "endpoint": "bucket/:bucketId", + "responses": [ + { + "uuid": "ac71a6c8-d498-4ae3-acff-a7ea5a6431f6", + "body": "{\n \"message\": \"\"\n}", + "latency": 0, + "statusCode": 200, + "label": "Successful response", + "headers": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "bodyType": "INLINE", + "filePath": "", + "databucketID": "", + "sendFileAsBody": false, + "rules": [], + "rulesOperator": "OR", + "disableTemplating": false, + "fallbackTo404": false, + "default": true + } + ], + "enabled": true, + "responseMode": null + }, + { + "uuid": "2e753b7d-9204-443a-a13b-1e90f76713f9", + "type": "http", + "documentation": "Delete a bucket", + "method": "delete", + "endpoint": "bucket/:bucketId", + "responses": [ + { + "uuid": "eab49831-6274-4d13-9a72-6371b1097100", + "body": "{\n \"message\": \"\"\n}", + "latency": 0, + "statusCode": 200, + "label": "Successful response", + "headers": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "bodyType": "INLINE", + "filePath": "", + "databucketID": "", + "sendFileAsBody": false, + "rules": [], + "rulesOperator": "OR", + "disableTemplating": false, + "fallbackTo404": false, + "default": true + } + ], + "enabled": true, + "responseMode": null + }, + { + "uuid": "c1b7ca51-0257-426d-96a1-3660a4086b26", + "type": "http", + "documentation": "Delete an object", + "method": "delete", + "endpoint": "object/:bucketName/:wildcard", + "responses": [ + { + "uuid": "79b24a52-a51b-43a4-93a0-ca6d328c4d35", + "body": "{\n \"message\": \"\"\n}", + "latency": 0, + "statusCode": 200, + "label": "Successful response", + "headers": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "bodyType": "INLINE", + "filePath": "", + "databucketID": "", + "sendFileAsBody": false, + "rules": [], + "rulesOperator": "OR", + "disableTemplating": false, + "fallbackTo404": false, + "default": true + } + ], + "enabled": true, + "responseMode": null + }, + { + "uuid": "6cf60379-78e1-4765-8113-3a314cc2f8ad", + "type": "http", + "documentation": "Get object", + "method": "get", + "endpoint": "object/:bucketName/:wildcard", + "responses": [ + { + "uuid": "786caea0-43d3-4a8e-af20-74e9e791bbd1", + "body": "", + "latency": 0, + "statusCode": 200, + "label": "", + "headers": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "bodyType": "FILE", + "filePath": "./tulips.png", + "databucketID": "", + "sendFileAsBody": false, + "rules": [ + { + "target": "header", + "modifier": "apiKey", + "value": "", + "invert": true, + "operator": "null" + }, + { + "target": "header", + "modifier": "Authorization", + "value": "", + "invert": true, + "operator": "null" + } + ], + "rulesOperator": "OR", + "disableTemplating": false, + "fallbackTo404": false, + "default": true + }, + { + "uuid": "45c78e9e-853e-439b-96a5-446c7a931e4f", + "body": "{\n \"statusCode\": \"400\",\n \"error\": \"Error\",\n \"message\": \"headers must have required property 'authorization'\"\n}", + "latency": 0, + "statusCode": 400, + "label": "", + "headers": [], + "bodyType": "INLINE", + "filePath": "", + "databucketID": "", + "sendFileAsBody": false, + "rules": [ + { + "target": "header", + "modifier": "Authorization", + "value": "", + "invert": false, + "operator": "null" + } + ], + "rulesOperator": "OR", + "disableTemplating": false, + "fallbackTo404": false, + "default": false + } + ], + "enabled": true, + "responseMode": null + }, + { + "uuid": "4864d8ee-d1d1-48c0-aacb-7e1b5739850b", + "type": "http", + "documentation": "Update the object at an existing key", + "method": "put", + "endpoint": "object/:bucketName/:wildcard", + "responses": [ + { + "uuid": "66390d64-87f9-4b28-85b1-b71c8b4f5a7b", + "body": "{\n \"Id\": \"\",\n \"Key\": \"\"\n}", + "latency": 0, + "statusCode": 200, + "label": "Successful response", + "headers": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "bodyType": "INLINE", + "filePath": "", + "databucketID": "", + "sendFileAsBody": false, + "rules": [], + "rulesOperator": "OR", + "disableTemplating": false, + "fallbackTo404": false, + "default": true + } + ], + "enabled": true, + "responseMode": null + }, + { + "uuid": "ce2ef870-97e4-4ede-9690-623647b584ca", + "type": "http", + "documentation": "Upload a new object", + "method": "post", + "endpoint": "object/:bucketName/:wildcard", + "responses": [ + { + "uuid": "b92c745e-2f32-4721-9648-9346d36ffba0", + "body": "{\n \"Id\": \"\",\n \"Key\": \"\"\n}", + "latency": 0, + "statusCode": 200, + "label": "Successful response", + "headers": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "bodyType": "INLINE", + "filePath": "", + "databucketID": "", + "sendFileAsBody": false, + "rules": [], + "rulesOperator": "OR", + "disableTemplating": false, + "fallbackTo404": false, + "default": true + } + ], + "enabled": true, + "responseMode": null + }, + { + "uuid": "0a1a8956-d288-4c5b-ac55-ce285f9e6c85", + "type": "http", + "documentation": "Delete multiple objects", + "method": "delete", + "endpoint": "object/:bucketName", + "responses": [ + { + "uuid": "c4e34f2f-4690-41f2-8421-df35b4d2d80e", + "body": "[\n {\n \"name\": \"\",\n \"bucket_id\": \"\",\n \"owner\": \"\",\n \"owner_id\": \"\",\n \"version\": \"\",\n \"id\": \"\",\n \"updated_at\": \"\",\n \"created_at\": \"\",\n \"last_accessed_at\": \"\",\n \"metadata\": {},\n \"buckets\": {\n \"id\": \"\",\n \"name\": \"\",\n \"owner\": \"\",\n \"public\": {{faker 'datatype.boolean'}},\n \"file_size_limit\": \"\",\n \"allowed_mime_types\": \"\",\n \"created_at\": \"\",\n \"updated_at\": \"\"\n }\n }\n]", + "latency": 0, + "statusCode": 200, + "label": "Successful response", + "headers": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "bodyType": "INLINE", + "filePath": "", + "databucketID": "", + "sendFileAsBody": false, + "rules": [], + "rulesOperator": "OR", + "disableTemplating": false, + "fallbackTo404": false, + "default": true + } + ], + "enabled": true, + "responseMode": null + }, + { + "uuid": "e3d3f3bc-38e5-401c-ad35-7151f5938961", + "type": "http", + "documentation": "Retrieve an object", + "method": "get", + "endpoint": "object/authenticated/:bucketName/:wildcard", + "responses": [ + { + "uuid": "94a24d3a-6987-4981-b679-6df191da7b1f", + "body": "", + "latency": 0, + "statusCode": 200, + "label": "", + "headers": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "bodyType": "INLINE", + "filePath": "", + "databucketID": "", + "sendFileAsBody": false, + "rules": [], + "rulesOperator": "OR", + "disableTemplating": false, + "fallbackTo404": false, + "default": true + } + ], + "enabled": true, + "responseMode": null + }, + { + "uuid": "bd9f93bd-b91e-4e59-a27e-1fba3f7ea9c7", + "type": "http", + "documentation": "Generate a presigned url to upload an object", + "method": "post", + "endpoint": "object/upload/sign/:bucketName/:wildcard", + "responses": [ + { + "uuid": "f0fbdc6f-f816-4bc0-9070-e6b7cc44c0e9", + "body": "{\n \"url\": \"\"\n}", + "latency": 0, + "statusCode": 200, + "label": "Successful response", + "headers": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "bodyType": "INLINE", + "filePath": "", + "databucketID": "", + "sendFileAsBody": false, + "rules": [], + "rulesOperator": "OR", + "disableTemplating": false, + "fallbackTo404": false, + "default": true + } + ], + "enabled": true, + "responseMode": null + }, + { + "uuid": "e351a4b3-ae24-4425-bdea-61121d2faeb2", + "type": "http", + "documentation": "Uploads an object via a presigned URL", + "method": "put", + "endpoint": "object/upload/sign/:bucketName/:wildcard", + "responses": [ + { + "uuid": "fe6141a4-689e-40a1-b172-5bc71800f407", + "body": "{\n \"Key\": \"\"\n}", + "latency": 0, + "statusCode": 200, + "label": "Successful response", + "headers": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "bodyType": "INLINE", + "filePath": "", + "databucketID": "", + "sendFileAsBody": false, + "rules": [], + "rulesOperator": "OR", + "disableTemplating": false, + "fallbackTo404": false, + "default": true + } + ], + "enabled": true, + "responseMode": null + }, + { + "uuid": "1e0bdf63-7610-467b-bdba-c3cb7736fcb5", + "type": "http", + "documentation": "Generate a presigned url to retrieve an object", + "method": "post", + "endpoint": "object/sign/:bucketName/:wildcard", + "responses": [ + { + "uuid": "79f3aaf6-05c3-4649-b9f1-73181ac38fb4", + "body": "{\n \"signedURL\": \"\"\n}", + "latency": 0, + "statusCode": 200, + "label": "Successful response", + "headers": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "bodyType": "INLINE", + "filePath": "", + "databucketID": "", + "sendFileAsBody": false, + "rules": [], + "rulesOperator": "OR", + "disableTemplating": false, + "fallbackTo404": false, + "default": true + } + ], + "enabled": true, + "responseMode": null + }, + { + "uuid": "279b3116-4d72-4aa7-90d7-4fbeb293e722", + "type": "http", + "documentation": "Retrieve an object via a presigned URL", + "method": "get", + "endpoint": "object/sign/:bucketName/:wildcard", + "responses": [ + { + "uuid": "80fca685-82ca-474f-ac74-0f1ecb52c885", + "body": "", + "latency": 0, + "statusCode": 200, + "label": "", + "headers": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "bodyType": "INLINE", + "filePath": "", + "databucketID": "", + "sendFileAsBody": false, + "rules": [], + "rulesOperator": "OR", + "disableTemplating": false, + "fallbackTo404": false, + "default": true + } + ], + "enabled": true, + "responseMode": null + }, + { + "uuid": "93d1e18c-7fe9-497d-9256-8a37058536ec", + "type": "http", + "documentation": "Generate presigned urls to retrieve objects", + "method": "post", + "endpoint": "object/sign/:bucketName", + "responses": [ + { + "uuid": "73654f64-97f7-4ce5-b5d5-7f84040c48d0", + "body": "[\n {\n \"error\": \"\",\n \"version\": \"\",\n \"signedURL\": \"\"\n }\n]", + "latency": 0, + "statusCode": 200, + "label": "Successful response", + "headers": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "bodyType": "INLINE", + "filePath": "", + "databucketID": "", + "sendFileAsBody": false, + "rules": [], + "rulesOperator": "OR", + "disableTemplating": false, + "fallbackTo404": false, + "default": true + } + ], + "enabled": true, + "responseMode": null + }, + { + "uuid": "4e803d50-ceae-485e-ba30-ca7849796f7c", + "type": "http", + "documentation": "Moves an object", + "method": "post", + "endpoint": "object/move", + "responses": [ + { + "uuid": "b3058469-fecb-4799-8694-cce2774769f9", + "body": "{\n \"message\": \"\"\n}", + "latency": 0, + "statusCode": 200, + "label": "Successful response", + "headers": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "bodyType": "INLINE", + "filePath": "", + "databucketID": "", + "sendFileAsBody": false, + "rules": [], + "rulesOperator": "OR", + "disableTemplating": false, + "fallbackTo404": false, + "default": true + } + ], + "enabled": true, + "responseMode": null + }, + { + "uuid": "5be75f51-df4a-4b59-89a4-b1ac65a6dc80", + "type": "http", + "documentation": "Search for objects under a prefix", + "method": "post", + "endpoint": "object/list/:bucketName", + "responses": [ + { + "uuid": "731144ec-d427-4f5a-8d12-b70c7a3dd221", + "body": "[\n {\n \"name\": \"\",\n \"bucket_id\": \"\",\n \"owner\": \"\",\n \"owner_id\": \"\",\n \"version\": \"\",\n \"id\": \"\",\n \"updated_at\": \"\",\n \"created_at\": \"\",\n \"last_accessed_at\": \"\",\n \"metadata\": {},\n \"buckets\": {\n \"id\": \"\",\n \"name\": \"\",\n \"owner\": \"\",\n \"public\": {{faker 'datatype.boolean'}},\n \"file_size_limit\": \"\",\n \"allowed_mime_types\": \"\",\n \"created_at\": \"\",\n \"updated_at\": \"\"\n }\n }\n]", + "latency": 0, + "statusCode": 200, + "label": "Successful response", + "headers": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "bodyType": "INLINE", + "filePath": "", + "databucketID": "", + "sendFileAsBody": false, + "rules": [], + "rulesOperator": "OR", + "disableTemplating": false, + "fallbackTo404": false, + "default": true + } + ], + "enabled": true, + "responseMode": null + }, + { + "uuid": "e1ce3bb0-bf21-4746-b184-94bed1cca5e2", + "type": "http", + "documentation": "Retrieve object info", + "method": "get", + "endpoint": "object/info/authenticated/:bucketName/:wildcard", + "responses": [ + { + "uuid": "9d3abd16-2c48-4a97-b467-da8510767cdd", + "body": "", + "latency": 0, + "statusCode": 200, + "label": "", + "headers": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "bodyType": "INLINE", + "filePath": "", + "databucketID": "", + "sendFileAsBody": false, + "rules": [], + "rulesOperator": "OR", + "disableTemplating": false, + "fallbackTo404": false, + "default": true + } + ], + "enabled": true, + "responseMode": null + }, + { + "uuid": "a8a0f080-a810-4c81-b60d-36794d75a42d", + "type": "http", + "documentation": "Retrieve object info", + "method": "get", + "endpoint": "object/info/:bucketName/:wildcard", + "responses": [ + { + "uuid": "9b469fb0-28a4-4759-b7e3-37e9a63bc424", + "body": "", + "latency": 0, + "statusCode": 200, + "label": "", + "headers": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "bodyType": "INLINE", + "filePath": "", + "databucketID": "", + "sendFileAsBody": false, + "rules": [], + "rulesOperator": "OR", + "disableTemplating": false, + "fallbackTo404": false, + "default": true + } + ], + "enabled": true, + "responseMode": null + }, + { + "uuid": "6afa36a9-8697-49f4-a753-53d47bf4c585", + "type": "http", + "documentation": "Copies an object", + "method": "post", + "endpoint": "object/copy", + "responses": [ + { + "uuid": "62a52180-aa2b-4ddf-9937-f3016fa142f5", + "body": "{\n \"Key\": \"\"\n}", + "latency": 0, + "statusCode": 200, + "label": "Successful response", + "headers": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "bodyType": "INLINE", + "filePath": "", + "databucketID": "", + "sendFileAsBody": false, + "rules": [], + "rulesOperator": "OR", + "disableTemplating": false, + "fallbackTo404": false, + "default": true + } + ], + "enabled": true, + "responseMode": null + }, + { + "uuid": "3fe77822-6d08-416e-81e9-d7af319a318a", + "type": "http", + "documentation": "Retrieve an object from a public bucket", + "method": "get", + "endpoint": "object/public/:bucketName/:wildcard", + "responses": [ + { + "uuid": "d5063379-859a-4ca4-98b7-fc43f3c0addb", + "body": "", + "latency": 0, + "statusCode": 200, + "label": "", + "headers": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "bodyType": "INLINE", + "filePath": "", + "databucketID": "", + "sendFileAsBody": false, + "rules": [], + "rulesOperator": "OR", + "disableTemplating": false, + "fallbackTo404": false, + "default": true + } + ], + "enabled": true, + "responseMode": null + }, + { + "uuid": "a9c5a40e-6ef2-415a-83e4-287f2ef6648b", + "type": "http", + "documentation": "Get object info", + "method": "get", + "endpoint": "object/info/public/:bucketName/:wildcard", + "responses": [ + { + "uuid": "1511e7ef-669a-4bd3-a733-e2b7618bb8ce", + "body": "", + "latency": 0, + "statusCode": 200, + "label": "", + "headers": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "bodyType": "INLINE", + "filePath": "", + "databucketID": "", + "sendFileAsBody": false, + "rules": [], + "rulesOperator": "OR", + "disableTemplating": false, + "fallbackTo404": false, + "default": true + } + ], + "enabled": true, + "responseMode": null + }, + { + "uuid": "c030739d-e155-45ce-b42a-3be8e8a86054", + "type": "http", + "documentation": "Render an authenticated image with the given transformations", + "method": "get", + "endpoint": "render/image/authenticated/:bucketName/:wildcard", + "responses": [ + { + "uuid": "59c37e1b-2a61-402c-8061-b137b76b7518", + "body": "", + "latency": 0, + "statusCode": 200, + "label": "", + "headers": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "bodyType": "INLINE", + "filePath": "", + "databucketID": "", + "sendFileAsBody": false, + "rules": [], + "rulesOperator": "OR", + "disableTemplating": false, + "fallbackTo404": false, + "default": true + } + ], + "enabled": true, + "responseMode": null + }, + { + "uuid": "81be8924-09eb-4d8a-bbe9-7d6ad498fde1", + "type": "http", + "documentation": "Render an authenticated image with the given transformations", + "method": "get", + "endpoint": "render/image/sign/:bucketName/:wildcard", + "responses": [ + { + "uuid": "b9fec83b-e2be-4376-9329-921f6697da32", + "body": "", + "latency": 0, + "statusCode": 200, + "label": "", + "headers": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "bodyType": "INLINE", + "filePath": "", + "databucketID": "", + "sendFileAsBody": false, + "rules": [], + "rulesOperator": "OR", + "disableTemplating": false, + "fallbackTo404": false, + "default": true + } + ], + "enabled": true, + "responseMode": null + }, + { + "uuid": "88f2739a-8bf6-4e5d-8715-9f987052389e", + "type": "http", + "documentation": "Render a public image with the given transformations", + "method": "get", + "endpoint": "render/image/public/:bucketName/:wildcard", + "responses": [ + { + "uuid": "cc201a52-81c8-4727-9740-87b73266fd86", + "body": "", + "latency": 0, + "statusCode": 200, + "label": "", + "headers": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "bodyType": "INLINE", + "filePath": "", + "databucketID": "", + "sendFileAsBody": false, + "rules": [], + "rulesOperator": "OR", + "disableTemplating": false, + "fallbackTo404": false, + "default": true + } + ], + "enabled": true, + "responseMode": null + } + ], + "rootChildren": [ + { + "type": "route", + "uuid": "63e28c16-b61e-443c-9635-c83b5b039229" + }, + { + "type": "route", + "uuid": "d1a447eb-af06-4268-8f0f-31befa77b499" + }, + { + "type": "route", + "uuid": "9540d8f5-0b06-4c7c-af8d-f91035357a48" + }, + { + "type": "route", + "uuid": "4411e477-06ea-4715-a114-e3e121176dbd" + }, + { + "type": "route", + "uuid": "0e8bffeb-d499-4d37-8b92-a41fcedc4c62" + }, + { + "type": "route", + "uuid": "8102ef27-9c16-41d2-8232-44cdaa612da7" + }, + { + "type": "route", + "uuid": "e9d260ac-dbc4-4fcd-b972-61aa98347b90" + }, + { + "type": "route", + "uuid": "13271d9c-ca44-408d-8f84-f2335620b7e1" + }, + { + "type": "route", + "uuid": "7cec2368-22a0-488c-aee6-ab0b48a62d66" + }, + { + "type": "route", + "uuid": "61e76e46-b390-4acc-8731-dc3233cae4a2" + }, + { + "type": "route", + "uuid": "a9cda983-2620-48ad-8ddb-eb6fbb9d2435" + }, + { + "type": "route", + "uuid": "21848d44-988b-4ff7-953e-eb162cfd5bc0" + }, + { + "type": "route", + "uuid": "2e753b7d-9204-443a-a13b-1e90f76713f9" + }, + { + "type": "route", + "uuid": "c1b7ca51-0257-426d-96a1-3660a4086b26" + }, + { + "type": "route", + "uuid": "6cf60379-78e1-4765-8113-3a314cc2f8ad" + }, + { + "type": "route", + "uuid": "4864d8ee-d1d1-48c0-aacb-7e1b5739850b" + }, + { + "type": "route", + "uuid": "ce2ef870-97e4-4ede-9690-623647b584ca" + }, + { + "type": "route", + "uuid": "0a1a8956-d288-4c5b-ac55-ce285f9e6c85" + }, + { + "type": "route", + "uuid": "e3d3f3bc-38e5-401c-ad35-7151f5938961" + }, + { + "type": "route", + "uuid": "bd9f93bd-b91e-4e59-a27e-1fba3f7ea9c7" + }, + { + "type": "route", + "uuid": "e351a4b3-ae24-4425-bdea-61121d2faeb2" + }, + { + "type": "route", + "uuid": "1e0bdf63-7610-467b-bdba-c3cb7736fcb5" + }, + { + "type": "route", + "uuid": "279b3116-4d72-4aa7-90d7-4fbeb293e722" + }, + { + "type": "route", + "uuid": "93d1e18c-7fe9-497d-9256-8a37058536ec" + }, + { + "type": "route", + "uuid": "4e803d50-ceae-485e-ba30-ca7849796f7c" + }, + { + "type": "route", + "uuid": "5be75f51-df4a-4b59-89a4-b1ac65a6dc80" + }, + { + "type": "route", + "uuid": "e1ce3bb0-bf21-4746-b184-94bed1cca5e2" + }, + { + "type": "route", + "uuid": "a8a0f080-a810-4c81-b60d-36794d75a42d" + }, + { + "type": "route", + "uuid": "6afa36a9-8697-49f4-a753-53d47bf4c585" + }, + { + "type": "route", + "uuid": "3fe77822-6d08-416e-81e9-d7af319a318a" + }, + { + "type": "route", + "uuid": "a9c5a40e-6ef2-415a-83e4-287f2ef6648b" + }, + { + "type": "route", + "uuid": "c030739d-e155-45ce-b42a-3be8e8a86054" + }, + { + "type": "route", + "uuid": "81be8924-09eb-4d8a-bbe9-7d6ad498fde1" + }, + { + "type": "route", + "uuid": "88f2739a-8bf6-4e5d-8715-9f987052389e" + } + ], + "proxyMode": false, + "proxyHost": "", + "proxyRemovePrefix": false, + "tlsOptions": { + "enabled": false, + "type": "CERT", + "pfxPath": "", + "certPath": "", + "keyPath": "", + "caPath": "", + "passphrase": "" + }, + "cors": true, + "headers": [], + "proxyReqHeaders": [ + { + "key": "", + "value": "" + } + ], + "proxyResHeaders": [ + { + "key": "", + "value": "" + } + ], + "data": [] +} \ No newline at end of file diff --git a/tests/Transfer/resources/supabase/tulips.png b/tests/Transfer/resources/supabase/tulips.png new file mode 100644 index 00000000..c8bfa4b5 Binary files /dev/null and b/tests/Transfer/resources/supabase/tulips.png differ diff --git a/tests/Transfer/resources/updateBackups.sh b/tests/Transfer/resources/updateBackups.sh index feabfeba..1422b6b7 100755 --- a/tests/Transfer/resources/updateBackups.sh +++ b/tests/Transfer/resources/updateBackups.sh @@ -1,15 +1,15 @@ # This script uses your .env file and updates the backups.tar for NHost and Supabase. -source ../../.env +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 +pg_dump -U $SUPABASE_TEST_USERNAME -h $SUPABASE_TEST_HOST -p 5432 --clean --file=supabase/dump.sql $SUPABASE_TEST_DATABASE 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 +pg_dump -U $NHOST_TEST_USERNAME -h $NHOST_TEST_SUBDOMAIN.db.$NHOST_TEST_REGION.nhost.run -p 5432 --clean --file=nhost/dump.sql $NHOST_TEST_DATABASE unset PGPASSWORD echo "Done" diff --git a/tests/e2e/adapters/Mock.php b/tests/e2e/adapters/Mock.php deleted file mode 100644 index 5d1aad35..00000000 --- a/tests/e2e/adapters/Mock.php +++ /dev/null @@ -1,77 +0,0 @@ -