groups(['api', 'transfers']) ->desc('Create Transfer') ->label('scope', 'transfers.write') ->label('event', 'transfers.[transferId].create') ->label('audits.event', 'transfers.create') ->label('audits.resource', 'transfers/{response.$id}') ->label('sdk.auth', [APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'transfers') ->label('sdk.method', 'create') ->label('sdk.description', '/docs/references/transfers/create-transfer.md') ->label('sdk.response.code', Response::STATUS_CODE_CREATED) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_TRANSFER) ->param('transferId', 'unique()', new CustomId(), 'Transfer unique ID. Use \'unique()\' to auto generate a unique ID for this transfer.') ->param('source', '', new UID(), 'Source UID. [Learn more about sources](https://appwrite.io/docs/transfers/sources)', false) ->param('destination', '', new UID(), 'Destination UID. [Learn more about destinations](https://appwrite.io/docs/transfers/sources)', false) ->param('resources', [], new ArrayList(new WhiteList(TRANSFER_RESOURCES)), 'List of resources to transfer. [A list of resources can be found here.](https://appwrite.io/docs/transfers#resources)', false) ->inject('response') ->inject('dbForProject') ->inject('project') ->inject('user') ->inject('events') ->action(function (string $transferId, string $source, string $destination, array $resources, Response $response, Database $dbForProject, Document $project, Document $user, Event $eventsInstance) { $cron = !empty($schedule) ? new CronExpression($schedule) : null; $next = !empty($schedule) ? DateTime::format($cron->getNextRunDate()) : null; $transferId = ($transferId == 'unique()') ? ID::unique() : $transferId; $transfer = $dbForProject->createDocument('transfers', new Document([ '$id' => $transferId, 'status' => 'pending', 'stage' => 'init', 'source' => $source, 'destination' => $destination, 'resources' => $resources, 'totalProgress' => json_encode([ 'source' => [], 'destination' => [], ]), 'latestProgress' => "{}", 'errorData' => "" ])); $eventsInstance->setParam('transferId', $transfer->getId()); // Trigger Transfer $event = new Transfer(); $event ->setTransfer($transfer) ->setProject($project) ->setUser($user) ->trigger(); $response ->setStatusCode(Response::STATUS_CODE_CREATED) ->dynamic($transfer, Response::MODEL_TRANSFER); }); App::get('/v1/transfers') ->groups(['api', 'transfers']) ->desc('List Transfers') ->label('scope', 'transfers.read') ->label('sdk.auth', [APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'transfers') ->label('sdk.method', 'list') ->label('sdk.description', '/docs/references/transfers/list-transfers.md') ->label('sdk.response.code', Response::STATUS_CODE_OK) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_TRANSFER_LIST) ->param('queries', [], new Transfers(), '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(', ', Transfers::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 */ $transferId = $cursor->getValue(); $cursorDocument = $dbForProject->getDocument('transfers', $transferId); if ($cursorDocument->isEmpty()) { throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "Transfer '{$transferId}' for the 'cursor' value not found."); } $cursor->setValue($cursorDocument); } $filterQueries = Query::groupByType($queries)['filters']; $response->dynamic(new Document([ 'transfers' => $dbForProject->find('transfers', $queries), 'total' => $dbForProject->count('transfers', $filterQueries, APP_LIMIT_COUNT), ]), Response::MODEL_TRANSFER_LIST); }); App::get('/v1/transfers/:transferId') ->groups(['api', 'transfers']) ->desc('Get Transfer') ->label('scope', 'transfers.read') ->label('sdk.auth', [APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'transfers') ->label('sdk.method', 'get') ->label('sdk.description', '/docs/references/transfers/get-transfer.md') ->label('sdk.response.code', Response::STATUS_CODE_OK) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_TRANSFER) ->param('transferId', '', new UID(), 'Transfer unique ID.') ->inject('response') ->inject('dbForProject') ->action(function (string $transferId, Response $response, Database $dbForProject) { $transfer = $dbForProject->getDocument('transfers', $transferId); if ($transfer->isEmpty()) { throw new Exception(Exception::TRANSFER_NOT_FOUND, 'Transfer not found', 404); } $response->dynamic($transfer, Response::MODEL_TRANSFER); }); App::post('/v1/transfers/:transferId') ->groups(['api', 'transfers']) ->desc('Retry Transfer') ->label('scope', 'transfers.write') ->label('event', 'transfers.[transferId].retry') ->label('audits.event', 'transfer.retry') ->label('audits.resource', 'transfers/{request.transferId}') ->label('sdk.auth', [APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'transfers') ->label('sdk.method', 'retry') ->label('sdk.description', '/docs/references/transfers/retry-transfer.md') ->label('sdk.response.code', Response::STATUS_CODE_OK) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_TRANSFER) ->param('transferId', '', new UID(), 'Transfer unique ID.') ->inject('response') ->inject('dbForProject') ->inject('project') ->inject('user') ->inject('events') ->action(function (string $transferId, Response $response, Database $dbForProject, Document $project, Document $user, Event $eventInstance) { $transfer = $dbForProject->getDocument('transfers', $transferId); if ($transfer->isEmpty()) { throw new Exception(Exception::TRANSFER_NOT_FOUND); } // if ($transfer->getAttribute('status') !== 'failed') { // throw new Exception(Exception::TRANSFER_IN_PROGRESS, 'Transfer not failed'); // } $transfer ->setAttribute('status', 'pending') ->setAttribute('dateUpdated', \time()); // Trigger Transfer $event = new Transfer(); $event ->setTransfer($transfer) ->setProject($project) ->setUser($user) ->trigger(); $response->noContent(); }); App::delete('/v1/transfers/:transferId') ->groups(['api', 'transfers']) ->desc('Delete Transfer') ->label('scope', 'transfers.write') ->label('event', 'transfers.[transferId].delete') ->label('audits.event', 'transfer.delete') ->label('audits.resource', 'transfers/{request.transferId}') ->label('sdk.auth', [APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'transfers') ->label('sdk.method', 'delete') ->label('sdk.description', '/docs/references/functions/delete-transfer.md') ->label('sdk.response.code', Response::STATUS_CODE_NOCONTENT) ->label('sdk.response.model', Response::MODEL_NONE) ->param('transferId', '', new UID(), 'Transfer ID.') ->inject('response') ->inject('dbForProject') ->inject('deletes') ->inject('events') ->action(function (string $transferId, Response $response, Database $dbForProject, Delete $deletes, Event $events) { $transfer = $dbForProject->getDocument('transfers', $transferId); if ($transfer->isEmpty()) { throw new Exception(Exception::TRANSFER_NOT_FOUND, 'Transfer not found', 404); } if (!$dbForProject->deleteDocument('transfers', $transfer->getId())) { throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Failed to remove transfer from DB', 500); } $deletes ->setType(DELETE_TYPE_DOCUMENT) ->setDocument($transfer); $events->setParam('transferId', $transfer->getId()); $response->noContent(); }); App::get('/v1/transfers/sources') ->groups(['api', 'transfers']) ->desc('List Sources') ->label('scope', 'transfers.read') ->label('sdk.auth', [APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'transfers') ->label('sdk.method', 'listSources') ->label('sdk.description', '/docs/references/transfers/list-sources.md') ->label('sdk.response.code', Response::STATUS_CODE_OK) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_SOURCE_LIST) ->param('queries', [], new Sources(), '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(', ', Transfers::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 */ $sourceId = $cursor->getValue(); $cursorDocument = $dbForProject->getDocument('sources', $sourceId); if ($cursorDocument->isEmpty()) { throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "Source '{$sourceId}' for the 'cursor' value not found."); } $cursor->setValue($cursorDocument); } $filterQueries = Query::groupByType($queries)['filters']; $response->dynamic(new Document([ 'sources' => $dbForProject->find('sources', $queries), 'total' => $dbForProject->count('sources', $filterQueries, APP_LIMIT_COUNT), ]), Response::MODEL_SOURCE_LIST); }); App::get('/v1/transfers/source/:sourceId') ->groups(['api', 'transfers']) ->desc('Get Source') ->label('scope', 'transfers.read') ->label('sdk.auth', [APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'transfers') ->label('sdk.method', 'getSource') ->label('sdk.description', '/docs/references/transfers/get-source.md') ->label('sdk.response.code', Response::STATUS_CODE_OK) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_SOURCE) ->param('sourceId', '', new UID(), 'Source unique ID.') ->inject('response') ->inject('dbForProject') ->action(function (string $sourceId, Response $response, Database $dbForProject) { $source = $dbForProject->getDocument('sources', $sourceId); if ($source->isEmpty()) { throw new Exception(Exception::TRANSFER_SOURCE_NOT_FOUND, 'Source not found', 404); } $response->dynamic($source, Response::MODEL_SOURCE); }); App::post('/v1/transfers/source/:sourceId/validate') ->groups(['api', 'transfers']) ->desc('Validate Source') ->label('scope', 'transfers.write') ->label('sdk.auth', [APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'transfers') ->label('sdk.method', 'validateSource') ->label('sdk.description', '/docs/references/transfers/validate-source.md') ->label('sdk.response.code', Response::STATUS_CODE_OK) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_SOURCE_VALIDATION) ->param('sourceId', '', new UID(), 'Source unique ID.') ->param('resources', TRANSFER_RESOURCES, new ArrayList(new WhiteList(TRANSFER_RESOURCES)), 'List of resources to test. If none are sent then all resources are tested. [A list of resources can be found here.](https://appwrite.io/docs/transfers#resources)', true) ->inject('response') ->inject('dbForProject') ->action(function (string $sourceId, array $resources, Response $response, Database $dbForProject) { $source = $dbForProject->getDocument('sources', $sourceId); if ($source->isEmpty()) { throw new Exception(Exception::TRANSFER_SOURCE_NOT_FOUND, 'Source not found', 404); } $authData = json_decode($source->getAttribute('data', "{}"), true); try { $testAdapter = null; switch ($source['type']) { case 'appwrite': { $testAdapter = new SourcesAppwrite($authData['project'], $authData['endpoint'], $authData['key']); break; } case 'firebase': { $testAdapter = new Firebase($authData['authObject'], Firebase::AUTH_SERVICEACCOUNT); break; } case 'supabase': { $testAdapter = new Supabase($authData['url'], $authData['database'], $authData['username'], $authData['password'], $authData['port']); break; } case 'nhost': { $testAdapter = new NHost($authData['url'], $authData['database'], $authData['username'], $authData['password'], $authData['port']); break; } default: { throw new Exception(Exception::TRANSFER_SOURCE_NOT_FOUND, 'Source not found', 404); } } $result = $testAdapter->check($resources); $result = array_filter($result, function ($value) { return $value !== []; }); if (count($result) == 0) { $source->setAttribute('lastCheck', json_encode([ 'success' => true, 'message' => 'Source is valid' ])); return $response->dynamic(new Document([ 'success' => true, 'message' => 'Source is valid' ]), Response::MODEL_SOURCE_VALIDATION); } else { $source->setAttribute('lastCheck', json_encode([ 'success' => false, 'message' => 'Missing Permissions', 'errors' => $result ])); return $response->setStatusCode(401)->dynamic(new Document([ 'success' => false, 'message' => 'Missing Permissions', 'errors' => $result ]), Response::MODEL_SOURCE_VALIDATION); } } catch (Throwable $e) { $source->setAttribute('lastCheck', json_encode([ 'success' => false, 'message' => 'Missing Permissions', 'errors' => [ 'Databases' => [$e->getMessage()], ], ])); return $response->setStatusCode(401)->dynamic(new Document([ 'success' => false, 'message' => 'Missing Permissions', 'errors' => [ 'Databases' => [$e->getMessage()], ], ]), Response::MODEL_SOURCE_VALIDATION); } finally { $dbForProject->updateDocument('sources', $source->getId(), $source); } }); App::delete('/v1/transfers/source/:sourceId') ->groups(['api', 'transfers']) ->desc('Delete Source') ->label('scope', 'transfers.write') ->label('sdk.auth', [APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'transfers') ->label('sdk.method', 'deleteSource') ->label('sdk.description', '/docs/references/transfers/delete-source.md') ->label('sdk.response.code', Response::STATUS_CODE_NOCONTENT) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->param('sourceId', '', new UID(), 'Source unique ID.') ->inject('response') ->inject('dbForProject') ->action(function (string $sourceId, Response $response, Database $dbForProject) { $source = $dbForProject->getDocument('sources', $sourceId); if ($source->isEmpty()) { throw new Exception(Exception::TRANSFER_SOURCE_NOT_FOUND, 'Source not found', 404); } $dbForProject->deleteDocument('sources', $source->getId()); $response->noContent(); }); App::get('/v1/transfers/destinations') ->groups(['api', 'transfers']) ->desc('List Destinations') ->label('scope', 'transfers.read') ->label('sdk.auth', [APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'transfers') ->label('sdk.method', 'listDestinations') ->label('sdk.description', '/docs/references/transfers/list-destinations.md') ->label('sdk.response.code', Response::STATUS_CODE_OK) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_DESTINATION_LIST) ->param('queries', [], new Destinations(), '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(', ', Transfers::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 */ $destinationId = $cursor->getValue(); $cursorDocument = $dbForProject->getDocument('destinations', $destinationId); if ($cursorDocument->isEmpty()) { throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "Source '{$destinationId}' for the 'cursor' value not found."); } $cursor->setValue($cursorDocument); } $filterQueries = Query::groupByType($queries)['filters']; $response->dynamic(new Document([ 'destinations' => $dbForProject->find('destinations', $queries), 'total' => $dbForProject->count('destinations', $filterQueries, APP_LIMIT_COUNT), ]), Response::MODEL_DESTINATION_LIST); }); App::get('/v1/transfers/destination/:destinationId') ->groups(['api', 'transfers']) ->desc('Get Destination') ->label('scope', 'transfers.read') ->label('sdk.auth', [APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'transfers') ->label('sdk.method', 'getDestination') ->label('sdk.description', '/docs/references/transfers/get-destination.md') ->label('sdk.response.code', Response::STATUS_CODE_OK) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_DESTINATION) ->param('destinationId', '', new UID(), 'Destination unique ID.') ->inject('response') ->inject('dbForProject') ->action(function (string $desinationId, Response $response, Database $dbForProject) { $destination = $dbForProject->getDocument('destinations', $desinationId); if ($destination->isEmpty()) { throw new Exception(Exception::TRANSFER_DESTINATION_NOT_FOUND, 'Destination not found', 404); } $response->dynamic($destination, Response::MODEL_DESTINATION); }); App::post('/v1/transfers/destination/:destinationId/validate') ->groups(['api', 'transfers']) ->desc('Validate Destination') ->label('scope', 'transfers.write') ->label('sdk.auth', [APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'transfers') ->label('sdk.method', 'validateDestination') ->label('sdk.description', '/docs/references/transfers/validate-destination.md') ->label('sdk.response.code', Response::STATUS_CODE_OK) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_DESTINATION_VALIDATION) ->param('destinationId', '', new UID(), 'Destination unique ID.') ->param('resources', TRANSFER_RESOURCES, new ArrayList(new WhiteList(TRANSFER_RESOURCES)), 'List of resources to test. If none are sent then all resources are tested. [A list of resources can be found here.](https://appwrite.io/docs/transfers#resources)', true) ->inject('response') ->inject('dbForProject') ->action(function (string $destinationId, array $resources, Response $response, Database $dbForProject) { $destination = $dbForProject->getDocument('destinations', $destinationId); if ($destination->isEmpty()) { throw new Exception(Exception::TRANSFER_DESTINATION_NOT_FOUND, 'Destination not found', 404); } $authData = json_decode($destination->getAttribute('data', "{}"), true); try { $testAdapter = null; switch ($destination['type']) { case 'appwrite': { $testAdapter = new Appwrite($authData['projectId'], $authData['endpoint'], $authData['key']); break; } default: { throw new Exception(Exception::TRANSFER_DESTINATION_NOT_FOUND, 'Destination not found', 404); } } $result = $testAdapter->check($resources); $result = array_filter($result, function ($value) { return $value !== []; }); if (count($result) == 0) { $destination->setAttribute('lastCheck', json_encode([ 'success' => true, 'message' => 'Destination is valid' ])); return $response->dynamic(new Document([ 'success' => true, 'message' => 'Destination is valid' ]), Response::MODEL_DESTINATION_VALIDATION); } else { $destination->setAttribute('lastCheck', json_encode([ 'success' => false, 'message' => 'Missing Permissions', 'errors' => $result ])); return $response->setStatusCode(401)->dynamic(new Document([ 'success' => false, 'message' => 'Missing Permissions', 'errors' => $result ]), Response::MODEL_DESTINATION_VALIDATION); } } catch (Throwable $e) { $destination->setAttribute('lastCheck', json_encode([ 'success' => false, 'message' => 'Missing Permissions', 'errors' => [ 'Databases' => [$e->getMessage()], ], ])); return $response->setStatusCode(401)->dynamic(new Document([ 'success' => false, 'message' => 'Missing Permissions', 'errors' => [ 'Databases' => [$e->getMessage()], ], ]), Response::MODEL_DESTINATION_VALIDATION); } finally { $dbForProject->updateDocument('destinations', $destination->getId(), $destination); } }); App::delete('/v1/transfers/destinations/:destinationId') ->groups(['api', 'transfers']) ->desc('Delete Destination') ->label('scope', 'transfers.write') ->label('sdk.auth', [APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'transfers') ->label('sdk.method', 'deleteDestination') ->label('sdk.description', '/docs/references/transfers/delete-destination.md') ->label('sdk.response.code', Response::STATUS_CODE_NOCONTENT) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_NONE) ->param('destinationId', '', new UID(), 'Destination unique ID.') ->inject('response') ->inject('dbForProject') ->action(function (string $destinationId, Response $response, Database $dbForProject) { $destination = $dbForProject->getDocument('destinations', $destinationId); if ($destination->isEmpty()) { throw new Exception(Exception::TRANSFER_DESTINATION_NOT_FOUND, 'Destination not found', 404); } $dbForProject->deleteDocument('destinations', $destination->getId()); $response->noContent(); }); App::post('/v1/transfers/sources/appwrite') ->groups(['api', 'transfers']) ->desc('Create Appwrite Source') ->label('scope', 'transfers.write') ->label('event', 'transfers.[sourceId].createAppwriteSource') ->label('audits.event', 'transfers.createAppwriteSource') ->label('audits.resource', 'sources/{response.$id}') ->label('sdk.auth', [APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'transfers') ->label('sdk.method', 'createAppwriteSource') ->label('sdk.description', '/docs/references/transfers/create-appwrite-source.md') ->label('sdk.response.code', Response::STATUS_CODE_CREATED) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_SOURCE) ->param('sourceId', 'unique()', new CustomId(), 'Source unique ID. Use \'unique()\' to auto generate a unique ID for this source.', true) ->param('name', '', new Text(256), 'Source Name. Max length: 256 chars.', true) ->param('projectId', '', new UID(), 'Source Project UID. The UID of the project to transfer.', false) ->param('endpoint', '', new URL(), 'Source Endpoint. The endpoint of the project to transfer.', false) ->param('key', '', new Text(100), 'Source Key. The key of the project to transfer.', false) ->inject('response') ->inject('dbForProject') ->inject('project') ->inject('user') ->inject('events') ->action(function (string $sourceId, string $name, string $projectId, string $endpoint, string $key, Response $response, Database $dbForProject, Document $project, Document $user, Event $eventsInstance) { $sourceId = ($sourceId == 'unique()') ? ID::unique() : $sourceId; $testAdapter = new SourcesAppwrite($projectId, $endpoint, $key); $testAdapter->check(); // Throws exception on failure $source = $dbForProject->createDocument('sources', new Document([ '$id' => $sourceId, '$collection' => ID::custom('sources'), 'type' => 'appwrite', 'name' => empty($name) ? 'Appwrite Project: ' . $sourceId : $name, 'lastCheck' => json_encode([ 'success' => true, 'message' => 'Source is valid' ]), 'data' => json_encode([ 'projectId' => $projectId, 'endpoint' => $endpoint, 'key' => $key, ]) ])); $eventsInstance->setParam('sourceId', $source->getId()); $response ->setStatusCode(Response::STATUS_CODE_CREATED) ->dynamic($source, Response::MODEL_SOURCE); }); App::post('/v1/transfers/sources/appwrite/validate') ->groups(['api', 'transfers']) ->desc('Validate Appwrite Source') ->label('scope', 'transfers.write') ->label('audits.resource', 'sources/{response.$id}') ->label('sdk.auth', [APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'transfers') ->label('sdk.method', 'validateAppwriteSource') ->label('sdk.description', '/docs/references/transfers/validate-appwrite-source.md') ->label('sdk.response.code', Response::STATUS_CODE_OK) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_SOURCE_VALIDATION) ->param('projectId', '', new UID(), 'Source Project UID. The UID of the project to transfer.', false) ->param('endpoint', '', new URL(), 'Source Endpoint. The endpoint of the project to transfer.', false) ->param('key', '', new Text(100), 'Source Key. The key of the project to transfer.', false) ->inject('response') ->action(function (string $projectId, string $endpoint, string $key, Response $response) { $testAdapter = new SourcesAppwrite($projectId, $endpoint, $key); try { $testAdapter->check(); // Throws exception on failure } catch (Exception $e) { return $response->setStatusCode(401)->dynamic(new Document([ 'success' => false, 'message' => 'Missing Permissions', 'errors' => [ 'Databases' => [$e->getMessage()], ], ]), Response::MODEL_DESTINATION_VALIDATION); } return $response->json(TRANSFER_RESOURCES); }); App::post('/v1/transfers/sources/firebase') ->groups(['api', 'transfers']) ->desc('Create Firebase Source') ->label('scope', 'transfers.write') ->label('event', 'transfers.[sourceId].createFirebaseSource') ->label('audits.event', 'transfers.createFirebaseSource') ->label('audits.resource', 'sources/{response.$id}') ->label('sdk.auth', [APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'transfers') ->label('sdk.method', 'createFirebaseSource') ->label('sdk.description', '/docs/references/transfers/create-firebase-source.md') ->label('sdk.response.code', Response::STATUS_CODE_CREATED) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_SOURCE) ->param('sourceId', 'unique()', new CustomId(), 'Source unique ID. Use \'unique()\' to auto generate a unique ID for this source.', true) ->param('name', '', new Text(256), 'Source Name. Max length: 256 chars.', true) ->param('serviceAccount', '', new JSON(), 'Firebase Service account with all required scopes, [Learn more about Firebase Transfer](https://appwrite.io/docs/transfers/sources#firebase)', false) ->inject('response') ->inject('dbForProject') ->inject('project') ->inject('user') ->inject('events') ->action(function (string $sourceId, string $name, array $serviceAccount, Response $response, Database $dbForProject, Document $project, Document $user, Event $eventsInstance) { $sourceId = ($sourceId == 'unique()') ? ID::unique() : $sourceId; $testAdapter = new Firebase($serviceAccount, Firebase::AUTH_SERVICEACCOUNT); $testAdapter->check(); // Throws exception on failure $source = $dbForProject->createDocument('sources', new Document([ '$id' => $sourceId, '$collection' => ID::custom('sources'), 'type' => 'firebase', 'name' => empty($name) ? 'Firebase Project ' . $serviceAccount['project_id'] : $name, 'lastCheck' => json_encode([ 'success' => true, 'message' => 'Source is valid' ]), 'data' => json_encode([ 'serviceAccount' => $serviceAccount, ]) ])); $eventsInstance->setParam('sourceId', $source->getId()); $response ->setStatusCode(Response::STATUS_CODE_CREATED) ->dynamic($source, Response::MODEL_SOURCE); }); App::post('/v1/transfers/sources/firebase/validate') ->groups(['api', 'transfers']) ->desc('Validate Firebase Source') ->label('scope', 'transfers.write') ->label('audits.resource', 'sources/{response.$id}') ->label('sdk.auth', [APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'transfers') ->label('sdk.method', 'validateFirebaseSource') ->label('sdk.description', '/docs/references/transfers/validate-firebase-source.md') ->label('sdk.response.code', Response::STATUS_CODE_OK) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_SOURCE_VALIDATION) ->param('serviceAccount', '', new JSON(), 'Firebase Service account with all required scopes, [Learn more about Firebase Transfer](https://appwrite.io/docs/transfers/sources#firebase)', false) ->inject('response') ->inject('events') ->action(function (string $serviceAccount, Response $response, Event $eventsInstance) { $testAdapter = new Firebase(json_decode($serviceAccount, true), Firebase::AUTH_SERVICEACCOUNT); try { $testAdapter->check(); // Throws exception on failure } catch (Exception $e) { throw new Exception($e->getMessage(), 400); } $response->noContent(); }); App::post('/v1/transfers/sources/supabase') ->groups(['api', 'transfers']) ->desc('Create Supabase Source') ->label('scope', 'transfers.write') ->label('event', 'transfers.[sourceId].createSupabaseSource') ->label('audits.event', 'transfers.createSupabaseSource') ->label('audits.resource', 'sources/{response.$id}') ->label('sdk.auth', [APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'transfers') ->label('sdk.method', 'createSupabaseSource') ->label('sdk.description', '/docs/references/transfers/create-supabase-source.md') ->label('sdk.response.code', Response::STATUS_CODE_CREATED) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_SOURCE) ->param('sourceId', 'unique()', new CustomId(), 'Source unique ID. Use \'unique()\' to auto generate a unique ID for this source.', true) ->param('name', '', new Text(256), 'Source Name. Max length: 256 chars.', true) ->param('host', '', new Text(100), 'Supabase Database Host. The host of the project to transfer.', false) ->param('database', 'postgres', new Text(100), 'Supabase Database Name. The name of the database to transfer.', true) ->param('username', 'postgres', new Text(100), 'Supabase Database Username. The username of the database to transfer.', true) ->param('password', '', new Text(100), 'Supabase Database Password. The password of the database to transfer.', false) ->param('port', '5432', new Integer(true), 'Supabase Database Port. The port of the database to transfer.', true) ->inject('response') ->inject('dbForProject') ->inject('project') ->inject('user') ->inject('events') ->action(function (string $sourceId, string $name, string $url, string $database, string $username, string $password, string $port, Response $response, Database $dbForProject, Document $project, Document $user, Event $eventsInstance) { $sourceId = ($sourceId == 'unique()') ? ID::unique() : $sourceId; $testAdapter = new Supabase($url, $database, $username, $password, intval($port)); $testAdapter->check(); // Throws exception on failure $source = $dbForProject->createDocument('sources', new Document([ '$id' => $sourceId, '$collection' => ID::custom('sources'), 'type' => 'supabase', 'name' => empty($name) ? 'Supabase Project ' . $sourceId : $name, 'lastCheck' => json_encode([ 'success' => true, 'message' => 'Source is valid' ]), 'data' => json_encode([ 'url' => $url, 'database' => $database, 'username' => $username, 'password' => $password, 'port' => intval($port), ]) ])); $eventsInstance->setParam('sourceId', $source->getId()); $response ->setStatusCode(Response::STATUS_CODE_CREATED) ->dynamic($source, Response::MODEL_SOURCE); }); App::post('/v1/transfers/sources/supabase/validate') ->groups(['api', 'transfers']) ->desc('Validate Supabase Source') ->label('scope', 'transfers.write') ->label('audits.resource', 'sources/{response.$id}') ->label('sdk.auth', [APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'transfers') ->label('sdk.method', 'validateSupabaseSource') ->label('sdk.description', '/docs/references/transfers/validate-supabase-source.md') ->label('sdk.response.code', Response::STATUS_CODE_OK) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_SOURCE_VALIDATION) ->param('host', '', new Text(100), 'Supabase Database Host. The host of the project to transfer.', false) ->param('database', 'postgres', new Text(100), 'Supabase Database Name. The name of the database to transfer.', true) ->param('username', 'postgres', new Text(100), 'Supabase Database Username. The username of the database to transfer.', true) ->param('password', '', new Text(100), 'Supabase Database Password. The password of the database to transfer.', false) ->param('port', '5432', new Integer(true), 'Supabase Database Port. The port of the database to transfer.', true) ->inject('response') ->action(function (string $host, string $database, string $username, string $password, string $port, Response $response) { $testAdapter = new Supabase($host, $database, $username, $password, $port); $result = $testAdapter->check(); $result = array_filter($result, function ($value) { return $value !== []; }); if (count($result) == 0) { return $response->dynamic(new Document([ 'success' => true, 'message' => 'Source is valid', 'errors' => $result ]), Response::MODEL_SOURCE_VALIDATION); } else { return $response->setStatusCode(401)->dynamic(new Document([ 'success' => false, 'message' => 'Missing Permissions', 'errors' => $result ]), Response::MODEL_SOURCE_VALIDATION); } }); App::post('/v1/transfers/sources/nhost') ->groups(['api', 'transfers']) ->desc('Create Nhost Source') ->label('scope', 'transfers.write') ->label('event', 'transfers.[sourceId].createNhostSource') ->label('audits.event', 'transfers.createNhostSource') ->label('audits.resource', 'sources/{response.$id}') ->label('sdk.auth', [APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'transfers') ->label('sdk.method', 'createNhostSource') ->label('sdk.description', '/docs/references/transfers/create-nhost-source.md') ->label('sdk.response.code', Response::STATUS_CODE_CREATED) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_SOURCE) ->param('sourceId', 'unique()', new CustomId(), 'Source unique ID. Use \'unique()\' to auto generate a unique ID for this source.', true) ->param('name', '', new Text(256), 'Source Name. Max length: 256 chars.', true) ->param('host', '', new Text(100), 'Nhost Database Host. The host of the project to transfer.', false) ->param('database', 'postgres', new Text(100), 'Nhost Database Name. The name of the database to transfer.', true) ->param('username', 'postgres', new Text(100), 'Nhost Database Username. The username of the database to transfer.', true) ->param('password', '', new Text(100), 'Nhost Database Password. The password of the database to transfer.', false) ->param('port', '5432', new Integer(true), 'Nhost Database Port. The port of the database to transfer.', true) ->inject('response') ->inject('dbForProject') ->inject('project') ->inject('user') ->inject('events') ->action(function (string $sourceId, string $name, string $url, string $database, string $username, string $password, int $port, Response $response, Database $dbForProject, Document $project, Document $user, Event $eventsInstance) { $sourceId = ($sourceId == 'unique()') ? ID::unique() : $sourceId; $testAdapter = new NHost($url, $database, $username, $password, $port); $testAdapter->check(); // Throws exception on failure $source = $dbForProject->createDocument('sources', new Document([ '$id' => $sourceId, '$collection' => ID::custom('sources'), 'type' => 'nhost', 'name' => empty($name) ? 'Nhost Project ' . $sourceId : $name, 'lastCheck' => json_encode([ 'success' => true, 'message' => 'Source is valid' ]), 'data' => json_encode([ 'url' => $url, 'database' => $database, 'username' => $username, 'password' => $password, 'port' => $port, ]) ])); $eventsInstance->setParam('sourceId', $source->getId()); $response ->setStatusCode(Response::STATUS_CODE_CREATED) ->dynamic($source, Response::MODEL_SOURCE); }); App::post('/v1/transfers/sources/nhost/validate') ->groups(['api', 'transfers']) ->desc('Validate Nhost Source') ->label('scope', 'transfers.write') ->label('audits.resource', 'sources/{response.$id}') ->label('sdk.auth', [APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'transfers') ->label('sdk.method', 'validateNhostSource') ->label('sdk.description', '/docs/references/transfers/validate-nhost-source.md') ->label('sdk.response.code', Response::STATUS_CODE_OK) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_SOURCE_VALIDATION) ->param('host', '', new Text(100), 'Nhost Database Host. The host of the project to validate.', false) ->param('database', 'postgres', new Text(100), 'Nhost Database Name. The name of the database to validate.', true) ->param('username', 'postgres', new Text(100), 'Nhost Database Username. The username of the database to validate.', true) ->param('password', '', new Text(100), 'Nhost Database Password. The password of the database to validate.', false) ->param('port', '5432', new Integer(true), 'Nhost Database Port. The port of the database to validate.', true) ->inject('response') ->action(function (string $url, string $database, string $username, string $password, string $port, Response $response) { try { $testAdapter = new NHost($url, $database, $username, $password, $port); } catch (Throwable $e) { return $response->setStatusCode(401)->dynamic(new Document([ 'success' => false, 'message' => 'Invalid Nhost Source', 'errors' => [ 'Databases' => [$e->getMessage()], ] ]), Response::MODEL_SOURCE_VALIDATION); }; $result = $testAdapter->check(); $result = array_filter($result, function ($value) { return $value !== []; }); if (count($result) == 0) { return $response->dynamic(new Document([ 'success' => true, 'message' => 'Source is valid', 'errors' => $result ]), Response::MODEL_SOURCE_VALIDATION); } else { return $response->setStatusCode(401)->dynamic(new Document([ 'success' => false, 'message' => 'Missing Permissions', 'errors' => $result ]), Response::MODEL_SOURCE_VALIDATION); } }); App::post('/v1/transfers/destinations/appwrite') ->groups(['api', 'transfers']) ->desc('Create Appwrite Transfer Destination') ->label('scope', 'transfers.write') ->label('event', 'transfers.[sourceId].createAppwriteDestination') ->label('audits.event', 'transfers.createAppwriteDestination') ->label('audits.resource', 'sources/{response.$id}') ->label('sdk.auth', [APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'transfers') ->label('sdk.method', 'createAppwriteDestination') ->label('sdk.description', '/docs/references/transfers/create-appwrite-source.md') ->label('sdk.response.code', Response::STATUS_CODE_CREATED) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_DESTINATION) ->param('destinationId', 'unique()', new CustomId(), 'Destination unique ID. Use \'unique()\' to auto generate a unique ID for this source.', true) ->param('name', '', new Text(256), 'Destination Name. Max length: 256 chars.', true) ->param('projectId', '', new UID(), 'Destination Project UID. The UID of the project to transfer.', false) ->param('endpoint', '', new URL(), 'Destination Endpoint. The endpoint of the project to transfer.', false) ->param('key', '', new Text(1024), 'Destination Key. The key of the project to transfer.', false) ->inject('response') ->inject('dbForProject') ->inject('project') ->inject('user') ->inject('events') ->action(function (string $destinationId, string $name, string $projectId, string $endpoint, string $key, Response $response, Database $dbForProject, Document $project, Document $user, Event $eventsInstance) { $destinationId = ($destinationId == 'unique()') ? ID::unique() : $destinationId; $testAdapter = new Appwrite($projectId, $endpoint, $key); $result = $testAdapter->check(); $result = array_filter($result, function ($value) { return $value !== []; }); if (count($result) > 0) { throw new Exception('Missing Permissions', 401); } $destination = $dbForProject->createDocument('destinations', new Document([ '$id' => $destinationId, '$collection' => ID::custom('destinations'), 'type' => 'appwrite', 'name' => empty($name) ? 'Appwrite Project ' . $destinationId : $name, 'lastCheck' => json_encode([ 'success' => true, 'message' => 'Source is valid' ]), 'data' => json_encode([ 'projectId' => $projectId, 'endpoint' => $endpoint, 'key' => $key, ]) ])); $eventsInstance->setParam('desinationId', $destination->getId()); $response ->setStatusCode(Response::STATUS_CODE_CREATED) ->dynamic($destination, Response::MODEL_DESTINATION); }); App::post('/v1/transfers/destinations/appwrite/validate') ->groups(['api', 'transfers']) ->desc('Validate Appwrite Destination') ->label('scope', 'transfers.write') ->label('audits.resource', 'sources/{response.$id}') ->label('sdk.auth', [APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'transfers') ->label('sdk.method', 'validateAppwriteDestination') ->label('sdk.description', '/docs/references/transfers/validate-appwrite-destination.md') ->label('sdk.response.code', Response::STATUS_CODE_OK) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_DESTINATION_VALIDATION) ->param('projectId', '', new UID(), 'Destination Project UID. The UID of the project to transfer.', false) ->param('endpoint', '', new URL(), 'Destination Endpoint. The endpoint of the project to transfer.', false) ->param('key', '', new Text(1024), 'Destination Key. The key of the project to transfer.', false) ->inject('response') ->action(function (string $projectId, string $endpoint, string $key, Response $response) { $testAdapter = new Appwrite($projectId, $endpoint, $key); $result = $testAdapter->check(); $result = array_filter($result, function ($value) { return $value !== []; }); if (count($result) == 0) { return $response->dynamic(new Document([ 'success' => true, 'message' => 'Destination is valid', 'errors' => $result ]), Response::MODEL_DESTINATION_VALIDATION); } else { return $response->setStatusCode(401)->dynamic(new Document([ 'success' => false, 'message' => 'Missing Permissions', 'errors' => $result ]), Response::MODEL_DESTINATION_VALIDATION); } });