Move to new branch
This commit is contained in:
parent
9f7f99ba83
commit
8f5d79e668
21 changed files with 2168 additions and 33 deletions
|
@ -177,7 +177,8 @@ RUN chmod +x /usr/local/bin/doctor && \
|
||||||
chmod +x /usr/local/bin/worker-builds && \
|
chmod +x /usr/local/bin/worker-builds && \
|
||||||
chmod +x /usr/local/bin/worker-mails && \
|
chmod +x /usr/local/bin/worker-mails && \
|
||||||
chmod +x /usr/local/bin/worker-messaging && \
|
chmod +x /usr/local/bin/worker-messaging && \
|
||||||
chmod +x /usr/local/bin/worker-webhooks
|
chmod +x /usr/local/bin/worker-webhooks && \
|
||||||
|
chmod +x /usr/local/bin/worker-migrations
|
||||||
|
|
||||||
# Letsencrypt Permissions
|
# Letsencrypt Permissions
|
||||||
RUN mkdir -p /etc/letsencrypt/live/ && chmod -Rf 755 /etc/letsencrypt/live/
|
RUN mkdir -p /etc/letsencrypt/live/ && chmod -Rf 755 /etc/letsencrypt/live/
|
||||||
|
|
|
@ -3664,6 +3664,143 @@ $collections = [
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
|
||||||
|
'migrations' => [
|
||||||
|
'$collection' => ID::custom(Database::METADATA),
|
||||||
|
'$id' => ID::custom('migrations'),
|
||||||
|
'name' => 'Migrations',
|
||||||
|
'attributes' => [
|
||||||
|
[
|
||||||
|
'$id' => ID::custom('status'),
|
||||||
|
'type' => Database::VAR_STRING,
|
||||||
|
'format' => '',
|
||||||
|
'size' => Database::LENGTH_KEY,
|
||||||
|
'signed' => true,
|
||||||
|
'required' => true,
|
||||||
|
'default' => null,
|
||||||
|
'array' => false,
|
||||||
|
'filters' => [],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'$id' => ID::custom('stage'),
|
||||||
|
'type' => Database::VAR_STRING,
|
||||||
|
'format' => '',
|
||||||
|
'size' => Database::LENGTH_KEY,
|
||||||
|
'signed' => true,
|
||||||
|
'required' => true,
|
||||||
|
'default' => null,
|
||||||
|
'array' => false,
|
||||||
|
'filters' => [],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'$id' => ID::custom('source'),
|
||||||
|
'type' => Database::VAR_STRING,
|
||||||
|
'format' => '',
|
||||||
|
'size' => 8192,
|
||||||
|
'signed' => true,
|
||||||
|
'required' => true,
|
||||||
|
'default' => null,
|
||||||
|
'array' => false,
|
||||||
|
'filters' => [],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'$id' => ID::custom('credentials'),
|
||||||
|
'type' => Database::VAR_STRING,
|
||||||
|
'format' => '',
|
||||||
|
'size' => 65536,
|
||||||
|
'signed' => true,
|
||||||
|
'required' => false,
|
||||||
|
'default' => [],
|
||||||
|
'array' => false,
|
||||||
|
'filters' => ['json'],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'$id' => ID::custom('resources'),
|
||||||
|
'type' => Database::VAR_STRING,
|
||||||
|
'format' => '',
|
||||||
|
'size' => Database::LENGTH_KEY,
|
||||||
|
'signed' => true,
|
||||||
|
'required' => true,
|
||||||
|
'default' => [],
|
||||||
|
'array' => true,
|
||||||
|
'filters' => [],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'$id' => ID::custom('statusCounters'),
|
||||||
|
'type' => Database::VAR_STRING,
|
||||||
|
'format' => '',
|
||||||
|
'size' => 3000,
|
||||||
|
'signed' => true,
|
||||||
|
'required' => true,
|
||||||
|
'default' => null,
|
||||||
|
'array' => false,
|
||||||
|
'filters' => ['json'],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'$id' => ID::custom('resourceData'),
|
||||||
|
'type' => Database::VAR_STRING,
|
||||||
|
'format' => '',
|
||||||
|
'size' => 131070,
|
||||||
|
'signed' => true,
|
||||||
|
'required' => true,
|
||||||
|
'default' => null,
|
||||||
|
'array' => false,
|
||||||
|
'filters' => ['json'],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'$id' => ID::custom('errors'),
|
||||||
|
'type' => Database::VAR_STRING,
|
||||||
|
'format' => '',
|
||||||
|
'size' => 65535,
|
||||||
|
'signed' => true,
|
||||||
|
'required' => true,
|
||||||
|
'default' => null,
|
||||||
|
'array' => true,
|
||||||
|
'filters' => [],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'$id' => ID::custom('search'),
|
||||||
|
'type' => Database::VAR_STRING,
|
||||||
|
'format' => '',
|
||||||
|
'size' => 16384,
|
||||||
|
'signed' => true,
|
||||||
|
'required' => false,
|
||||||
|
'default' => null,
|
||||||
|
'array' => false,
|
||||||
|
'filters' => [],
|
||||||
|
]
|
||||||
|
],
|
||||||
|
'indexes' => [
|
||||||
|
[
|
||||||
|
'$id' => '_key_status',
|
||||||
|
'type' => Database::INDEX_KEY,
|
||||||
|
'attributes' => ['status'],
|
||||||
|
'lengths' => [Database::LENGTH_KEY],
|
||||||
|
'orders' => [Database::ORDER_ASC],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'$id' => '_key_stage',
|
||||||
|
'type' => Database::INDEX_KEY,
|
||||||
|
'attributes' => ['stage'],
|
||||||
|
'lengths' => [Database::LENGTH_KEY],
|
||||||
|
'orders' => [Database::ORDER_ASC],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'$id' => '_key_source',
|
||||||
|
'type' => Database::INDEX_KEY,
|
||||||
|
'attributes' => ['source'],
|
||||||
|
'lengths' => [Database::LENGTH_KEY],
|
||||||
|
'orders' => [Database::ORDER_ASC],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'$id' => ID::custom('_fulltext_search'),
|
||||||
|
'type' => Database::INDEX_FULLTEXT,
|
||||||
|
'attributes' => ['search'],
|
||||||
|
'lengths' => [],
|
||||||
|
'orders' => [],
|
||||||
|
]
|
||||||
|
],
|
||||||
|
],
|
||||||
];
|
];
|
||||||
|
|
||||||
return $collections;
|
return $collections;
|
||||||
|
|
|
@ -629,4 +629,21 @@ return [
|
||||||
'description' => 'Too many queries.',
|
'description' => 'Too many queries.',
|
||||||
'code' => 400,
|
'code' => 400,
|
||||||
],
|
],
|
||||||
|
|
||||||
|
/** Migrations */
|
||||||
|
Exception::MIGRATION_NOT_FOUND => [
|
||||||
|
'name' => Exception::MIGRATION_NOT_FOUND,
|
||||||
|
'description' => 'Migration with the requested ID could not be found.',
|
||||||
|
'code' => 404,
|
||||||
|
],
|
||||||
|
Exception::MIGRATION_ALREADY_EXISTS => [
|
||||||
|
'name' => Exception::MIGRATION_ALREADY_EXISTS,
|
||||||
|
'description' => 'Migration with the requested ID already exists.',
|
||||||
|
'code' => 409,
|
||||||
|
],
|
||||||
|
Exception::MIGRATION_IN_PROGRESS => [
|
||||||
|
'name' => Exception::MIGRATION_IN_PROGRESS,
|
||||||
|
'description' => 'Migration is already in progress.',
|
||||||
|
'code' => 409,
|
||||||
|
],
|
||||||
];
|
];
|
||||||
|
|
|
@ -51,6 +51,8 @@ $admins = [
|
||||||
'functions.write',
|
'functions.write',
|
||||||
'execution.read',
|
'execution.read',
|
||||||
'execution.write',
|
'execution.write',
|
||||||
|
'migrations.read',
|
||||||
|
'migrations.write',
|
||||||
];
|
];
|
||||||
|
|
||||||
return [
|
return [
|
||||||
|
|
|
@ -76,4 +76,10 @@ return [ // List of publicly visible scopes
|
||||||
'health.read' => [
|
'health.read' => [
|
||||||
'description' => 'Access to read your project\'s health status',
|
'description' => 'Access to read your project\'s health status',
|
||||||
],
|
],
|
||||||
|
'migrations.read' => [
|
||||||
|
'description' => 'Access to read your project\'s migrations',
|
||||||
|
],
|
||||||
|
'migrations.write' => [
|
||||||
|
'description' => 'Access to create, update, and delete your project\'s migrations.',
|
||||||
|
]
|
||||||
];
|
];
|
||||||
|
|
|
@ -199,4 +199,17 @@ return [
|
||||||
'optional' => false,
|
'optional' => false,
|
||||||
'icon' => '',
|
'icon' => '',
|
||||||
],
|
],
|
||||||
|
'migrations' => [
|
||||||
|
'key' => 'migrations',
|
||||||
|
'name' => 'Migrations',
|
||||||
|
'subtitle' => 'The Migrations service allows you to migrate third-party data to your Appwrite server.',
|
||||||
|
'description' => '/docs/services/migrations.md',
|
||||||
|
'controller' => 'api/migrations.php',
|
||||||
|
'sdk' => true,
|
||||||
|
'docs' => true,
|
||||||
|
'docsUrl' => 'https://appwrite.io/docs/migrations',
|
||||||
|
'tests' => true,
|
||||||
|
'optional' => true,
|
||||||
|
'icon' => '/images/services/migrations.png',
|
||||||
|
],
|
||||||
];
|
];
|
||||||
|
|
888
app/controllers/api/migrations.php
Normal file
888
app/controllers/api/migrations.php
Normal file
|
@ -0,0 +1,888 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Appwrite\Auth\OAuth2\Firebase as OAuth2Firebase;
|
||||||
|
use Appwrite\Event\Delete;
|
||||||
|
use Appwrite\Event\Event;
|
||||||
|
use Appwrite\Event\Migration;
|
||||||
|
use Appwrite\Extend\Exception;
|
||||||
|
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;
|
||||||
|
use Utopia\Transfer\Sources\Appwrite;
|
||||||
|
use Utopia\Transfer\Sources\Firebase;
|
||||||
|
use Utopia\Transfer\Sources\NHost;
|
||||||
|
use Utopia\Transfer\Sources\Supabase;
|
||||||
|
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')
|
||||||
|
->label('event', 'migrations.create')
|
||||||
|
->label('audits.event', 'migration.create')
|
||||||
|
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
|
||||||
|
->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')
|
||||||
|
->inject('events')
|
||||||
|
->action(function (array $resources, string $endpoint, string $projectId, string $apiKey, Response $response, Database $dbForProject, Document $project, Document $user, Event $events) {
|
||||||
|
$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' => [],
|
||||||
|
]));
|
||||||
|
|
||||||
|
$events->setParam('migrationId', $migration->getId());
|
||||||
|
|
||||||
|
// Trigger Transfer
|
||||||
|
$event = new Migration();
|
||||||
|
$event
|
||||||
|
->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')
|
||||||
|
->label('event', 'migrations.create')
|
||||||
|
->label('audits.event', 'migration.create')
|
||||||
|
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
|
||||||
|
->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')
|
||||||
|
->inject('events')
|
||||||
|
->action(function (array $resources, string $serviceAccount, Response $response, Database $dbForProject, Document $project, Document $user, Event $events) {
|
||||||
|
$migration = $dbForProject->createDocument('migrations', new Document([
|
||||||
|
'$id' => ID::unique(),
|
||||||
|
'status' => 'pending',
|
||||||
|
'stage' => 'init',
|
||||||
|
'source' => Firebase::getName(),
|
||||||
|
'credentials' => [
|
||||||
|
'serviceAccount' => $serviceAccount,
|
||||||
|
],
|
||||||
|
'resources' => $resources,
|
||||||
|
'statusCounters' => '{}',
|
||||||
|
'resourceData' => '{}',
|
||||||
|
'errors' => [],
|
||||||
|
]));
|
||||||
|
|
||||||
|
$events->setParam('migrationId', $migration->getId());
|
||||||
|
|
||||||
|
// Trigger Transfer
|
||||||
|
$event = new Migration();
|
||||||
|
$event
|
||||||
|
->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')
|
||||||
|
->label('event', 'migrations.create')
|
||||||
|
->label('audits.event', 'migration.create')
|
||||||
|
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
|
||||||
|
->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')
|
||||||
|
->inject('events')
|
||||||
|
->inject('request')
|
||||||
|
->action(function (array $resources, string $projectId, Response $response, Database $dbForProject, Database $dbForConsole, Document $project, Document $user, Event $events, 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'
|
||||||
|
);
|
||||||
|
|
||||||
|
$accessToken = $user->getAttribute('migrationsFirebaseAccessToken');
|
||||||
|
$refreshToken = $user->getAttribute('migrationsFirebaseRefreshToken');
|
||||||
|
$accessTokenExpiry = $user->getAttribute('migrationsFirebaseAccessTokenExpiry');
|
||||||
|
|
||||||
|
$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.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$user = $user
|
||||||
|
->setAttribute('migrationsFirebaseAccessToken', $accessToken)
|
||||||
|
->setAttribute('migrationsFirebaseRefreshToken', $refreshToken)
|
||||||
|
->setAttribute('migrationsFirebaseAccessTokenExpiry', DateTime::addSeconds(new \DateTime(), (int) $firebase->getAccessTokenExpiry('')));
|
||||||
|
|
||||||
|
$dbForConsole->updateDocument('users', $user->getId(), $user);
|
||||||
|
}
|
||||||
|
|
||||||
|
$serviceAccount = $firebase->createServiceAccount($accessToken, $projectId);
|
||||||
|
|
||||||
|
// $migration = $dbForProject->createDocument('migrations', new Document([
|
||||||
|
// '$id' => ID::unique(),
|
||||||
|
// 'status' => 'pending',
|
||||||
|
// 'stage' => 'init',
|
||||||
|
// 'source' => Firebase::getName(),
|
||||||
|
// 'credentials' => [
|
||||||
|
// 'serviceAccount' => $serviceAccount,
|
||||||
|
// ],
|
||||||
|
// 'resources' => $resources,
|
||||||
|
// 'statusCounters' => '{}',
|
||||||
|
// 'resourceData' => '{}',
|
||||||
|
// 'errors' => []
|
||||||
|
// ]));
|
||||||
|
|
||||||
|
// $events->setParam('migrationId', $migration->getId());
|
||||||
|
|
||||||
|
// // Trigger Transfer
|
||||||
|
// $event = new Migration();
|
||||||
|
// $event
|
||||||
|
// ->setMigration($migration)
|
||||||
|
// ->setProject($project)
|
||||||
|
// ->setUser($user)
|
||||||
|
// ->trigger();
|
||||||
|
|
||||||
|
// $response
|
||||||
|
// ->setStatusCode(Response::STATUS_CODE_ACCEPTED)
|
||||||
|
// ->dynamic($migration, Response::MODEL_MIGRATION);
|
||||||
|
});
|
||||||
|
|
||||||
|
App::post('/v1/migrations/supabase')
|
||||||
|
->groups(['api', 'migrations'])
|
||||||
|
->desc('Migrate Supabase Data')
|
||||||
|
->label('scope', 'migrations.write')
|
||||||
|
->label('event', 'migrations.create')
|
||||||
|
->label('audits.event', 'migration.create')
|
||||||
|
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
|
||||||
|
->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')
|
||||||
|
->inject('events')
|
||||||
|
->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 $events) {
|
||||||
|
$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' => [],
|
||||||
|
]));
|
||||||
|
|
||||||
|
$events->setParam('migrationId', $migration->getId());
|
||||||
|
|
||||||
|
// Trigger Transfer
|
||||||
|
$event = new Migration();
|
||||||
|
$event
|
||||||
|
->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')
|
||||||
|
->label('event', 'migrations.create')
|
||||||
|
->label('audits.event', 'migration.create')
|
||||||
|
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
|
||||||
|
->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')
|
||||||
|
->param('subdomain', '', new URL(), '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)
|
||||||
|
->inject('response')
|
||||||
|
->inject('dbForProject')
|
||||||
|
->inject('project')
|
||||||
|
->inject('user')
|
||||||
|
->inject('events')
|
||||||
|
->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 $events) {
|
||||||
|
$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' => [],
|
||||||
|
]));
|
||||||
|
|
||||||
|
$events->setParam('migrationId', $migration->getId());
|
||||||
|
|
||||||
|
// Trigger Transfer
|
||||||
|
$event = new Migration();
|
||||||
|
$event
|
||||||
|
->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')
|
||||||
|
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
|
||||||
|
->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
|
||||||
|
$cursor = Query::getByType($queries, [Query::TYPE_CURSORAFTER, Query::TYPE_CURSORBEFORE]);
|
||||||
|
$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')
|
||||||
|
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
|
||||||
|
->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')
|
||||||
|
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
|
||||||
|
->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) {
|
||||||
|
try {
|
||||||
|
$appwrite = new Appwrite($projectID, $endpoint, $key);
|
||||||
|
|
||||||
|
$response
|
||||||
|
->setStatusCode(Response::STATUS_CODE_OK)
|
||||||
|
->dynamic(new Document($appwrite->report($resources)), Response::MODEL_MIGRATION_REPORT);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
throw new Exception(Exception::GENERAL_SERVER_ERROR, $e->getMessage());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
App::get('/v1/migrations/firebase/report')
|
||||||
|
->groups(['api', 'migrations'])
|
||||||
|
->desc('Generate a report on Firebase Data')
|
||||||
|
->label('scope', 'migrations.write')
|
||||||
|
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
|
||||||
|
->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) {
|
||||||
|
try {
|
||||||
|
$firebase = new Firebase(json_decode($serviceAccount, true));
|
||||||
|
|
||||||
|
$response
|
||||||
|
->setStatusCode(Response::STATUS_CODE_OK)
|
||||||
|
->dynamic(new Document($firebase->report($resources)), Response::MODEL_MIGRATION_REPORT);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
throw new Exception(Exception::GENERAL_SERVER_ERROR, $e->getMessage());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
App::get('/v1/migrations/firebase/report/oauth')
|
||||||
|
->groups(['api', 'migrations'])
|
||||||
|
->desc('Generate a report on Firebase Data using OAuth')
|
||||||
|
->label('scope', 'migrations.write')
|
||||||
|
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
|
||||||
|
->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('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) {
|
||||||
|
try {
|
||||||
|
$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'
|
||||||
|
);
|
||||||
|
|
||||||
|
$accessToken = $user->getAttribute('migrationsFirebaseAccessToken');
|
||||||
|
$refreshToken = $user->getAttribute('migrationsFirebaseRefreshToken');
|
||||||
|
$accessTokenExpiry = $user->getAttribute('migrationsFirebaseAccessTokenExpiry');
|
||||||
|
|
||||||
|
$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.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$user = $user
|
||||||
|
->setAttribute('migrationsFirebaseAccessToken', $accessToken)
|
||||||
|
->setAttribute('migrationsFirebaseRefreshToken', $refreshToken)
|
||||||
|
->setAttribute('migrationsFirebaseAccessTokenExpiry', DateTime::addSeconds(new \DateTime(), (int) $firebase->getAccessTokenExpiry('')));
|
||||||
|
|
||||||
|
$dbForConsole->updateDocument('users', $user->getId(), $user);
|
||||||
|
}
|
||||||
|
|
||||||
|
$serviceAccount = $firebase->createServiceAccount($accessToken, $projectId);
|
||||||
|
|
||||||
|
$firebase = new Firebase(json_decode($serviceAccount, true));
|
||||||
|
|
||||||
|
$response
|
||||||
|
->setStatusCode(Response::STATUS_CODE_OK)
|
||||||
|
->dynamic(new Document($firebase->report($resources)), Response::MODEL_MIGRATION_REPORT);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
throw new Exception(Exception::GENERAL_SERVER_ERROR, $e->getMessage());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
App::get('/v1/migrations/firebase/connect')
|
||||||
|
->desc('Authorize with firebase')
|
||||||
|
->groups(['api', 'migrations'])
|
||||||
|
->label('scope', 'public')
|
||||||
|
->label('origin', '*')
|
||||||
|
->label('sdk.auth', [])
|
||||||
|
->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')
|
||||||
|
->param('redirect', '', fn ($clients) => new Host($clients), 'URL to redirect back to your Firebase authorization. Only console hostnames are allowed.', true, ['clients'])
|
||||||
|
->param('projectId', '', new UID(), 'Project ID')
|
||||||
|
->inject('response')
|
||||||
|
->inject('request')
|
||||||
|
->inject('user')
|
||||||
|
->inject('dbForConsole')
|
||||||
|
->action(function (string $redirect, string $projectId, Response $response, Request $request, Document $user, Database $dbForConsole) {
|
||||||
|
$state = \json_encode([
|
||||||
|
'projectId' => $projectId,
|
||||||
|
'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'])
|
||||||
|
->label('scope', 'public')
|
||||||
|
->label('error', __DIR__ . '/../../views/general/error.phtml')
|
||||||
|
->param('code', '', new Text(2048), 'OAuth2 code.', true)
|
||||||
|
->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);
|
||||||
|
|
||||||
|
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.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$user = $user
|
||||||
|
->setAttribute('migrationsFirebaseAccessToken', $accessToken)
|
||||||
|
->setAttribute('migrationsFirebaseRefreshToken', $refreshToken)
|
||||||
|
->setAttribute('migrationsFirebaseAccessTokenExpiry', DateTime::addSeconds(new \DateTime(), (int) $accessTokenExpiry));
|
||||||
|
|
||||||
|
$dbForConsole->updateDocument('users', $user->getId(), $user);
|
||||||
|
} 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'])
|
||||||
|
->label('scope', 'public')
|
||||||
|
->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) {
|
||||||
|
if (empty($user->getAttribute('migrationsFirebaseAccessToken')) || empty($user->getAttribute('migrationsFirebaseRefreshToken')) || empty($user->getAttribute('migrationsFirebaseAccessTokenExpiry'))) {
|
||||||
|
throw new Exception(Exception::GENERAL_ACCESS_FORBIDDEN, 'Not authenticated with Firebase');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (App::getEnv('_APP_MIGRATIONS_FIREBASE_CLIENT_ID', '') === '' || App::getEnv('_APP_MIGRATIONS_FIREBASE_CLIENT_SECRET', '') === '') {
|
||||||
|
throw new Exception(Exception::GENERAL_ACCESS_FORBIDDEN, 'Missing Google OAuth credentials');
|
||||||
|
}
|
||||||
|
|
||||||
|
$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'
|
||||||
|
);
|
||||||
|
|
||||||
|
$accessToken = $user->getAttribute('migrationsFirebaseAccessToken');
|
||||||
|
$refreshToken = $user->getAttribute('migrationsFirebaseRefreshToken');
|
||||||
|
$accessTokenExpiry = $user->getAttribute('migrationsFirebaseAccessTokenExpiry');
|
||||||
|
|
||||||
|
$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.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$user = $user
|
||||||
|
->setAttribute('migrationsFirebaseAccessToken', $accessToken)
|
||||||
|
->setAttribute('migrationsFirebaseRefreshToken', $refreshToken)
|
||||||
|
->setAttribute('migrationsFirebaseAccessTokenExpiry', DateTime::addSeconds(new \DateTime(), (int) $firebase->getAccessTokenExpiry('')));
|
||||||
|
|
||||||
|
$dbForConsole->updateDocument('users', $user->getId(), $user);
|
||||||
|
}
|
||||||
|
|
||||||
|
$projects = $firebase->getProjects($accessToken);
|
||||||
|
|
||||||
|
$output = [];
|
||||||
|
foreach ($projects as $project) {
|
||||||
|
$output[] = [
|
||||||
|
'displayName' => $project['displayName'],
|
||||||
|
'projectId' => $project['projectId'],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
$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'])
|
||||||
|
->label('scope', 'public')
|
||||||
|
->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) {
|
||||||
|
$user = $user
|
||||||
|
->setAttribute('migrationsFirebaseAccessToken', '')
|
||||||
|
->setAttribute('migrationsFirebaseRefreshToken', '')
|
||||||
|
->setAttribute('migrationsFirebaseAccessTokenExpiry', '');
|
||||||
|
|
||||||
|
$dbForConsole->updateDocument('users', $user->getId(), $user);
|
||||||
|
|
||||||
|
$response->noContent();
|
||||||
|
});
|
||||||
|
|
||||||
|
App::get('/v1/migrations/supabase/report')
|
||||||
|
->groups(['api', 'migrations'])
|
||||||
|
->desc('Generate a report on Supabase Data')
|
||||||
|
->label('scope', 'migrations.write')
|
||||||
|
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
|
||||||
|
->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')
|
||||||
|
->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')
|
||||||
|
->action(function (array $resources, string $endpoint, string $apiKey, string $databaseHost, string $username, string $password, int $port, Response $response) {
|
||||||
|
try {
|
||||||
|
$supabase = new Supabase($endpoint, $apiKey, $databaseHost, 'postgres', $username, $password, $port);
|
||||||
|
|
||||||
|
$response
|
||||||
|
->setStatusCode(Response::STATUS_CODE_OK)
|
||||||
|
->dynamic(new Document($supabase->report($resources)), Response::MODEL_MIGRATION_REPORT);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
throw new Exception(Exception::GENERAL_SERVER_ERROR, $e->getMessage());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
App::get('/v1/migrations/nhost/report')
|
||||||
|
->groups(['api', 'migrations'])
|
||||||
|
->desc('Generate a report on NHost Data')
|
||||||
|
->label('scope', 'migrations.write')
|
||||||
|
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
|
||||||
|
->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)
|
||||||
|
->param('resources', [], new ArrayList(new WhiteList(NHost::getSupportedResources())), 'List of resources to migrate')
|
||||||
|
->param('subdomain', '', new URL(), '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)
|
||||||
|
->inject('response')
|
||||||
|
->action(function (array $resources, string $subdomain, string $region, string $adminSecret, string $database, string $username, string $password, int $port, Response $response) {
|
||||||
|
try {
|
||||||
|
$nhost = new NHost($subdomain, $region, $adminSecret, $database, $username, $password, $port);
|
||||||
|
|
||||||
|
$response
|
||||||
|
->setStatusCode(Response::STATUS_CODE_OK)
|
||||||
|
->dynamic(new Document($nhost->report($resources)), Response::MODEL_MIGRATION_REPORT);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
throw new Exception(Exception::GENERAL_SERVER_ERROR, $e->getMessage());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
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}')
|
||||||
|
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
|
||||||
|
->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')
|
||||||
|
->inject('events')
|
||||||
|
->action(function (string $migrationId, Response $response, Database $dbForProject, Document $project, Document $user, Event $eventInstance) {
|
||||||
|
$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
|
||||||
|
$event = new Migration();
|
||||||
|
$event
|
||||||
|
->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}')
|
||||||
|
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
|
||||||
|
->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')
|
||||||
|
->inject('deletes')
|
||||||
|
->inject('events')
|
||||||
|
->action(function (string $migrationId, Response $response, Database $dbForProject, Delete $deletes, Event $events) {
|
||||||
|
$migration = $dbForProject->getDocument('migrations', $migrationId);
|
||||||
|
|
||||||
|
if ($migration->isEmpty()) {
|
||||||
|
throw new Exception(Exception::MIGRATION_NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$dbForProject->deleteDocument('migrations', $migration->getId())) {
|
||||||
|
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Failed to remove migration from DB', 500);
|
||||||
|
}
|
||||||
|
|
||||||
|
$events->setParam('migrationId', $migration->getId());
|
||||||
|
|
||||||
|
$response->noContent();
|
||||||
|
});
|
315
app/workers/migrations.php
Normal file
315
app/workers/migrations.php
Normal file
|
@ -0,0 +1,315 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Appwrite\Event\Event;
|
||||||
|
use Appwrite\Messaging\Adapter\Realtime;
|
||||||
|
use Appwrite\Permission;
|
||||||
|
use Appwrite\Resque\Worker;
|
||||||
|
use Appwrite\Role;
|
||||||
|
use Appwrite\Utopia\Response\Model\Migration;
|
||||||
|
use Utopia\CLI\Console;
|
||||||
|
use Utopia\Database\Database;
|
||||||
|
use Utopia\Database\Document;
|
||||||
|
use Utopia\Database\Helpers\ID;
|
||||||
|
use Utopia\Transfer\Destinations\Appwrite as DestinationsAppwrite;
|
||||||
|
use Utopia\Transfer\Resource;
|
||||||
|
use Utopia\Transfer\Source;
|
||||||
|
use Utopia\Transfer\Sources\Appwrite;
|
||||||
|
use Utopia\Transfer\Sources\Firebase;
|
||||||
|
use Utopia\Transfer\Sources\NHost;
|
||||||
|
use Utopia\Transfer\Sources\Supabase;
|
||||||
|
use Utopia\Transfer\Transfer;
|
||||||
|
|
||||||
|
require_once __DIR__.'/../init.php';
|
||||||
|
|
||||||
|
Console::title('Migrations V1 Worker');
|
||||||
|
Console::success(APP_NAME.' Migrations worker v1 has started');
|
||||||
|
|
||||||
|
class MigrationsV1 extends Worker
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Database connection shared across all methods of this file
|
||||||
|
*
|
||||||
|
* @var Database
|
||||||
|
*/
|
||||||
|
private Database $dbForProject;
|
||||||
|
|
||||||
|
public function getName(): string
|
||||||
|
{
|
||||||
|
return 'migrations';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function init(): void
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public function run(): void
|
||||||
|
{
|
||||||
|
$type = $this->args['type'] ?? '';
|
||||||
|
$events = $this->args['events'] ?? [];
|
||||||
|
$project = new Document($this->args['project'] ?? []);
|
||||||
|
$user = new Document($this->args['user'] ?? []);
|
||||||
|
$payload = json_encode($this->args['payload'] ?? []);
|
||||||
|
|
||||||
|
if ($project->getId() === 'console') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle Event execution.
|
||||||
|
*/
|
||||||
|
if (! empty($events)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->dbForProject = $this->getProjectDB($this->args['project']['$id']);
|
||||||
|
|
||||||
|
$this->processMigration();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process Source
|
||||||
|
*
|
||||||
|
* @return Source
|
||||||
|
*
|
||||||
|
* @throws \Exception
|
||||||
|
*/
|
||||||
|
protected function processSource(string $source, array $credentials): Source
|
||||||
|
{
|
||||||
|
switch ($source) {
|
||||||
|
case Firebase::getName():
|
||||||
|
return new Firebase(
|
||||||
|
json_decode($credentials['serviceAccount'], true),
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case Supabase::getName():
|
||||||
|
return new Supabase(
|
||||||
|
$credentials['endpoint'],
|
||||||
|
$credentials['apiKey'],
|
||||||
|
$credentials['databaseHost'],
|
||||||
|
'postgres',
|
||||||
|
$credentials['username'],
|
||||||
|
$credentials['password'],
|
||||||
|
$credentials['port'],
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case NHost::getName():
|
||||||
|
return new NHost(
|
||||||
|
$credentials['subdomain'],
|
||||||
|
$credentials['region'],
|
||||||
|
$credentials['adminSecret'],
|
||||||
|
$credentials['database'],
|
||||||
|
$credentials['username'],
|
||||||
|
$credentials['password'],
|
||||||
|
$credentials['port'],
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case Appwrite::getName():
|
||||||
|
return new Appwrite($credentials['projectId'], str_starts_with($credentials['endpoint'], 'http://localhost/v1') ? 'http://appwrite/v1' : $credentials['endpoint'], $credentials['apiKey']);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new \Exception('Invalid source type');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function updateMigrationDocument(Document $migration, Document $project): Document
|
||||||
|
{
|
||||||
|
/** Trigger Realtime */
|
||||||
|
$allEvents = Event::generateEvents('migrations.[migrationId].update', [
|
||||||
|
'migrationId' => $migration->getId(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
$target = Realtime::fromPayload(
|
||||||
|
event: $allEvents[0],
|
||||||
|
payload: $migration,
|
||||||
|
project: $project
|
||||||
|
);
|
||||||
|
|
||||||
|
Realtime::send(
|
||||||
|
projectId: 'console',
|
||||||
|
payload: $migration->getArrayCopy(),
|
||||||
|
events: $allEvents,
|
||||||
|
channels: $target['channels'],
|
||||||
|
roles: $target['roles'],
|
||||||
|
);
|
||||||
|
|
||||||
|
Realtime::send(
|
||||||
|
projectId: $project->getId(),
|
||||||
|
payload: $migration->getArrayCopy(),
|
||||||
|
events: $allEvents,
|
||||||
|
channels: $target['channels'],
|
||||||
|
roles: $target['roles'],
|
||||||
|
);
|
||||||
|
|
||||||
|
return $this->dbForProject->updateDocument('migrations', $migration->getId(), $migration);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function removeAPIKey(Document $apiKey)
|
||||||
|
{
|
||||||
|
$consoleDB = $this->getConsoleDB();
|
||||||
|
|
||||||
|
$consoleDB->deleteDocument('keys', $apiKey->getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function generateAPIKey(Document $project): Document
|
||||||
|
{
|
||||||
|
$consoleDB = $this->getConsoleDB();
|
||||||
|
$generatedSecret = bin2hex(\random_bytes(128));
|
||||||
|
|
||||||
|
$key = new Document([
|
||||||
|
'$id' => ID::unique(),
|
||||||
|
'$permissions' => [
|
||||||
|
Permission::read(Role::any()),
|
||||||
|
Permission::update(Role::any()),
|
||||||
|
Permission::delete(Role::any()),
|
||||||
|
],
|
||||||
|
'projectInternalId' => $project->getInternalId(),
|
||||||
|
'projectId' => $project->getId(),
|
||||||
|
'name' => 'Transfer API Key',
|
||||||
|
'scopes' => [
|
||||||
|
'users.read',
|
||||||
|
'users.write',
|
||||||
|
'teams.read',
|
||||||
|
'teams.write',
|
||||||
|
'databases.read',
|
||||||
|
'databases.write',
|
||||||
|
'collections.read',
|
||||||
|
'collections.write',
|
||||||
|
'documents.read',
|
||||||
|
'documents.write',
|
||||||
|
'buckets.read',
|
||||||
|
'buckets.write',
|
||||||
|
'files.read',
|
||||||
|
'files.write',
|
||||||
|
'functions.read',
|
||||||
|
'functions.write',
|
||||||
|
],
|
||||||
|
'expire' => null,
|
||||||
|
'sdks' => [],
|
||||||
|
'accessedAt' => null,
|
||||||
|
'secret' => $generatedSecret,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$consoleDB->createDocument('keys', $key);
|
||||||
|
$consoleDB->deleteCachedDocument('projects', $project->getId());
|
||||||
|
|
||||||
|
return $key;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process Migration
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
protected function processMigration(): void
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var Document $migrationDocument
|
||||||
|
* @var Transfer $transfer
|
||||||
|
*/
|
||||||
|
$migrationDocument = null;
|
||||||
|
$transfer = null;
|
||||||
|
$projectDocument = $this->getConsoleDB()->getDocument('projects', $this->args['project']['$id']);
|
||||||
|
$tempAPIKey = $this->generateAPIKey($projectDocument);
|
||||||
|
|
||||||
|
try {
|
||||||
|
$migrationDocument = $this->dbForProject->getDocument('migrations', $this->args['migration']['$id']);
|
||||||
|
$migrationDocument->setAttribute('stage', 'processing');
|
||||||
|
$migrationDocument->setAttribute('status', 'processing');
|
||||||
|
$this->updateMigrationDocument($migrationDocument, $projectDocument);
|
||||||
|
|
||||||
|
$source = $this->processSource($migrationDocument->getAttribute('source'), $migrationDocument->getAttribute('credentials'));
|
||||||
|
|
||||||
|
$source->report();
|
||||||
|
|
||||||
|
$destination = new DestinationsAppwrite(
|
||||||
|
$projectDocument->getId(),
|
||||||
|
'http://appwrite/v1',
|
||||||
|
$tempAPIKey['secret'],
|
||||||
|
);
|
||||||
|
|
||||||
|
$transfer = new Transfer(
|
||||||
|
$source,
|
||||||
|
$destination
|
||||||
|
);
|
||||||
|
|
||||||
|
$migrationDocument->setAttribute('stage', 'source-check');
|
||||||
|
$this->updateMigrationDocument($migrationDocument, $projectDocument);
|
||||||
|
|
||||||
|
$migrationDocument->setAttribute('stage', 'destination-check');
|
||||||
|
$this->updateMigrationDocument($migrationDocument, $projectDocument);
|
||||||
|
|
||||||
|
/** Start Transfer */
|
||||||
|
$migrationDocument->setAttribute('stage', 'migrating');
|
||||||
|
$this->updateMigrationDocument($migrationDocument, $projectDocument);
|
||||||
|
$transfer->run($migrationDocument->getAttribute('resources'), function () use ($migrationDocument, $transfer, $projectDocument) {
|
||||||
|
$migrationDocument->setAttribute('resourceData', json_encode($transfer->getCache()));
|
||||||
|
$migrationDocument->setAttribute('statusCounters', json_encode($transfer->getStatusCounters()));
|
||||||
|
|
||||||
|
$this->updateMigrationDocument($migrationDocument, $projectDocument);
|
||||||
|
});
|
||||||
|
|
||||||
|
$errors = $transfer->getReport(Resource::STATUS_ERROR);
|
||||||
|
|
||||||
|
if (count($errors) > 0) {
|
||||||
|
$migrationDocument->setAttribute('status', 'failed');
|
||||||
|
$migrationDocument->setAttribute('stage', 'finished');
|
||||||
|
|
||||||
|
$errorMessages = [];
|
||||||
|
foreach ($errors as $error) {
|
||||||
|
$errorMessages[] = "Failed to transfer resource '{$error['id']}:{$error['resource']}' with message '{$error['message']}'";
|
||||||
|
}
|
||||||
|
|
||||||
|
$migrationDocument->setAttribute('errors', $errorMessages);
|
||||||
|
$this->updateMigrationDocument($migrationDocument, $projectDocument);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$migrationDocument->setAttribute('status', 'completed');
|
||||||
|
$migrationDocument->setAttribute('stage', 'finished');
|
||||||
|
} catch (\Throwable $th) {
|
||||||
|
Console::error($th->getMessage());
|
||||||
|
|
||||||
|
if ($migrationDocument) {
|
||||||
|
Console::error($th->getMessage());
|
||||||
|
Console::error($th->getTraceAsString());
|
||||||
|
$migrationDocument->setAttribute('status', 'failed');
|
||||||
|
$migrationDocument->setAttribute('stage', 'finished');
|
||||||
|
$migrationDocument->setAttribute('errors', [$th->getMessage()]);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($transfer) {
|
||||||
|
$errors = $transfer->getReport(Resource::STATUS_ERROR);
|
||||||
|
|
||||||
|
if (count($errors) > 0) {
|
||||||
|
$migrationDocument->setAttribute('status', 'failed');
|
||||||
|
$migrationDocument->setAttribute('stage', 'finished');
|
||||||
|
$migrationDocument->setAttribute('errors', $errors);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
if ($migrationDocument) {
|
||||||
|
$this->updateMigrationDocument($migrationDocument, $projectDocument);
|
||||||
|
}
|
||||||
|
if ($tempAPIKey) {
|
||||||
|
$this->removeAPIKey($tempAPIKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process Verification
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
protected function processVerification(): void
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public function shutdown(): void
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
10
bin/worker-migrations
Normal file
10
bin/worker-migrations
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
if [ -z "$_APP_REDIS_USER" ] && [ -z "$_APP_REDIS_PASS" ]
|
||||||
|
then
|
||||||
|
REDIS_BACKEND="${_APP_REDIS_HOST}:${_APP_REDIS_PORT}"
|
||||||
|
else
|
||||||
|
REDIS_BACKEND="redis://${_APP_REDIS_USER}:${_APP_REDIS_PASS}@${_APP_REDIS_HOST}:${_APP_REDIS_PORT}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
INTERVAL=0.1 QUEUE='v1-migrations' APP_INCLUDE='/usr/src/code/app/workers/migrations.php' php /usr/src/code/vendor/bin/resque -dopcache.preload=opcache.preload=/usr/src/code/app/preload.php
|
|
@ -62,6 +62,7 @@
|
||||||
"utopia-php/storage": "0.14.*",
|
"utopia-php/storage": "0.14.*",
|
||||||
"utopia-php/swoole": "0.8.*",
|
"utopia-php/swoole": "0.8.*",
|
||||||
"utopia-php/websocket": "0.1.*",
|
"utopia-php/websocket": "0.1.*",
|
||||||
|
"utopia-php/transfer": "dev-feat-improve-features",
|
||||||
"resque/php-resque": "1.3.6",
|
"resque/php-resque": "1.3.6",
|
||||||
"matomo/device-detector": "6.1.*",
|
"matomo/device-detector": "6.1.*",
|
||||||
"dragonmantank/cron-expression": "3.3.2",
|
"dragonmantank/cron-expression": "3.3.2",
|
||||||
|
@ -76,6 +77,10 @@
|
||||||
{
|
{
|
||||||
"url": "https://github.com/appwrite/runtimes.git",
|
"url": "https://github.com/appwrite/runtimes.git",
|
||||||
"type": "git"
|
"type": "git"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://github.com/utopia-php/transfer.git",
|
||||||
|
"type": "vcs"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
|
|
177
composer.lock
generated
177
composer.lock
generated
|
@ -4,7 +4,7 @@
|
||||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||||
"This file is @generated automatically"
|
"This file is @generated automatically"
|
||||||
],
|
],
|
||||||
"content-hash": "acb2fc370254dfd67f7d44aa1fa0e694",
|
"content-hash": "ad6ca93acb312945f1f0edc58c8002ec",
|
||||||
"packages": [
|
"packages": [
|
||||||
{
|
{
|
||||||
"name": "adhocore/jwt",
|
"name": "adhocore/jwt",
|
||||||
|
@ -63,6 +63,47 @@
|
||||||
],
|
],
|
||||||
"time": "2021-02-20T09:56:44+00:00"
|
"time": "2021-02-20T09:56:44+00:00"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "appwrite/appwrite",
|
||||||
|
"version": "8.0.0",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/appwrite/sdk-for-php.git",
|
||||||
|
"reference": "2b9e966edf35c4061179ed98ea364698ab30de8b"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/appwrite/sdk-for-php/zipball/2b9e966edf35c4061179ed98ea364698ab30de8b",
|
||||||
|
"reference": "2b9e966edf35c4061179ed98ea364698ab30de8b",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"ext-curl": "*",
|
||||||
|
"ext-json": "*",
|
||||||
|
"php": ">=7.1.0"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"phpunit/phpunit": "3.7.35"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Appwrite\\": "src/Appwrite"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"BSD-3-Clause"
|
||||||
|
],
|
||||||
|
"description": "Appwrite is an open-source self-hosted backend server that abstract and simplify complex and repetitive development tasks behind a very simple REST API",
|
||||||
|
"support": {
|
||||||
|
"email": "team@appwrite.io",
|
||||||
|
"issues": "https://github.com/appwrite/sdk-for-php/issues",
|
||||||
|
"source": "https://github.com/appwrite/sdk-for-php/tree/8.0.0",
|
||||||
|
"url": "https://appwrite.io/support"
|
||||||
|
},
|
||||||
|
"time": "2023-04-12T10:16:28+00:00"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "appwrite/php-clamav",
|
"name": "appwrite/php-clamav",
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
|
@ -607,16 +648,16 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "guzzlehttp/promises",
|
"name": "guzzlehttp/promises",
|
||||||
"version": "2.0.0",
|
"version": "2.0.1",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/guzzle/promises.git",
|
"url": "https://github.com/guzzle/promises.git",
|
||||||
"reference": "3a494dc7dc1d7d12e511890177ae2d0e6c107da6"
|
"reference": "111166291a0f8130081195ac4556a5587d7f1b5d"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/guzzle/promises/zipball/3a494dc7dc1d7d12e511890177ae2d0e6c107da6",
|
"url": "https://api.github.com/repos/guzzle/promises/zipball/111166291a0f8130081195ac4556a5587d7f1b5d",
|
||||||
"reference": "3a494dc7dc1d7d12e511890177ae2d0e6c107da6",
|
"reference": "111166291a0f8130081195ac4556a5587d7f1b5d",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
|
@ -670,7 +711,7 @@
|
||||||
],
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"issues": "https://github.com/guzzle/promises/issues",
|
"issues": "https://github.com/guzzle/promises/issues",
|
||||||
"source": "https://github.com/guzzle/promises/tree/2.0.0"
|
"source": "https://github.com/guzzle/promises/tree/2.0.1"
|
||||||
},
|
},
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
|
@ -686,20 +727,20 @@
|
||||||
"type": "tidelift"
|
"type": "tidelift"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"time": "2023-05-21T13:50:22+00:00"
|
"time": "2023-08-03T15:11:55+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "guzzlehttp/psr7",
|
"name": "guzzlehttp/psr7",
|
||||||
"version": "2.5.0",
|
"version": "2.6.0",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/guzzle/psr7.git",
|
"url": "https://github.com/guzzle/psr7.git",
|
||||||
"reference": "b635f279edd83fc275f822a1188157ffea568ff6"
|
"reference": "8bd7c33a0734ae1c5d074360512beb716bef3f77"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/guzzle/psr7/zipball/b635f279edd83fc275f822a1188157ffea568ff6",
|
"url": "https://api.github.com/repos/guzzle/psr7/zipball/8bd7c33a0734ae1c5d074360512beb716bef3f77",
|
||||||
"reference": "b635f279edd83fc275f822a1188157ffea568ff6",
|
"reference": "8bd7c33a0734ae1c5d074360512beb716bef3f77",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
|
@ -786,7 +827,7 @@
|
||||||
],
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"issues": "https://github.com/guzzle/psr7/issues",
|
"issues": "https://github.com/guzzle/psr7/issues",
|
||||||
"source": "https://github.com/guzzle/psr7/tree/2.5.0"
|
"source": "https://github.com/guzzle/psr7/tree/2.6.0"
|
||||||
},
|
},
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
|
@ -802,7 +843,7 @@
|
||||||
"type": "tidelift"
|
"type": "tidelift"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"time": "2023-04-17T16:11:26+00:00"
|
"time": "2023-08-03T15:06:02+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "influxdb/influxdb-php",
|
"name": "influxdb/influxdb-php",
|
||||||
|
@ -994,16 +1035,16 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "matomo/device-detector",
|
"name": "matomo/device-detector",
|
||||||
"version": "6.1.3",
|
"version": "6.1.4",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/matomo-org/device-detector.git",
|
"url": "https://github.com/matomo-org/device-detector.git",
|
||||||
"reference": "3e0fac7e77f3faadc3858fea9f5fa7efeb9cf239"
|
"reference": "74f6c4f6732b3ad6cdf25560746841d522969112"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/matomo-org/device-detector/zipball/3e0fac7e77f3faadc3858fea9f5fa7efeb9cf239",
|
"url": "https://api.github.com/repos/matomo-org/device-detector/zipball/74f6c4f6732b3ad6cdf25560746841d522969112",
|
||||||
"reference": "3e0fac7e77f3faadc3858fea9f5fa7efeb9cf239",
|
"reference": "74f6c4f6732b3ad6cdf25560746841d522969112",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
|
@ -1059,7 +1100,7 @@
|
||||||
"source": "https://github.com/matomo-org/matomo",
|
"source": "https://github.com/matomo-org/matomo",
|
||||||
"wiki": "https://dev.matomo.org/"
|
"wiki": "https://dev.matomo.org/"
|
||||||
},
|
},
|
||||||
"time": "2023-06-06T11:58:07+00:00"
|
"time": "2023-08-02T08:48:53+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "mongodb/mongodb",
|
"name": "mongodb/mongodb",
|
||||||
|
@ -2846,6 +2887,76 @@
|
||||||
},
|
},
|
||||||
"time": "2022-11-07T13:51:59+00:00"
|
"time": "2022-11-07T13:51:59+00:00"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "utopia-php/transfer",
|
||||||
|
"version": "dev-feat-improve-features",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/utopia-php/transfer.git",
|
||||||
|
"reference": "7b5fe7059a6e23f2a59a448d79ffb978fa3cc60a"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/utopia-php/transfer/zipball/7b5fe7059a6e23f2a59a448d79ffb978fa3cc60a",
|
||||||
|
"reference": "7b5fe7059a6e23f2a59a448d79ffb978fa3cc60a",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"appwrite/appwrite": "^8.0",
|
||||||
|
"php": ">=8.0",
|
||||||
|
"utopia-php/cli": "^0.13.0"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"laravel/pint": "^1.10",
|
||||||
|
"phpunit/phpunit": "^9.3",
|
||||||
|
"vlucas/phpdotenv": "^5.5"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Utopia\\Transfer\\": "src/Transfer"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload-dev": {
|
||||||
|
"psr-4": {
|
||||||
|
"Utopia\\Tests\\": "tests/Transfer"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"lint": [
|
||||||
|
"./vendor/bin/pint --test"
|
||||||
|
],
|
||||||
|
"format": [
|
||||||
|
"./vendor/bin/pint"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Eldad Fux",
|
||||||
|
"email": "eldad@appwrite.io"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Bradley Schofield",
|
||||||
|
"email": "bradley@appwrite.io"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "A simple library to transfer resources between services.",
|
||||||
|
"keywords": [
|
||||||
|
"framework",
|
||||||
|
"php",
|
||||||
|
"transfer",
|
||||||
|
"upf",
|
||||||
|
"utopia"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"source": "https://github.com/utopia-php/transfer/tree/feat-improve-features",
|
||||||
|
"issues": "https://github.com/utopia-php/transfer/issues"
|
||||||
|
},
|
||||||
|
"time": "2023-08-04T16:09:30+00:00"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "utopia-php/websocket",
|
"name": "utopia-php/websocket",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
|
@ -3785,16 +3896,16 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "phpstan/phpdoc-parser",
|
"name": "phpstan/phpdoc-parser",
|
||||||
"version": "1.23.0",
|
"version": "1.23.1",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/phpstan/phpdoc-parser.git",
|
"url": "https://github.com/phpstan/phpdoc-parser.git",
|
||||||
"reference": "a2b24135c35852b348894320d47b3902a94bc494"
|
"reference": "846ae76eef31c6d7790fac9bc399ecee45160b26"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/a2b24135c35852b348894320d47b3902a94bc494",
|
"url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/846ae76eef31c6d7790fac9bc399ecee45160b26",
|
||||||
"reference": "a2b24135c35852b348894320d47b3902a94bc494",
|
"reference": "846ae76eef31c6d7790fac9bc399ecee45160b26",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
|
@ -3826,9 +3937,9 @@
|
||||||
"description": "PHPDoc parser with support for nullable, intersection and generic types",
|
"description": "PHPDoc parser with support for nullable, intersection and generic types",
|
||||||
"support": {
|
"support": {
|
||||||
"issues": "https://github.com/phpstan/phpdoc-parser/issues",
|
"issues": "https://github.com/phpstan/phpdoc-parser/issues",
|
||||||
"source": "https://github.com/phpstan/phpdoc-parser/tree/1.23.0"
|
"source": "https://github.com/phpstan/phpdoc-parser/tree/1.23.1"
|
||||||
},
|
},
|
||||||
"time": "2023-07-23T22:17:56+00:00"
|
"time": "2023-08-03T16:32:59+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "phpunit/php-code-coverage",
|
"name": "phpunit/php-code-coverage",
|
||||||
|
@ -4758,16 +4869,16 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "sebastian/global-state",
|
"name": "sebastian/global-state",
|
||||||
"version": "5.0.5",
|
"version": "5.0.6",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/sebastianbergmann/global-state.git",
|
"url": "https://github.com/sebastianbergmann/global-state.git",
|
||||||
"reference": "0ca8db5a5fc9c8646244e629625ac486fa286bf2"
|
"reference": "bde739e7565280bda77be70044ac1047bc007e34"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/0ca8db5a5fc9c8646244e629625ac486fa286bf2",
|
"url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bde739e7565280bda77be70044ac1047bc007e34",
|
||||||
"reference": "0ca8db5a5fc9c8646244e629625ac486fa286bf2",
|
"reference": "bde739e7565280bda77be70044ac1047bc007e34",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
|
@ -4810,7 +4921,7 @@
|
||||||
],
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"issues": "https://github.com/sebastianbergmann/global-state/issues",
|
"issues": "https://github.com/sebastianbergmann/global-state/issues",
|
||||||
"source": "https://github.com/sebastianbergmann/global-state/tree/5.0.5"
|
"source": "https://github.com/sebastianbergmann/global-state/tree/5.0.6"
|
||||||
},
|
},
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
|
@ -4818,7 +4929,7 @@
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"time": "2022-02-14T08:28:10+00:00"
|
"time": "2023-08-02T09:26:13+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "sebastian/lines-of-code",
|
"name": "sebastian/lines-of-code",
|
||||||
|
@ -5643,7 +5754,9 @@
|
||||||
],
|
],
|
||||||
"aliases": [],
|
"aliases": [],
|
||||||
"minimum-stability": "stable",
|
"minimum-stability": "stable",
|
||||||
"stability-flags": [],
|
"stability-flags": {
|
||||||
|
"utopia-php/transfer": 20
|
||||||
|
},
|
||||||
"prefer-stable": false,
|
"prefer-stable": false,
|
||||||
"prefer-lowest": false,
|
"prefer-lowest": false,
|
||||||
"platform": {
|
"platform": {
|
||||||
|
@ -5667,5 +5780,5 @@
|
||||||
"platform-overrides": {
|
"platform-overrides": {
|
||||||
"php": "8.0"
|
"php": "8.0"
|
||||||
},
|
},
|
||||||
"plugin-api-version": "2.3.0"
|
"plugin-api-version": "2.1.0"
|
||||||
}
|
}
|
||||||
|
|
|
@ -576,6 +576,39 @@ services:
|
||||||
- _APP_LOGGING_PROVIDER
|
- _APP_LOGGING_PROVIDER
|
||||||
- _APP_LOGGING_CONFIG
|
- _APP_LOGGING_CONFIG
|
||||||
|
|
||||||
|
appwrite-worker-migrations:
|
||||||
|
entrypoint: worker-migrations
|
||||||
|
<<: *x-logging
|
||||||
|
container_name: appwrite-worker-migrations
|
||||||
|
restart: unless-stopped
|
||||||
|
image: appwrite-dev
|
||||||
|
networks:
|
||||||
|
- appwrite
|
||||||
|
volumes:
|
||||||
|
- ./app:/usr/src/code/app
|
||||||
|
- ./src:/usr/src/code/src
|
||||||
|
- ./tests:/usr/src/code/tests
|
||||||
|
- ./vendor:/usr/src/code/tests
|
||||||
|
depends_on:
|
||||||
|
- mariadb
|
||||||
|
environment:
|
||||||
|
- _APP_ENV
|
||||||
|
- _APP_OPENSSL_KEY_V1
|
||||||
|
- _APP_DOMAIN
|
||||||
|
- _APP_DOMAIN_TARGET
|
||||||
|
- _APP_SYSTEM_SECURITY_EMAIL_ADDRESS
|
||||||
|
- _APP_REDIS_HOST
|
||||||
|
- _APP_REDIS_PORT
|
||||||
|
- _APP_REDIS_USER
|
||||||
|
- _APP_REDIS_PASS
|
||||||
|
- _APP_DB_HOST
|
||||||
|
- _APP_DB_PORT
|
||||||
|
- _APP_DB_SCHEMA
|
||||||
|
- _APP_DB_USER
|
||||||
|
- _APP_DB_PASS
|
||||||
|
- _APP_LOGGING_PROVIDER
|
||||||
|
- _APP_LOGGING_CONFIG
|
||||||
|
|
||||||
appwrite-maintenance:
|
appwrite-maintenance:
|
||||||
entrypoint: maintenance
|
entrypoint: maintenance
|
||||||
<<: *x-logging
|
<<: *x-logging
|
||||||
|
|
217
src/Appwrite/Auth/OAuth2/Firebase.php
Normal file
217
src/Appwrite/Auth/OAuth2/Firebase.php
Normal file
|
@ -0,0 +1,217 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Appwrite\Auth\OAuth2;
|
||||||
|
|
||||||
|
use Appwrite\Auth\OAuth2;
|
||||||
|
|
||||||
|
class Firebase extends OAuth2
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected array $user = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected array $tokens = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected array $scopes = [
|
||||||
|
'https://www.googleapis.com/auth/firebase',
|
||||||
|
'https://www.googleapis.com/auth/datastore',
|
||||||
|
'https://www.googleapis.com/auth/cloud-platform',
|
||||||
|
'https://www.googleapis.com/auth/identitytoolkit',
|
||||||
|
'https://www.googleapis.com/auth/userinfo.profile'
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getName(): string
|
||||||
|
{
|
||||||
|
return 'firebase';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getLoginURL(): string
|
||||||
|
{
|
||||||
|
return 'https://accounts.google.com/o/oauth2/v2/auth?' . \http_build_query([
|
||||||
|
'access_type' => 'offline',
|
||||||
|
'client_id' => $this->appID,
|
||||||
|
'redirect_uri' => $this->callback,
|
||||||
|
'scope' => \implode(' ', $this->getScopes()),
|
||||||
|
'state' => \json_encode($this->state),
|
||||||
|
'response_type' => 'code',
|
||||||
|
'prompt' => 'consent',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $code
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
protected function getTokens(string $code): array
|
||||||
|
{
|
||||||
|
if (empty($this->tokens)) {
|
||||||
|
$response = $this->request(
|
||||||
|
'POST',
|
||||||
|
'https://oauth2.googleapis.com/token',
|
||||||
|
[],
|
||||||
|
\http_build_query([
|
||||||
|
'client_id' => $this->appID,
|
||||||
|
'redirect_uri' => $this->callback,
|
||||||
|
'client_secret' => $this->appSecret,
|
||||||
|
'code' => $code,
|
||||||
|
'grant_type' => 'authorization_code'
|
||||||
|
])
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->tokens = \json_decode($response, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->tokens;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $refreshToken
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function refreshTokens(string $refreshToken): array
|
||||||
|
{
|
||||||
|
$response = $this->request(
|
||||||
|
'POST',
|
||||||
|
'https://github.com/login/oauth/access_token',
|
||||||
|
[],
|
||||||
|
\http_build_query([
|
||||||
|
'client_id' => $this->appID,
|
||||||
|
'client_secret' => $this->appSecret,
|
||||||
|
'grant_type' => 'refresh_token',
|
||||||
|
'refresh_token' => $refreshToken
|
||||||
|
])
|
||||||
|
);
|
||||||
|
|
||||||
|
$output = [];
|
||||||
|
\parse_str($response, $output);
|
||||||
|
$this->tokens = $output;
|
||||||
|
|
||||||
|
if (empty($this->tokens['refresh_token'])) {
|
||||||
|
$this->tokens['refresh_token'] = $refreshToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->tokens;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $accessToken
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getUserID(string $accessToken): string
|
||||||
|
{
|
||||||
|
$user = $this->getUser($accessToken);
|
||||||
|
|
||||||
|
return $user['id'] ?? '';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $accessToken
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getUserEmail(string $accessToken): string
|
||||||
|
{
|
||||||
|
$user = $this->getUser($accessToken);
|
||||||
|
|
||||||
|
return $user['email'] ?? '';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the OAuth email is verified
|
||||||
|
*
|
||||||
|
* @link https://docs.github.com/en/rest/users/emails#list-email-addresses-for-the-authenticated-user
|
||||||
|
*
|
||||||
|
* @param string $accessToken
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function isEmailVerified(string $accessToken): bool
|
||||||
|
{
|
||||||
|
$user = $this->getUser($accessToken);
|
||||||
|
|
||||||
|
if ($user['verified'] ?? false) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $accessToken
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getUserName(string $accessToken): string
|
||||||
|
{
|
||||||
|
$user = $this->getUser($accessToken);
|
||||||
|
|
||||||
|
return $user['name'] ?? '';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $accessToken
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
protected function getUser(string $accessToken)
|
||||||
|
{
|
||||||
|
if (empty($this->user)) {
|
||||||
|
$response = $this->request(
|
||||||
|
'GET',
|
||||||
|
'https://www.googleapis.com/oauth2/v1/userinfo',
|
||||||
|
['Authorization: Bearer ' . \urlencode($accessToken)]
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->user = \json_decode($response, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->user;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getProjects(string $accessToken): array
|
||||||
|
{
|
||||||
|
$projects = $this->request('GET', 'https://firebase.googleapis.com/v1beta1/projects', ['Authorization: Bearer ' . \urlencode($accessToken)]);
|
||||||
|
|
||||||
|
$projects = \json_decode($projects, true);
|
||||||
|
|
||||||
|
return $projects['results'];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function createServiceAccount(string $accessToken, string $projectID): string
|
||||||
|
{
|
||||||
|
$response = $this->request(
|
||||||
|
'POST',
|
||||||
|
"https://iam.googleapis.com/v1/projects/{$projectID}/serviceAccounts",
|
||||||
|
[
|
||||||
|
'Authorization: Bearer ' . \urlencode($accessToken),
|
||||||
|
'Content-Type: application/json'
|
||||||
|
],
|
||||||
|
json_encode([
|
||||||
|
'accountId' => 'appwrite-migrations',
|
||||||
|
'serviceAccount' => [
|
||||||
|
'displayName' => 'Appwrite Migrations'
|
||||||
|
]
|
||||||
|
])
|
||||||
|
);
|
||||||
|
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
}
|
|
@ -35,6 +35,9 @@ class Event
|
||||||
public const MESSAGING_QUEUE_NAME = 'v1-messaging';
|
public const MESSAGING_QUEUE_NAME = 'v1-messaging';
|
||||||
public const MESSAGING_CLASS_NAME = 'MessagingV1';
|
public const MESSAGING_CLASS_NAME = 'MessagingV1';
|
||||||
|
|
||||||
|
public const MIGRATIONS_QUEUE_NAME = 'v1-migrations';
|
||||||
|
public const MIGRATIONS_CLASS_NAME = 'MigrationsV1';
|
||||||
|
|
||||||
protected string $queue = '';
|
protected string $queue = '';
|
||||||
protected string $class = '';
|
protected string $class = '';
|
||||||
protected string $event = '';
|
protected string $event = '';
|
||||||
|
|
98
src/Appwrite/Event/Migration.php
Normal file
98
src/Appwrite/Event/Migration.php
Normal file
|
@ -0,0 +1,98 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Appwrite\Event;
|
||||||
|
|
||||||
|
use DateTime;
|
||||||
|
use Resque;
|
||||||
|
use ResqueScheduler;
|
||||||
|
use Utopia\Database\Document;
|
||||||
|
|
||||||
|
class Migration extends Event
|
||||||
|
{
|
||||||
|
protected string $type = '';
|
||||||
|
protected ?Document $migration = null;
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
parent::__construct(Event::MIGRATIONS_QUEUE_NAME, Event::MIGRATIONS_CLASS_NAME);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets migration document for the migration event.
|
||||||
|
*
|
||||||
|
* @param Document $migration
|
||||||
|
* @return self
|
||||||
|
*/
|
||||||
|
public function setMigration(Document $migration): self
|
||||||
|
{
|
||||||
|
$this->migration = $migration;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns set migration document for the function event.
|
||||||
|
*
|
||||||
|
* @return null|Document
|
||||||
|
*/
|
||||||
|
public function getMigration(): ?Document
|
||||||
|
{
|
||||||
|
return $this->migration;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets migration type for the migration event.
|
||||||
|
*
|
||||||
|
* @param string $type
|
||||||
|
*
|
||||||
|
* @return self
|
||||||
|
*/
|
||||||
|
public function setType(string $type): self
|
||||||
|
{
|
||||||
|
$this->type = $type;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns set migration type for the migration event.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getType(): string
|
||||||
|
{
|
||||||
|
return $this->type;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Executes the migration event and sends it to the migrations worker.
|
||||||
|
*
|
||||||
|
* @return string|bool
|
||||||
|
* @throws \InvalidArgumentException
|
||||||
|
*/
|
||||||
|
public function trigger(): string|bool
|
||||||
|
{
|
||||||
|
return Resque::enqueue($this->queue, $this->class, [
|
||||||
|
'project' => $this->project,
|
||||||
|
'user' => $this->user,
|
||||||
|
'migration' => $this->migration
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Schedules the migration event and schedules it in the migrations worker queue.
|
||||||
|
*
|
||||||
|
* @param \DateTime|int $at
|
||||||
|
* @return void
|
||||||
|
* @throws \Resque_Exception
|
||||||
|
* @throws \ResqueScheduler_InvalidTimestampException
|
||||||
|
*/
|
||||||
|
public function schedule(DateTime|int $at): void
|
||||||
|
{
|
||||||
|
ResqueScheduler::enqueueAt($at, $this->queue, $this->class, [
|
||||||
|
'project' => $this->project,
|
||||||
|
'user' => $this->user,
|
||||||
|
'migration' => $this->migration
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
|
@ -32,6 +32,7 @@ class Exception extends \Exception
|
||||||
* - Platform
|
* - Platform
|
||||||
* - Domain
|
* - Domain
|
||||||
* - GraphQL
|
* - GraphQL
|
||||||
|
* - Migrations
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/** General */
|
/** General */
|
||||||
|
@ -199,6 +200,11 @@ class Exception extends \Exception
|
||||||
public const GRAPHQL_NO_QUERY = 'graphql_no_query';
|
public const GRAPHQL_NO_QUERY = 'graphql_no_query';
|
||||||
public const GRAPHQL_TOO_MANY_QUERIES = 'graphql_too_many_queries';
|
public const GRAPHQL_TOO_MANY_QUERIES = 'graphql_too_many_queries';
|
||||||
|
|
||||||
|
/** Migrations */
|
||||||
|
public const MIGRATION_NOT_FOUND = 'migration_not_found';
|
||||||
|
public const MIGRATION_ALREADY_EXISTS = 'migration_already_exists';
|
||||||
|
public const MIGRATION_IN_PROGRESS = 'migration_in_progress';
|
||||||
|
|
||||||
protected $type = '';
|
protected $type = '';
|
||||||
|
|
||||||
public function __construct(string $type = Exception::GENERAL_UNKNOWN, string $message = null, int $code = null, \Throwable $previous = null)
|
public function __construct(string $type = Exception::GENERAL_UNKNOWN, string $message = null, int $code = null, \Throwable $previous = null)
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Appwrite\Utopia\Database\Validator\Queries;
|
||||||
|
|
||||||
|
class Migrations extends Base
|
||||||
|
{
|
||||||
|
public const ALLOWED_ATTRIBUTES = [
|
||||||
|
'status',
|
||||||
|
'stage',
|
||||||
|
'source',
|
||||||
|
'resources',
|
||||||
|
'statusCounters',
|
||||||
|
'resourceData',
|
||||||
|
'errors'
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Expression constructor
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
parent::__construct('migrations', self::ALLOWED_ATTRIBUTES);
|
||||||
|
}
|
||||||
|
}
|
|
@ -88,6 +88,9 @@ use Appwrite\Utopia\Response\Model\UsageProject;
|
||||||
use Appwrite\Utopia\Response\Model\UsageStorage;
|
use Appwrite\Utopia\Response\Model\UsageStorage;
|
||||||
use Appwrite\Utopia\Response\Model\UsageUsers;
|
use Appwrite\Utopia\Response\Model\UsageUsers;
|
||||||
use Appwrite\Utopia\Response\Model\Variable;
|
use Appwrite\Utopia\Response\Model\Variable;
|
||||||
|
use Appwrite\Utopia\Response\Model\Migration;
|
||||||
|
use Appwrite\Utopia\Response\Model\MigrationFirebaseProject;
|
||||||
|
use Appwrite\Utopia\Response\Model\MigrationReport;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @method int getStatusCode()
|
* @method int getStatusCode()
|
||||||
|
@ -198,6 +201,13 @@ class Response extends SwooleResponse
|
||||||
public const MODEL_BUILD_LIST = 'buildList'; // Not used anywhere yet
|
public const MODEL_BUILD_LIST = 'buildList'; // Not used anywhere yet
|
||||||
public const MODEL_FUNC_PERMISSIONS = 'funcPermissions';
|
public const MODEL_FUNC_PERMISSIONS = 'funcPermissions';
|
||||||
|
|
||||||
|
// Migrations
|
||||||
|
public const MODEL_MIGRATION = 'migration';
|
||||||
|
public const MODEL_MIGRATION_LIST = 'migrationList';
|
||||||
|
public const MODEL_MIGRATION_REPORT = 'migrationReport';
|
||||||
|
public const MODEL_MIGRATION_FIREBASE_PROJECT = 'firebaseProject';
|
||||||
|
public const MODEL_MIGRATION_FIREBASE_PROJECT_LIST = 'firebaseProjectList';
|
||||||
|
|
||||||
// Project
|
// Project
|
||||||
public const MODEL_PROJECT = 'project';
|
public const MODEL_PROJECT = 'project';
|
||||||
public const MODEL_PROJECT_LIST = 'projectList';
|
public const MODEL_PROJECT_LIST = 'projectList';
|
||||||
|
@ -288,6 +298,8 @@ class Response extends SwooleResponse
|
||||||
->setModel(new BaseList('Metric List', self::MODEL_METRIC_LIST, 'metrics', self::MODEL_METRIC, true, false))
|
->setModel(new BaseList('Metric List', self::MODEL_METRIC_LIST, 'metrics', self::MODEL_METRIC, true, false))
|
||||||
->setModel(new BaseList('Variables List', self::MODEL_VARIABLE_LIST, 'variables', self::MODEL_VARIABLE))
|
->setModel(new BaseList('Variables List', self::MODEL_VARIABLE_LIST, 'variables', self::MODEL_VARIABLE))
|
||||||
->setModel(new BaseList('Locale codes list', self::MODEL_LOCALE_CODE_LIST, 'localeCodes', self::MODEL_LOCALE_CODE))
|
->setModel(new BaseList('Locale codes list', self::MODEL_LOCALE_CODE_LIST, 'localeCodes', self::MODEL_LOCALE_CODE))
|
||||||
|
->setModel(new BaseList('Migrations List', self::MODEL_MIGRATION_LIST, 'migrations', self::MODEL_MIGRATION))
|
||||||
|
->setModel(new BaseList('Migrations Firebase Projects List', self::MODEL_MIGRATION_FIREBASE_PROJECT_LIST, 'projects', self::MODEL_MIGRATION_FIREBASE_PROJECT))
|
||||||
// Entities
|
// Entities
|
||||||
->setModel(new Database())
|
->setModel(new Database())
|
||||||
->setModel(new Collection())
|
->setModel(new Collection())
|
||||||
|
@ -360,6 +372,9 @@ class Response extends SwooleResponse
|
||||||
->setModel(new TemplateSMS())
|
->setModel(new TemplateSMS())
|
||||||
->setModel(new TemplateEmail())
|
->setModel(new TemplateEmail())
|
||||||
->setModel(new ConsoleVariables())
|
->setModel(new ConsoleVariables())
|
||||||
|
->setModel(new Migration())
|
||||||
|
->setModel(new MigrationReport())
|
||||||
|
->setModel(new MigrationFirebaseProject())
|
||||||
// Verification
|
// Verification
|
||||||
// Recovery
|
// Recovery
|
||||||
// Tests (keep last)
|
// Tests (keep last)
|
||||||
|
|
96
src/Appwrite/Utopia/Response/Model/Migration.php
Normal file
96
src/Appwrite/Utopia/Response/Model/Migration.php
Normal file
|
@ -0,0 +1,96 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Appwrite\Utopia\Response\Model;
|
||||||
|
|
||||||
|
use Appwrite\Utopia\Response;
|
||||||
|
use Appwrite\Utopia\Response\Model;
|
||||||
|
|
||||||
|
class Migration extends Model
|
||||||
|
{
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this
|
||||||
|
->addRule('$id', [
|
||||||
|
'type' => self::TYPE_STRING,
|
||||||
|
'description' => 'Migration ID.',
|
||||||
|
'default' => '',
|
||||||
|
'example' => '5e5ea5c16897e',
|
||||||
|
])
|
||||||
|
->addRule('$createdAt', [
|
||||||
|
'type' => self::TYPE_DATETIME,
|
||||||
|
'description' => 'Variable creation date in ISO 8601 format.',
|
||||||
|
'default' => '',
|
||||||
|
'example' => self::TYPE_DATETIME_EXAMPLE,
|
||||||
|
])
|
||||||
|
->addRule('$updatedAt', [
|
||||||
|
'type' => self::TYPE_DATETIME,
|
||||||
|
'description' => 'Variable creation date in ISO 8601 format.',
|
||||||
|
'default' => '',
|
||||||
|
'example' => self::TYPE_DATETIME_EXAMPLE,
|
||||||
|
])
|
||||||
|
->addRule('status', [
|
||||||
|
'type' => self::TYPE_STRING,
|
||||||
|
'description' => 'Migration status ( pending, processing, failed. completed ) ',
|
||||||
|
'default' => '',
|
||||||
|
'example' => 'pending',
|
||||||
|
])
|
||||||
|
->addRule('stage', [
|
||||||
|
'type' => self::TYPE_STRING,
|
||||||
|
'description' => 'Migration stage ( init, processing, source-check, destination-check, migrating, finished )',
|
||||||
|
'default' => '',
|
||||||
|
'example' => 'init',
|
||||||
|
])
|
||||||
|
->addRule('source', [
|
||||||
|
'type' => self::TYPE_STRING,
|
||||||
|
'description' => 'A string containing the type of source of the migration.',
|
||||||
|
'default' => '',
|
||||||
|
'example' => 'Appwrite',
|
||||||
|
])
|
||||||
|
->addRule('resources', [
|
||||||
|
'type' => self::TYPE_STRING,
|
||||||
|
'description' => 'Resources to migration.',
|
||||||
|
'default' => [],
|
||||||
|
'example' => ['user'],
|
||||||
|
'array' => true
|
||||||
|
])
|
||||||
|
->addRule('statusCounters', [
|
||||||
|
'type' => self::TYPE_JSON,
|
||||||
|
'description' => 'A group of counters that represent the total progress of the migration.',
|
||||||
|
'default' => [],
|
||||||
|
'example' => '{"Database": {"PENDING": 0, "SUCCESS": 1, "ERROR": 0, "SKIP": 0, "PROCESSING": 0, "WARNING": 0}}',
|
||||||
|
])
|
||||||
|
->addRule('resourceData', [
|
||||||
|
'type' => self::TYPE_JSON,
|
||||||
|
'description' => 'An array of objects containing the report data of the resources that were migrated.',
|
||||||
|
'default' => [],
|
||||||
|
'example' => '[{"resource":"Database","id":"public","status":"SUCCESS","message":""}]',
|
||||||
|
])
|
||||||
|
->addRule('errors', [
|
||||||
|
'type' => self::TYPE_STRING,
|
||||||
|
'description' => 'All errors that occurred during the migration process.',
|
||||||
|
'default' => [],
|
||||||
|
'example' => [],
|
||||||
|
])
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Name
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getName(): string
|
||||||
|
{
|
||||||
|
return 'Migration';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Type
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getType(): string
|
||||||
|
{
|
||||||
|
return Response::MODEL_MIGRATION;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,46 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Appwrite\Utopia\Response\Model;
|
||||||
|
|
||||||
|
use Appwrite\Utopia\Response;
|
||||||
|
use Appwrite\Utopia\Response\Model;
|
||||||
|
|
||||||
|
class MigrationFirebaseProject extends Model
|
||||||
|
{
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this
|
||||||
|
->addRule('projectId', [
|
||||||
|
'type' => self::TYPE_STRING,
|
||||||
|
'description' => 'Project ID.',
|
||||||
|
'default' => '',
|
||||||
|
'example' => 'my-project',
|
||||||
|
])
|
||||||
|
->addRule('displayName', [
|
||||||
|
'type' => self::TYPE_STRING,
|
||||||
|
'description' => 'Project display name.',
|
||||||
|
'default' => '',
|
||||||
|
'example' => 'My Project',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Name
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getName(): string
|
||||||
|
{
|
||||||
|
return 'MigrationFirebaseProject';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Type
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getType(): string
|
||||||
|
{
|
||||||
|
return Response::MODEL_MIGRATION_FIREBASE_PROJECT;
|
||||||
|
}
|
||||||
|
}
|
89
src/Appwrite/Utopia/Response/Model/MigrationReport.php
Normal file
89
src/Appwrite/Utopia/Response/Model/MigrationReport.php
Normal file
|
@ -0,0 +1,89 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Appwrite\Utopia\Response\Model;
|
||||||
|
|
||||||
|
use Appwrite\Utopia\Response;
|
||||||
|
use Appwrite\Utopia\Response\Model;
|
||||||
|
use Utopia\Transfer\Resource;
|
||||||
|
|
||||||
|
class MigrationReport extends Model
|
||||||
|
{
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this
|
||||||
|
->addRule(Resource::TYPE_USER, [
|
||||||
|
'type' => self::TYPE_INTEGER,
|
||||||
|
'description' => 'Number of users to be migrated.',
|
||||||
|
'default' => 0,
|
||||||
|
'example' => 20,
|
||||||
|
])
|
||||||
|
->addRule(Resource::TYPE_TEAM, [
|
||||||
|
'type' => self::TYPE_INTEGER,
|
||||||
|
'description' => 'Number of teams to be migrated.',
|
||||||
|
'default' => 0,
|
||||||
|
'example' => 20,
|
||||||
|
])
|
||||||
|
->addRule(Resource::TYPE_DATABASE, [
|
||||||
|
'type' => self::TYPE_INTEGER,
|
||||||
|
'description' => 'Number of databases to be migrated.',
|
||||||
|
'default' => 0,
|
||||||
|
'example' => 20,
|
||||||
|
])
|
||||||
|
->addRule(Resource::TYPE_DOCUMENT, [
|
||||||
|
'type' => self::TYPE_INTEGER,
|
||||||
|
'description' => 'Number of documents to be migrated.',
|
||||||
|
'default' => 0,
|
||||||
|
'example' => 20,
|
||||||
|
])
|
||||||
|
->addRule(Resource::TYPE_FILE, [
|
||||||
|
'type' => self::TYPE_INTEGER,
|
||||||
|
'description' => 'Number of files to be migrated.',
|
||||||
|
'default' => 0,
|
||||||
|
'example' => 20,
|
||||||
|
])
|
||||||
|
->addRule(Resource::TYPE_BUCKET, [
|
||||||
|
'type' => self::TYPE_INTEGER,
|
||||||
|
'description' => 'Number of buckets to be migrated.',
|
||||||
|
'default' => 0,
|
||||||
|
'example' => 20,
|
||||||
|
])
|
||||||
|
->addRule(Resource::TYPE_FUNCTION, [
|
||||||
|
'type' => self::TYPE_INTEGER,
|
||||||
|
'description' => 'Number of functions to be migrated.',
|
||||||
|
'default' => 0,
|
||||||
|
'example' => 20,
|
||||||
|
])
|
||||||
|
->addRule('size', [
|
||||||
|
'type' => self::TYPE_INTEGER,
|
||||||
|
'description' => 'Size of files to be migrated in mb.',
|
||||||
|
'default' => 0,
|
||||||
|
'example' => 30000,
|
||||||
|
])
|
||||||
|
->addRule('version', [
|
||||||
|
'type' => self::TYPE_STRING,
|
||||||
|
'description' => 'Version of the Appwrite instance to be migrated.',
|
||||||
|
'default' => '',
|
||||||
|
'example' => '1.4.0',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Name
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getName(): string
|
||||||
|
{
|
||||||
|
return 'Migration Report';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Type
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getType(): string
|
||||||
|
{
|
||||||
|
return Response::MODEL_MIGRATION_REPORT;
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue