2023-08-05 04:21:41 +12:00
< ? php
use Appwrite\Auth\OAuth2\Firebase as OAuth2Firebase ;
use Appwrite\Event\Delete ;
use Appwrite\Event\Event ;
use Appwrite\Event\Migration ;
use Appwrite\Extend\Exception ;
2023-08-10 03:18:17 +12:00
use Appwrite\Permission ;
use Appwrite\Role ;
2023-08-05 04:21:41 +12:00
use Appwrite\Utopia\Database\Validator\Queries\Migrations ;
use Appwrite\Utopia\Request ;
use Appwrite\Utopia\Response ;
use Utopia\App ;
use Utopia\Database\Database ;
use Utopia\Database\DateTime ;
use Utopia\Database\Document ;
use Utopia\Database\Helpers\ID ;
use Utopia\Database\Query ;
use Utopia\Database\Validator\UID ;
2023-08-10 05:08:10 +12:00
use Utopia\Migration\Sources\Appwrite ;
use Utopia\Migration\Sources\Firebase ;
use Utopia\Migration\Sources\NHost ;
use Utopia\Migration\Sources\Supabase ;
2023-08-05 04:21:41 +12:00
use Utopia\Validator\ArrayList ;
use Utopia\Validator\Host ;
use Utopia\Validator\Integer ;
use Utopia\Validator\Text ;
use Utopia\Validator\URL ;
use Utopia\Validator\WhiteList ;
include_once __DIR__ . '/../shared/api.php' ;
App :: post ( '/v1/migrations/appwrite' )
-> groups ([ 'api' , 'migrations' ])
-> desc ( 'Migrate Appwrite Data' )
-> label ( 'scope' , 'migrations.write' )
2023-08-17 03:01:56 +12:00
-> label ( 'event' , 'migrations.[migrationId].create' )
2023-08-05 04:21:41 +12:00
-> label ( 'audits.event' , 'migration.create' )
2023-08-30 21:19:55 +12:00
-> label ( 'sdk.auth' , [ APP_AUTH_TYPE_ADMIN ])
2023-08-05 04:21:41 +12:00
-> label ( 'sdk.namespace' , 'migrations' )
-> label ( 'sdk.method' , 'createAppwriteMigration' )
-> label ( 'sdk.description' , '/docs/references/migrations/migration-appwrite.md' )
-> label ( 'sdk.response.code' , Response :: STATUS_CODE_ACCEPTED )
-> label ( 'sdk.response.type' , Response :: CONTENT_TYPE_JSON )
-> label ( 'sdk.response.model' , Response :: MODEL_MIGRATION )
-> param ( 'resources' , [], new ArrayList ( new WhiteList ( Appwrite :: getSupportedResources ())), 'List of resources to migrate' )
-> param ( 'endpoint' , '' , new URL (), " Source's Appwrite Endpoint " )
-> param ( 'projectId' , '' , new UID (), " Source's Project ID " )
-> param ( 'apiKey' , '' , new Text ( 512 ), " Source's API Key " )
-> inject ( 'response' )
-> inject ( 'dbForProject' )
-> inject ( 'project' )
-> inject ( 'user' )
2023-10-02 06:39:26 +13:00
-> inject ( 'queueForEvents' )
-> inject ( 'queueForMigrations' )
-> action ( function ( array $resources , string $endpoint , string $projectId , string $apiKey , Response $response , Database $dbForProject , Document $project , Document $user , Event $queueForEvents , Migration $queueForMigrations ) {
2023-08-05 04:21:41 +12:00
$migration = $dbForProject -> createDocument ( 'migrations' , new Document ([
'$id' => ID :: unique (),
'status' => 'pending' ,
'stage' => 'init' ,
'source' => Appwrite :: getName (),
'credentials' => [
'endpoint' => $endpoint ,
'projectId' => $projectId ,
'apiKey' => $apiKey ,
],
'resources' => $resources ,
'statusCounters' => '{}' ,
'resourceData' => '{}' ,
'errors' => [],
]));
2023-10-02 06:39:26 +13:00
$queueForEvents -> setParam ( 'migrationId' , $migration -> getId ());
2023-08-05 04:21:41 +12:00
// Trigger Transfer
2023-10-02 06:39:26 +13:00
$queueForMigrations
2023-08-05 04:21:41 +12:00
-> setMigration ( $migration )
-> setProject ( $project )
-> setUser ( $user )
-> trigger ();
$response
-> setStatusCode ( Response :: STATUS_CODE_ACCEPTED )
-> dynamic ( $migration , Response :: MODEL_MIGRATION );
});
App :: post ( '/v1/migrations/firebase/oauth' )
-> groups ([ 'api' , 'migrations' ])
-> desc ( 'Migrate Firebase Data (OAuth)' )
-> label ( 'scope' , 'migrations.write' )
2023-08-17 03:01:56 +12:00
-> label ( 'event' , 'migrations.[migrationId].create' )
2023-08-05 04:21:41 +12:00
-> label ( 'audits.event' , 'migration.create' )
2023-08-30 21:19:55 +12:00
-> label ( 'sdk.auth' , [ APP_AUTH_TYPE_ADMIN ])
2023-08-05 04:21:41 +12:00
-> label ( 'sdk.namespace' , 'migrations' )
-> label ( 'sdk.method' , 'createFirebaseOAuthMigration' )
-> label ( 'sdk.description' , '/docs/references/migrations/migration-firebase.md' )
-> label ( 'sdk.response.code' , Response :: STATUS_CODE_ACCEPTED )
-> label ( 'sdk.response.type' , Response :: CONTENT_TYPE_JSON )
-> label ( 'sdk.response.model' , Response :: MODEL_MIGRATION )
-> param ( 'resources' , [], new ArrayList ( new WhiteList ( Firebase :: getSupportedResources ())), 'List of resources to migrate' )
-> param ( 'projectId' , '' , new Text ( 65536 ), 'Project ID of the Firebase Project' )
-> inject ( 'response' )
-> inject ( 'dbForProject' )
-> inject ( 'dbForConsole' )
-> inject ( 'project' )
-> inject ( 'user' )
2023-10-02 06:39:26 +13:00
-> inject ( 'queueForEvents' )
-> inject ( 'queueForMigrations' )
2023-08-05 04:21:41 +12:00
-> inject ( 'request' )
2023-10-02 06:39:26 +13:00
-> action ( function ( array $resources , string $projectId , Response $response , Database $dbForProject , Database $dbForConsole , Document $project , Document $user , Event $queueForEvents , Migration $queueForMigrations , Request $request ) {
2023-08-05 04:21:41 +12:00
$firebase = new OAuth2Firebase (
App :: getEnv ( '_APP_MIGRATIONS_FIREBASE_CLIENT_ID' , '' ),
App :: getEnv ( '_APP_MIGRATIONS_FIREBASE_CLIENT_SECRET' , '' ),
$request -> getProtocol () . '://' . $request -> getHostname () . '/v1/migrations/firebase/redirect'
);
2023-08-10 03:18:17 +12:00
$identity = $dbForConsole -> findOne ( 'identities' , [
Query :: equal ( 'provider' , [ 'firebase' ]),
Query :: equal ( 'userInternalId' , [ $user -> getInternalId ()]),
]);
if ( $identity === false || $identity -> isEmpty ()) {
2023-08-10 10:46:23 +12:00
throw new Exception ( Exception :: USER_IDENTITY_NOT_FOUND );
2023-08-10 03:18:17 +12:00
}
$accessToken = $identity -> getAttribute ( 'providerAccessToken' );
$refreshToken = $identity -> getAttribute ( 'providerRefreshToken' );
$accessTokenExpiry = $identity -> getAttribute ( 'providerAccessTokenExpiry' );
2023-08-05 04:21:41 +12:00
$isExpired = new \DateTime ( $accessTokenExpiry ) < new \DateTime ( 'now' );
if ( $isExpired ) {
$firebase -> refreshTokens ( $refreshToken );
$accessToken = $firebase -> getAccessToken ( '' );
$refreshToken = $firebase -> getRefreshToken ( '' );
$verificationId = $firebase -> getUserID ( $accessToken );
if ( empty ( $verificationId )) {
throw new Exception ( Exception :: GENERAL_RATE_LIMIT_EXCEEDED , 'Another request is currently refreshing OAuth token. Please try again.' );
}
2023-08-10 03:18:17 +12:00
$identity = $identity
-> setAttribute ( 'providerAccessToken' , $accessToken )
-> setAttribute ( 'providerRefreshToken' , $refreshToken )
-> setAttribute ( 'providerAccessTokenExpiry' , DateTime :: addSeconds ( new \DateTime (), ( int ) $firebase -> getAccessTokenExpiry ( '' )));
2023-08-05 04:21:41 +12:00
2023-08-10 03:18:17 +12:00
$dbForConsole -> updateDocument ( 'identities' , $identity -> getId (), $identity );
2023-08-05 04:21:41 +12:00
}
2023-08-17 03:01:56 +12:00
if ( $identity -> getAttribute ( 'secrets' )) {
$serviceAccount = $identity -> getAttribute ( 'secrets' );
2023-08-09 07:28:38 +12:00
} else {
2023-08-17 03:01:56 +12:00
$firebase -> cleanupServiceAccounts ( $accessToken , $projectId );
2023-08-09 07:28:38 +12:00
$serviceAccount = $firebase -> createServiceAccount ( $accessToken , $projectId );
2023-08-10 11:02:13 +12:00
$identity = $identity
2023-08-17 03:01:56 +12:00
-> setAttribute ( 'secrets' , json_encode ( $serviceAccount ));
2023-08-09 07:28:38 +12:00
2023-08-10 11:02:13 +12:00
$dbForConsole -> updateDocument ( 'identities' , $identity -> getId (), $identity );
2023-08-09 07:28:38 +12:00
}
$migration = $dbForProject -> createDocument ( 'migrations' , new Document ([
'$id' => ID :: unique (),
'status' => 'pending' ,
'stage' => 'init' ,
'source' => Firebase :: getName (),
'credentials' => [
2023-08-10 10:46:23 +12:00
'serviceAccount' => json_encode ( $serviceAccount ),
2023-08-09 07:28:38 +12:00
],
'resources' => $resources ,
'statusCounters' => '{}' ,
'resourceData' => '{}' ,
'errors' => []
]));
2023-10-02 06:39:26 +13:00
$queueForEvents -> setParam ( 'migrationId' , $migration -> getId ());
2023-08-09 07:28:38 +12:00
// Trigger Transfer
2023-10-02 06:39:26 +13:00
$queueForMigrations
2023-08-10 10:46:23 +12:00
-> setMigration ( $migration )
-> setProject ( $project )
-> setUser ( $user )
-> trigger ();
$response
-> setStatusCode ( Response :: STATUS_CODE_ACCEPTED )
-> dynamic ( $migration , Response :: MODEL_MIGRATION );
});
App :: post ( '/v1/migrations/firebase' )
-> groups ([ 'api' , 'migrations' ])
-> desc ( 'Migrate Firebase Data (Service Account)' )
-> label ( 'scope' , 'migrations.write' )
2023-08-17 03:01:56 +12:00
-> label ( 'event' , 'migrations.[migrationId].create' )
2023-08-10 10:46:23 +12:00
-> label ( 'audits.event' , 'migration.create' )
2023-08-30 21:19:55 +12:00
-> label ( 'sdk.auth' , [ APP_AUTH_TYPE_ADMIN ])
2023-08-10 10:46:23 +12:00
-> label ( 'sdk.namespace' , 'migrations' )
-> label ( 'sdk.method' , 'createFirebaseMigration' )
-> label ( 'sdk.description' , '/docs/references/migrations/migration-firebase.md' )
-> label ( 'sdk.response.code' , Response :: STATUS_CODE_ACCEPTED )
-> label ( 'sdk.response.type' , Response :: CONTENT_TYPE_JSON )
-> label ( 'sdk.response.model' , Response :: MODEL_MIGRATION )
-> 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' )
-> inject ( 'user' )
2023-10-02 06:39:26 +13:00
-> inject ( 'queueForEvents' )
-> inject ( 'queueForMigrations' )
-> action ( function ( array $resources , string $serviceAccount , Response $response , Database $dbForProject , Document $project , Document $user , Event $queueForEvents , Migration $queueForMigrations ) {
2023-10-07 05:02:01 +13: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-14 04:23:20 +13:00
2023-08-10 10:46:23 +12:00
$migration = $dbForProject -> createDocument ( 'migrations' , new Document ([
'$id' => ID :: unique (),
'status' => 'pending' ,
'stage' => 'init' ,
'source' => Firebase :: getName (),
'credentials' => [
'serviceAccount' => $serviceAccount ,
],
'resources' => $resources ,
'statusCounters' => '{}' ,
'resourceData' => '{}' ,
'errors' => [],
]));
2023-10-02 06:39:26 +13:00
$queueForEvents -> setParam ( 'migrationId' , $migration -> getId ());
2023-08-10 10:46:23 +12:00
// Trigger Transfer
2023-10-02 06:39:26 +13:00
$queueForMigrations
2023-08-09 07:28:38 +12:00
-> setMigration ( $migration )
-> setProject ( $project )
-> setUser ( $user )
-> trigger ();
$response
-> setStatusCode ( Response :: STATUS_CODE_ACCEPTED )
-> dynamic ( $migration , Response :: MODEL_MIGRATION );
2023-08-05 04:21:41 +12:00
});
App :: post ( '/v1/migrations/supabase' )
-> groups ([ 'api' , 'migrations' ])
-> desc ( 'Migrate Supabase Data' )
-> label ( 'scope' , 'migrations.write' )
2023-08-17 03:01:56 +12:00
-> label ( 'event' , 'migrations.[migrationId].create' )
2023-08-05 04:21:41 +12:00
-> label ( 'audits.event' , 'migration.create' )
2023-08-30 21:19:55 +12:00
-> label ( 'sdk.auth' , [ APP_AUTH_TYPE_ADMIN ])
2023-08-05 04:21:41 +12:00
-> label ( 'sdk.namespace' , 'migrations' )
-> label ( 'sdk.method' , 'createSupabaseMigration' )
-> label ( 'sdk.description' , '/docs/references/migrations/migration-supabase.md' )
-> label ( 'sdk.response.code' , Response :: STATUS_CODE_ACCEPTED )
-> label ( 'sdk.response.type' , Response :: CONTENT_TYPE_JSON )
-> label ( 'sdk.response.model' , Response :: MODEL_MIGRATION )
-> 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' )
-> inject ( 'user' )
2023-10-02 06:39:26 +13:00
-> inject ( 'queueForEvents' )
-> inject ( 'queueForMigrations' )
-> action ( function ( array $resources , string $endpoint , string $apiKey , string $databaseHost , string $username , string $password , int $port , Response $response , Database $dbForProject , Document $project , Document $user , Event $queueForEvents , Migration $queueForMigrations ) {
2023-08-05 04:21:41 +12:00
$migration = $dbForProject -> createDocument ( 'migrations' , new Document ([
'$id' => ID :: unique (),
'status' => 'pending' ,
'stage' => 'init' ,
'source' => Supabase :: getName (),
'credentials' => [
'endpoint' => $endpoint ,
'apiKey' => $apiKey ,
'databaseHost' => $databaseHost ,
'username' => $username ,
'password' => $password ,
'port' => $port ,
],
'resources' => $resources ,
'statusCounters' => '{}' ,
'resourceData' => '{}' ,
'errors' => [],
]));
2023-10-02 06:39:26 +13:00
$queueForEvents -> setParam ( 'migrationId' , $migration -> getId ());
2023-08-05 04:21:41 +12:00
// Trigger Transfer
2023-10-02 06:39:26 +13:00
$queueForMigrations
2023-08-05 04:21:41 +12:00
-> setMigration ( $migration )
-> setProject ( $project )
-> setUser ( $user )
-> trigger ();
$response
-> setStatusCode ( Response :: STATUS_CODE_ACCEPTED )
-> dynamic ( $migration , Response :: MODEL_MIGRATION );
});
App :: post ( '/v1/migrations/nhost' )
-> groups ([ 'api' , 'migrations' ])
-> desc ( 'Migrate NHost Data' )
-> label ( 'scope' , 'migrations.write' )
2023-08-17 03:01:56 +12:00
-> label ( 'event' , 'migrations.[migrationId].create' )
2023-08-05 04:21:41 +12:00
-> label ( 'audits.event' , 'migration.create' )
2023-08-30 21:19:55 +12:00
-> label ( 'sdk.auth' , [ APP_AUTH_TYPE_ADMIN ])
2023-08-05 04:21:41 +12:00
-> label ( 'sdk.namespace' , 'migrations' )
-> label ( 'sdk.method' , 'createNHostMigration' )
-> label ( 'sdk.description' , '/docs/references/migrations/migration-nhost.md' )
-> label ( 'sdk.response.code' , Response :: STATUS_CODE_ACCEPTED )
-> label ( 'sdk.response.type' , Response :: CONTENT_TYPE_JSON )
-> label ( 'sdk.response.model' , Response :: MODEL_MIGRATION )
-> param ( 'resources' , [], new ArrayList ( new WhiteList ( NHost :: getSupportedResources ())), 'List of resources to migrate' )
2023-08-17 03:01:56 +12:00
-> param ( 'subdomain' , '' , new Text ( 512 ), 'Source\'s Subdomain' )
2023-08-05 04:21:41 +12: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' )
-> inject ( 'user' )
2023-10-02 06:39:26 +13:00
-> inject ( 'queueForEvents' )
-> inject ( 'queueForMigrations' )
-> action ( function ( array $resources , string $subdomain , string $region , string $adminSecret , string $database , string $username , string $password , int $port , Response $response , Database $dbForProject , Document $project , Document $user , Event $queueForEvents , Migration $queueForMigrations ) {
2023-08-05 04:21:41 +12:00
$migration = $dbForProject -> createDocument ( 'migrations' , new Document ([
'$id' => ID :: unique (),
'status' => 'pending' ,
'stage' => 'init' ,
'source' => NHost :: getName (),
'credentials' => [
'subdomain' => $subdomain ,
'region' => $region ,
'adminSecret' => $adminSecret ,
'database' => $database ,
'username' => $username ,
'password' => $password ,
'port' => $port ,
],
'resources' => $resources ,
'statusCounters' => '{}' ,
'resourceData' => '{}' ,
'errors' => [],
]));
2023-10-02 06:39:26 +13:00
$queueForEvents -> setParam ( 'migrationId' , $migration -> getId ());
2023-08-05 04:21:41 +12:00
// Trigger Transfer
2023-10-02 06:39:26 +13:00
$queueForMigrations
2023-08-05 04:21:41 +12:00
-> setMigration ( $migration )
-> setProject ( $project )
-> setUser ( $user )
-> trigger ();
$response
-> setStatusCode ( Response :: STATUS_CODE_ACCEPTED )
-> dynamic ( $migration , Response :: MODEL_MIGRATION );
});
App :: get ( '/v1/migrations' )
-> groups ([ 'api' , 'migrations' ])
-> desc ( 'List Migrations' )
-> label ( 'scope' , 'migrations.read' )
2023-08-30 21:19:55 +12:00
-> label ( 'sdk.auth' , [ APP_AUTH_TYPE_ADMIN ])
2023-08-05 04:21:41 +12:00
-> label ( 'sdk.namespace' , 'migrations' )
-> label ( 'sdk.method' , 'list' )
-> label ( 'sdk.description' , '/docs/references/migrations/list-migrations.md' )
-> label ( 'sdk.response.code' , Response :: STATUS_CODE_OK )
-> label ( 'sdk.response.type' , Response :: CONTENT_TYPE_JSON )
-> label ( 'sdk.response.model' , Response :: MODEL_MIGRATION_LIST )
-> 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 )
-> inject ( 'response' )
-> inject ( 'dbForProject' )
-> action ( function ( array $queries , string $search , Response $response , Database $dbForProject ) {
$queries = Query :: parseQueries ( $queries );
if ( ! empty ( $search )) {
$queries [] = Query :: search ( 'search' , $search );
}
// Get cursor document if there was a cursor query
2023-08-22 15:25:55 +12:00
$cursor = \array_filter ( $queries , function ( $query ) {
return \in_array ( $query -> getMethod (), [ Query :: TYPE_CURSORAFTER , Query :: TYPE_CURSORBEFORE ]);
});
2023-08-05 04:21:41 +12:00
$cursor = reset ( $cursor );
if ( $cursor ) {
/** @var Query $cursor */
$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' ];
$response -> dynamic ( new Document ([
'migrations' => $dbForProject -> find ( 'migrations' , $queries ),
'total' => $dbForProject -> count ( 'migrations' , $filterQueries , APP_LIMIT_COUNT ),
]), Response :: MODEL_MIGRATION_LIST );
});
App :: get ( '/v1/migrations/:migrationId' )
-> groups ([ 'api' , 'migrations' ])
-> desc ( 'Get Migration' )
-> label ( 'scope' , 'migrations.read' )
2023-08-30 21:19:55 +12:00
-> label ( 'sdk.auth' , [ APP_AUTH_TYPE_ADMIN ])
2023-08-05 04:21:41 +12:00
-> label ( 'sdk.namespace' , 'migrations' )
-> label ( 'sdk.method' , 'get' )
-> label ( 'sdk.description' , '/docs/references/migrations/get-migration.md' )
-> label ( 'sdk.response.code' , Response :: STATUS_CODE_OK )
-> label ( 'sdk.response.type' , Response :: CONTENT_TYPE_JSON )
-> label ( 'sdk.response.model' , Response :: MODEL_MIGRATION )
-> 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 );
});
App :: get ( '/v1/migrations/appwrite/report' )
-> groups ([ 'api' , 'migrations' ])
-> desc ( 'Generate a report on Appwrite Data' )
-> label ( 'scope' , 'migrations.write' )
2023-08-30 21:19:55 +12:00
-> label ( 'sdk.auth' , [ APP_AUTH_TYPE_ADMIN ])
2023-08-05 04:21:41 +12:00
-> label ( 'sdk.namespace' , 'migrations' )
-> label ( 'sdk.method' , 'getAppwriteReport' )
-> label ( 'sdk.description' , '/docs/references/migrations/migration-appwrite-report.md' )
-> label ( 'sdk.response.code' , Response :: STATUS_CODE_OK )
-> label ( 'sdk.response.type' , Response :: CONTENT_TYPE_JSON )
-> label ( 'sdk.response.model' , Response :: MODEL_MIGRATION_REPORT )
-> 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 ) {
2023-10-07 05:02:01 +13:00
$appwrite = new Appwrite ( $projectID , $endpoint , $key );
2023-08-05 04:21:41 +12:00
2023-10-07 05:02:01 +13:00
try {
$report = $appwrite -> report ( $resources );
2023-08-18 02:54:19 +12:00
} catch ( \Throwable $e ) {
2023-10-07 05:02:01 +13: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-05 04:21:41 +12:00
}
2023-10-07 05:02:01 +13:00
$response
-> setStatusCode ( Response :: STATUS_CODE_OK )
-> dynamic ( new Document ( $report ), Response :: MODEL_MIGRATION_REPORT );
2023-08-05 04:21:41 +12:00
});
App :: get ( '/v1/migrations/firebase/report' )
-> groups ([ 'api' , 'migrations' ])
-> desc ( 'Generate a report on Firebase Data' )
-> label ( 'scope' , 'migrations.write' )
2023-08-30 21:19:55 +12:00
-> label ( 'sdk.auth' , [ APP_AUTH_TYPE_ADMIN ])
2023-08-05 04:21:41 +12:00
-> label ( 'sdk.namespace' , 'migrations' )
-> label ( 'sdk.method' , 'getFirebaseReport' )
-> label ( 'sdk.description' , '/docs/references/migrations/migration-firebase-report.md' )
-> label ( 'sdk.response.code' , Response :: STATUS_CODE_OK )
-> label ( 'sdk.response.type' , Response :: CONTENT_TYPE_JSON )
-> label ( 'sdk.response.model' , Response :: MODEL_MIGRATION_REPORT )
-> 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-07 05:02:01 +13: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-05 04:21:41 +12:00
try {
2023-10-07 05:02:01 +13: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-05 04:21:41 +12:00
2023-10-07 05:02:01 +13:00
throw new Exception ( Exception :: MIGRATION_PROVIDER_ERROR , 'Source Error: ' . $e -> getMessage ());
2023-08-05 04:21:41 +12:00
}
2023-10-07 05:02:01 +13:00
$response
-> setStatusCode ( Response :: STATUS_CODE_OK )
-> dynamic ( new Document ( $report ), Response :: MODEL_MIGRATION_REPORT );
2023-08-05 04:21:41 +12:00
});
App :: get ( '/v1/migrations/firebase/report/oauth' )
-> groups ([ 'api' , 'migrations' ])
-> desc ( 'Generate a report on Firebase Data using OAuth' )
-> label ( 'scope' , 'migrations.write' )
2023-08-30 21:19:55 +12:00
-> label ( 'sdk.auth' , [ APP_AUTH_TYPE_ADMIN ])
2023-08-05 04:21:41 +12:00
-> label ( 'sdk.namespace' , 'migrations' )
2023-08-07 22:56:42 +12:00
-> label ( 'sdk.method' , 'getFirebaseReportOAuth' )
2023-08-05 04:21:41 +12:00
-> label ( 'sdk.description' , '/docs/references/migrations/migration-firebase-report.md' )
-> label ( 'sdk.response.code' , Response :: STATUS_CODE_OK )
-> label ( 'sdk.response.type' , Response :: CONTENT_TYPE_JSON )
-> label ( 'sdk.response.model' , Response :: MODEL_MIGRATION_REPORT )
-> param ( 'resources' , [], new ArrayList ( new WhiteList ( Firebase :: getSupportedResources ())), 'List of resources to migrate' )
-> param ( 'projectId' , '' , new Text ( 65536 ), 'Project ID' )
-> inject ( 'response' )
-> inject ( 'request' )
-> inject ( 'user' )
-> inject ( 'dbForConsole' )
-> action ( function ( array $resources , string $projectId , Response $response , Request $request , Document $user , Database $dbForConsole ) {
2023-08-17 03:01:56 +12:00
$firebase = new OAuth2Firebase (
App :: getEnv ( '_APP_MIGRATIONS_FIREBASE_CLIENT_ID' , '' ),
App :: getEnv ( '_APP_MIGRATIONS_FIREBASE_CLIENT_SECRET' , '' ),
$request -> getProtocol () . '://' . $request -> getHostname () . '/v1/migrations/firebase/redirect'
);
2023-08-10 03:18:17 +12:00
2023-08-17 03:01:56 +12:00
$identity = $dbForConsole -> findOne ( 'identities' , [
Query :: equal ( 'provider' , [ 'firebase' ]),
Query :: equal ( 'userInternalId' , [ $user -> getInternalId ()]),
]);
2023-08-05 04:21:41 +12:00
2023-08-17 03:01:56 +12:00
if ( $identity === false || $identity -> isEmpty ()) {
throw new Exception ( Exception :: USER_IDENTITY_NOT_FOUND );
}
2023-08-05 04:21:41 +12:00
2023-08-17 03:01:56 +12:00
$accessToken = $identity -> getAttribute ( 'providerAccessToken' );
$refreshToken = $identity -> getAttribute ( 'providerRefreshToken' );
$accessTokenExpiry = $identity -> getAttribute ( 'providerAccessTokenExpiry' );
2023-08-05 04:21:41 +12:00
2023-08-17 03:01:56 +12:00
if ( empty ( $accessToken ) || empty ( $refreshToken ) || empty ( $accessTokenExpiry )) {
throw new Exception ( Exception :: USER_IDENTITY_NOT_FOUND );
}
2023-08-05 04:21:41 +12:00
2023-08-17 03:01:56 +12:00
if ( App :: getEnv ( '_APP_MIGRATIONS_FIREBASE_CLIENT_ID' , '' ) === '' || App :: getEnv ( '_APP_MIGRATIONS_FIREBASE_CLIENT_SECRET' , '' ) === '' ) {
throw new Exception ( Exception :: USER_IDENTITY_NOT_FOUND );
}
2023-08-05 04:21:41 +12:00
2023-08-17 03:01:56 +12:00
$isExpired = new \DateTime ( $accessTokenExpiry ) < new \DateTime ( 'now' );
if ( $isExpired ) {
$firebase -> refreshTokens ( $refreshToken );
2023-08-05 04:21:41 +12:00
2023-08-17 03:01:56 +12:00
$accessToken = $firebase -> getAccessToken ( '' );
$refreshToken = $firebase -> getRefreshToken ( '' );
2023-08-05 04:21:41 +12:00
2023-08-17 03:01:56 +12:00
$verificationId = $firebase -> getUserID ( $accessToken );
2023-08-05 04:21:41 +12:00
2023-08-17 03:01:56 +12:00
if ( empty ( $verificationId )) {
throw new Exception ( Exception :: GENERAL_RATE_LIMIT_EXCEEDED , 'Another request is currently refreshing OAuth token. Please try again.' );
2023-08-09 07:28:38 +12:00
}
2023-08-17 03:01:56 +12:00
$identity = $identity
-> setAttribute ( 'providerAccessToken' , $accessToken )
-> setAttribute ( 'providerRefreshToken' , $refreshToken )
-> setAttribute ( 'providerAccessTokenExpiry' , DateTime :: addSeconds ( new \DateTime (), ( int ) $firebase -> getAccessTokenExpiry ( '' )));
2023-08-09 07:28:38 +12:00
2023-08-17 03:01:56 +12:00
$dbForConsole -> updateDocument ( 'identities' , $identity -> getId (), $identity );
}
2023-08-05 04:21:41 +12:00
2023-08-17 03:01:56 +12:00
// Get Service Account
if ( $identity -> getAttribute ( 'secrets' )) {
$serviceAccount = $identity -> getAttribute ( 'secrets' );
} else {
$firebase -> cleanupServiceAccounts ( $accessToken , $projectId );
$serviceAccount = $firebase -> createServiceAccount ( $accessToken , $projectId );
$identity = $identity
-> setAttribute ( 'secrets' , json_encode ( $serviceAccount ));
$dbForConsole -> updateDocument ( 'identities' , $identity -> getId (), $identity );
2023-08-05 04:21:41 +12:00
}
2023-08-17 03:01:56 +12:00
2023-08-17 05:17:20 +12:00
$firebase = new Firebase ( $serviceAccount );
2023-08-17 03:01:56 +12:00
2023-08-18 02:54:19 +12:00
try {
$report = $firebase -> report ( $resources );
} catch ( \Exception $e ) {
2023-08-18 02:55:28 +12:00
throw new Exception ( Exception :: GENERAL_SERVER_ERROR , 'Source Error: ' . $e -> getMessage ());
2023-08-18 02:54:19 +12:00
}
2023-08-17 03:01:56 +12:00
$response
-> setStatusCode ( Response :: STATUS_CODE_OK )
-> dynamic ( new Document ( $report ), Response :: MODEL_MIGRATION_REPORT );
2023-08-05 04:21:41 +12:00
});
App :: get ( '/v1/migrations/firebase/connect' )
-> desc ( 'Authorize with firebase' )
-> groups ([ 'api' , 'migrations' ])
2023-08-31 14:48:51 +12:00
-> label ( 'scope' , 'migrations.write' )
2023-08-30 21:19:55 +12:00
-> label ( 'sdk.auth' , [ APP_AUTH_TYPE_ADMIN ])
2023-08-05 04:21:41 +12:00
-> label ( 'sdk.namespace' , 'migrations' )
-> label ( 'sdk.method' , 'createFirebaseAuth' )
-> label ( 'sdk.description' , '' )
-> label ( 'sdk.response.code' , Response :: STATUS_CODE_MOVED_PERMANENTLY )
-> label ( 'sdk.response.type' , Response :: CONTENT_TYPE_HTML )
-> label ( 'sdk.methodType' , 'webAuth' )
2023-08-07 22:56:42 +12:00
-> label ( 'sdk.hide' , true )
2023-08-05 04:21:41 +12:00
-> param ( 'redirect' , '' , fn ( $clients ) => new Host ( $clients ), 'URL to redirect back to your Firebase authorization. Only console hostnames are allowed.' , true , [ 'clients' ])
2023-08-09 01:36:44 +12:00
-> param ( 'projectId' , '' , new UID (), 'Project ID' )
2023-08-05 04:21:41 +12:00
-> inject ( 'response' )
-> inject ( 'request' )
-> inject ( 'user' )
-> inject ( 'dbForConsole' )
2023-08-09 01:36:44 +12:00
-> action ( function ( string $redirect , string $projectId , Response $response , Request $request , Document $user , Database $dbForConsole ) {
2023-08-05 04:21:41 +12:00
$state = \json_encode ([
2023-08-09 01:36:44 +12:00
'projectId' => $projectId ,
2023-08-05 04:21:41 +12:00
'redirect' => $redirect ,
]);
$prefs = $user -> getAttribute ( 'prefs' , []);
$prefs [ 'migrationState' ] = $state ;
$user -> setAttribute ( 'prefs' , $prefs );
$dbForConsole -> updateDocument ( 'users' , $user -> getId (), $user );
$oauth2 = new OAuth2Firebase (
App :: getEnv ( '_APP_MIGRATIONS_FIREBASE_CLIENT_ID' , '' ),
App :: getEnv ( '_APP_MIGRATIONS_FIREBASE_CLIENT_SECRET' , '' ),
$request -> getProtocol () . '://' . $request -> getHostname () . '/v1/migrations/firebase/redirect'
);
$url = $oauth2 -> getLoginURL ();
$response
-> addHeader ( 'Cache-Control' , 'no-store, no-cache, must-revalidate, max-age=0' )
-> addHeader ( 'Pragma' , 'no-cache' )
-> redirect ( $url );
});
App :: get ( '/v1/migrations/firebase/redirect' )
-> desc ( 'Capture and receive data on Firebase authorization' )
-> groups ([ 'api' , 'migrations' ])
2023-08-31 06:44:33 +12:00
-> label ( 'scope' , 'public' )
2023-08-05 04:21:41 +12:00
-> label ( 'error' , __DIR__ . '/../../views/general/error.phtml' )
2023-08-23 08:11:33 +12:00
-> param ( 'code' , '' , new Text ( 2048 ), 'OAuth2 code. This is a temporary code that the will be later exchanged for an access token.' , true )
2023-08-05 04:21:41 +12:00
-> inject ( 'user' )
-> inject ( 'project' )
-> inject ( 'request' )
-> inject ( 'response' )
-> inject ( 'dbForConsole' )
-> action ( function ( string $code , Document $user , Document $project , Request $request , Response $response , Database $dbForConsole ) {
$state = $user [ 'prefs' ][ 'migrationState' ] ? ? '{}' ;
$prefs [ 'migrationState' ] = '' ;
$user -> setAttribute ( 'prefs' , $prefs );
$dbForConsole -> updateDocument ( 'users' , $user -> getId (), $user );
if ( empty ( $state )) {
throw new Exception ( Exception :: GENERAL_ARGUMENT_INVALID , 'Installation requests from organisation members for the Appwrite Google App are currently unsupported.' );
}
$state = \json_decode ( $state , true );
$redirect = $state [ 'redirect' ] ? ? '' ;
$projectId = $state [ 'projectId' ] ? ? '' ;
$project = $dbForConsole -> getDocument ( 'projects' , $projectId );
if ( empty ( $redirect )) {
$redirect = $request -> getProtocol () . '://' . $request -> getHostname () . '/console/project-$projectId/settings/migrations' ;
}
if ( $project -> isEmpty ()) {
$response
-> addHeader ( 'Cache-Control' , 'no-store, no-cache, must-revalidate, max-age=0' )
-> addHeader ( 'Pragma' , 'no-cache' )
-> redirect ( $redirect );
return ;
}
// OAuth Authroization
if ( ! empty ( $code )) {
$oauth2 = new OAuth2Firebase (
App :: getEnv ( '_APP_MIGRATIONS_FIREBASE_CLIENT_ID' , '' ),
App :: getEnv ( '_APP_MIGRATIONS_FIREBASE_CLIENT_SECRET' , '' ),
$request -> getProtocol () . '://' . $request -> getHostname () . '/v1/migrations/firebase/redirect'
);
$accessToken = $oauth2 -> getAccessToken ( $code );
$refreshToken = $oauth2 -> getRefreshToken ( $code );
$accessTokenExpiry = $oauth2 -> getAccessTokenExpiry ( $code );
2023-08-10 03:18:17 +12:00
$email = $oauth2 -> getUserEmail ( $accessToken );
$oauth2ID = $oauth2 -> getUserID ( $accessToken );
2023-08-05 04:21:41 +12:00
if ( empty ( $accessToken )) {
throw new Exception ( Exception :: GENERAL_SERVER_ERROR , 'Failed to get access token.' );
}
if ( empty ( $refreshToken )) {
throw new Exception ( Exception :: GENERAL_SERVER_ERROR , 'Failed to get refresh token.' );
}
if ( empty ( $accessTokenExpiry )) {
throw new Exception ( Exception :: GENERAL_SERVER_ERROR , 'Failed to get access token expiry.' );
}
2023-08-10 03:18:17 +12:00
// Makes sure this email is not already used in another identity
$identity = $dbForConsole -> findOne ( 'identities' , [
Query :: equal ( 'providerEmail' , [ $email ]),
]);
2023-08-05 04:21:41 +12:00
2023-08-10 03:18:17 +12:00
if ( $identity !== false && ! $identity -> isEmpty ()) {
if ( $identity -> getAttribute ( 'userInternalId' , '' ) !== $user -> getInternalId ()) {
throw new Exception ( Exception :: USER_EMAIL_ALREADY_EXISTS );
}
}
if ( $identity !== false && ! $identity -> isEmpty ()) {
$identity = $identity
-> setAttribute ( 'providerAccessToken' , $accessToken )
-> setAttribute ( 'providerRefreshToken' , $refreshToken )
-> setAttribute ( 'providerAccessTokenExpiry' , DateTime :: addSeconds ( new \DateTime (), ( int ) $accessTokenExpiry ));
$dbForConsole -> updateDocument ( 'identities' , $identity -> getId (), $identity );
} else {
$identity = $dbForConsole -> createDocument ( 'identities' , new Document ([
'$id' => ID :: unique (),
'$permissions' => [
Permission :: read ( Role :: any ()),
Permission :: update ( Role :: user ( $user -> getId ())),
Permission :: delete ( Role :: user ( $user -> getId ())),
],
'userInternalId' => $user -> getInternalId (),
'userId' => $user -> getId (),
'provider' => 'firebase' ,
'providerUid' => $oauth2ID ,
'providerEmail' => $email ,
'providerAccessToken' => $accessToken ,
'providerRefreshToken' => $refreshToken ,
'providerAccessTokenExpiry' => DateTime :: addSeconds ( new \DateTime (), ( int ) $accessTokenExpiry ),
]));
}
2023-08-05 04:21:41 +12:00
} else {
throw new Exception ( Exception :: GENERAL_SERVER_ERROR , 'Missing OAuth2 code.' );
}
$response
-> addHeader ( 'Cache-Control' , 'no-store, no-cache, must-revalidate, max-age=0' )
-> addHeader ( 'Pragma' , 'no-cache' )
-> redirect ( $redirect );
});
App :: get ( '/v1/migrations/firebase/projects' )
-> desc ( 'List Firebase Projects' )
-> groups ([ 'api' , 'migrations' ])
2023-08-31 06:44:33 +12:00
-> label ( 'scope' , 'migrations.read' )
2023-08-30 21:19:55 +12:00
-> label ( 'sdk.auth' , [ APP_AUTH_TYPE_ADMIN ])
2023-08-05 04:21:41 +12:00
-> label ( 'sdk.namespace' , 'migrations' )
-> label ( 'sdk.method' , 'listFirebaseProjects' )
-> label ( 'sdk.description' , '' )
-> label ( 'sdk.response.code' , Response :: STATUS_CODE_OK )
-> label ( 'sdk.response.type' , Response :: CONTENT_TYPE_JSON )
-> label ( 'sdk.response.model' , Response :: MODEL_MIGRATION_FIREBASE_PROJECT_LIST )
-> inject ( 'user' )
-> inject ( 'response' )
-> inject ( 'project' )
-> inject ( 'dbForConsole' )
-> inject ( 'request' )
-> action ( function ( Document $user , Response $response , Document $project , Database $dbForConsole , Request $request ) {
$firebase = new OAuth2Firebase (
App :: getEnv ( '_APP_MIGRATIONS_FIREBASE_CLIENT_ID' , '' ),
App :: getEnv ( '_APP_MIGRATIONS_FIREBASE_CLIENT_SECRET' , '' ),
$request -> getProtocol () . '://' . $request -> getHostname () . '/v1/migrations/firebase/redirect'
);
2023-08-10 03:18:17 +12:00
$identity = $dbForConsole -> findOne ( 'identities' , [
Query :: equal ( 'provider' , [ 'firebase' ]),
Query :: equal ( 'userInternalId' , [ $user -> getInternalId ()]),
]);
2023-08-17 03:01:56 +12:00
2023-08-10 03:18:17 +12:00
if ( $identity === false || $identity -> isEmpty ()) {
2023-08-10 10:46:23 +12:00
throw new Exception ( Exception :: USER_IDENTITY_NOT_FOUND );
2023-08-10 03:18:17 +12:00
}
$accessToken = $identity -> getAttribute ( 'providerAccessToken' );
$refreshToken = $identity -> getAttribute ( 'providerRefreshToken' );
$accessTokenExpiry = $identity -> getAttribute ( 'providerAccessTokenExpiry' );
if ( empty ( $accessToken ) || empty ( $refreshToken ) || empty ( $accessTokenExpiry )) {
2023-08-17 03:01:56 +12:00
throw new Exception ( Exception :: USER_IDENTITY_NOT_FOUND );
2023-08-10 03:18:17 +12:00
}
if ( App :: getEnv ( '_APP_MIGRATIONS_FIREBASE_CLIENT_ID' , '' ) === '' || App :: getEnv ( '_APP_MIGRATIONS_FIREBASE_CLIENT_SECRET' , '' ) === '' ) {
2023-08-17 03:01:56 +12:00
throw new Exception ( Exception :: USER_IDENTITY_NOT_FOUND );
2023-08-10 03:18:17 +12:00
}
2023-08-05 04:21:41 +12:00
2023-08-17 03:01:56 +12:00
try {
$isExpired = new \DateTime ( $accessTokenExpiry ) < new \DateTime ( 'now' );
if ( $isExpired ) {
try {
$firebase -> refreshTokens ( $refreshToken );
} catch ( \Exception $e ) {
throw new Exception ( Exception :: USER_IDENTITY_NOT_FOUND );
}
2023-08-05 04:21:41 +12:00
2023-08-17 03:01:56 +12:00
$accessToken = $firebase -> getAccessToken ( '' );
$refreshToken = $firebase -> getRefreshToken ( '' );
2023-08-05 04:21:41 +12:00
2023-08-17 03:01:56 +12:00
$verificationId = $firebase -> getUserID ( $accessToken );
2023-08-05 04:21:41 +12:00
2023-08-17 03:01:56 +12:00
if ( empty ( $verificationId )) {
throw new Exception ( Exception :: GENERAL_RATE_LIMIT_EXCEEDED , 'Another request is currently refreshing OAuth token. Please try again.' );
}
2023-08-05 04:21:41 +12:00
2023-08-17 03:01:56 +12:00
$identity = $identity
-> setAttribute ( 'providerAccessToken' , $accessToken )
-> setAttribute ( 'providerRefreshToken' , $refreshToken )
-> setAttribute ( 'providerAccessTokenExpiry' , DateTime :: addSeconds ( new \DateTime (), ( int ) $firebase -> getAccessTokenExpiry ( '' )));
2023-08-05 04:21:41 +12:00
2023-08-17 03:01:56 +12:00
$dbForConsole -> updateDocument ( 'identities' , $identity -> getId (), $identity );
}
2023-08-05 04:21:41 +12:00
2023-08-17 03:01:56 +12:00
$projects = $firebase -> getProjects ( $accessToken );
2023-08-05 04:21:41 +12:00
2023-08-17 03:01:56 +12:00
$output = [];
foreach ( $projects as $project ) {
$output [] = [
'displayName' => $project [ 'displayName' ],
'projectId' => $project [ 'projectId' ],
];
}
} catch ( \Exception $e ) {
throw new Exception ( Exception :: USER_IDENTITY_NOT_FOUND );
2023-08-05 04:21:41 +12:00
}
$response -> dynamic ( new Document ([
'projects' => $output ,
'total' => count ( $output ),
]), Response :: MODEL_MIGRATION_FIREBASE_PROJECT_LIST );
});
App :: get ( '/v1/migrations/firebase/deauthorize' )
-> desc ( 'Revoke Appwrite\'s authorization to access Firebase Projects' )
-> groups ([ 'api' , 'migrations' ])
2023-08-31 06:44:33 +12:00
-> label ( 'scope' , 'migrations.write' )
2023-08-30 21:19:55 +12:00
-> label ( 'sdk.auth' , [ APP_AUTH_TYPE_ADMIN ])
2023-08-05 04:21:41 +12:00
-> label ( 'sdk.namespace' , 'migrations' )
-> label ( 'sdk.method' , 'deleteFirebaseAuth' )
-> label ( 'sdk.description' , '' )
-> label ( 'sdk.response.code' , Response :: STATUS_CODE_OK )
-> label ( 'sdk.response.type' , Response :: CONTENT_TYPE_JSON )
-> inject ( 'user' )
-> inject ( 'response' )
-> inject ( 'dbForConsole' )
-> action ( function ( Document $user , Response $response , Database $dbForConsole ) {
2023-08-10 03:18:17 +12:00
$identity = $dbForConsole -> findOne ( 'identities' , [
Query :: equal ( 'provider' , [ 'firebase' ]),
Query :: equal ( 'userInternalId' , [ $user -> getInternalId ()]),
]);
2023-08-05 04:21:41 +12:00
2023-08-10 03:18:17 +12:00
if ( $identity === false || $identity -> isEmpty ()) {
throw new Exception ( Exception :: GENERAL_ACCESS_FORBIDDEN , 'Not authenticated with Firebase' ); //TODO: Replace with USER_IDENTITY_NOT_FOUND
}
$dbForConsole -> deleteDocument ( 'identities' , $identity -> getId ());
2023-08-05 04:21:41 +12:00
$response -> noContent ();
});
App :: get ( '/v1/migrations/supabase/report' )
-> groups ([ 'api' , 'migrations' ])
-> desc ( 'Generate a report on Supabase Data' )
-> label ( 'scope' , 'migrations.write' )
2023-08-30 21:19:55 +12:00
-> label ( 'sdk.auth' , [ APP_AUTH_TYPE_ADMIN ])
2023-08-05 04:21:41 +12:00
-> label ( 'sdk.namespace' , 'migrations' )
-> label ( 'sdk.method' , 'getSupabaseReport' )
-> label ( 'sdk.description' , '/docs/references/migrations/migration-supabase-report.md' )
-> label ( 'sdk.response.code' , Response :: STATUS_CODE_OK )
-> label ( 'sdk.response.type' , Response :: CONTENT_TYPE_JSON )
-> label ( 'sdk.response.model' , Response :: MODEL_MIGRATION_REPORT )
-> param ( 'resources' , [], new ArrayList ( new WhiteList ( Supabase :: getSupportedResources (), true )), 'List of resources to migrate' )
2023-08-23 08:11:33 +12: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-05 04:21:41 +12: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-07 05:02:01 +13:00
$supabase = new Supabase ( $endpoint , $apiKey , $databaseHost , 'postgres' , $username , $password , $port );
2023-08-05 04:21:41 +12:00
try {
2023-10-07 05:02:01 +13: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-05 04:21:41 +12:00
2023-10-07 05:02:01 +13:00
throw new Exception ( Exception :: MIGRATION_PROVIDER_ERROR , 'Source Error: ' . $e -> getMessage ());
2023-08-05 04:21:41 +12:00
}
2023-10-07 05:02:01 +13:00
$response
-> setStatusCode ( Response :: STATUS_CODE_OK )
-> dynamic ( new Document ( $report ), Response :: MODEL_MIGRATION_REPORT );
2023-08-05 04:21:41 +12:00
});
App :: get ( '/v1/migrations/nhost/report' )
-> groups ([ 'api' , 'migrations' ])
-> desc ( 'Generate a report on NHost Data' )
-> label ( 'scope' , 'migrations.write' )
2023-08-30 21:19:55 +12:00
-> label ( 'sdk.auth' , [ APP_AUTH_TYPE_ADMIN ])
2023-08-05 04:21:41 +12:00
-> label ( 'sdk.namespace' , 'migrations' )
-> label ( 'sdk.method' , 'getNHostReport' )
-> label ( 'sdk.description' , '/docs/references/migrations/migration-nhost-report.md' )
-> label ( 'sdk.response.code' , Response :: STATUS_CODE_OK )
-> label ( 'sdk.response.type' , Response :: CONTENT_TYPE_JSON )
-> label ( 'sdk.response.model' , Response :: MODEL_MIGRATION_REPORT )
2023-08-23 08:11:33 +12: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-05 04:21:41 +12: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-07 05:02:01 +13:00
$nhost = new NHost ( $subdomain , $region , $adminSecret , $database , $username , $password , $port );
2023-08-05 04:21:41 +12:00
try {
2023-10-07 05:02:01 +13: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-05 04:21:41 +12:00
2023-10-07 05:02:01 +13:00
throw new Exception ( Exception :: MIGRATION_PROVIDER_ERROR , 'Source Error: ' . $e -> getMessage ());
2023-08-05 04:21:41 +12:00
}
2023-10-07 05:02:01 +13:00
$response
-> setStatusCode ( Response :: STATUS_CODE_OK )
-> dynamic ( new Document ( $report ), Response :: MODEL_MIGRATION_REPORT );
2023-08-05 04:21:41 +12:00
});
App :: patch ( '/v1/migrations/:migrationId' )
-> groups ([ 'api' , 'migrations' ])
-> desc ( 'Retry Migration' )
-> label ( 'scope' , 'migrations.write' )
-> label ( 'event' , 'migrations.[migrationId].retry' )
-> label ( 'audits.event' , 'migration.retry' )
-> label ( 'audits.resource' , 'migrations/{request.migrationId}' )
2023-08-30 21:19:55 +12:00
-> label ( 'sdk.auth' , [ APP_AUTH_TYPE_ADMIN ])
2023-08-05 04:21:41 +12:00
-> label ( 'sdk.namespace' , 'migrations' )
-> label ( 'sdk.method' , 'retry' )
-> label ( 'sdk.description' , '/docs/references/migrations/retry-migration.md' )
-> label ( 'sdk.response.code' , Response :: STATUS_CODE_ACCEPTED )
-> label ( 'sdk.response.type' , Response :: CONTENT_TYPE_JSON )
-> label ( 'sdk.response.model' , Response :: MODEL_MIGRATION )
-> param ( 'migrationId' , '' , new UID (), 'Migration unique ID.' )
-> inject ( 'response' )
-> inject ( 'dbForProject' )
-> inject ( 'project' )
-> inject ( 'user' )
2023-10-02 06:39:26 +13:00
-> inject ( 'queueForMigrations' )
-> action ( function ( string $migrationId , Response $response , Database $dbForProject , Document $project , Document $user , Migration $queueForMigrations ) {
2023-08-05 04:21:41 +12: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-02 06:39:26 +13:00
$queueForMigrations
2023-08-05 04:21:41 +12:00
-> setMigration ( $migration )
-> setProject ( $project )
-> setUser ( $user )
-> trigger ();
$response -> noContent ();
});
App :: delete ( '/v1/migrations/:migrationId' )
-> groups ([ 'api' , 'migrations' ])
-> desc ( 'Delete Migration' )
-> label ( 'scope' , 'migrations.write' )
-> label ( 'event' , 'migrations.[migrationId].delete' )
-> label ( 'audits.event' , 'migrationId.delete' )
-> label ( 'audits.resource' , 'migrations/{request.migrationId}' )
2023-08-30 21:19:55 +12:00
-> label ( 'sdk.auth' , [ APP_AUTH_TYPE_ADMIN ])
2023-08-05 04:21:41 +12:00
-> label ( 'sdk.namespace' , 'migrations' )
-> label ( 'sdk.method' , 'delete' )
-> label ( 'sdk.description' , '/docs/references/functions/delete-migration.md' )
-> label ( 'sdk.response.code' , Response :: STATUS_CODE_NOCONTENT )
-> label ( 'sdk.response.model' , Response :: MODEL_NONE )
-> param ( 'migrationId' , '' , new UID (), 'Migration ID.' )
-> inject ( 'response' )
-> inject ( 'dbForProject' )
2023-10-02 06:39:26 +13:00
-> inject ( 'queueForEvents' )
-> action ( function ( string $migrationId , Response $response , Database $dbForProject , Event $queueForEvents ) {
2023-08-05 04:21:41 +12:00
$migration = $dbForProject -> getDocument ( 'migrations' , $migrationId );
if ( $migration -> isEmpty ()) {
throw new Exception ( Exception :: MIGRATION_NOT_FOUND );
}
if ( ! $dbForProject -> deleteDocument ( 'migrations' , $migration -> getId ())) {
2023-08-18 02:54:19 +12:00
throw new Exception ( Exception :: GENERAL_SERVER_ERROR , 'Failed to remove migration from DB' );
2023-08-05 04:21:41 +12:00
}
2023-10-02 06:39:26 +13:00
$queueForEvents -> setParam ( 'migrationId' , $migration -> getId ());
2023-08-05 04:21:41 +12:00
$response -> noContent ();
});