2023-08-04 16:21:41 +00:00
< ? php
use Appwrite\Event\Event ;
use Appwrite\Event\Migration ;
use Appwrite\Extend\Exception ;
2025-04-21 06:34:36 +00:00
use Appwrite\OpenSSL\OpenSSL ;
2025-01-17 04:31:39 +00:00
use Appwrite\SDK\AuthType ;
use Appwrite\SDK\ContentType ;
use Appwrite\SDK\Method ;
use Appwrite\SDK\Response as SDKResponse ;
2025-04-12 11:40:06 +00:00
use Appwrite\Utopia\Database\Validator\CompoundUID ;
2023-08-04 16:21:41 +00:00
use Appwrite\Utopia\Database\Validator\Queries\Migrations ;
use Appwrite\Utopia\Response ;
use Utopia\Database\Database ;
use Utopia\Database\Document ;
2025-04-16 11:59:36 +00:00
use Utopia\Database\Exception\Order as OrderException ;
2024-02-12 16:02:04 +00:00
use Utopia\Database\Exception\Query as QueryException ;
2023-08-04 16:21:41 +00:00
use Utopia\Database\Helpers\ID ;
use Utopia\Database\Query ;
2025-10-29 18:21:41 +00:00
use Utopia\Database\Validator\Authorization ;
2025-10-28 09:29:14 +00:00
use Utopia\Database\Validator\Queries\Documents ;
2025-10-28 13:01:08 +00:00
use Utopia\Database\Validator\Query\Cursor ;
2023-08-04 16:21:41 +00:00
use Utopia\Database\Validator\UID ;
2026-02-10 05:04:24 +00:00
use Utopia\Http\Http ;
2025-04-08 05:49:39 +00:00
use Utopia\Migration\Resource ;
2023-08-09 17:08:10 +00:00
use Utopia\Migration\Sources\Appwrite ;
2025-04-16 06:25:04 +00:00
use Utopia\Migration\Sources\CSV ;
2023-08-09 17:08:10 +00:00
use Utopia\Migration\Sources\Firebase ;
use Utopia\Migration\Sources\NHost ;
use Utopia\Migration\Sources\Supabase ;
2025-04-09 11:05:27 +00:00
use Utopia\Migration\Transfer ;
2025-04-21 04:31:13 +00:00
use Utopia\Storage\Compression\Algorithms\GZIP ;
use Utopia\Storage\Compression\Algorithms\Zstd ;
2025-04-09 11:05:27 +00:00
use Utopia\Storage\Compression\Compression ;
2025-04-08 05:49:39 +00:00
use Utopia\Storage\Device ;
2025-04-21 06:34:36 +00:00
use Utopia\System\System ;
2024-10-08 07:54:40 +00:00
use Utopia\Validator\ArrayList ;
2025-08-14 12:40:00 +00:00
use Utopia\Validator\Boolean ;
2024-10-08 07:54:40 +00:00
use Utopia\Validator\Integer ;
use Utopia\Validator\Text ;
use Utopia\Validator\URL ;
use Utopia\Validator\WhiteList ;
2023-08-04 16:21:41 +00:00
include_once __DIR__ . '/../shared/api.php' ;
2026-02-04 05:30:22 +00:00
Http :: post ( '/v1/migrations/appwrite' )
2023-08-04 16:21:41 +00:00
-> groups ([ 'api' , 'migrations' ])
2025-04-14 18:40:48 +00:00
-> desc ( 'Create Appwrite migration' )
2023-08-04 16:21:41 +00:00
-> label ( 'scope' , 'migrations.write' )
2023-08-16 15:01:56 +00:00
-> label ( 'event' , 'migrations.[migrationId].create' )
2023-08-04 16:21:41 +00:00
-> label ( 'audits.event' , 'migration.create' )
2025-01-17 04:31:39 +00:00
-> label ( 'sdk' , new Method (
namespace : 'migrations' ,
2025-04-12 06:41:57 +00:00
group : null ,
2025-01-17 04:31:39 +00:00
name : 'createAppwriteMigration' ,
description : '/docs/references/migrations/migration-appwrite.md' ,
auth : [ AuthType :: ADMIN ],
responses : [
new SDKResponse (
code : Response :: STATUS_CODE_ACCEPTED ,
model : Response :: MODEL_MIGRATION ,
)
]
))
2023-08-04 16:21:41 +00:00
-> param ( 'resources' , [], new ArrayList ( new WhiteList ( Appwrite :: getSupportedResources ())), 'List of resources to migrate' )
2025-02-11 07:56:39 +00:00
-> param ( 'endpoint' , '' , new URL (), 'Source Appwrite endpoint' )
-> param ( 'projectId' , '' , new UID (), 'Source Project ID' )
-> param ( 'apiKey' , '' , new Text ( 512 ), 'Source API Key' )
2023-08-04 16:21:41 +00:00
-> inject ( 'response' )
-> inject ( 'dbForProject' )
-> inject ( 'project' )
2025-12-17 12:11:14 +00:00
-> inject ( 'platform' )
2023-08-04 16:21:41 +00:00
-> inject ( 'user' )
2023-10-01 17:39:26 +00:00
-> inject ( 'queueForEvents' )
-> inject ( 'queueForMigrations' )
2025-12-17 12:11:14 +00:00
-> action ( function ( array $resources , string $endpoint , string $projectId , string $apiKey , Response $response , Database $dbForProject , Document $project , array $platform , Document $user , Event $queueForEvents , Migration $queueForMigrations ) {
2023-08-04 16:21:41 +00:00
$migration = $dbForProject -> createDocument ( 'migrations' , new Document ([
'$id' => ID :: unique (),
'status' => 'pending' ,
'stage' => 'init' ,
'source' => Appwrite :: getName (),
2024-05-27 15:56:28 +00:00
'destination' => Appwrite :: getName (),
2023-08-04 16:21:41 +00:00
'credentials' => [
'endpoint' => $endpoint ,
'projectId' => $projectId ,
'apiKey' => $apiKey ,
],
'resources' => $resources ,
'statusCounters' => '{}' ,
'resourceData' => '{}' ,
'errors' => [],
]));
2023-10-01 17:39:26 +00:00
$queueForEvents -> setParam ( 'migrationId' , $migration -> getId ());
2023-08-04 16:21:41 +00:00
// Trigger Transfer
2023-10-01 17:39:26 +00:00
$queueForMigrations
2023-08-04 16:21:41 +00:00
-> setMigration ( $migration )
-> setProject ( $project )
2025-12-17 12:11:14 +00:00
-> setPlatform ( $platform )
2023-08-04 16:21:41 +00:00
-> setUser ( $user )
-> trigger ();
$response
-> setStatusCode ( Response :: STATUS_CODE_ACCEPTED )
-> dynamic ( $migration , Response :: MODEL_MIGRATION );
});
2026-02-04 05:30:22 +00:00
Http :: post ( '/v1/migrations/firebase' )
2023-08-09 22:46:23 +00:00
-> groups ([ 'api' , 'migrations' ])
2025-04-14 18:40:48 +00:00
-> desc ( 'Create Firebase migration' )
2023-08-09 22:46:23 +00:00
-> label ( 'scope' , 'migrations.write' )
2023-08-16 15:01:56 +00:00
-> label ( 'event' , 'migrations.[migrationId].create' )
2023-08-09 22:46:23 +00:00
-> label ( 'audits.event' , 'migration.create' )
2025-01-17 04:31:39 +00:00
-> label ( 'sdk' , new Method (
namespace : 'migrations' ,
2025-04-12 06:41:57 +00:00
group : null ,
2025-01-17 04:31:39 +00:00
name : 'createFirebaseMigration' ,
description : '/docs/references/migrations/migration-firebase.md' ,
auth : [ AuthType :: ADMIN ],
responses : [
new SDKResponse (
code : Response :: STATUS_CODE_ACCEPTED ,
model : Response :: MODEL_MIGRATION ,
)
]
))
2023-08-09 22:46:23 +00:00
-> param ( 'resources' , [], new ArrayList ( new WhiteList ( Firebase :: getSupportedResources ())), 'List of resources to migrate' )
-> param ( 'serviceAccount' , '' , new Text ( 65536 ), 'JSON of the Firebase service account credentials' )
-> inject ( 'response' )
-> inject ( 'dbForProject' )
-> inject ( 'project' )
2025-12-17 12:11:14 +00:00
-> inject ( 'platform' )
2023-08-09 22:46:23 +00:00
-> inject ( 'user' )
2023-10-01 17:39:26 +00:00
-> inject ( 'queueForEvents' )
-> inject ( 'queueForMigrations' )
2025-12-17 12:11:14 +00:00
-> action ( function ( array $resources , string $serviceAccount , Response $response , Database $dbForProject , Document $project , array $platform , Document $user , Event $queueForEvents , Migration $queueForMigrations ) {
2023-10-06 16:02:01 +00:00
$serviceAccountData = json_decode ( $serviceAccount , true );
if ( empty ( $serviceAccountData )) {
throw new Exception ( Exception :: MIGRATION_PROVIDER_ERROR , 'Invalid Service Account JSON' );
}
if ( ! isset ( $serviceAccountData [ 'project_id' ]) || ! isset ( $serviceAccountData [ 'client_email' ]) || ! isset ( $serviceAccountData [ 'private_key' ])) {
throw new Exception ( Exception :: MIGRATION_PROVIDER_ERROR , 'Invalid Service Account JSON' );
}
2023-10-13 15:23:20 +00:00
2023-08-09 22:46:23 +00:00
$migration = $dbForProject -> createDocument ( 'migrations' , new Document ([
'$id' => ID :: unique (),
'status' => 'pending' ,
'stage' => 'init' ,
'source' => Firebase :: getName (),
2024-05-27 15:56:28 +00:00
'destination' => Appwrite :: getName (),
2023-08-09 22:46:23 +00:00
'credentials' => [
'serviceAccount' => $serviceAccount ,
],
'resources' => $resources ,
'statusCounters' => '{}' ,
'resourceData' => '{}' ,
'errors' => [],
]));
2023-10-01 17:39:26 +00:00
$queueForEvents -> setParam ( 'migrationId' , $migration -> getId ());
2023-08-09 22:46:23 +00:00
// Trigger Transfer
2023-10-01 17:39:26 +00:00
$queueForMigrations
2023-08-08 19:28:38 +00:00
-> setMigration ( $migration )
-> setProject ( $project )
2025-12-17 12:11:14 +00:00
-> setPlatform ( $platform )
2023-08-08 19:28:38 +00:00
-> setUser ( $user )
-> trigger ();
$response
-> setStatusCode ( Response :: STATUS_CODE_ACCEPTED )
-> dynamic ( $migration , Response :: MODEL_MIGRATION );
2023-08-04 16:21:41 +00:00
});
2026-02-04 05:30:22 +00:00
Http :: post ( '/v1/migrations/supabase' )
2023-08-04 16:21:41 +00:00
-> groups ([ 'api' , 'migrations' ])
2025-04-14 18:40:48 +00:00
-> desc ( 'Create Supabase migration' )
2023-08-04 16:21:41 +00:00
-> label ( 'scope' , 'migrations.write' )
2023-08-16 15:01:56 +00:00
-> label ( 'event' , 'migrations.[migrationId].create' )
2023-08-04 16:21:41 +00:00
-> label ( 'audits.event' , 'migration.create' )
2025-01-17 04:31:39 +00:00
-> label ( 'sdk' , new Method (
namespace : 'migrations' ,
2025-04-12 06:41:57 +00:00
group : null ,
2025-01-17 04:31:39 +00:00
name : 'createSupabaseMigration' ,
description : '/docs/references/migrations/migration-supabase.md' ,
auth : [ AuthType :: ADMIN ],
responses : [
new SDKResponse (
code : Response :: STATUS_CODE_ACCEPTED ,
model : Response :: MODEL_MIGRATION ,
)
]
))
2023-08-04 16:21:41 +00:00
-> param ( 'resources' , [], new ArrayList ( new WhiteList ( Supabase :: getSupportedResources (), true )), 'List of resources to migrate' )
-> param ( 'endpoint' , '' , new URL (), 'Source\'s Supabase Endpoint' )
-> param ( 'apiKey' , '' , new Text ( 512 ), 'Source\'s API Key' )
-> param ( 'databaseHost' , '' , new Text ( 512 ), 'Source\'s Database Host' )
-> param ( 'username' , '' , new Text ( 512 ), 'Source\'s Database Username' )
-> param ( 'password' , '' , new Text ( 512 ), 'Source\'s Database Password' )
-> param ( 'port' , 5432 , new Integer ( true ), 'Source\'s Database Port' , true )
-> inject ( 'response' )
-> inject ( 'dbForProject' )
-> inject ( 'project' )
2025-12-17 12:11:14 +00:00
-> inject ( 'platform' )
2023-08-04 16:21:41 +00:00
-> inject ( 'user' )
2023-10-01 17:39:26 +00:00
-> inject ( 'queueForEvents' )
-> inject ( 'queueForMigrations' )
2025-12-17 12:11:14 +00:00
-> action ( function ( array $resources , string $endpoint , string $apiKey , string $databaseHost , string $username , string $password , int $port , Response $response , Database $dbForProject , Document $project , array $platform , Document $user , Event $queueForEvents , Migration $queueForMigrations ) {
2023-08-04 16:21:41 +00:00
$migration = $dbForProject -> createDocument ( 'migrations' , new Document ([
'$id' => ID :: unique (),
'status' => 'pending' ,
'stage' => 'init' ,
'source' => Supabase :: getName (),
2024-05-27 15:56:28 +00:00
'destination' => Appwrite :: getName (),
2023-08-04 16:21:41 +00:00
'credentials' => [
'endpoint' => $endpoint ,
'apiKey' => $apiKey ,
'databaseHost' => $databaseHost ,
'username' => $username ,
'password' => $password ,
'port' => $port ,
],
'resources' => $resources ,
'statusCounters' => '{}' ,
'resourceData' => '{}' ,
'errors' => [],
]));
2023-10-01 17:39:26 +00:00
$queueForEvents -> setParam ( 'migrationId' , $migration -> getId ());
2023-08-04 16:21:41 +00:00
// Trigger Transfer
2023-10-01 17:39:26 +00:00
$queueForMigrations
2023-08-04 16:21:41 +00:00
-> setMigration ( $migration )
-> setProject ( $project )
2025-12-17 12:11:14 +00:00
-> setPlatform ( $platform )
2023-08-04 16:21:41 +00:00
-> setUser ( $user )
-> trigger ();
$response
-> setStatusCode ( Response :: STATUS_CODE_ACCEPTED )
-> dynamic ( $migration , Response :: MODEL_MIGRATION );
});
2026-02-04 05:30:22 +00:00
Http :: post ( '/v1/migrations/nhost' )
2023-08-04 16:21:41 +00:00
-> groups ([ 'api' , 'migrations' ])
2025-04-14 18:40:48 +00:00
-> desc ( 'Create NHost migration' )
2023-08-04 16:21:41 +00:00
-> label ( 'scope' , 'migrations.write' )
2023-08-16 15:01:56 +00:00
-> label ( 'event' , 'migrations.[migrationId].create' )
2023-08-04 16:21:41 +00:00
-> label ( 'audits.event' , 'migration.create' )
2025-01-17 04:31:39 +00:00
-> label ( 'sdk' , new Method (
namespace : 'migrations' ,
2025-04-12 06:41:57 +00:00
group : null ,
2025-01-17 04:31:39 +00:00
name : 'createNHostMigration' ,
description : '/docs/references/migrations/migration-nhost.md' ,
auth : [ AuthType :: ADMIN ],
responses : [
new SDKResponse (
code : Response :: STATUS_CODE_ACCEPTED ,
model : Response :: MODEL_MIGRATION ,
)
]
))
2023-08-04 16:21:41 +00:00
-> param ( 'resources' , [], new ArrayList ( new WhiteList ( NHost :: getSupportedResources ())), 'List of resources to migrate' )
2023-08-16 15:01:56 +00:00
-> param ( 'subdomain' , '' , new Text ( 512 ), 'Source\'s Subdomain' )
2023-08-04 16:21:41 +00:00
-> param ( 'region' , '' , new Text ( 512 ), 'Source\'s Region' )
-> param ( 'adminSecret' , '' , new Text ( 512 ), 'Source\'s Admin Secret' )
-> param ( 'database' , '' , new Text ( 512 ), 'Source\'s Database Name' )
-> param ( 'username' , '' , new Text ( 512 ), 'Source\'s Database Username' )
-> param ( 'password' , '' , new Text ( 512 ), 'Source\'s Database Password' )
-> param ( 'port' , 5432 , new Integer ( true ), 'Source\'s Database Port' , true )
-> inject ( 'response' )
-> inject ( 'dbForProject' )
-> inject ( 'project' )
2025-12-17 12:11:14 +00:00
-> inject ( 'platform' )
2023-08-04 16:21:41 +00:00
-> inject ( 'user' )
2023-10-01 17:39:26 +00:00
-> inject ( 'queueForEvents' )
-> inject ( 'queueForMigrations' )
2025-12-17 12:11:14 +00:00
-> action ( function ( array $resources , string $subdomain , string $region , string $adminSecret , string $database , string $username , string $password , int $port , Response $response , Database $dbForProject , Document $project , array $platform , Document $user , Event $queueForEvents , Migration $queueForMigrations ) {
2023-08-04 16:21:41 +00:00
$migration = $dbForProject -> createDocument ( 'migrations' , new Document ([
'$id' => ID :: unique (),
'status' => 'pending' ,
'stage' => 'init' ,
'source' => NHost :: getName (),
2024-05-27 15:56:28 +00:00
'destination' => Appwrite :: getName (),
2023-08-04 16:21:41 +00:00
'credentials' => [
'subdomain' => $subdomain ,
'region' => $region ,
'adminSecret' => $adminSecret ,
'database' => $database ,
'username' => $username ,
'password' => $password ,
'port' => $port ,
],
'resources' => $resources ,
'statusCounters' => '{}' ,
'resourceData' => '{}' ,
'errors' => [],
]));
2023-10-01 17:39:26 +00:00
$queueForEvents -> setParam ( 'migrationId' , $migration -> getId ());
2023-08-04 16:21:41 +00:00
// Trigger Transfer
2023-10-01 17:39:26 +00:00
$queueForMigrations
2023-08-04 16:21:41 +00:00
-> setMigration ( $migration )
-> setProject ( $project )
2025-12-17 12:11:14 +00:00
-> setPlatform ( $platform )
2023-08-04 16:21:41 +00:00
-> setUser ( $user )
-> trigger ();
$response
-> setStatusCode ( Response :: STATUS_CODE_ACCEPTED )
-> dynamic ( $migration , Response :: MODEL_MIGRATION );
});
2026-02-04 05:30:22 +00:00
Http :: post ( '/v1/migrations/csv/imports' )
2025-08-05 12:40:39 +00:00
-> alias ( '/v1/migrations/csv' )
2025-04-08 05:49:39 +00:00
-> groups ([ 'api' , 'migrations' ])
-> desc ( 'Import documents from a CSV' )
-> label ( 'scope' , 'migrations.write' )
-> label ( 'event' , 'migrations.[migrationId].create' )
-> label ( 'audits.event' , 'migration.create' )
-> label ( 'sdk' , new Method (
namespace : 'migrations' ,
2025-04-25 10:26:10 +00:00
group : null ,
2025-10-21 12:26:01 +00:00
name : 'createCSVImport' ,
2025-08-05 05:04:31 +00:00
description : '/docs/references/migrations/migration-csv-import.md' ,
2025-04-08 05:49:39 +00:00
auth : [ AuthType :: ADMIN ],
responses : [
new SDKResponse (
code : Response :: STATUS_CODE_ACCEPTED ,
model : Response :: MODEL_MIGRATION ,
)
]
))
-> param ( 'bucketId' , '' , new UID (), 'Storage bucket unique ID. You can create a new storage bucket using the Storage service [server integration](https://appwrite.io/docs/server/storage#createBucket).' )
-> param ( 'fileId' , '' , new UID (), 'File ID.' )
2025-04-12 11:40:06 +00:00
-> param ( 'resourceId' , null , new CompoundUID (), 'Composite ID in the format {databaseId:collectionId}, identifying a collection within a database.' )
2025-08-15 09:34:16 +00:00
-> param ( 'internalFile' , false , new Boolean (), 'Is the file stored in an internal bucket?' , true )
2025-04-08 05:49:39 +00:00
-> inject ( 'response' )
-> inject ( 'dbForProject' )
2025-08-14 12:40:00 +00:00
-> inject ( 'dbForPlatform' )
2026-01-14 15:08:00 +00:00
-> inject ( 'authorization' )
2025-04-08 05:49:39 +00:00
-> inject ( 'project' )
2025-12-17 12:11:14 +00:00
-> inject ( 'platform' )
2025-04-08 05:49:39 +00:00
-> inject ( 'deviceForFiles' )
2025-08-05 12:40:39 +00:00
-> inject ( 'deviceForMigrations' )
2025-04-09 11:05:27 +00:00
-> inject ( 'queueForEvents' )
2025-04-08 05:49:39 +00:00
-> inject ( 'queueForMigrations' )
2025-11-27 16:17:04 +00:00
-> action ( function (
string $bucketId ,
string $fileId ,
string $resourceId ,
bool $internalFile ,
Response $response ,
Database $dbForProject ,
Database $dbForPlatform ,
2026-01-14 15:08:00 +00:00
Authorization $authorization ,
2025-11-27 16:17:04 +00:00
Document $project ,
2025-12-17 12:11:14 +00:00
array $platform ,
2025-11-27 16:17:04 +00:00
Device $deviceForFiles ,
Device $deviceForMigrations ,
Event $queueForEvents ,
Migration $queueForMigrations
) {
2026-01-14 15:08:00 +00:00
$bucket = $authorization -> skip ( function () use ( $internalFile , $dbForPlatform , $dbForProject , $bucketId ) {
2025-08-14 12:40:00 +00:00
if ( $internalFile ) {
return $dbForPlatform -> getDocument ( 'buckets' , 'default' );
}
return $dbForProject -> getDocument ( 'buckets' , $bucketId );
});
2025-04-08 05:49:39 +00:00
2025-09-24 10:37:12 +00:00
if ( $bucket -> isEmpty ()) {
2025-04-08 05:49:39 +00:00
throw new Exception ( Exception :: STORAGE_BUCKET_NOT_FOUND );
}
2026-01-14 15:08:00 +00:00
$file = $authorization -> skip ( fn () => $internalFile ? $dbForPlatform -> getDocument ( 'bucket_' . $bucket -> getSequence (), $fileId ) : $dbForProject -> getDocument ( 'bucket_' . $bucket -> getSequence (), $fileId ));
2025-04-08 05:49:39 +00:00
if ( $file -> isEmpty ()) {
throw new Exception ( Exception :: STORAGE_FILE_NOT_FOUND );
}
$path = $file -> getAttribute ( 'path' , '' );
if ( ! $deviceForFiles -> exists ( $path )) {
throw new Exception ( Exception :: STORAGE_FILE_NOT_FOUND , 'File not found in ' . $path );
}
2025-09-24 10:37:12 +00:00
// No encryption or compression on files above 20MB.
2025-04-21 06:34:36 +00:00
$hasEncryption = ! empty ( $file -> getAttribute ( 'openSSLCipher' ));
2025-04-21 04:31:13 +00:00
$compression = $file -> getAttribute ( 'algorithm' , Compression :: NONE );
2025-04-21 06:34:36 +00:00
$hasCompression = $compression !== Compression :: NONE ;
2025-04-21 04:31:13 +00:00
2025-04-09 11:05:27 +00:00
$migrationId = ID :: unique ();
2025-08-05 12:40:39 +00:00
$newPath = $deviceForMigrations -> getPath ( $migrationId . '_' . $fileId . '.csv' );
2025-04-21 04:31:13 +00:00
2025-04-21 06:34:36 +00:00
if ( $hasEncryption || $hasCompression ) {
2025-04-21 04:31:13 +00:00
$source = $deviceForFiles -> read ( $path );
2025-04-21 06:34:36 +00:00
if ( $hasEncryption ) {
$source = OpenSSL :: decrypt (
$source ,
$file -> getAttribute ( 'openSSLCipher' ),
System :: getEnv ( '_APP_OPENSSL_KEY_V' . $file -> getAttribute ( 'openSSLVersion' )),
0 ,
hex2bin ( $file -> getAttribute ( 'openSSLIV' )),
hex2bin ( $file -> getAttribute ( 'openSSLTag' ))
);
}
if ( $hasCompression ) {
switch ( $compression ) {
case Compression :: ZSTD :
$source = ( new Zstd ()) -> decompress ( $source );
break ;
case Compression :: GZIP :
$source = ( new GZIP ()) -> decompress ( $source );
break ;
}
2025-04-21 04:31:13 +00:00
}
2025-09-24 10:37:12 +00:00
// Manual write after decryption and/or decompression
if ( ! $deviceForMigrations -> write ( $newPath , $source , 'text/csv' )) {
throw new \Exception ( 'Unable to copy file' );
2025-04-21 04:31:13 +00:00
}
2025-09-24 10:37:12 +00:00
} elseif ( ! $deviceForFiles -> transfer ( $path , $newPath , $deviceForMigrations )) {
throw new \Exception ( 'Unable to copy file' );
2025-04-14 09:33:30 +00:00
}
2025-08-05 12:40:39 +00:00
$fileSize = $deviceForMigrations -> getFileSize ( $newPath );
2025-04-09 11:05:27 +00:00
$resources = Transfer :: extractServices ([ Transfer :: GROUP_DATABASES ]);
2025-04-08 05:49:39 +00:00
$migration = $dbForProject -> createDocument ( 'migrations' , new Document ([
2025-04-09 11:05:27 +00:00
'$id' => $migrationId ,
2025-04-08 05:49:39 +00:00
'status' => 'pending' ,
'stage' => 'init' ,
2025-04-16 06:25:04 +00:00
'source' => CSV :: getName (),
2025-04-17 05:24:23 +00:00
'destination' => Appwrite :: getName (),
2025-04-09 11:05:27 +00:00
'resources' => $resources ,
2025-04-08 05:49:39 +00:00
'resourceId' => $resourceId ,
'resourceType' => Resource :: TYPE_DATABASE ,
2025-07-02 09:02:16 +00:00
'statusCounters' => '{}' ,
'resourceData' => '{}' ,
2025-04-08 05:49:39 +00:00
'errors' => [],
2025-04-14 09:33:30 +00:00
'options' => [
'path' => $newPath ,
2025-04-09 11:05:27 +00:00
'size' => $fileSize ,
],
2025-04-08 05:49:39 +00:00
]));
$queueForEvents -> setParam ( 'migrationId' , $migration -> getId ());
$queueForMigrations
-> setMigration ( $migration )
-> setProject ( $project )
2025-12-17 12:11:14 +00:00
-> setProject ( $project )
2025-04-08 05:49:39 +00:00
-> trigger ();
2025-04-09 11:05:27 +00:00
$response
-> setStatusCode ( Response :: STATUS_CODE_ACCEPTED )
-> dynamic ( $migration , Response :: MODEL_MIGRATION );
2025-04-08 05:49:39 +00:00
});
2026-02-04 05:30:22 +00:00
Http :: post ( '/v1/migrations/csv/exports' )
2025-08-05 05:04:31 +00:00
-> groups ([ 'api' , 'migrations' ])
-> desc ( 'Export documents to CSV' )
-> label ( 'scope' , 'migrations.write' )
-> label ( 'event' , 'migrations.[migrationId].create' )
-> label ( 'audits.event' , 'migration.create' )
-> label ( 'sdk' , new Method (
namespace : 'migrations' ,
group : null ,
2025-10-21 12:26:01 +00:00
name : 'createCSVExport' ,
2025-08-05 05:04:31 +00:00
description : '/docs/references/migrations/migration-csv-export.md' ,
auth : [ AuthType :: ADMIN ],
responses : [
new SDKResponse (
code : Response :: STATUS_CODE_ACCEPTED ,
model : Response :: MODEL_MIGRATION ,
)
]
))
-> param ( 'resourceId' , null , new CompoundUID (), 'Composite ID in the format {databaseId:collectionId}, identifying a collection within a database to export.' )
2025-09-24 10:37:12 +00:00
-> param ( 'filename' , '' , new Text ( 255 ), 'The name of the file to be created for the export, excluding the .csv extension.' )
-> param ( 'columns' , [], new ArrayList ( new Text ( Database :: LENGTH_KEY )), 'List of attributes to export. If empty, all attributes will be exported. You can use the `*` wildcard to export all attributes from the collection.' , true )
2025-10-21 12:12:18 +00:00
-> param ( 'queries' , [], new ArrayList ( new Text ( 0 )), 'Array of query strings generated using the Query class provided by the SDK to filter documents to export. [Learn more about queries](https://appwrite.io/docs/databases#querying-documents). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' queries are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long.' , true )
2025-10-22 03:41:00 +00:00
-> param ( 'delimiter' , ',' , new Text ( 1 ), 'The character that separates each column value. Default is comma.' , true )
-> param ( 'enclosure' , '"' , new Text ( 1 ), 'The character that encloses each column value. Default is double quotes.' , true )
-> param ( 'escape' , '"' , new Text ( 1 ), 'The escape character for the enclosure character. Default is double quotes.' , true )
2025-09-24 10:37:12 +00:00
-> param ( 'header' , true , new Boolean (), 'Whether to include the header row with column names. Default is true.' , true )
-> param ( 'notify' , true , new Boolean (), 'Set to true to receive an email when the export is complete. Default is true.' , true )
-> inject ( 'user' )
2025-08-05 05:04:31 +00:00
-> inject ( 'response' )
-> inject ( 'dbForProject' )
2025-11-12 09:18:25 +00:00
-> inject ( 'dbForPlatform' )
2026-01-14 15:08:00 +00:00
-> inject ( 'authorization' )
2025-08-05 05:04:31 +00:00
-> inject ( 'project' )
2025-12-17 12:11:14 +00:00
-> inject ( 'platform' )
2025-08-05 05:04:31 +00:00
-> inject ( 'queueForEvents' )
-> inject ( 'queueForMigrations' )
2025-09-24 10:37:12 +00:00
-> action ( function (
string $resourceId ,
string $filename ,
array $columns ,
2025-10-21 12:12:18 +00:00
array $queries ,
2025-09-24 10:37:12 +00:00
string $delimiter ,
string $enclosure ,
string $escape ,
bool $header ,
bool $notify ,
Document $user ,
Response $response ,
Database $dbForProject ,
2025-11-12 09:18:25 +00:00
Database $dbForPlatform ,
2026-01-14 15:08:00 +00:00
Authorization $authorization ,
2025-09-24 10:37:12 +00:00
Document $project ,
2025-12-17 12:11:14 +00:00
array $platform ,
2025-09-24 10:37:12 +00:00
Event $queueForEvents ,
Migration $queueForMigrations
) {
2025-10-21 12:12:18 +00:00
try {
2025-10-22 12:28:06 +00:00
$parsedQueries = Query :: parseQueries ( $queries );
2025-10-21 12:12:18 +00:00
} catch ( QueryException $e ) {
throw new Exception ( Exception :: GENERAL_QUERY_INVALID , $e -> getMessage ());
}
2026-01-14 15:08:00 +00:00
$bucket = $authorization -> skip ( fn () => $dbForPlatform -> getDocument ( 'buckets' , 'default' ));
2025-09-24 10:37:12 +00:00
if ( $bucket -> isEmpty ()) {
2025-08-05 05:04:31 +00:00
throw new Exception ( Exception :: STORAGE_BUCKET_NOT_FOUND );
}
2025-09-24 10:37:12 +00:00
[ $databaseId , $collectionId ] = \explode ( ':' , $resourceId , 2 );
if ( empty ( $databaseId )) {
throw new Exception ( Exception :: DATABASE_NOT_FOUND );
}
if ( empty ( $collectionId )) {
throw new Exception ( Exception :: COLLECTION_NOT_FOUND );
}
2026-01-14 15:08:00 +00:00
$database = $authorization -> skip ( fn () => $dbForProject -> getDocument ( 'databases' , $databaseId ));
2025-09-24 10:37:12 +00:00
if ( $database -> isEmpty ()) {
throw new Exception ( Exception :: DATABASE_NOT_FOUND );
}
2026-01-14 15:08:00 +00:00
$collection = $authorization -> skip ( fn () => $dbForProject -> getDocument ( 'database_' . $database -> getSequence (), $collectionId ));
2025-09-24 10:37:12 +00:00
if ( $collection -> isEmpty ()) {
throw new Exception ( Exception :: COLLECTION_NOT_FOUND );
}
2025-08-05 05:04:31 +00:00
2025-10-21 12:12:18 +00:00
$validator = new Documents (
attributes : $collection -> getAttribute ( 'attributes' , []),
indexes : $collection -> getAttribute ( 'indexes' , []),
idAttributeType : $dbForProject -> getAdapter () -> getIdAttributeType (),
);
2025-10-22 12:28:06 +00:00
if ( ! $validator -> isValid ( $parsedQueries )) {
2025-10-21 12:12:18 +00:00
throw new Exception ( Exception :: GENERAL_QUERY_INVALID , $validator -> getDescription ());
}
2025-08-05 05:04:31 +00:00
$migration = $dbForProject -> createDocument ( 'migrations' , new Document ([
2025-09-24 10:37:12 +00:00
'$id' => ID :: unique (),
2025-08-05 05:04:31 +00:00
'status' => 'pending' ,
'stage' => 'init' ,
'source' => Appwrite :: getName (),
'destination' => CSV :: getName (),
2025-09-24 10:37:12 +00:00
'resources' => Transfer :: extractServices ([ Transfer :: GROUP_DATABASES ]),
2025-08-05 05:04:31 +00:00
'resourceId' => $resourceId ,
'resourceType' => Resource :: TYPE_DATABASE ,
'statusCounters' => '{}' ,
'resourceData' => '{}' ,
'errors' => [],
'options' => [
2025-11-12 09:18:25 +00:00
'bucketId' => 'default' , // Always use internal bucket
2025-09-24 10:37:12 +00:00
'filename' => $filename ,
2025-08-05 12:40:39 +00:00
'columns' => $columns ,
2025-10-21 12:12:18 +00:00
'queries' => $queries ,
2025-09-24 10:37:12 +00:00
'delimiter' => $delimiter ,
'enclosure' => $enclosure ,
'escape' => $escape ,
'header' => $header ,
'notify' => $notify ,
'userInternalId' => $user -> getSequence (),
2025-08-05 05:04:31 +00:00
],
]));
$queueForEvents -> setParam ( 'migrationId' , $migration -> getId ());
$queueForMigrations
-> setMigration ( $migration )
-> setProject ( $project )
2025-12-17 12:11:14 +00:00
-> setPlatform ( $platform )
2025-08-05 05:04:31 +00:00
-> trigger ();
$response
-> setStatusCode ( Response :: STATUS_CODE_ACCEPTED )
-> dynamic ( $migration , Response :: MODEL_MIGRATION );
});
2026-02-04 05:30:22 +00:00
Http :: get ( '/v1/migrations' )
2023-08-04 16:21:41 +00:00
-> groups ([ 'api' , 'migrations' ])
2024-09-03 16:32:11 +00:00
-> desc ( 'List migrations' )
2023-08-04 16:21:41 +00:00
-> label ( 'scope' , 'migrations.read' )
2025-01-17 04:31:39 +00:00
-> label ( 'sdk' , new Method (
namespace : 'migrations' ,
2025-04-12 06:41:57 +00:00
group : null ,
2025-01-17 04:31:39 +00:00
name : 'list' ,
description : '/docs/references/migrations/list-migrations.md' ,
auth : [ AuthType :: ADMIN ],
responses : [
new SDKResponse (
code : Response :: STATUS_CODE_OK ,
model : Response :: MODEL_MIGRATION_LIST ,
)
]
))
2023-08-04 16:21:41 +00:00
-> param ( 'queries' , [], new Migrations (), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/databases#querying-documents). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' queries are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long. You may filter on the following attributes: ' . implode ( ', ' , Migrations :: ALLOWED_ATTRIBUTES ), true )
-> param ( 'search' , '' , new Text ( 256 ), 'Search term to filter your list results. Max length: 256 chars.' , true )
2025-10-29 09:08:08 +00:00
-> param ( 'total' , true , new Boolean ( true ), 'When set to false, the total count returned will be 0 and will not be calculated.' , true )
2023-08-04 16:21:41 +00:00
-> inject ( 'response' )
-> inject ( 'dbForProject' )
2025-10-19 12:45:16 +00:00
-> action ( function ( array $queries , string $search , bool $includeTotal , Response $response , Database $dbForProject ) {
2024-02-12 16:02:04 +00:00
try {
$queries = Query :: parseQueries ( $queries );
} catch ( QueryException $e ) {
throw new Exception ( Exception :: GENERAL_QUERY_INVALID , $e -> getMessage ());
}
2023-08-04 16:21:41 +00:00
if ( ! empty ( $search )) {
$queries [] = Query :: search ( 'search' , $search );
}
2026-01-28 12:53:24 +00:00
$cursor = Query :: getCursorQueries ( $queries , false );
$cursor = \reset ( $cursor );
2024-10-17 05:41:24 +00:00
2026-01-28 12:53:24 +00:00
if ( $cursor !== false ) {
2024-10-17 05:41:24 +00:00
$validator = new Cursor ();
if ( ! $validator -> isValid ( $cursor )) {
throw new Exception ( Exception :: GENERAL_QUERY_INVALID , $validator -> getDescription ());
}
2023-08-04 16:21:41 +00:00
$migrationId = $cursor -> getValue ();
$cursorDocument = $dbForProject -> getDocument ( 'migrations' , $migrationId );
if ( $cursorDocument -> isEmpty ()) {
throw new Exception ( Exception :: GENERAL_CURSOR_NOT_FOUND , " Migration ' { $migrationId } ' for the 'cursor' value not found. " );
}
$cursor -> setValue ( $cursorDocument );
}
$filterQueries = Query :: groupByType ( $queries )[ 'filters' ];
2025-04-16 11:59:36 +00:00
try {
2025-04-17 04:46:26 +00:00
$migrations = $dbForProject -> find ( 'migrations' , $queries );
2025-10-19 12:45:16 +00:00
$total = $includeTotal ? $dbForProject -> count ( 'migrations' , $filterQueries , APP_LIMIT_COUNT ) : 0 ;
2025-04-16 11:59:36 +00:00
} catch ( OrderException $e ) {
2025-04-17 04:46:26 +00:00
throw new Exception ( Exception :: DATABASE_QUERY_ORDER_NULL , " The order attribute ' { $e -> getAttribute () } ' had a null value. Cursor pagination requires all documents order attribute values are non-null. " );
2025-04-16 11:59:36 +00:00
}
2023-08-04 16:21:41 +00:00
$response -> dynamic ( new Document ([
2025-04-17 04:46:26 +00:00
'migrations' => $migrations ,
'total' => $total ,
2023-08-04 16:21:41 +00:00
]), Response :: MODEL_MIGRATION_LIST );
});
2026-02-04 05:30:22 +00:00
Http :: get ( '/v1/migrations/:migrationId' )
2023-08-04 16:21:41 +00:00
-> groups ([ 'api' , 'migrations' ])
2024-09-03 16:32:11 +00:00
-> desc ( 'Get migration' )
2023-08-04 16:21:41 +00:00
-> label ( 'scope' , 'migrations.read' )
2025-01-17 04:31:39 +00:00
-> label ( 'sdk' , new Method (
namespace : 'migrations' ,
2025-04-12 06:41:57 +00:00
group : null ,
2025-01-17 04:31:39 +00:00
name : 'get' ,
description : '/docs/references/migrations/get-migration.md' ,
auth : [ AuthType :: ADMIN ],
responses : [
new SDKResponse (
code : Response :: STATUS_CODE_OK ,
model : Response :: MODEL_MIGRATION ,
)
]
))
2023-08-04 16:21:41 +00:00
-> param ( 'migrationId' , '' , new UID (), 'Migration unique ID.' )
-> inject ( 'response' )
-> inject ( 'dbForProject' )
-> action ( function ( string $migrationId , Response $response , Database $dbForProject ) {
$migration = $dbForProject -> getDocument ( 'migrations' , $migrationId );
if ( $migration -> isEmpty ()) {
throw new Exception ( Exception :: MIGRATION_NOT_FOUND );
}
$response -> dynamic ( $migration , Response :: MODEL_MIGRATION );
});
2026-02-04 05:30:22 +00:00
Http :: get ( '/v1/migrations/appwrite/report' )
2023-08-04 16:21:41 +00:00
-> groups ([ 'api' , 'migrations' ])
2025-04-14 18:40:48 +00:00
-> desc ( 'Get Appwrite migration report' )
2023-08-04 16:21:41 +00:00
-> label ( 'scope' , 'migrations.write' )
2025-01-17 04:31:39 +00:00
-> label ( 'sdk' , new Method (
namespace : 'migrations' ,
2025-04-12 06:41:57 +00:00
group : null ,
2025-01-17 04:31:39 +00:00
name : 'getAppwriteReport' ,
description : '/docs/references/migrations/migration-appwrite-report.md' ,
auth : [ AuthType :: ADMIN ],
responses : [
new SDKResponse (
code : Response :: STATUS_CODE_OK ,
model : Response :: MODEL_MIGRATION_REPORT ,
)
]
))
2023-08-04 16:21:41 +00:00
-> param ( 'resources' , [], new ArrayList ( new WhiteList ( Appwrite :: getSupportedResources ())), 'List of resources to migrate' )
-> param ( 'endpoint' , '' , new URL (), " Source's Appwrite Endpoint " )
-> param ( 'projectID' , '' , new Text ( 512 ), " Source's Project ID " )
-> param ( 'key' , '' , new Text ( 512 ), " Source's API Key " )
-> inject ( 'response' )
-> inject ( 'dbForProject' )
-> inject ( 'project' )
-> inject ( 'user' )
-> action ( function ( array $resources , string $endpoint , string $projectID , string $key , Response $response ) {
2025-04-11 14:52:19 +00:00
2023-10-06 16:02:01 +00:00
$appwrite = new Appwrite ( $projectID , $endpoint , $key );
2023-08-04 16:21:41 +00:00
2023-10-06 16:02:01 +00:00
try {
$report = $appwrite -> report ( $resources );
2023-08-17 14:54:19 +00:00
} catch ( \Throwable $e ) {
2023-10-06 16:02:01 +00:00
switch ( $e -> getCode ()) {
case 401 :
throw new Exception ( Exception :: GENERAL_UNAUTHORIZED_SCOPE , 'Source Error: ' . $e -> getMessage ());
case 429 :
throw new Exception ( Exception :: GENERAL_RATE_LIMIT_EXCEEDED , 'Source Error: Rate Limit Exceeded, Is your Cloud Provider blocking Appwrite\'s IP?' );
case 500 :
throw new Exception ( Exception :: MIGRATION_PROVIDER_ERROR , 'Source Error: ' . $e -> getMessage ());
}
throw new Exception ( Exception :: MIGRATION_PROVIDER_ERROR , 'Source Error: ' . $e -> getMessage ());
2023-08-04 16:21:41 +00:00
}
2023-10-06 16:02:01 +00:00
$response
-> setStatusCode ( Response :: STATUS_CODE_OK )
-> dynamic ( new Document ( $report ), Response :: MODEL_MIGRATION_REPORT );
2023-08-04 16:21:41 +00:00
});
2026-02-04 05:30:22 +00:00
Http :: get ( '/v1/migrations/firebase/report' )
2023-08-04 16:21:41 +00:00
-> groups ([ 'api' , 'migrations' ])
2025-04-14 18:40:48 +00:00
-> desc ( 'Get Firebase migration report' )
2023-08-04 16:21:41 +00:00
-> label ( 'scope' , 'migrations.write' )
2025-01-17 04:31:39 +00:00
-> label ( 'sdk' , new Method (
namespace : 'migrations' ,
2025-04-12 06:41:57 +00:00
group : null ,
2025-01-17 04:31:39 +00:00
name : 'getFirebaseReport' ,
description : '/docs/references/migrations/migration-firebase-report.md' ,
auth : [ AuthType :: ADMIN ],
responses : [
new SDKResponse (
code : Response :: STATUS_CODE_OK ,
model : Response :: MODEL_MIGRATION_REPORT ,
)
]
))
2023-08-04 16:21:41 +00:00
-> param ( 'resources' , [], new ArrayList ( new WhiteList ( Firebase :: getSupportedResources ())), 'List of resources to migrate' )
-> param ( 'serviceAccount' , '' , new Text ( 65536 ), 'JSON of the Firebase service account credentials' )
-> inject ( 'response' )
-> action ( function ( array $resources , string $serviceAccount , Response $response ) {
2023-10-06 16:02:01 +00:00
$serviceAccount = json_decode ( $serviceAccount , true );
if ( empty ( $serviceAccount )) {
throw new Exception ( Exception :: MIGRATION_PROVIDER_ERROR , 'Invalid Service Account JSON' );
}
if ( ! isset ( $serviceAccount [ 'project_id' ]) || ! isset ( $serviceAccount [ 'client_email' ]) || ! isset ( $serviceAccount [ 'private_key' ])) {
throw new Exception ( Exception :: MIGRATION_PROVIDER_ERROR , 'Invalid Service Account JSON' );
}
$firebase = new Firebase ( $serviceAccount );
2023-08-04 16:21:41 +00:00
try {
2023-10-06 16:02:01 +00:00
$report = $firebase -> report ( $resources );
} catch ( \Throwable $e ) {
switch ( $e -> getCode ()) {
case 401 :
throw new Exception ( Exception :: GENERAL_UNAUTHORIZED_SCOPE , 'Source Error: ' . $e -> getMessage ());
case 429 :
throw new Exception ( Exception :: GENERAL_RATE_LIMIT_EXCEEDED , 'Source Error: Rate Limit Exceeded, Is your Cloud Provider blocking Appwrite\'s IP?' );
case 500 :
throw new Exception ( Exception :: MIGRATION_PROVIDER_ERROR , 'Source Error: ' . $e -> getMessage ());
}
2023-08-04 16:21:41 +00:00
2023-10-06 16:02:01 +00:00
throw new Exception ( Exception :: MIGRATION_PROVIDER_ERROR , 'Source Error: ' . $e -> getMessage ());
2023-08-04 16:21:41 +00:00
}
2023-10-06 16:02:01 +00:00
$response
-> setStatusCode ( Response :: STATUS_CODE_OK )
-> dynamic ( new Document ( $report ), Response :: MODEL_MIGRATION_REPORT );
2023-08-04 16:21:41 +00:00
});
2026-02-04 05:30:22 +00:00
Http :: get ( '/v1/migrations/supabase/report' )
2023-08-04 16:21:41 +00:00
-> groups ([ 'api' , 'migrations' ])
2025-04-14 18:40:48 +00:00
-> desc ( 'Get Supabase migration report' )
2023-08-04 16:21:41 +00:00
-> label ( 'scope' , 'migrations.write' )
2025-01-17 04:31:39 +00:00
-> label ( 'sdk' , new Method (
namespace : 'migrations' ,
2025-04-12 06:41:57 +00:00
group : null ,
2025-01-17 04:31:39 +00:00
name : 'getSupabaseReport' ,
description : '/docs/references/migrations/migration-supabase-report.md' ,
auth : [ AuthType :: ADMIN ],
responses : [
new SDKResponse (
code : Response :: STATUS_CODE_OK ,
model : Response :: MODEL_MIGRATION_REPORT ,
)
]
))
2023-08-04 16:21:41 +00:00
-> param ( 'resources' , [], new ArrayList ( new WhiteList ( Supabase :: getSupportedResources (), true )), 'List of resources to migrate' )
2023-08-22 20:11:33 +00:00
-> param ( 'endpoint' , '' , new URL (), 'Source\'s Supabase Endpoint.' )
-> param ( 'apiKey' , '' , new Text ( 512 ), 'Source\'s API Key.' )
-> param ( 'databaseHost' , '' , new Text ( 512 ), 'Source\'s Database Host.' )
-> param ( 'username' , '' , new Text ( 512 ), 'Source\'s Database Username.' )
-> param ( 'password' , '' , new Text ( 512 ), 'Source\'s Database Password.' )
-> param ( 'port' , 5432 , new Integer ( true ), 'Source\'s Database Port.' , true )
2023-08-04 16:21:41 +00:00
-> inject ( 'response' )
-> inject ( 'dbForProject' )
-> action ( function ( array $resources , string $endpoint , string $apiKey , string $databaseHost , string $username , string $password , int $port , Response $response ) {
2023-10-06 16:02:01 +00:00
$supabase = new Supabase ( $endpoint , $apiKey , $databaseHost , 'postgres' , $username , $password , $port );
2023-08-04 16:21:41 +00:00
try {
2023-10-06 16:02:01 +00:00
$report = $supabase -> report ( $resources );
} catch ( \Throwable $e ) {
switch ( $e -> getCode ()) {
case 401 :
throw new Exception ( Exception :: GENERAL_UNAUTHORIZED_SCOPE , 'Source Error: ' . $e -> getMessage ());
case 429 :
throw new Exception ( Exception :: GENERAL_RATE_LIMIT_EXCEEDED , 'Source Error: Rate Limit Exceeded, Is your Cloud Provider blocking Appwrite\'s IP?' );
case 500 :
throw new Exception ( Exception :: MIGRATION_PROVIDER_ERROR , 'Source Error: ' . $e -> getMessage ());
}
2023-08-04 16:21:41 +00:00
2023-10-06 16:02:01 +00:00
throw new Exception ( Exception :: MIGRATION_PROVIDER_ERROR , 'Source Error: ' . $e -> getMessage ());
2023-08-04 16:21:41 +00:00
}
2023-10-06 16:02:01 +00:00
$response
-> setStatusCode ( Response :: STATUS_CODE_OK )
-> dynamic ( new Document ( $report ), Response :: MODEL_MIGRATION_REPORT );
2023-08-04 16:21:41 +00:00
});
2026-02-04 05:30:22 +00:00
Http :: get ( '/v1/migrations/nhost/report' )
2023-08-04 16:21:41 +00:00
-> groups ([ 'api' , 'migrations' ])
2025-04-14 18:40:48 +00:00
-> desc ( 'Get NHost migration report' )
2023-08-04 16:21:41 +00:00
-> label ( 'scope' , 'migrations.write' )
2025-01-17 04:31:39 +00:00
-> label ( 'sdk' , new Method (
namespace : 'migrations' ,
2025-04-12 06:41:57 +00:00
group : null ,
2025-01-17 04:31:39 +00:00
name : 'getNHostReport' ,
description : '/docs/references/migrations/migration-nhost-report.md' ,
auth : [ AuthType :: ADMIN ],
responses : [
new SDKResponse (
code : Response :: STATUS_CODE_OK ,
model : Response :: MODEL_MIGRATION_REPORT ,
)
]
))
2023-08-22 20:11:33 +00:00
-> param ( 'resources' , [], new ArrayList ( new WhiteList ( NHost :: getSupportedResources ())), 'List of resources to migrate.' )
-> param ( 'subdomain' , '' , new Text ( 512 ), 'Source\'s Subdomain.' )
-> param ( 'region' , '' , new Text ( 512 ), 'Source\'s Region.' )
-> param ( 'adminSecret' , '' , new Text ( 512 ), 'Source\'s Admin Secret.' )
-> param ( 'database' , '' , new Text ( 512 ), 'Source\'s Database Name.' )
-> param ( 'username' , '' , new Text ( 512 ), 'Source\'s Database Username.' )
-> param ( 'password' , '' , new Text ( 512 ), 'Source\'s Database Password.' )
-> param ( 'port' , 5432 , new Integer ( true ), 'Source\'s Database Port.' , true )
2023-08-04 16:21:41 +00:00
-> inject ( 'response' )
-> action ( function ( array $resources , string $subdomain , string $region , string $adminSecret , string $database , string $username , string $password , int $port , Response $response ) {
2023-10-06 16:02:01 +00:00
$nhost = new NHost ( $subdomain , $region , $adminSecret , $database , $username , $password , $port );
2023-08-04 16:21:41 +00:00
try {
2023-10-06 16:02:01 +00:00
$report = $nhost -> report ( $resources );
} catch ( \Throwable $e ) {
switch ( $e -> getCode ()) {
case 401 :
throw new Exception ( Exception :: GENERAL_UNAUTHORIZED_SCOPE , 'Source Error: ' . $e -> getMessage ());
case 429 :
throw new Exception ( Exception :: GENERAL_RATE_LIMIT_EXCEEDED , 'Source Error: Rate Limit Exceeded, Is your Cloud Provider blocking Appwrite\'s IP?' );
case 500 :
throw new Exception ( Exception :: MIGRATION_PROVIDER_ERROR , 'Source Error: ' . $e -> getMessage ());
}
2023-08-04 16:21:41 +00:00
2023-10-06 16:02:01 +00:00
throw new Exception ( Exception :: MIGRATION_PROVIDER_ERROR , 'Source Error: ' . $e -> getMessage ());
2023-08-04 16:21:41 +00:00
}
2023-10-06 16:02:01 +00:00
$response
-> setStatusCode ( Response :: STATUS_CODE_OK )
-> dynamic ( new Document ( $report ), Response :: MODEL_MIGRATION_REPORT );
2023-08-04 16:21:41 +00:00
});
2026-02-04 05:30:22 +00:00
Http :: patch ( '/v1/migrations/:migrationId' )
2023-08-04 16:21:41 +00:00
-> groups ([ 'api' , 'migrations' ])
2025-04-14 18:40:48 +00:00
-> desc ( 'Update retry migration' )
2023-08-04 16:21:41 +00:00
-> label ( 'scope' , 'migrations.write' )
-> label ( 'event' , 'migrations.[migrationId].retry' )
-> label ( 'audits.event' , 'migration.retry' )
-> label ( 'audits.resource' , 'migrations/{request.migrationId}' )
2025-01-17 04:31:39 +00:00
-> label ( 'sdk' , new Method (
namespace : 'migrations' ,
2025-04-12 06:41:57 +00:00
group : null ,
2025-01-17 04:31:39 +00:00
name : 'retry' ,
description : '/docs/references/migrations/retry-migration.md' ,
auth : [ AuthType :: ADMIN ],
responses : [
new SDKResponse (
code : Response :: STATUS_CODE_ACCEPTED ,
model : Response :: MODEL_MIGRATION ,
)
]
))
2023-08-04 16:21:41 +00:00
-> param ( 'migrationId' , '' , new UID (), 'Migration unique ID.' )
-> inject ( 'response' )
-> inject ( 'dbForProject' )
-> inject ( 'project' )
2025-12-17 12:11:14 +00:00
-> inject ( 'platform' )
2023-08-04 16:21:41 +00:00
-> inject ( 'user' )
2023-10-01 17:39:26 +00:00
-> inject ( 'queueForMigrations' )
2025-12-17 12:11:14 +00:00
-> action ( function ( string $migrationId , Response $response , Database $dbForProject , Document $project , array $platform , Document $user , Migration $queueForMigrations ) {
2023-08-04 16:21:41 +00:00
$migration = $dbForProject -> getDocument ( 'migrations' , $migrationId );
if ( $migration -> isEmpty ()) {
throw new Exception ( Exception :: MIGRATION_NOT_FOUND );
}
if ( $migration -> getAttribute ( 'status' ) !== 'failed' ) {
throw new Exception ( Exception :: MIGRATION_IN_PROGRESS , 'Migration not failed yet' );
}
$migration
-> setAttribute ( 'status' , 'pending' )
-> setAttribute ( 'dateUpdated' , \time ());
// Trigger Migration
2023-10-01 17:39:26 +00:00
$queueForMigrations
2023-08-04 16:21:41 +00:00
-> setMigration ( $migration )
-> setProject ( $project )
2025-12-17 12:11:14 +00:00
-> setPlatform ( $platform )
2023-08-04 16:21:41 +00:00
-> setUser ( $user )
-> trigger ();
$response -> noContent ();
});
2026-02-04 05:30:22 +00:00
Http :: delete ( '/v1/migrations/:migrationId' )
2023-08-04 16:21:41 +00:00
-> groups ([ 'api' , 'migrations' ])
2024-09-03 16:32:11 +00:00
-> desc ( 'Delete migration' )
2023-08-04 16:21:41 +00:00
-> label ( 'scope' , 'migrations.write' )
-> label ( 'event' , 'migrations.[migrationId].delete' )
-> label ( 'audits.event' , 'migrationId.delete' )
-> label ( 'audits.resource' , 'migrations/{request.migrationId}' )
2025-01-17 04:31:39 +00:00
-> label ( 'sdk' , new Method (
namespace : 'migrations' ,
2025-04-12 06:41:57 +00:00
group : null ,
2025-01-17 04:31:39 +00:00
name : 'delete' ,
description : '/docs/references/migrations/delete-migration.md' ,
auth : [ AuthType :: ADMIN ],
responses : [
new SDKResponse (
code : Response :: STATUS_CODE_NOCONTENT ,
model : Response :: MODEL_NONE ,
)
],
contentType : ContentType :: NONE
))
2023-08-04 16:21:41 +00:00
-> param ( 'migrationId' , '' , new UID (), 'Migration ID.' )
-> inject ( 'response' )
-> inject ( 'dbForProject' )
2023-10-01 17:39:26 +00:00
-> inject ( 'queueForEvents' )
-> action ( function ( string $migrationId , Response $response , Database $dbForProject , Event $queueForEvents ) {
2023-08-04 16:21:41 +00:00
$migration = $dbForProject -> getDocument ( 'migrations' , $migrationId );
if ( $migration -> isEmpty ()) {
throw new Exception ( Exception :: MIGRATION_NOT_FOUND );
}
if ( ! $dbForProject -> deleteDocument ( 'migrations' , $migration -> getId ())) {
2023-08-17 14:54:19 +00:00
throw new Exception ( Exception :: GENERAL_SERVER_ERROR , 'Failed to remove migration from DB' );
2023-08-04 16:21:41 +00:00
}
2023-10-01 17:39:26 +00:00
$queueForEvents -> setParam ( 'migrationId' , $migration -> getId ());
2023-08-04 16:21:41 +00:00
$response -> noContent ();
2025-01-17 04:39:16 +00:00
});