mirror of
https://github.com/appwrite/appwrite.git
synced 2026-05-26 13:51:13 +00:00
Merge remote-tracking branch 'origin/1.6.x' into feat-docker-geo
This commit is contained in:
+14
-13
@@ -52,7 +52,7 @@ CLI::setResource('pools', function (Registry $register) {
|
||||
return $register->get('pools');
|
||||
}, ['register']);
|
||||
|
||||
CLI::setResource('dbForConsole', function ($pools, $cache) {
|
||||
CLI::setResource('dbForPlatform', function ($pools, $cache) {
|
||||
$sleep = 3;
|
||||
$maxAttempts = 5;
|
||||
$attempts = 0;
|
||||
@@ -67,9 +67,9 @@ CLI::setResource('dbForConsole', function ($pools, $cache) {
|
||||
->pop()
|
||||
->getResource();
|
||||
|
||||
$dbForConsole = new Database($dbAdapter, $cache);
|
||||
$dbForPlatform = new Database($dbAdapter, $cache);
|
||||
|
||||
$dbForConsole
|
||||
$dbForPlatform
|
||||
->setNamespace('_console')
|
||||
->setMetadata('host', \gethostname())
|
||||
->setMetadata('project', 'console');
|
||||
@@ -78,7 +78,7 @@ CLI::setResource('dbForConsole', function ($pools, $cache) {
|
||||
$collections = Config::getParam('collections', [])['console'];
|
||||
$last = \array_key_last($collections);
|
||||
|
||||
if (!($dbForConsole->exists($dbForConsole->getDatabase(), $last))) { /** TODO cache ready variable using registry */
|
||||
if (!($dbForPlatform->exists($dbForPlatform->getDatabase(), $last))) { /** TODO cache ready variable using registry */
|
||||
throw new Exception('Tables not ready yet.');
|
||||
}
|
||||
|
||||
@@ -94,15 +94,15 @@ CLI::setResource('dbForConsole', function ($pools, $cache) {
|
||||
throw new Exception("Console is not ready yet. Please try again later.");
|
||||
}
|
||||
|
||||
return $dbForConsole;
|
||||
return $dbForPlatform;
|
||||
}, ['pools', 'cache']);
|
||||
|
||||
CLI::setResource('getProjectDB', function (Group $pools, Database $dbForConsole, $cache) {
|
||||
CLI::setResource('getProjectDB', function (Group $pools, Database $dbForPlatform, $cache) {
|
||||
$databases = []; // TODO: @Meldiron This should probably be responsibility of utopia-php/pools
|
||||
|
||||
return function (Document $project) use ($pools, $dbForConsole, $cache, &$databases) {
|
||||
return function (Document $project) use ($pools, $dbForPlatform, $cache, &$databases) {
|
||||
if ($project->isEmpty() || $project->getId() === 'console') {
|
||||
return $dbForConsole;
|
||||
return $dbForPlatform;
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -114,8 +114,9 @@ CLI::setResource('getProjectDB', function (Group $pools, Database $dbForConsole,
|
||||
|
||||
if (isset($databases[$dsn->getHost()])) {
|
||||
$database = $databases[$dsn->getHost()];
|
||||
$sharedTables = \explode(',', System::getEnv('_APP_DATABASE_SHARED_TABLES', ''));
|
||||
|
||||
if ($dsn->getHost() === System::getEnv('_APP_DATABASE_SHARED_TABLES', '')) {
|
||||
if (\in_array($dsn->getHost(), $sharedTables)) {
|
||||
$database
|
||||
->setSharedTables(true)
|
||||
->setTenant($project->getInternalId())
|
||||
@@ -136,10 +137,10 @@ CLI::setResource('getProjectDB', function (Group $pools, Database $dbForConsole,
|
||||
->getResource();
|
||||
|
||||
$database = new Database($dbAdapter, $cache);
|
||||
|
||||
$databases[$dsn->getHost()] = $database;
|
||||
$sharedTables = \explode(',', System::getEnv('_APP_DATABASE_SHARED_TABLES', ''));
|
||||
|
||||
if ($dsn->getHost() === System::getEnv('_APP_DATABASE_SHARED_TABLES', '')) {
|
||||
if (\in_array($dsn->getHost(), $sharedTables)) {
|
||||
$database
|
||||
->setSharedTables(true)
|
||||
->setTenant($project->getInternalId())
|
||||
@@ -157,7 +158,7 @@ CLI::setResource('getProjectDB', function (Group $pools, Database $dbForConsole,
|
||||
|
||||
return $database;
|
||||
};
|
||||
}, ['pools', 'dbForConsole', 'cache']);
|
||||
}, ['pools', 'dbForPlatform', 'cache']);
|
||||
|
||||
CLI::setResource('queue', function (Group $pools) {
|
||||
return $pools->get('queue')->pop()->getResource();
|
||||
@@ -180,7 +181,7 @@ CLI::setResource('logError', function (Registry $register) {
|
||||
|
||||
$log = new Log();
|
||||
$log->setNamespace($namespace);
|
||||
$log->setServer(\gethostname());
|
||||
$log->setServer(System::getEnv('_APP_LOGGING_SERVICE_IDENTIFIER', \gethostname()));
|
||||
$log->setVersion($version);
|
||||
$log->setType(Log::TYPE_ERROR);
|
||||
$log->setMessage($error->getMessage());
|
||||
|
||||
@@ -686,7 +686,7 @@ return [
|
||||
],
|
||||
Exception::ATTRIBUTE_LIMIT_EXCEEDED => [
|
||||
'name' => Exception::ATTRIBUTE_LIMIT_EXCEEDED,
|
||||
'description' => 'The maximum number of attributes has been reached.',
|
||||
'description' => 'The maximum number or size of attributes for this collection has been reached.',
|
||||
'code' => 400,
|
||||
],
|
||||
Exception::ATTRIBUTE_VALUE_INVALID => [
|
||||
|
||||
@@ -11,7 +11,7 @@ const TEMPLATE_RUNTIMES = [
|
||||
],
|
||||
'DART' => [
|
||||
'name' => 'dart',
|
||||
'versions' => ['3.5', '3.3', '3.1', '3.0', '2.19', '2.18', '2.17', '2.16', '2.16']
|
||||
'versions' => ['3.5', '3.3', '3.1', '3.0', '2.19', '2.18', '2.17', '2.16']
|
||||
],
|
||||
'GO' => [
|
||||
'name' => 'go',
|
||||
|
||||
@@ -11,7 +11,7 @@ return [
|
||||
[
|
||||
'key' => 'web',
|
||||
'name' => 'Web',
|
||||
'version' => '16.0.2',
|
||||
'version' => '16.1.0',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-web',
|
||||
'package' => 'https://www.npmjs.com/package/appwrite',
|
||||
'enabled' => true,
|
||||
@@ -59,7 +59,7 @@ return [
|
||||
[
|
||||
'key' => 'flutter',
|
||||
'name' => 'Flutter',
|
||||
'version' => '13.0.0',
|
||||
'version' => '13.1.1',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-flutter',
|
||||
'package' => 'https://pub.dev/packages/appwrite',
|
||||
'enabled' => true,
|
||||
@@ -77,7 +77,7 @@ return [
|
||||
[
|
||||
'key' => 'apple',
|
||||
'name' => 'Apple',
|
||||
'version' => '7.0.0',
|
||||
'version' => '7.1.0',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-apple',
|
||||
'package' => 'https://github.com/appwrite/sdk-for-apple',
|
||||
'enabled' => true,
|
||||
@@ -112,7 +112,7 @@ return [
|
||||
[
|
||||
'key' => 'android',
|
||||
'name' => 'Android',
|
||||
'version' => '6.0.0',
|
||||
'version' => '6.1.0',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-android',
|
||||
'package' => 'https://search.maven.org/artifact/io.appwrite/sdk-for-android',
|
||||
'enabled' => true,
|
||||
@@ -134,7 +134,7 @@ return [
|
||||
[
|
||||
'key' => 'react-native',
|
||||
'name' => 'React Native',
|
||||
'version' => '0.5.0',
|
||||
'version' => '0.6.0',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-react-native',
|
||||
'package' => 'https://npmjs.com/package/react-native-appwrite',
|
||||
'enabled' => true,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"openapi": "3.0.0",
|
||||
"info": {
|
||||
"version": "1.6.0",
|
||||
"version": "1.6.1",
|
||||
"title": "Appwrite",
|
||||
"description": "Appwrite backend as a service cuts up to 70% of the time and costs required for building a modern application. We abstract and simplify common development tasks behind a REST APIs, to help you develop your app in a fast and secure way. For full API documentation and tutorials go to [https:\/\/appwrite.io\/docs](https:\/\/appwrite.io\/docs)",
|
||||
"termsOfService": "https:\/\/appwrite.io\/policy\/terms",
|
||||
@@ -2784,7 +2784,7 @@
|
||||
"tags": [
|
||||
"account"
|
||||
],
|
||||
"description": "Sends the user an email with a secret key for creating a session. If the provided user ID has not been registered, a new user will be created. When the user clicks the link in the email, the user is redirected back to the URL you provided with the secret key and userId values attached to the URL query string. Use the query string parameters to submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The link sent to the user's email address is valid for 1 hour. If you are on a mobile device you can leave the URL parameter empty, so that the login completion will be handled by your Appwrite instance by default.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).\n",
|
||||
"description": "Sends the user an email with a secret key for creating a session. If the provided user ID has not been registered, a new user will be created. When the user clicks the link in the email, the user is redirected back to the URL you provided with the secret key and userId values attached to the URL query string. Use the query string parameters to submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The link sent to the user's email address is valid for 1 hour.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).\n",
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": "Token",
|
||||
@@ -5766,7 +5766,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "createSubscriber",
|
||||
"weight": 382,
|
||||
"weight": 376,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -5851,7 +5851,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "deleteSubscriber",
|
||||
"weight": 386,
|
||||
"weight": 380,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"openapi": "3.0.0",
|
||||
"info": {
|
||||
"version": "1.6.0",
|
||||
"version": "1.6.1",
|
||||
"title": "Appwrite",
|
||||
"description": "Appwrite backend as a service cuts up to 70% of the time and costs required for building a modern application. We abstract and simplify common development tasks behind a REST APIs, to help you develop your app in a fast and secure way. For full API documentation and tutorials go to [https:\/\/appwrite.io\/docs](https:\/\/appwrite.io\/docs)",
|
||||
"termsOfService": "https:\/\/appwrite.io\/policy\/terms",
|
||||
@@ -2795,7 +2795,7 @@
|
||||
"tags": [
|
||||
"account"
|
||||
],
|
||||
"description": "Sends the user an email with a secret key for creating a session. If the provided user ID has not been registered, a new user will be created. When the user clicks the link in the email, the user is redirected back to the URL you provided with the secret key and userId values attached to the URL query string. Use the query string parameters to submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The link sent to the user's email address is valid for 1 hour. If you are on a mobile device you can leave the URL parameter empty, so that the login completion will be handled by your Appwrite instance by default.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).\n",
|
||||
"description": "Sends the user an email with a secret key for creating a session. If the provided user ID has not been registered, a new user will be created. When the user clicks the link in the email, the user is redirected back to the URL you provided with the secret key and userId values attached to the URL query string. Use the query string parameters to submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The link sent to the user's email address is valid for 1 hour.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).\n",
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": "Token",
|
||||
@@ -9488,7 +9488,8 @@
|
||||
"bun-1.0",
|
||||
"bun-1.1",
|
||||
"go-1.23",
|
||||
"static-1"
|
||||
"static-1",
|
||||
"flutter-3.24"
|
||||
],
|
||||
"x-enum-name": null,
|
||||
"x-enum-keys": []
|
||||
@@ -10145,7 +10146,8 @@
|
||||
"bun-1.0",
|
||||
"bun-1.1",
|
||||
"go-1.23",
|
||||
"static-1"
|
||||
"static-1",
|
||||
"flutter-3.24"
|
||||
],
|
||||
"x-enum-name": null,
|
||||
"x-enum-keys": []
|
||||
@@ -13673,7 +13675,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "listMessages",
|
||||
"weight": 390,
|
||||
"weight": 384,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -13751,7 +13753,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "createEmail",
|
||||
"weight": 387,
|
||||
"weight": 381,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -13897,7 +13899,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "updateEmail",
|
||||
"weight": 394,
|
||||
"weight": 388,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -14045,7 +14047,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "createPush",
|
||||
"weight": 389,
|
||||
"weight": 383,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -14202,7 +14204,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "updatePush",
|
||||
"weight": 396,
|
||||
"weight": 390,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -14361,7 +14363,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "createSms",
|
||||
"weight": 388,
|
||||
"weight": 382,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -14472,7 +14474,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "updateSms",
|
||||
"weight": 395,
|
||||
"weight": 389,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -14586,7 +14588,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "getMessage",
|
||||
"weight": 393,
|
||||
"weight": 387,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -14641,7 +14643,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "delete",
|
||||
"weight": 397,
|
||||
"weight": 391,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -14705,7 +14707,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "listMessageLogs",
|
||||
"weight": 391,
|
||||
"weight": 385,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -14782,7 +14784,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "listTargets",
|
||||
"weight": 392,
|
||||
"weight": 386,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -14859,7 +14861,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "listProviders",
|
||||
"weight": 362,
|
||||
"weight": 356,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -14937,7 +14939,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "createApnsProvider",
|
||||
"weight": 361,
|
||||
"weight": 355,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -15044,7 +15046,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "updateApnsProvider",
|
||||
"weight": 374,
|
||||
"weight": 368,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -15154,7 +15156,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "createFcmProvider",
|
||||
"weight": 360,
|
||||
"weight": 354,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -15241,7 +15243,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "updateFcmProvider",
|
||||
"weight": 373,
|
||||
"weight": 367,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -15331,7 +15333,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "createMailgunProvider",
|
||||
"weight": 352,
|
||||
"weight": 346,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -15448,7 +15450,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "updateMailgunProvider",
|
||||
"weight": 365,
|
||||
"weight": 359,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -15568,7 +15570,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "createMsg91Provider",
|
||||
"weight": 355,
|
||||
"weight": 349,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -15665,7 +15667,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "updateMsg91Provider",
|
||||
"weight": 368,
|
||||
"weight": 362,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -15765,7 +15767,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "createSendgridProvider",
|
||||
"weight": 353,
|
||||
"weight": 347,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -15872,7 +15874,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "updateSendgridProvider",
|
||||
"weight": 366,
|
||||
"weight": 360,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -15982,7 +15984,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "createSmtpProvider",
|
||||
"weight": 354,
|
||||
"weight": 348,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -16127,7 +16129,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "updateSmtpProvider",
|
||||
"weight": 367,
|
||||
"weight": 361,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -16274,7 +16276,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "createTelesignProvider",
|
||||
"weight": 356,
|
||||
"weight": 350,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -16371,7 +16373,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "updateTelesignProvider",
|
||||
"weight": 369,
|
||||
"weight": 363,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -16471,7 +16473,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "createTextmagicProvider",
|
||||
"weight": 357,
|
||||
"weight": 351,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -16568,7 +16570,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "updateTextmagicProvider",
|
||||
"weight": 370,
|
||||
"weight": 364,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -16668,7 +16670,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "createTwilioProvider",
|
||||
"weight": 358,
|
||||
"weight": 352,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -16765,7 +16767,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "updateTwilioProvider",
|
||||
"weight": 371,
|
||||
"weight": 365,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -16865,7 +16867,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "createVonageProvider",
|
||||
"weight": 359,
|
||||
"weight": 353,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -16962,7 +16964,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "updateVonageProvider",
|
||||
"weight": 372,
|
||||
"weight": 366,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -17062,7 +17064,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "getProvider",
|
||||
"weight": 364,
|
||||
"weight": 358,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -17117,7 +17119,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "deleteProvider",
|
||||
"weight": 375,
|
||||
"weight": 369,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -17181,7 +17183,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "listProviderLogs",
|
||||
"weight": 363,
|
||||
"weight": 357,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -17258,7 +17260,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "listSubscriberLogs",
|
||||
"weight": 384,
|
||||
"weight": 378,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -17335,7 +17337,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "listTopics",
|
||||
"weight": 377,
|
||||
"weight": 371,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -17411,7 +17413,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "createTopic",
|
||||
"weight": 376,
|
||||
"weight": 370,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -17496,7 +17498,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "getTopic",
|
||||
"weight": 379,
|
||||
"weight": 373,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -17558,7 +17560,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "updateTopic",
|
||||
"weight": 380,
|
||||
"weight": 374,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -17637,7 +17639,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "deleteTopic",
|
||||
"weight": 381,
|
||||
"weight": 375,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -17701,7 +17703,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "listTopicLogs",
|
||||
"weight": 378,
|
||||
"weight": 372,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -17778,7 +17780,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "listSubscribers",
|
||||
"weight": 383,
|
||||
"weight": 377,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -17864,7 +17866,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "createSubscriber",
|
||||
"weight": 382,
|
||||
"weight": 376,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -17956,7 +17958,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "getSubscriber",
|
||||
"weight": 385,
|
||||
"weight": 379,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -18021,7 +18023,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "deleteSubscriber",
|
||||
"weight": 386,
|
||||
"weight": 380,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -18098,7 +18100,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "list",
|
||||
"weight": 339,
|
||||
"weight": 338,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -18264,7 +18266,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "getAppwriteReport",
|
||||
"weight": 341,
|
||||
"weight": 340,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -18339,7 +18341,7 @@
|
||||
},
|
||||
"\/migrations\/firebase": {
|
||||
"post": {
|
||||
"summary": "Migrate Firebase data (Service Account)",
|
||||
"summary": "Migrate Firebase data",
|
||||
"operationId": "migrationsCreateFirebaseMigration",
|
||||
"tags": [
|
||||
"migrations"
|
||||
@@ -18359,7 +18361,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "createFirebaseMigration",
|
||||
"weight": 336,
|
||||
"weight": 335,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -18415,177 +18417,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"\/migrations\/firebase\/deauthorize": {
|
||||
"get": {
|
||||
"summary": "Revoke Appwrite's authorization to access Firebase projects",
|
||||
"operationId": "migrationsDeleteFirebaseAuth",
|
||||
"tags": [
|
||||
"migrations"
|
||||
],
|
||||
"description": "",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "File"
|
||||
}
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "deleteFirebaseAuth",
|
||||
"weight": 347,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
"demo": "migrations\/delete-firebase-auth.md",
|
||||
"edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master",
|
||||
"rate-limit": 0,
|
||||
"rate-time": 3600,
|
||||
"rate-key": "url:{url},ip:{ip}",
|
||||
"scope": "migrations.write",
|
||||
"platforms": [
|
||||
"console"
|
||||
],
|
||||
"packaging": false,
|
||||
"offline-model": "",
|
||||
"offline-key": "",
|
||||
"offline-response-key": "$id",
|
||||
"auth": {
|
||||
"Project": []
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"Project": []
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"\/migrations\/firebase\/oauth": {
|
||||
"post": {
|
||||
"summary": "Migrate Firebase data (OAuth)",
|
||||
"operationId": "migrationsCreateFirebaseOAuthMigration",
|
||||
"tags": [
|
||||
"migrations"
|
||||
],
|
||||
"description": "",
|
||||
"responses": {
|
||||
"202": {
|
||||
"description": "Migration",
|
||||
"content": {
|
||||
"application\/json": {
|
||||
"schema": {
|
||||
"$ref": "#\/components\/schemas\/migration"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "createFirebaseOAuthMigration",
|
||||
"weight": 335,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
"demo": "migrations\/create-firebase-o-auth-migration.md",
|
||||
"edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/migrations\/migration-firebase.md",
|
||||
"rate-limit": 0,
|
||||
"rate-time": 3600,
|
||||
"rate-key": "url:{url},ip:{ip}",
|
||||
"scope": "migrations.write",
|
||||
"platforms": [
|
||||
"console"
|
||||
],
|
||||
"packaging": false,
|
||||
"offline-model": "",
|
||||
"offline-key": "",
|
||||
"offline-response-key": "$id",
|
||||
"auth": {
|
||||
"Project": []
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"Project": []
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application\/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"resources": {
|
||||
"type": "array",
|
||||
"description": "List of resources to migrate",
|
||||
"x-example": null,
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"projectId": {
|
||||
"type": "string",
|
||||
"description": "Project ID of the Firebase Project",
|
||||
"x-example": "<PROJECT_ID>"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"resources",
|
||||
"projectId"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"\/migrations\/firebase\/projects": {
|
||||
"get": {
|
||||
"summary": "List Firebase projects",
|
||||
"operationId": "migrationsListFirebaseProjects",
|
||||
"tags": [
|
||||
"migrations"
|
||||
],
|
||||
"description": "",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Migrations Firebase Projects List",
|
||||
"content": {
|
||||
"application\/json": {
|
||||
"schema": {
|
||||
"$ref": "#\/components\/schemas\/firebaseProjectList"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "listFirebaseProjects",
|
||||
"weight": 346,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
"demo": "migrations\/list-firebase-projects.md",
|
||||
"edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master",
|
||||
"rate-limit": 0,
|
||||
"rate-time": 3600,
|
||||
"rate-key": "url:{url},ip:{ip}",
|
||||
"scope": "migrations.read",
|
||||
"platforms": [
|
||||
"console"
|
||||
],
|
||||
"packaging": false,
|
||||
"offline-model": "",
|
||||
"offline-key": "",
|
||||
"offline-response-key": "$id",
|
||||
"auth": {
|
||||
"Project": []
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"Project": []
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"\/migrations\/firebase\/report": {
|
||||
"get": {
|
||||
"summary": "Generate a report on Firebase data",
|
||||
@@ -18608,7 +18439,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "getFirebaseReport",
|
||||
"weight": 342,
|
||||
"weight": 341,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -18660,80 +18491,6 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"\/migrations\/firebase\/report\/oauth": {
|
||||
"get": {
|
||||
"summary": "Generate a report on Firebase data using OAuth",
|
||||
"operationId": "migrationsGetFirebaseReportOAuth",
|
||||
"tags": [
|
||||
"migrations"
|
||||
],
|
||||
"description": "",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Migration Report",
|
||||
"content": {
|
||||
"application\/json": {
|
||||
"schema": {
|
||||
"$ref": "#\/components\/schemas\/migrationReport"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "getFirebaseReportOAuth",
|
||||
"weight": 343,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
"demo": "migrations\/get-firebase-report-o-auth.md",
|
||||
"edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/migrations\/migration-firebase-report.md",
|
||||
"rate-limit": 0,
|
||||
"rate-time": 3600,
|
||||
"rate-key": "url:{url},ip:{ip}",
|
||||
"scope": "migrations.write",
|
||||
"platforms": [
|
||||
"console"
|
||||
],
|
||||
"packaging": false,
|
||||
"offline-model": "",
|
||||
"offline-key": "",
|
||||
"offline-response-key": "$id",
|
||||
"auth": {
|
||||
"Project": []
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"Project": []
|
||||
}
|
||||
],
|
||||
"parameters": [
|
||||
{
|
||||
"name": "resources",
|
||||
"description": "List of resources to migrate",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"name": "projectId",
|
||||
"description": "Project ID",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"x-example": "<PROJECT_ID>"
|
||||
},
|
||||
"in": "query"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"\/migrations\/nhost": {
|
||||
"post": {
|
||||
"summary": "Migrate NHost data",
|
||||
@@ -18756,7 +18513,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "createNHostMigration",
|
||||
"weight": 338,
|
||||
"weight": 337,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -18869,7 +18626,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "getNHostReport",
|
||||
"weight": 349,
|
||||
"weight": 343,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -19004,7 +18761,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "createSupabaseMigration",
|
||||
"weight": 337,
|
||||
"weight": 336,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -19111,7 +18868,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "getSupabaseReport",
|
||||
"weight": 348,
|
||||
"weight": 342,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -19237,7 +18994,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "get",
|
||||
"weight": 340,
|
||||
"weight": 339,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -19297,7 +19054,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "retry",
|
||||
"weight": 350,
|
||||
"weight": 344,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -19350,7 +19107,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "delete",
|
||||
"weight": 351,
|
||||
"weight": 345,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -20672,7 +20429,7 @@
|
||||
},
|
||||
"\/projects\/{projectId}\/auth\/memberships-privacy": {
|
||||
"patch": {
|
||||
"summary": "Update project team sensitive attributes",
|
||||
"summary": "Update project memberships privacy attributes",
|
||||
"operationId": "projectsUpdateMembershipsPrivacy",
|
||||
"tags": [
|
||||
"projects"
|
||||
@@ -32749,30 +32506,6 @@
|
||||
"migrations"
|
||||
]
|
||||
},
|
||||
"firebaseProjectList": {
|
||||
"description": "Migrations Firebase Projects List",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"total": {
|
||||
"type": "integer",
|
||||
"description": "Total number of projects documents that matched your query.",
|
||||
"x-example": 5,
|
||||
"format": "int32"
|
||||
},
|
||||
"projects": {
|
||||
"type": "array",
|
||||
"description": "List of projects.",
|
||||
"items": {
|
||||
"$ref": "#\/components\/schemas\/firebaseProject"
|
||||
},
|
||||
"x-example": ""
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"total",
|
||||
"projects"
|
||||
]
|
||||
},
|
||||
"specificationList": {
|
||||
"description": "Specifications List",
|
||||
"type": "object",
|
||||
@@ -35109,7 +34842,7 @@
|
||||
},
|
||||
"schedule": {
|
||||
"type": "string",
|
||||
"description": "Function execution schedult in CRON format.",
|
||||
"description": "Function execution schedule in CRON format.",
|
||||
"x-example": "5 4 * * *"
|
||||
},
|
||||
"timeout": {
|
||||
@@ -36077,17 +35810,17 @@
|
||||
"description": "Whether or not to send session alert emails to users.",
|
||||
"x-example": true
|
||||
},
|
||||
"membershipsUserName": {
|
||||
"authMembershipsUserName": {
|
||||
"type": "boolean",
|
||||
"description": "Whether or not to show user names in the teams membership response.",
|
||||
"x-example": true
|
||||
},
|
||||
"membershipsUserEmail": {
|
||||
"authMembershipsUserEmail": {
|
||||
"type": "boolean",
|
||||
"description": "Whether or not to show user emails in the teams membership response.",
|
||||
"x-example": true
|
||||
},
|
||||
"membershipsMfa": {
|
||||
"authMembershipsMfa": {
|
||||
"type": "boolean",
|
||||
"description": "Whether or not to show user MFA status in the teams membership response.",
|
||||
"x-example": true
|
||||
@@ -36297,9 +36030,9 @@
|
||||
"authPersonalDataCheck",
|
||||
"authMockNumbers",
|
||||
"authSessionAlerts",
|
||||
"membershipsUserName",
|
||||
"membershipsUserEmail",
|
||||
"membershipsMfa",
|
||||
"authMembershipsUserName",
|
||||
"authMembershipsUserEmail",
|
||||
"authMembershipsMfa",
|
||||
"oAuthProviders",
|
||||
"platforms",
|
||||
"webhooks",
|
||||
@@ -38707,26 +38440,6 @@
|
||||
"size",
|
||||
"version"
|
||||
]
|
||||
},
|
||||
"firebaseProject": {
|
||||
"description": "MigrationFirebaseProject",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"projectId": {
|
||||
"type": "string",
|
||||
"description": "Project ID.",
|
||||
"x-example": "my-project"
|
||||
},
|
||||
"displayName": {
|
||||
"type": "string",
|
||||
"description": "Project display name.",
|
||||
"x-example": "My Project"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"projectId",
|
||||
"displayName"
|
||||
]
|
||||
}
|
||||
},
|
||||
"securitySchemes": {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"openapi": "3.0.0",
|
||||
"info": {
|
||||
"version": "1.6.0",
|
||||
"version": "1.6.1",
|
||||
"title": "Appwrite",
|
||||
"description": "Appwrite backend as a service cuts up to 70% of the time and costs required for building a modern application. We abstract and simplify common development tasks behind a REST APIs, to help you develop your app in a fast and secure way. For full API documentation and tutorials go to [https:\/\/appwrite.io\/docs](https:\/\/appwrite.io\/docs)",
|
||||
"termsOfService": "https:\/\/appwrite.io\/policy\/terms",
|
||||
@@ -2451,7 +2451,7 @@
|
||||
"tags": [
|
||||
"account"
|
||||
],
|
||||
"description": "Sends the user an email with a secret key for creating a session. If the provided user ID has not been registered, a new user will be created. When the user clicks the link in the email, the user is redirected back to the URL you provided with the secret key and userId values attached to the URL query string. Use the query string parameters to submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The link sent to the user's email address is valid for 1 hour. If you are on a mobile device you can leave the URL parameter empty, so that the login completion will be handled by your Appwrite instance by default.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).\n",
|
||||
"description": "Sends the user an email with a secret key for creating a session. If the provided user ID has not been registered, a new user will be created. When the user clicks the link in the email, the user is redirected back to the URL you provided with the secret key and userId values attached to the URL query string. Use the query string parameters to submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The link sent to the user's email address is valid for 1 hour.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).\n",
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": "Token",
|
||||
@@ -8596,7 +8596,8 @@
|
||||
"bun-1.0",
|
||||
"bun-1.1",
|
||||
"go-1.23",
|
||||
"static-1"
|
||||
"static-1",
|
||||
"flutter-3.24"
|
||||
],
|
||||
"x-enum-name": null,
|
||||
"x-enum-keys": []
|
||||
@@ -9019,7 +9020,8 @@
|
||||
"bun-1.0",
|
||||
"bun-1.1",
|
||||
"go-1.23",
|
||||
"static-1"
|
||||
"static-1",
|
||||
"flutter-3.24"
|
||||
],
|
||||
"x-enum-name": null,
|
||||
"x-enum-keys": []
|
||||
@@ -12527,7 +12529,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "listMessages",
|
||||
"weight": 390,
|
||||
"weight": 384,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -12606,7 +12608,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "createEmail",
|
||||
"weight": 387,
|
||||
"weight": 381,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -12753,7 +12755,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "updateEmail",
|
||||
"weight": 394,
|
||||
"weight": 388,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -12902,7 +12904,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "createPush",
|
||||
"weight": 389,
|
||||
"weight": 383,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -13060,7 +13062,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "updatePush",
|
||||
"weight": 396,
|
||||
"weight": 390,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -13220,7 +13222,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "createSms",
|
||||
"weight": 388,
|
||||
"weight": 382,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -13332,7 +13334,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "updateSms",
|
||||
"weight": 395,
|
||||
"weight": 389,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -13447,7 +13449,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "getMessage",
|
||||
"weight": 393,
|
||||
"weight": 387,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -13503,7 +13505,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "delete",
|
||||
"weight": 397,
|
||||
"weight": 391,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -13568,7 +13570,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "listMessageLogs",
|
||||
"weight": 391,
|
||||
"weight": 385,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -13646,7 +13648,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "listTargets",
|
||||
"weight": 392,
|
||||
"weight": 386,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -13724,7 +13726,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "listProviders",
|
||||
"weight": 362,
|
||||
"weight": 356,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -13803,7 +13805,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "createApnsProvider",
|
||||
"weight": 361,
|
||||
"weight": 355,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -13911,7 +13913,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "updateApnsProvider",
|
||||
"weight": 374,
|
||||
"weight": 368,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -14022,7 +14024,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "createFcmProvider",
|
||||
"weight": 360,
|
||||
"weight": 354,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -14110,7 +14112,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "updateFcmProvider",
|
||||
"weight": 373,
|
||||
"weight": 367,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -14201,7 +14203,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "createMailgunProvider",
|
||||
"weight": 352,
|
||||
"weight": 346,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -14319,7 +14321,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "updateMailgunProvider",
|
||||
"weight": 365,
|
||||
"weight": 359,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -14440,7 +14442,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "createMsg91Provider",
|
||||
"weight": 355,
|
||||
"weight": 349,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -14538,7 +14540,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "updateMsg91Provider",
|
||||
"weight": 368,
|
||||
"weight": 362,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -14639,7 +14641,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "createSendgridProvider",
|
||||
"weight": 353,
|
||||
"weight": 347,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -14747,7 +14749,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "updateSendgridProvider",
|
||||
"weight": 366,
|
||||
"weight": 360,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -14858,7 +14860,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "createSmtpProvider",
|
||||
"weight": 354,
|
||||
"weight": 348,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -15004,7 +15006,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "updateSmtpProvider",
|
||||
"weight": 367,
|
||||
"weight": 361,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -15152,7 +15154,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "createTelesignProvider",
|
||||
"weight": 356,
|
||||
"weight": 350,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -15250,7 +15252,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "updateTelesignProvider",
|
||||
"weight": 369,
|
||||
"weight": 363,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -15351,7 +15353,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "createTextmagicProvider",
|
||||
"weight": 357,
|
||||
"weight": 351,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -15449,7 +15451,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "updateTextmagicProvider",
|
||||
"weight": 370,
|
||||
"weight": 364,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -15550,7 +15552,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "createTwilioProvider",
|
||||
"weight": 358,
|
||||
"weight": 352,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -15648,7 +15650,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "updateTwilioProvider",
|
||||
"weight": 371,
|
||||
"weight": 365,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -15749,7 +15751,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "createVonageProvider",
|
||||
"weight": 359,
|
||||
"weight": 353,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -15847,7 +15849,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "updateVonageProvider",
|
||||
"weight": 372,
|
||||
"weight": 366,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -15948,7 +15950,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "getProvider",
|
||||
"weight": 364,
|
||||
"weight": 358,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -16004,7 +16006,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "deleteProvider",
|
||||
"weight": 375,
|
||||
"weight": 369,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -16069,7 +16071,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "listProviderLogs",
|
||||
"weight": 363,
|
||||
"weight": 357,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -16147,7 +16149,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "listSubscriberLogs",
|
||||
"weight": 384,
|
||||
"weight": 378,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -16225,7 +16227,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "listTopics",
|
||||
"weight": 377,
|
||||
"weight": 371,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -16302,7 +16304,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "createTopic",
|
||||
"weight": 376,
|
||||
"weight": 370,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -16388,7 +16390,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "getTopic",
|
||||
"weight": 379,
|
||||
"weight": 373,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -16451,7 +16453,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "updateTopic",
|
||||
"weight": 380,
|
||||
"weight": 374,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -16531,7 +16533,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "deleteTopic",
|
||||
"weight": 381,
|
||||
"weight": 375,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -16596,7 +16598,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "listTopicLogs",
|
||||
"weight": 378,
|
||||
"weight": 372,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -16674,7 +16676,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "listSubscribers",
|
||||
"weight": 383,
|
||||
"weight": 377,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -16761,7 +16763,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "createSubscriber",
|
||||
"weight": 382,
|
||||
"weight": 376,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -16855,7 +16857,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "getSubscriber",
|
||||
"weight": 385,
|
||||
"weight": 379,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -16921,7 +16923,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "deleteSubscriber",
|
||||
"weight": 386,
|
||||
"weight": 380,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -25746,7 +25748,7 @@
|
||||
},
|
||||
"schedule": {
|
||||
"type": "string",
|
||||
"description": "Function execution schedult in CRON format.",
|
||||
"description": "Function execution schedule in CRON format.",
|
||||
"x-example": "5 4 * * *"
|
||||
},
|
||||
"timeout": {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"swagger": "2.0",
|
||||
"info": {
|
||||
"version": "1.6.0",
|
||||
"version": "1.6.1",
|
||||
"title": "Appwrite",
|
||||
"description": "Appwrite backend as a service cuts up to 70% of the time and costs required for building a modern application. We abstract and simplify common development tasks behind a REST APIs, to help you develop your app in a fast and secure way. For full API documentation and tutorials go to [https:\/\/appwrite.io\/docs](https:\/\/appwrite.io\/docs)",
|
||||
"termsOfService": "https:\/\/appwrite.io\/policy\/terms",
|
||||
@@ -2922,7 +2922,7 @@
|
||||
"tags": [
|
||||
"account"
|
||||
],
|
||||
"description": "Sends the user an email with a secret key for creating a session. If the provided user ID has not been registered, a new user will be created. When the user clicks the link in the email, the user is redirected back to the URL you provided with the secret key and userId values attached to the URL query string. Use the query string parameters to submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The link sent to the user's email address is valid for 1 hour. If you are on a mobile device you can leave the URL parameter empty, so that the login completion will be handled by your Appwrite instance by default.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).\n",
|
||||
"description": "Sends the user an email with a secret key for creating a session. If the provided user ID has not been registered, a new user will be created. When the user clicks the link in the email, the user is redirected back to the URL you provided with the secret key and userId values attached to the URL query string. Use the query string parameters to submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The link sent to the user's email address is valid for 1 hour.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).\n",
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": "Token",
|
||||
@@ -5981,7 +5981,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "createSubscriber",
|
||||
"weight": 382,
|
||||
"weight": 376,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -6070,7 +6070,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "deleteSubscriber",
|
||||
"weight": 386,
|
||||
"weight": 380,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"swagger": "2.0",
|
||||
"info": {
|
||||
"version": "1.6.0",
|
||||
"version": "1.6.1",
|
||||
"title": "Appwrite",
|
||||
"description": "Appwrite backend as a service cuts up to 70% of the time and costs required for building a modern application. We abstract and simplify common development tasks behind a REST APIs, to help you develop your app in a fast and secure way. For full API documentation and tutorials go to [https:\/\/appwrite.io\/docs](https:\/\/appwrite.io\/docs)",
|
||||
"termsOfService": "https:\/\/appwrite.io\/policy\/terms",
|
||||
@@ -2949,7 +2949,7 @@
|
||||
"tags": [
|
||||
"account"
|
||||
],
|
||||
"description": "Sends the user an email with a secret key for creating a session. If the provided user ID has not been registered, a new user will be created. When the user clicks the link in the email, the user is redirected back to the URL you provided with the secret key and userId values attached to the URL query string. Use the query string parameters to submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The link sent to the user's email address is valid for 1 hour. If you are on a mobile device you can leave the URL parameter empty, so that the login completion will be handled by your Appwrite instance by default.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).\n",
|
||||
"description": "Sends the user an email with a secret key for creating a session. If the provided user ID has not been registered, a new user will be created. When the user clicks the link in the email, the user is redirected back to the URL you provided with the secret key and userId values attached to the URL query string. Use the query string parameters to submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The link sent to the user's email address is valid for 1 hour.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).\n",
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": "Token",
|
||||
@@ -9610,7 +9610,8 @@
|
||||
"bun-1.0",
|
||||
"bun-1.1",
|
||||
"go-1.23",
|
||||
"static-1"
|
||||
"static-1",
|
||||
"flutter-3.24"
|
||||
],
|
||||
"x-enum-name": null,
|
||||
"x-enum-keys": []
|
||||
@@ -10286,7 +10287,8 @@
|
||||
"bun-1.0",
|
||||
"bun-1.1",
|
||||
"go-1.23",
|
||||
"static-1"
|
||||
"static-1",
|
||||
"flutter-3.24"
|
||||
],
|
||||
"x-enum-name": null,
|
||||
"x-enum-keys": []
|
||||
@@ -13890,7 +13892,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "listMessages",
|
||||
"weight": 390,
|
||||
"weight": 384,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -13967,7 +13969,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "createEmail",
|
||||
"weight": 387,
|
||||
"weight": 381,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -14127,7 +14129,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "updateEmail",
|
||||
"weight": 394,
|
||||
"weight": 388,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -14284,7 +14286,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "createPush",
|
||||
"weight": 389,
|
||||
"weight": 383,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -14459,7 +14461,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "updatePush",
|
||||
"weight": 396,
|
||||
"weight": 390,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -14631,7 +14633,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "createSms",
|
||||
"weight": 388,
|
||||
"weight": 382,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -14751,7 +14753,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "updateSms",
|
||||
"weight": 395,
|
||||
"weight": 389,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -14869,7 +14871,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "getMessage",
|
||||
"weight": 393,
|
||||
"weight": 387,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -14928,7 +14930,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "delete",
|
||||
"weight": 397,
|
||||
"weight": 391,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -14992,7 +14994,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "listMessageLogs",
|
||||
"weight": 391,
|
||||
"weight": 385,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -15068,7 +15070,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "listTargets",
|
||||
"weight": 392,
|
||||
"weight": 386,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -15144,7 +15146,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "listProviders",
|
||||
"weight": 362,
|
||||
"weight": 356,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -15221,7 +15223,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "createApnsProvider",
|
||||
"weight": 361,
|
||||
"weight": 355,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -15338,7 +15340,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "updateApnsProvider",
|
||||
"weight": 374,
|
||||
"weight": 368,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -15453,7 +15455,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "createFcmProvider",
|
||||
"weight": 360,
|
||||
"weight": 354,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -15546,7 +15548,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "updateFcmProvider",
|
||||
"weight": 373,
|
||||
"weight": 367,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -15637,7 +15639,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "createMailgunProvider",
|
||||
"weight": 352,
|
||||
"weight": 346,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -15766,7 +15768,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "updateMailgunProvider",
|
||||
"weight": 365,
|
||||
"weight": 359,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -15893,7 +15895,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "createMsg91Provider",
|
||||
"weight": 355,
|
||||
"weight": 349,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -15998,7 +16000,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "updateMsg91Provider",
|
||||
"weight": 368,
|
||||
"weight": 362,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -16101,7 +16103,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "createSendgridProvider",
|
||||
"weight": 353,
|
||||
"weight": 347,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -16218,7 +16220,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "updateSendgridProvider",
|
||||
"weight": 366,
|
||||
"weight": 360,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -16333,7 +16335,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "createSmtpProvider",
|
||||
"weight": 354,
|
||||
"weight": 348,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -16494,7 +16496,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "updateSmtpProvider",
|
||||
"weight": 367,
|
||||
"weight": 361,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -16652,7 +16654,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "createTelesignProvider",
|
||||
"weight": 356,
|
||||
"weight": 350,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -16757,7 +16759,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "updateTelesignProvider",
|
||||
"weight": 369,
|
||||
"weight": 363,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -16860,7 +16862,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "createTextmagicProvider",
|
||||
"weight": 357,
|
||||
"weight": 351,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -16965,7 +16967,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "updateTextmagicProvider",
|
||||
"weight": 370,
|
||||
"weight": 364,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -17068,7 +17070,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "createTwilioProvider",
|
||||
"weight": 358,
|
||||
"weight": 352,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -17173,7 +17175,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "updateTwilioProvider",
|
||||
"weight": 371,
|
||||
"weight": 365,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -17276,7 +17278,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "createVonageProvider",
|
||||
"weight": 359,
|
||||
"weight": 353,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -17381,7 +17383,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "updateVonageProvider",
|
||||
"weight": 372,
|
||||
"weight": 366,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -17484,7 +17486,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "getProvider",
|
||||
"weight": 364,
|
||||
"weight": 358,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -17543,7 +17545,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "deleteProvider",
|
||||
"weight": 375,
|
||||
"weight": 369,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -17607,7 +17609,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "listProviderLogs",
|
||||
"weight": 363,
|
||||
"weight": 357,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -17683,7 +17685,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "listSubscriberLogs",
|
||||
"weight": 384,
|
||||
"weight": 378,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -17759,7 +17761,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "listTopics",
|
||||
"weight": 377,
|
||||
"weight": 371,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -17834,7 +17836,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "createTopic",
|
||||
"weight": 376,
|
||||
"weight": 370,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -17926,7 +17928,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "getTopic",
|
||||
"weight": 379,
|
||||
"weight": 373,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -17988,7 +17990,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "updateTopic",
|
||||
"weight": 380,
|
||||
"weight": 374,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -18071,7 +18073,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "deleteTopic",
|
||||
"weight": 381,
|
||||
"weight": 375,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -18135,7 +18137,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "listTopicLogs",
|
||||
"weight": 378,
|
||||
"weight": 372,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -18211,7 +18213,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "listSubscribers",
|
||||
"weight": 383,
|
||||
"weight": 377,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -18294,7 +18296,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "createSubscriber",
|
||||
"weight": 382,
|
||||
"weight": 376,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -18386,7 +18388,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "getSubscriber",
|
||||
"weight": 385,
|
||||
"weight": 379,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -18453,7 +18455,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "deleteSubscriber",
|
||||
"weight": 386,
|
||||
"weight": 380,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -18528,7 +18530,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "list",
|
||||
"weight": 339,
|
||||
"weight": 338,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -18699,7 +18701,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "getAppwriteReport",
|
||||
"weight": 341,
|
||||
"weight": 340,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -18767,7 +18769,7 @@
|
||||
},
|
||||
"\/migrations\/firebase": {
|
||||
"post": {
|
||||
"summary": "Migrate Firebase data (Service Account)",
|
||||
"summary": "Migrate Firebase data",
|
||||
"operationId": "migrationsCreateFirebaseMigration",
|
||||
"consumes": [
|
||||
"application\/json"
|
||||
@@ -18789,7 +18791,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "createFirebaseMigration",
|
||||
"weight": 336,
|
||||
"weight": 335,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -18847,192 +18849,6 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"\/migrations\/firebase\/deauthorize": {
|
||||
"get": {
|
||||
"summary": "Revoke Appwrite's authorization to access Firebase projects",
|
||||
"operationId": "migrationsDeleteFirebaseAuth",
|
||||
"consumes": [
|
||||
"application\/json"
|
||||
],
|
||||
"produces": [
|
||||
"application\/json"
|
||||
],
|
||||
"tags": [
|
||||
"migrations"
|
||||
],
|
||||
"description": "",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "File",
|
||||
"schema": {
|
||||
"type": "file"
|
||||
}
|
||||
}
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "deleteFirebaseAuth",
|
||||
"weight": 347,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
"demo": "migrations\/delete-firebase-auth.md",
|
||||
"edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master",
|
||||
"rate-limit": 0,
|
||||
"rate-time": 3600,
|
||||
"rate-key": "url:{url},ip:{ip}",
|
||||
"scope": "migrations.write",
|
||||
"platforms": [
|
||||
"console"
|
||||
],
|
||||
"packaging": false,
|
||||
"offline-model": "",
|
||||
"offline-key": "",
|
||||
"offline-response-key": "$id",
|
||||
"auth": {
|
||||
"Project": []
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"Project": []
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"\/migrations\/firebase\/oauth": {
|
||||
"post": {
|
||||
"summary": "Migrate Firebase data (OAuth)",
|
||||
"operationId": "migrationsCreateFirebaseOAuthMigration",
|
||||
"consumes": [
|
||||
"application\/json"
|
||||
],
|
||||
"produces": [
|
||||
"application\/json"
|
||||
],
|
||||
"tags": [
|
||||
"migrations"
|
||||
],
|
||||
"description": "",
|
||||
"responses": {
|
||||
"202": {
|
||||
"description": "Migration",
|
||||
"schema": {
|
||||
"$ref": "#\/definitions\/migration"
|
||||
}
|
||||
}
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "createFirebaseOAuthMigration",
|
||||
"weight": 335,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
"demo": "migrations\/create-firebase-o-auth-migration.md",
|
||||
"edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/migrations\/migration-firebase.md",
|
||||
"rate-limit": 0,
|
||||
"rate-time": 3600,
|
||||
"rate-key": "url:{url},ip:{ip}",
|
||||
"scope": "migrations.write",
|
||||
"platforms": [
|
||||
"console"
|
||||
],
|
||||
"packaging": false,
|
||||
"offline-model": "",
|
||||
"offline-key": "",
|
||||
"offline-response-key": "$id",
|
||||
"auth": {
|
||||
"Project": []
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"Project": []
|
||||
}
|
||||
],
|
||||
"parameters": [
|
||||
{
|
||||
"name": "payload",
|
||||
"in": "body",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"resources": {
|
||||
"type": "array",
|
||||
"description": "List of resources to migrate",
|
||||
"default": null,
|
||||
"x-example": null,
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"projectId": {
|
||||
"type": "string",
|
||||
"description": "Project ID of the Firebase Project",
|
||||
"default": null,
|
||||
"x-example": "<PROJECT_ID>"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"resources",
|
||||
"projectId"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"\/migrations\/firebase\/projects": {
|
||||
"get": {
|
||||
"summary": "List Firebase projects",
|
||||
"operationId": "migrationsListFirebaseProjects",
|
||||
"consumes": [
|
||||
"application\/json"
|
||||
],
|
||||
"produces": [
|
||||
"application\/json"
|
||||
],
|
||||
"tags": [
|
||||
"migrations"
|
||||
],
|
||||
"description": "",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Migrations Firebase Projects List",
|
||||
"schema": {
|
||||
"$ref": "#\/definitions\/firebaseProjectList"
|
||||
}
|
||||
}
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "listFirebaseProjects",
|
||||
"weight": 346,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
"demo": "migrations\/list-firebase-projects.md",
|
||||
"edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master",
|
||||
"rate-limit": 0,
|
||||
"rate-time": 3600,
|
||||
"rate-key": "url:{url},ip:{ip}",
|
||||
"scope": "migrations.read",
|
||||
"platforms": [
|
||||
"console"
|
||||
],
|
||||
"packaging": false,
|
||||
"offline-model": "",
|
||||
"offline-key": "",
|
||||
"offline-response-key": "$id",
|
||||
"auth": {
|
||||
"Project": []
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"Project": []
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"\/migrations\/firebase\/report": {
|
||||
"get": {
|
||||
"summary": "Generate a report on Firebase data",
|
||||
@@ -19057,7 +18873,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "getFirebaseReport",
|
||||
"weight": 342,
|
||||
"weight": 341,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -19106,79 +18922,6 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"\/migrations\/firebase\/report\/oauth": {
|
||||
"get": {
|
||||
"summary": "Generate a report on Firebase data using OAuth",
|
||||
"operationId": "migrationsGetFirebaseReportOAuth",
|
||||
"consumes": [
|
||||
"application\/json"
|
||||
],
|
||||
"produces": [
|
||||
"application\/json"
|
||||
],
|
||||
"tags": [
|
||||
"migrations"
|
||||
],
|
||||
"description": "",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Migration Report",
|
||||
"schema": {
|
||||
"$ref": "#\/definitions\/migrationReport"
|
||||
}
|
||||
}
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "getFirebaseReportOAuth",
|
||||
"weight": 343,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
"demo": "migrations\/get-firebase-report-o-auth.md",
|
||||
"edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/migrations\/migration-firebase-report.md",
|
||||
"rate-limit": 0,
|
||||
"rate-time": 3600,
|
||||
"rate-key": "url:{url},ip:{ip}",
|
||||
"scope": "migrations.write",
|
||||
"platforms": [
|
||||
"console"
|
||||
],
|
||||
"packaging": false,
|
||||
"offline-model": "",
|
||||
"offline-key": "",
|
||||
"offline-response-key": "$id",
|
||||
"auth": {
|
||||
"Project": []
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"Project": []
|
||||
}
|
||||
],
|
||||
"parameters": [
|
||||
{
|
||||
"name": "resources",
|
||||
"description": "List of resources to migrate",
|
||||
"required": true,
|
||||
"type": "array",
|
||||
"collectionFormat": "multi",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"name": "projectId",
|
||||
"description": "Project ID",
|
||||
"required": true,
|
||||
"type": "string",
|
||||
"x-example": "<PROJECT_ID>",
|
||||
"in": "query"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"\/migrations\/nhost": {
|
||||
"post": {
|
||||
"summary": "Migrate NHost data",
|
||||
@@ -19203,7 +18946,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "createNHostMigration",
|
||||
"weight": 338,
|
||||
"weight": 337,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -19326,7 +19069,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "getNHostReport",
|
||||
"weight": 349,
|
||||
"weight": 343,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -19448,7 +19191,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "createSupabaseMigration",
|
||||
"weight": 337,
|
||||
"weight": 336,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -19564,7 +19307,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "getSupabaseReport",
|
||||
"weight": 348,
|
||||
"weight": 342,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -19679,7 +19422,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "get",
|
||||
"weight": 340,
|
||||
"weight": 339,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -19739,7 +19482,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "retry",
|
||||
"weight": 350,
|
||||
"weight": 344,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -19794,7 +19537,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "delete",
|
||||
"weight": 351,
|
||||
"weight": 345,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -21138,7 +20881,7 @@
|
||||
},
|
||||
"\/projects\/{projectId}\/auth\/memberships-privacy": {
|
||||
"patch": {
|
||||
"summary": "Update project team sensitive attributes",
|
||||
"summary": "Update project memberships privacy attributes",
|
||||
"operationId": "projectsUpdateMembershipsPrivacy",
|
||||
"consumes": [
|
||||
"application\/json"
|
||||
@@ -33250,31 +32993,6 @@
|
||||
"migrations"
|
||||
]
|
||||
},
|
||||
"firebaseProjectList": {
|
||||
"description": "Migrations Firebase Projects List",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"total": {
|
||||
"type": "integer",
|
||||
"description": "Total number of projects documents that matched your query.",
|
||||
"x-example": 5,
|
||||
"format": "int32"
|
||||
},
|
||||
"projects": {
|
||||
"type": "array",
|
||||
"description": "List of projects.",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"$ref": "#\/definitions\/firebaseProject"
|
||||
},
|
||||
"x-example": ""
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"total",
|
||||
"projects"
|
||||
]
|
||||
},
|
||||
"specificationList": {
|
||||
"description": "Specifications List",
|
||||
"type": "object",
|
||||
@@ -35618,7 +35336,7 @@
|
||||
},
|
||||
"schedule": {
|
||||
"type": "string",
|
||||
"description": "Function execution schedult in CRON format.",
|
||||
"description": "Function execution schedule in CRON format.",
|
||||
"x-example": "5 4 * * *"
|
||||
},
|
||||
"timeout": {
|
||||
@@ -36591,17 +36309,17 @@
|
||||
"description": "Whether or not to send session alert emails to users.",
|
||||
"x-example": true
|
||||
},
|
||||
"membershipsUserName": {
|
||||
"authMembershipsUserName": {
|
||||
"type": "boolean",
|
||||
"description": "Whether or not to show user names in the teams membership response.",
|
||||
"x-example": true
|
||||
},
|
||||
"membershipsUserEmail": {
|
||||
"authMembershipsUserEmail": {
|
||||
"type": "boolean",
|
||||
"description": "Whether or not to show user emails in the teams membership response.",
|
||||
"x-example": true
|
||||
},
|
||||
"membershipsMfa": {
|
||||
"authMembershipsMfa": {
|
||||
"type": "boolean",
|
||||
"description": "Whether or not to show user MFA status in the teams membership response.",
|
||||
"x-example": true
|
||||
@@ -36815,9 +36533,9 @@
|
||||
"authPersonalDataCheck",
|
||||
"authMockNumbers",
|
||||
"authSessionAlerts",
|
||||
"membershipsUserName",
|
||||
"membershipsUserEmail",
|
||||
"membershipsMfa",
|
||||
"authMembershipsUserName",
|
||||
"authMembershipsUserEmail",
|
||||
"authMembershipsMfa",
|
||||
"oAuthProviders",
|
||||
"platforms",
|
||||
"webhooks",
|
||||
@@ -39274,26 +38992,6 @@
|
||||
"size",
|
||||
"version"
|
||||
]
|
||||
},
|
||||
"firebaseProject": {
|
||||
"description": "MigrationFirebaseProject",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"projectId": {
|
||||
"type": "string",
|
||||
"description": "Project ID.",
|
||||
"x-example": "my-project"
|
||||
},
|
||||
"displayName": {
|
||||
"type": "string",
|
||||
"description": "Project display name.",
|
||||
"x-example": "My Project"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"projectId",
|
||||
"displayName"
|
||||
]
|
||||
}
|
||||
},
|
||||
"externalDocs": {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"swagger": "2.0",
|
||||
"info": {
|
||||
"version": "1.6.0",
|
||||
"version": "1.6.1",
|
||||
"title": "Appwrite",
|
||||
"description": "Appwrite backend as a service cuts up to 70% of the time and costs required for building a modern application. We abstract and simplify common development tasks behind a REST APIs, to help you develop your app in a fast and secure way. For full API documentation and tutorials go to [https:\/\/appwrite.io\/docs](https:\/\/appwrite.io\/docs)",
|
||||
"termsOfService": "https:\/\/appwrite.io\/policy\/terms",
|
||||
@@ -2604,7 +2604,7 @@
|
||||
"tags": [
|
||||
"account"
|
||||
],
|
||||
"description": "Sends the user an email with a secret key for creating a session. If the provided user ID has not been registered, a new user will be created. When the user clicks the link in the email, the user is redirected back to the URL you provided with the secret key and userId values attached to the URL query string. Use the query string parameters to submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The link sent to the user's email address is valid for 1 hour. If you are on a mobile device you can leave the URL parameter empty, so that the login completion will be handled by your Appwrite instance by default.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).\n",
|
||||
"description": "Sends the user an email with a secret key for creating a session. If the provided user ID has not been registered, a new user will be created. When the user clicks the link in the email, the user is redirected back to the URL you provided with the secret key and userId values attached to the URL query string. Use the query string parameters to submit a request to the [POST \/v1\/account\/sessions\/token](https:\/\/appwrite.io\/docs\/references\/cloud\/client-web\/account#createSession) endpoint to complete the login process. The link sent to the user's email address is valid for 1 hour.\n\nA user is limited to 10 active sessions at a time by default. [Learn more about session limits](https:\/\/appwrite.io\/docs\/authentication-security#limits).\n",
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": "Token",
|
||||
@@ -8720,7 +8720,8 @@
|
||||
"bun-1.0",
|
||||
"bun-1.1",
|
||||
"go-1.23",
|
||||
"static-1"
|
||||
"static-1",
|
||||
"flutter-3.24"
|
||||
],
|
||||
"x-enum-name": null,
|
||||
"x-enum-keys": []
|
||||
@@ -9166,7 +9167,8 @@
|
||||
"bun-1.0",
|
||||
"bun-1.1",
|
||||
"go-1.23",
|
||||
"static-1"
|
||||
"static-1",
|
||||
"flutter-3.24"
|
||||
],
|
||||
"x-enum-name": null,
|
||||
"x-enum-keys": []
|
||||
@@ -12752,7 +12754,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "listMessages",
|
||||
"weight": 390,
|
||||
"weight": 384,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -12830,7 +12832,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "createEmail",
|
||||
"weight": 387,
|
||||
"weight": 381,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -12991,7 +12993,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "updateEmail",
|
||||
"weight": 394,
|
||||
"weight": 388,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -13149,7 +13151,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "createPush",
|
||||
"weight": 389,
|
||||
"weight": 383,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -13325,7 +13327,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "updatePush",
|
||||
"weight": 396,
|
||||
"weight": 390,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -13498,7 +13500,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "createSms",
|
||||
"weight": 388,
|
||||
"weight": 382,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -13619,7 +13621,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "updateSms",
|
||||
"weight": 395,
|
||||
"weight": 389,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -13738,7 +13740,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "getMessage",
|
||||
"weight": 393,
|
||||
"weight": 387,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -13798,7 +13800,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "delete",
|
||||
"weight": 397,
|
||||
"weight": 391,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -13863,7 +13865,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "listMessageLogs",
|
||||
"weight": 391,
|
||||
"weight": 385,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -13940,7 +13942,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "listTargets",
|
||||
"weight": 392,
|
||||
"weight": 386,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -14017,7 +14019,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "listProviders",
|
||||
"weight": 362,
|
||||
"weight": 356,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -14095,7 +14097,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "createApnsProvider",
|
||||
"weight": 361,
|
||||
"weight": 355,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -14213,7 +14215,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "updateApnsProvider",
|
||||
"weight": 374,
|
||||
"weight": 368,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -14329,7 +14331,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "createFcmProvider",
|
||||
"weight": 360,
|
||||
"weight": 354,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -14423,7 +14425,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "updateFcmProvider",
|
||||
"weight": 373,
|
||||
"weight": 367,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -14515,7 +14517,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "createMailgunProvider",
|
||||
"weight": 352,
|
||||
"weight": 346,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -14645,7 +14647,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "updateMailgunProvider",
|
||||
"weight": 365,
|
||||
"weight": 359,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -14773,7 +14775,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "createMsg91Provider",
|
||||
"weight": 355,
|
||||
"weight": 349,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -14879,7 +14881,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "updateMsg91Provider",
|
||||
"weight": 368,
|
||||
"weight": 362,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -14983,7 +14985,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "createSendgridProvider",
|
||||
"weight": 353,
|
||||
"weight": 347,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -15101,7 +15103,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "updateSendgridProvider",
|
||||
"weight": 366,
|
||||
"weight": 360,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -15217,7 +15219,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "createSmtpProvider",
|
||||
"weight": 354,
|
||||
"weight": 348,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -15379,7 +15381,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "updateSmtpProvider",
|
||||
"weight": 367,
|
||||
"weight": 361,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -15538,7 +15540,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "createTelesignProvider",
|
||||
"weight": 356,
|
||||
"weight": 350,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -15644,7 +15646,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "updateTelesignProvider",
|
||||
"weight": 369,
|
||||
"weight": 363,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -15748,7 +15750,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "createTextmagicProvider",
|
||||
"weight": 357,
|
||||
"weight": 351,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -15854,7 +15856,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "updateTextmagicProvider",
|
||||
"weight": 370,
|
||||
"weight": 364,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -15958,7 +15960,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "createTwilioProvider",
|
||||
"weight": 358,
|
||||
"weight": 352,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -16064,7 +16066,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "updateTwilioProvider",
|
||||
"weight": 371,
|
||||
"weight": 365,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -16168,7 +16170,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "createVonageProvider",
|
||||
"weight": 359,
|
||||
"weight": 353,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -16274,7 +16276,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "updateVonageProvider",
|
||||
"weight": 372,
|
||||
"weight": 366,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -16378,7 +16380,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "getProvider",
|
||||
"weight": 364,
|
||||
"weight": 358,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -16438,7 +16440,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "deleteProvider",
|
||||
"weight": 375,
|
||||
"weight": 369,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -16503,7 +16505,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "listProviderLogs",
|
||||
"weight": 363,
|
||||
"weight": 357,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -16580,7 +16582,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "listSubscriberLogs",
|
||||
"weight": 384,
|
||||
"weight": 378,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -16657,7 +16659,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "listTopics",
|
||||
"weight": 377,
|
||||
"weight": 371,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -16733,7 +16735,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "createTopic",
|
||||
"weight": 376,
|
||||
"weight": 370,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -16826,7 +16828,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "getTopic",
|
||||
"weight": 379,
|
||||
"weight": 373,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -16889,7 +16891,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "updateTopic",
|
||||
"weight": 380,
|
||||
"weight": 374,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -16973,7 +16975,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "deleteTopic",
|
||||
"weight": 381,
|
||||
"weight": 375,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -17038,7 +17040,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "listTopicLogs",
|
||||
"weight": 378,
|
||||
"weight": 372,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -17115,7 +17117,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "listSubscribers",
|
||||
"weight": 383,
|
||||
"weight": 377,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -17199,7 +17201,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "createSubscriber",
|
||||
"weight": 382,
|
||||
"weight": 376,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -17293,7 +17295,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "getSubscriber",
|
||||
"weight": 385,
|
||||
"weight": 379,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -17361,7 +17363,7 @@
|
||||
},
|
||||
"x-appwrite": {
|
||||
"method": "deleteSubscriber",
|
||||
"weight": 386,
|
||||
"weight": 380,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
@@ -26233,7 +26235,7 @@
|
||||
},
|
||||
"schedule": {
|
||||
"type": "string",
|
||||
"description": "Function execution schedult in CRON format.",
|
||||
"description": "Function execution schedule in CRON format.",
|
||||
"x-example": "5 4 * * *"
|
||||
},
|
||||
"timeout": {
|
||||
|
||||
@@ -61,9 +61,9 @@ $avatarCallback = function (string $type, string $code, int $width, int $height,
|
||||
unset($image);
|
||||
};
|
||||
|
||||
$getUserGitHub = function (string $userId, Document $project, Database $dbForProject, Database $dbForConsole, ?Logger $logger) {
|
||||
$getUserGitHub = function (string $userId, Document $project, Database $dbForProject, Database $dbForPlatform, ?Logger $logger) {
|
||||
try {
|
||||
$user = Authorization::skip(fn () => $dbForConsole->getDocument('users', $userId));
|
||||
$user = Authorization::skip(fn () => $dbForPlatform->getDocument('users', $userId));
|
||||
|
||||
$sessions = $user->getAttribute('sessions', []);
|
||||
|
||||
@@ -122,7 +122,7 @@ $getUserGitHub = function (string $userId, Document $project, Database $dbForPro
|
||||
do {
|
||||
$previousAccessToken = $gitHubSession->getAttribute('providerAccessToken');
|
||||
|
||||
$user = Authorization::skip(fn () => $dbForConsole->getDocument('users', $userId));
|
||||
$user = Authorization::skip(fn () => $dbForPlatform->getDocument('users', $userId));
|
||||
$sessions = $user->getAttribute('sessions', []);
|
||||
|
||||
$gitHubSession = new Document();
|
||||
@@ -565,14 +565,14 @@ App::get('/v1/cards/cloud')
|
||||
->inject('user')
|
||||
->inject('project')
|
||||
->inject('dbForProject')
|
||||
->inject('dbForConsole')
|
||||
->inject('dbForPlatform')
|
||||
->inject('response')
|
||||
->inject('heroes')
|
||||
->inject('contributors')
|
||||
->inject('employees')
|
||||
->inject('logger')
|
||||
->action(function (string $userId, string $mock, int $width, int $height, Document $user, Document $project, Database $dbForProject, Database $dbForConsole, Response $response, array $heroes, array $contributors, array $employees, ?Logger $logger) use ($getUserGitHub) {
|
||||
$user = Authorization::skip(fn () => $dbForConsole->getDocument('users', $userId));
|
||||
->action(function (string $userId, string $mock, int $width, int $height, Document $user, Document $project, Database $dbForProject, Database $dbForPlatform, Response $response, array $heroes, array $contributors, array $employees, ?Logger $logger) use ($getUserGitHub) {
|
||||
$user = Authorization::skip(fn () => $dbForPlatform->getDocument('users', $userId));
|
||||
|
||||
if ($user->isEmpty() && empty($mock)) {
|
||||
throw new Exception(Exception::USER_NOT_FOUND);
|
||||
@@ -583,7 +583,7 @@ App::get('/v1/cards/cloud')
|
||||
$email = $user->getAttribute('email', '');
|
||||
$createdAt = new \DateTime($user->getCreatedAt());
|
||||
|
||||
$gitHub = $getUserGitHub($user->getId(), $project, $dbForProject, $dbForConsole, $logger);
|
||||
$gitHub = $getUserGitHub($user->getId(), $project, $dbForProject, $dbForPlatform, $logger);
|
||||
$githubName = $gitHub['name'] ?? '';
|
||||
$githubId = $gitHub['id'] ?? '';
|
||||
|
||||
@@ -772,14 +772,14 @@ App::get('/v1/cards/cloud-back')
|
||||
->inject('user')
|
||||
->inject('project')
|
||||
->inject('dbForProject')
|
||||
->inject('dbForConsole')
|
||||
->inject('dbForPlatform')
|
||||
->inject('response')
|
||||
->inject('heroes')
|
||||
->inject('contributors')
|
||||
->inject('employees')
|
||||
->inject('logger')
|
||||
->action(function (string $userId, string $mock, int $width, int $height, Document $user, Document $project, Database $dbForProject, Database $dbForConsole, Response $response, array $heroes, array $contributors, array $employees, ?Logger $logger) use ($getUserGitHub) {
|
||||
$user = Authorization::skip(fn () => $dbForConsole->getDocument('users', $userId));
|
||||
->action(function (string $userId, string $mock, int $width, int $height, Document $user, Document $project, Database $dbForProject, Database $dbForPlatform, Response $response, array $heroes, array $contributors, array $employees, ?Logger $logger) use ($getUserGitHub) {
|
||||
$user = Authorization::skip(fn () => $dbForPlatform->getDocument('users', $userId));
|
||||
|
||||
if ($user->isEmpty() && empty($mock)) {
|
||||
throw new Exception(Exception::USER_NOT_FOUND);
|
||||
@@ -789,7 +789,7 @@ App::get('/v1/cards/cloud-back')
|
||||
$userId = $user->getId();
|
||||
$email = $user->getAttribute('email', '');
|
||||
|
||||
$gitHub = $getUserGitHub($user->getId(), $project, $dbForProject, $dbForConsole, $logger);
|
||||
$gitHub = $getUserGitHub($user->getId(), $project, $dbForProject, $dbForPlatform, $logger);
|
||||
$githubId = $gitHub['id'] ?? '';
|
||||
|
||||
$isHero = \array_key_exists($email, $heroes);
|
||||
@@ -850,14 +850,14 @@ App::get('/v1/cards/cloud-og')
|
||||
->inject('user')
|
||||
->inject('project')
|
||||
->inject('dbForProject')
|
||||
->inject('dbForConsole')
|
||||
->inject('dbForPlatform')
|
||||
->inject('response')
|
||||
->inject('heroes')
|
||||
->inject('contributors')
|
||||
->inject('employees')
|
||||
->inject('logger')
|
||||
->action(function (string $userId, string $mock, int $width, int $height, Document $user, Document $project, Database $dbForProject, Database $dbForConsole, Response $response, array $heroes, array $contributors, array $employees, ?Logger $logger) use ($getUserGitHub) {
|
||||
$user = Authorization::skip(fn () => $dbForConsole->getDocument('users', $userId));
|
||||
->action(function (string $userId, string $mock, int $width, int $height, Document $user, Document $project, Database $dbForProject, Database $dbForPlatform, Response $response, array $heroes, array $contributors, array $employees, ?Logger $logger) use ($getUserGitHub) {
|
||||
$user = Authorization::skip(fn () => $dbForPlatform->getDocument('users', $userId));
|
||||
|
||||
if ($user->isEmpty() && empty($mock)) {
|
||||
throw new Exception(Exception::USER_NOT_FOUND);
|
||||
@@ -872,7 +872,7 @@ App::get('/v1/cards/cloud-og')
|
||||
$email = $user->getAttribute('email', '');
|
||||
$createdAt = new \DateTime($user->getCreatedAt());
|
||||
|
||||
$gitHub = $getUserGitHub($user->getId(), $project, $dbForProject, $dbForConsole, $logger);
|
||||
$gitHub = $getUserGitHub($user->getId(), $project, $dbForProject, $dbForPlatform, $logger);
|
||||
$githubName = $gitHub['name'] ?? '';
|
||||
$githubId = $gitHub['id'] ?? '';
|
||||
|
||||
|
||||
@@ -71,7 +71,7 @@ App::post('/v1/console/assistant')
|
||||
->param('prompt', '', new Text(2000), 'Prompt. A string containing questions asked to the AI assistant.')
|
||||
->inject('response')
|
||||
->action(function (string $prompt, Response $response) {
|
||||
$ch = curl_init('http://appwrite-assistant:3003/');
|
||||
$ch = curl_init('http://appwrite-assistant:3003/v1/models/assistant/prompt');
|
||||
$responseHeaders = [];
|
||||
$query = json_encode(['prompt' => $prompt]);
|
||||
$headers = ['accept: text/event-stream'];
|
||||
|
||||
@@ -19,7 +19,6 @@ use Utopia\Audit\Audit;
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Exception as DatabaseException;
|
||||
use Utopia\Database\Exception\Authorization as AuthorizationException;
|
||||
use Utopia\Database\Exception\Conflict as ConflictException;
|
||||
use Utopia\Database\Exception\Duplicate as DuplicateException;
|
||||
@@ -152,7 +151,7 @@ function createAttribute(string $databaseId, string $collectionId, Document $att
|
||||
} catch (DuplicateException) {
|
||||
throw new Exception(Exception::ATTRIBUTE_ALREADY_EXISTS);
|
||||
} catch (LimitException) {
|
||||
throw new Exception(Exception::ATTRIBUTE_LIMIT_EXCEEDED, 'Attribute limit exceeded');
|
||||
throw new Exception(Exception::ATTRIBUTE_LIMIT_EXCEEDED);
|
||||
} catch (\Throwable $e) {
|
||||
$dbForProject->purgeCachedDocument('database_' . $db->getInternalId(), $collectionId);
|
||||
$dbForProject->purgeCachedCollection('database_' . $db->getInternalId() . '_collection_' . $collection->getInternalId());
|
||||
@@ -196,7 +195,7 @@ function createAttribute(string $databaseId, string $collectionId, Document $att
|
||||
throw new Exception(Exception::ATTRIBUTE_ALREADY_EXISTS);
|
||||
} catch (LimitException) {
|
||||
$dbForProject->deleteDocument('attributes', $attribute->getId());
|
||||
throw new Exception(Exception::ATTRIBUTE_LIMIT_EXCEEDED, 'Attribute limit exceeded');
|
||||
throw new Exception(Exception::ATTRIBUTE_LIMIT_EXCEEDED);
|
||||
} catch (\Throwable $e) {
|
||||
$dbForProject->purgeCachedDocument('database_' . $db->getInternalId(), $relatedCollection->getId());
|
||||
$dbForProject->purgeCachedCollection('database_' . $db->getInternalId() . '_collection_' . $relatedCollection->getInternalId());
|
||||
@@ -290,15 +289,9 @@ function updateAttribute(
|
||||
$attribute->setAttribute('size', $size);
|
||||
}
|
||||
|
||||
$formatOptions = $attribute->getAttribute('formatOptions');
|
||||
|
||||
switch ($attribute->getAttribute('format')) {
|
||||
case APP_DATABASE_ATTRIBUTE_INT_RANGE:
|
||||
case APP_DATABASE_ATTRIBUTE_FLOAT_RANGE:
|
||||
if ($min === $formatOptions['min'] && $max === $formatOptions['max']) {
|
||||
break;
|
||||
}
|
||||
|
||||
if ($min > $max) {
|
||||
throw new Exception(Exception::ATTRIBUTE_VALUE_INVALID, 'Minimum value must be lesser than maximum value');
|
||||
}
|
||||
@@ -385,30 +378,42 @@ function updateAttribute(
|
||||
size: $size,
|
||||
required: $required,
|
||||
default: $default,
|
||||
formatOptions: $options ?? null,
|
||||
formatOptions: $options,
|
||||
newKey: $newKey ?? null
|
||||
);
|
||||
} catch (TruncateException) {
|
||||
throw new Exception(Exception::ATTRIBUTE_INVALID_RESIZE);
|
||||
} catch (NotFoundException) {
|
||||
throw new Exception(Exception::ATTRIBUTE_NOT_FOUND);
|
||||
} catch (LimitException) {
|
||||
throw new Exception(Exception::ATTRIBUTE_LIMIT_EXCEEDED);
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($newKey) && $key !== $newKey) {
|
||||
// Delete attribute and recreate since we can't modify IDs
|
||||
$original = clone $attribute;
|
||||
|
||||
$dbForProject->deleteDocument('attributes', $attribute->getId());
|
||||
$originalUid = $attribute->getId();
|
||||
|
||||
$attribute
|
||||
->setAttribute('$id', ID::custom($db->getInternalId() . '_' . $collection->getInternalId() . '_' . $newKey))
|
||||
->setAttribute('key', $newKey);
|
||||
|
||||
try {
|
||||
$attribute = $dbForProject->createDocument('attributes', $attribute);
|
||||
} catch (DatabaseException|PDOException) {
|
||||
$attribute = $dbForProject->createDocument('attributes', $original);
|
||||
$dbForProject->updateDocument('attributes', $originalUid, $attribute);
|
||||
|
||||
/**
|
||||
* @var Document $index
|
||||
*/
|
||||
foreach ($collection->getAttribute('indexes') as $index) {
|
||||
/**
|
||||
* @var string[] $attributes
|
||||
*/
|
||||
$attributes = $index->getAttribute('attributes', []);
|
||||
$found = \array_search($key, $attributes);
|
||||
|
||||
if ($found !== false) {
|
||||
$attributes[$found] = $newKey;
|
||||
$index->setAttribute('attributes', $attributes);
|
||||
$dbForProject->updateDocument('indexes', $index->getId(), $index);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$attribute = $dbForProject->updateDocument('attributes', $db->getInternalId() . '_' . $collection->getInternalId() . '_' . $key, $attribute);
|
||||
@@ -2572,7 +2577,6 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/indexes')
|
||||
|
||||
$attributeStatus = $oldAttributes[$attributeIndex]['status'];
|
||||
$attributeType = $oldAttributes[$attributeIndex]['type'];
|
||||
$attributeSize = $oldAttributes[$attributeIndex]['size'];
|
||||
$attributeArray = $oldAttributes[$attributeIndex]['array'] ?? false;
|
||||
|
||||
if ($attributeType === Database::VAR_RELATIONSHIP) {
|
||||
@@ -2586,10 +2590,6 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/indexes')
|
||||
|
||||
$lengths[$i] = null;
|
||||
|
||||
if ($attributeType === Database::VAR_STRING) {
|
||||
$lengths[$i] = $attributeSize; // set attribute size as index length only for strings
|
||||
}
|
||||
|
||||
if ($attributeArray === true) {
|
||||
$lengths[$i] = Database::ARRAY_INDEX_LENGTH;
|
||||
$orders[$i] = null;
|
||||
@@ -2612,7 +2612,8 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/indexes')
|
||||
|
||||
$validator = new IndexValidator(
|
||||
$collection->getAttribute('attributes'),
|
||||
$dbForProject->getAdapter()->getMaxIndexLength()
|
||||
$dbForProject->getAdapter()->getMaxIndexLength(),
|
||||
$dbForProject->getAdapter()->getInternalIndexesKeys(),
|
||||
);
|
||||
if (!$validator->isValid($index)) {
|
||||
throw new Exception(Exception::INDEX_INVALID, $validator->getDescription());
|
||||
@@ -2928,7 +2929,11 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/documents')
|
||||
$data['$permissions'] = $permissions;
|
||||
$document = new Document($data);
|
||||
|
||||
$checkPermissions = function (Document $collection, Document $document, string $permission) use (&$checkPermissions, $dbForProject, $database) {
|
||||
$operations = 0;
|
||||
|
||||
$checkPermissions = function (Document $collection, Document $document, string $permission) use (&$checkPermissions, $dbForProject, $database, &$operations) {
|
||||
$operations++;
|
||||
|
||||
$documentSecurity = $collection->getAttribute('documentSecurity', false);
|
||||
$validator = new Authorization($permission);
|
||||
|
||||
@@ -3020,6 +3025,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/documents')
|
||||
throw new Exception(Exception::COLLECTION_NOT_FOUND);
|
||||
}
|
||||
|
||||
|
||||
// Add $collectionId and $databaseId for all documents
|
||||
$processDocument = function (Document $collection, Document $document) use (&$processDocument, $dbForProject, $database) {
|
||||
$document->setAttribute('$databaseId', $database->getId());
|
||||
@@ -3055,6 +3061,13 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/documents')
|
||||
|
||||
$processDocument($collection, $document);
|
||||
|
||||
$queueForUsage
|
||||
->addMetric(METRIC_DATABASES_OPERATIONS_WRITES, $operations)
|
||||
->addMetric(str_replace('{databaseInternalId}', $database->getInternalId(), METRIC_DATABASE_ID_OPERATIONS_WRITES), $operations)
|
||||
->addMetric(str_replace(['{databaseInternalId}', '{collectionInternalId}'], [$database->getInternalId(), $collection->getInternalId()], METRIC_DATABASE_ID_COLLECTION_ID_STORAGE), 1); // per collection
|
||||
|
||||
$response->addHeader('X-Debug-Operations', $operations);
|
||||
|
||||
$response
|
||||
->setStatusCode(Response::STATUS_CODE_CREATED)
|
||||
->dynamic($document, Response::MODEL_DOCUMENT);
|
||||
@@ -3074,10 +3087,6 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/documents')
|
||||
->setContext('collection', $collection)
|
||||
->setContext('database', $database)
|
||||
->setPayload($response->getPayload(), sensitive: $relationships);
|
||||
|
||||
|
||||
$queueForUsage
|
||||
->addMetric(str_replace(['{databaseInternalId}', '{collectionInternalId}'], [$database->getInternalId(), $collection->getInternalId()], METRIC_DATABASE_ID_COLLECTION_ID_STORAGE), 1); // per collection
|
||||
});
|
||||
|
||||
App::get('/v1/databases/:databaseId/collections/:collectionId/documents')
|
||||
@@ -3100,7 +3109,8 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('mode')
|
||||
->action(function (string $databaseId, string $collectionId, array $queries, Response $response, Database $dbForProject, string $mode) {
|
||||
->inject('queueForUsage')
|
||||
->action(function (string $databaseId, string $collectionId, array $queries, Response $response, Database $dbForProject, string $mode, Usage $queueForUsage) {
|
||||
$database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
|
||||
$isAPIKey = Auth::isAppUser(Authorization::getRoles());
|
||||
$isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles());
|
||||
@@ -3136,7 +3146,6 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents')
|
||||
throw new Exception(Exception::GENERAL_QUERY_INVALID, $validator->getDescription());
|
||||
}
|
||||
|
||||
|
||||
$documentId = $cursor->getValue();
|
||||
|
||||
$cursorDocument = Authorization::skip(fn () => $dbForProject->getDocument('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $documentId));
|
||||
@@ -3151,12 +3160,16 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents')
|
||||
$documents = $dbForProject->find('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $queries);
|
||||
$total = $dbForProject->count('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $queries, APP_LIMIT_COUNT);
|
||||
|
||||
$operations = 0;
|
||||
|
||||
// Add $collectionId and $databaseId for all documents
|
||||
$processDocument = (function (Document $collection, Document $document) use (&$processDocument, $dbForProject, $database): bool {
|
||||
$processDocument = (function (Document $collection, Document $document) use (&$processDocument, $dbForProject, $database, &$operations): bool {
|
||||
if ($document->isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$operations++;
|
||||
|
||||
$document->removeAttribute('$collection');
|
||||
$document->setAttribute('$databaseId', $database->getId());
|
||||
$document->setAttribute('$collectionId', $collection->getId());
|
||||
@@ -3170,8 +3183,13 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents')
|
||||
$related = $document->getAttribute($relationship->getAttribute('key'));
|
||||
|
||||
if (empty($related)) {
|
||||
if (\in_array(\gettype($related), ['array', 'object'])) {
|
||||
$operations++;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!\is_array($related)) {
|
||||
$relations = [$related];
|
||||
} else {
|
||||
@@ -3179,6 +3197,7 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents')
|
||||
}
|
||||
|
||||
$relatedCollectionId = $relationship->getAttribute('relatedCollection');
|
||||
// todo: Use local cache for this getDocument
|
||||
$relatedCollection = Authorization::skip(fn () => $dbForProject->getDocument('database_' . $database->getInternalId(), $relatedCollectionId));
|
||||
|
||||
foreach ($relations as $index => $doc) {
|
||||
@@ -3203,6 +3222,13 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents')
|
||||
$processDocument($collection, $document);
|
||||
}
|
||||
|
||||
$queueForUsage
|
||||
->addMetric(METRIC_DATABASES_OPERATIONS_READS, $operations)
|
||||
->addMetric(str_replace('{databaseInternalId}', $database->getInternalId(), METRIC_DATABASE_ID_OPERATIONS_READS), $operations)
|
||||
;
|
||||
|
||||
$response->addHeader('X-Debug-Operations', $operations);
|
||||
|
||||
$select = \array_reduce($queries, function ($result, $query) {
|
||||
return $result || ($query->getMethod() === Query::TYPE_SELECT);
|
||||
}, false);
|
||||
@@ -3258,9 +3284,9 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents/:documen
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('mode')
|
||||
->action(function (string $databaseId, string $collectionId, string $documentId, array $queries, Response $response, Database $dbForProject, string $mode) {
|
||||
->inject('queueForUsage')
|
||||
->action(function (string $databaseId, string $collectionId, string $documentId, array $queries, Response $response, Database $dbForProject, string $mode, Usage $queueForUsage) {
|
||||
$database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
|
||||
|
||||
$isAPIKey = Auth::isAppUser(Authorization::getRoles());
|
||||
$isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles());
|
||||
|
||||
@@ -3287,12 +3313,16 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents/:documen
|
||||
throw new Exception(Exception::DOCUMENT_NOT_FOUND);
|
||||
}
|
||||
|
||||
$operations = 0;
|
||||
|
||||
// Add $collectionId and $databaseId for all documents
|
||||
$processDocument = function (Document $collection, Document $document) use (&$processDocument, $dbForProject, $database) {
|
||||
$processDocument = function (Document $collection, Document $document) use (&$processDocument, $dbForProject, $database, &$operations) {
|
||||
if ($document->isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$operations++;
|
||||
|
||||
$document->setAttribute('$databaseId', $database->getId());
|
||||
$document->setAttribute('$collectionId', $collection->getId());
|
||||
|
||||
@@ -3305,8 +3335,13 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents/:documen
|
||||
$related = $document->getAttribute($relationship->getAttribute('key'));
|
||||
|
||||
if (empty($related)) {
|
||||
if (\in_array(\gettype($related), ['array', 'object'])) {
|
||||
$operations++;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!\is_array($related)) {
|
||||
$related = [$related];
|
||||
}
|
||||
@@ -3326,6 +3361,13 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents/:documen
|
||||
|
||||
$processDocument($collection, $document);
|
||||
|
||||
$queueForUsage
|
||||
->addMetric(METRIC_DATABASES_OPERATIONS_READS, $operations)
|
||||
->addMetric(str_replace('{databaseInternalId}', $database->getInternalId(), METRIC_DATABASE_ID_OPERATIONS_READS), $operations)
|
||||
;
|
||||
|
||||
$response->addHeader('X-Debug-Operations', $operations);
|
||||
|
||||
$response->dynamic($document, Response::MODEL_DOCUMENT);
|
||||
});
|
||||
|
||||
@@ -3421,6 +3463,7 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents/:documen
|
||||
$output[$i]['countryCode'] = $locale->getText('countries.' . strtolower($geoRecord['countryCode']), false) ? \strtolower($geoRecord['countryCode']) : '--';
|
||||
$output[$i]['countryName'] = $locale->getText('countries.' . strtolower($geoRecord['countryCode']), $locale->getText('locale.country.unknown'));
|
||||
}
|
||||
|
||||
$response->dynamic(new Document([
|
||||
'total' => $audit->countLogsByResource($resource),
|
||||
'logs' => $output,
|
||||
@@ -3458,7 +3501,8 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/documents/:docum
|
||||
->inject('dbForProject')
|
||||
->inject('queueForEvents')
|
||||
->inject('mode')
|
||||
->action(function (string $databaseId, string $collectionId, string $documentId, string|array $data, ?array $permissions, ?\DateTime $requestTimestamp, Response $response, Database $dbForProject, Event $queueForEvents, string $mode) {
|
||||
->inject('queueForUsage')
|
||||
->action(function (string $databaseId, string $collectionId, string $documentId, string|array $data, ?array $permissions, ?\DateTime $requestTimestamp, Response $response, Database $dbForProject, Event $queueForEvents, string $mode, Usage $queueForUsage) {
|
||||
|
||||
$data = (\is_string($data)) ? \json_decode($data, true) : $data; // Cast to JSON array
|
||||
|
||||
@@ -3525,7 +3569,12 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/documents/:docum
|
||||
$data['$permissions'] = $permissions;
|
||||
$newDocument = new Document($data);
|
||||
|
||||
$setCollection = (function (Document $collection, Document $document) use (&$setCollection, $dbForProject, $database) {
|
||||
$operations = 0;
|
||||
|
||||
$setCollection = (function (Document $collection, Document $document) use (&$setCollection, $dbForProject, $database, &$operations) {
|
||||
|
||||
$operations++;
|
||||
|
||||
$relationships = \array_filter(
|
||||
$collection->getAttribute('attributes', []),
|
||||
fn ($attribute) => $attribute->getAttribute('type') === Database::VAR_RELATIONSHIP
|
||||
@@ -3593,6 +3642,13 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/documents/:docum
|
||||
|
||||
$setCollection($collection, $newDocument);
|
||||
|
||||
$queueForUsage
|
||||
->addMetric(METRIC_DATABASES_OPERATIONS_WRITES, $operations)
|
||||
->addMetric(str_replace('{databaseInternalId}', $database->getInternalId(), METRIC_DATABASE_ID_OPERATIONS_WRITES), $operations)
|
||||
;
|
||||
|
||||
$response->addHeader('X-Debug-Operations', $operations);
|
||||
|
||||
try {
|
||||
$document = $dbForProject->withRequestTimestamp(
|
||||
$requestTimestamp,
|
||||
@@ -3764,6 +3820,13 @@ App::delete('/v1/databases/:databaseId/collections/:collectionId/documents/:docu
|
||||
|
||||
$processDocument($collection, $document);
|
||||
|
||||
$queueForUsage
|
||||
->addMetric(METRIC_DATABASES_OPERATIONS_WRITES, 1)
|
||||
->addMetric(str_replace('{databaseInternalId}', $database->getInternalId(), METRIC_DATABASE_ID_OPERATIONS_WRITES), 1)
|
||||
->addMetric(str_replace(['{databaseInternalId}', '{collectionInternalId}'], [$database->getInternalId(), $collection->getInternalId()], METRIC_DATABASE_ID_COLLECTION_ID_STORAGE), 1); // per collection
|
||||
|
||||
$response->addHeader('X-Debug-Operations', 1);
|
||||
|
||||
$relationships = \array_map(
|
||||
fn ($document) => $document->getAttribute('key'),
|
||||
\array_filter(
|
||||
@@ -3780,9 +3843,6 @@ App::delete('/v1/databases/:databaseId/collections/:collectionId/documents/:docu
|
||||
->setContext('database', $database)
|
||||
->setPayload($response->output($document, Response::MODEL_DOCUMENT), sensitive: $relationships);
|
||||
|
||||
$queueForUsage
|
||||
->addMetric(str_replace(['{databaseInternalId}', '{collectionInternalId}'], [$database->getInternalId(), $collection->getInternalId()], METRIC_DATABASE_ID_COLLECTION_ID_STORAGE), 1); // per collection
|
||||
|
||||
$response->noContent();
|
||||
});
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ use Appwrite\Utopia\Database\Validator\Queries\Functions;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Appwrite\Utopia\Response\Model\Rule;
|
||||
use Executor\Executor;
|
||||
use Utopia\Abuse\Abuse;
|
||||
use Utopia\App;
|
||||
use Utopia\CLI\Console;
|
||||
use Utopia\Config\Config;
|
||||
@@ -177,15 +178,45 @@ App::post('/v1/functions')
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('timelimit')
|
||||
->inject('project')
|
||||
->inject('user')
|
||||
->inject('queueForEvents')
|
||||
->inject('queueForBuilds')
|
||||
->inject('dbForConsole')
|
||||
->inject('dbForPlatform')
|
||||
->inject('gitHub')
|
||||
->action(function (string $functionId, string $name, string $runtime, array $execute, array $events, string $schedule, int $timeout, bool $enabled, bool $logging, string $entrypoint, string $commands, array $scopes, string $installationId, string $providerRepositoryId, string $providerBranch, bool $providerSilentMode, string $providerRootDirectory, string $templateRepository, string $templateOwner, string $templateRootDirectory, string $templateVersion, string $specification, Request $request, Response $response, Database $dbForProject, Document $project, Document $user, Event $queueForEvents, Build $queueForBuilds, Database $dbForConsole, GitHub $github) use ($redeployVcs) {
|
||||
->action(function (string $functionId, string $name, string $runtime, array $execute, array $events, string $schedule, int $timeout, bool $enabled, bool $logging, string $entrypoint, string $commands, array $scopes, string $installationId, string $providerRepositoryId, string $providerBranch, bool $providerSilentMode, string $providerRootDirectory, string $templateRepository, string $templateOwner, string $templateRootDirectory, string $templateVersion, string $specification, Request $request, Response $response, Database $dbForProject, callable $timelimit, Document $project, Document $user, Event $queueForEvents, Build $queueForBuilds, Database $dbForPlatform, GitHub $github) use ($redeployVcs) {
|
||||
$functionId = ($functionId == 'unique()') ? ID::unique() : $functionId;
|
||||
|
||||
// Temporary abuse check
|
||||
$abuseCheck = function () use ($project, $timelimit, $response) {
|
||||
$abuseKey = "projectId:{projectId},url:{url}";
|
||||
$abuseLimit = App::getEnv('_APP_FUNCTIONS_CREATION_ABUSE_LIMIT', 50);
|
||||
$abuseTime = 86400; // 1 day
|
||||
|
||||
$timeLimit = $timelimit($abuseKey, $abuseLimit, $abuseTime);
|
||||
$timeLimit
|
||||
->setParam('{projectId}', $project->getId())
|
||||
->setParam('{url}', '/v1/functions');
|
||||
|
||||
$abuse = new Abuse($timeLimit);
|
||||
$remaining = $timeLimit->remaining();
|
||||
$limit = $timeLimit->limit();
|
||||
$time = $timeLimit->time() + $abuseTime;
|
||||
|
||||
$response
|
||||
->addHeader('X-RateLimit-Limit', $limit)
|
||||
->addHeader('X-RateLimit-Remaining', $remaining)
|
||||
->addHeader('X-RateLimit-Reset', $time);
|
||||
|
||||
$enabled = System::getEnv('_APP_OPTIONS_ABUSE', 'enabled') !== 'disabled';
|
||||
if ($enabled && $abuse->check()) {
|
||||
throw new Exception(Exception::GENERAL_RATE_LIMIT_EXCEEDED);
|
||||
}
|
||||
};
|
||||
|
||||
$abuseCheck();
|
||||
|
||||
$allowList = \array_filter(\explode(',', System::getEnv('_APP_FUNCTIONS_RUNTIMES', '')));
|
||||
|
||||
if (!empty($allowList) && !\in_array($runtime, $allowList)) {
|
||||
@@ -206,7 +237,7 @@ App::post('/v1/functions')
|
||||
->setAttribute('version', $templateVersion);
|
||||
}
|
||||
|
||||
$installation = $dbForConsole->getDocument('installations', $installationId);
|
||||
$installation = $dbForPlatform->getDocument('installations', $installationId);
|
||||
|
||||
if (!empty($installationId) && $installation->isEmpty()) {
|
||||
throw new Exception(Exception::INSTALLATION_NOT_FOUND);
|
||||
@@ -248,7 +279,7 @@ App::post('/v1/functions')
|
||||
]));
|
||||
|
||||
$schedule = Authorization::skip(
|
||||
fn () => $dbForConsole->createDocument('schedules', new Document([
|
||||
fn () => $dbForPlatform->createDocument('schedules', new Document([
|
||||
'region' => System::getEnv('_APP_REGION', 'default'), // Todo replace with projects region
|
||||
'resourceType' => 'function',
|
||||
'resourceId' => $function->getId(),
|
||||
@@ -267,7 +298,7 @@ App::post('/v1/functions')
|
||||
if (!empty($providerRepositoryId)) {
|
||||
$teamId = $project->getAttribute('teamId', '');
|
||||
|
||||
$repository = $dbForConsole->createDocument('repositories', new Document([
|
||||
$repository = $dbForPlatform->createDocument('repositories', new Document([
|
||||
'$id' => ID::unique(),
|
||||
'$permissions' => [
|
||||
Permission::read(Role::team(ID::custom($teamId))),
|
||||
@@ -327,10 +358,11 @@ App::post('/v1/functions')
|
||||
if (!empty($functionsDomain)) {
|
||||
$routeSubdomain = ID::unique();
|
||||
$domain = "{$routeSubdomain}.{$functionsDomain}";
|
||||
$ruleId = md5($domain);
|
||||
// TODO: @christyjacob remove once we migrate the rules in 1.7.x
|
||||
$ruleId = System::getEnv('_APP_RULES_FORMAT') === 'md5' ? md5($domain) : ID::unique();
|
||||
|
||||
$rule = Authorization::skip(
|
||||
fn () => $dbForConsole->createDocument('rules', new Document([
|
||||
fn () => $dbForPlatform->createDocument('rules', new Document([
|
||||
'$id' => $ruleId,
|
||||
'projectId' => $project->getId(),
|
||||
'projectInternalId' => $project->getInternalId(),
|
||||
@@ -802,9 +834,9 @@ App::put('/v1/functions/:functionId')
|
||||
->inject('project')
|
||||
->inject('queueForEvents')
|
||||
->inject('queueForBuilds')
|
||||
->inject('dbForConsole')
|
||||
->inject('dbForPlatform')
|
||||
->inject('gitHub')
|
||||
->action(function (string $functionId, string $name, string $runtime, array $execute, array $events, string $schedule, int $timeout, bool $enabled, bool $logging, string $entrypoint, string $commands, array $scopes, string $installationId, ?string $providerRepositoryId, string $providerBranch, bool $providerSilentMode, string $providerRootDirectory, string $specification, Request $request, Response $response, Database $dbForProject, Document $project, Event $queueForEvents, Build $queueForBuilds, Database $dbForConsole, GitHub $github) use ($redeployVcs) {
|
||||
->action(function (string $functionId, string $name, string $runtime, array $execute, array $events, string $schedule, int $timeout, bool $enabled, bool $logging, string $entrypoint, string $commands, array $scopes, string $installationId, ?string $providerRepositoryId, string $providerBranch, bool $providerSilentMode, string $providerRootDirectory, string $specification, Request $request, Response $response, Database $dbForProject, Document $project, Event $queueForEvents, Build $queueForBuilds, Database $dbForPlatform, GitHub $github) use ($redeployVcs) {
|
||||
// TODO: If only branch changes, re-deploy
|
||||
$function = $dbForProject->getDocument('functions', $functionId);
|
||||
|
||||
@@ -812,7 +844,7 @@ App::put('/v1/functions/:functionId')
|
||||
throw new Exception(Exception::FUNCTION_NOT_FOUND);
|
||||
}
|
||||
|
||||
$installation = $dbForConsole->getDocument('installations', $installationId);
|
||||
$installation = $dbForPlatform->getDocument('installations', $installationId);
|
||||
|
||||
if (!empty($installationId) && $installation->isEmpty()) {
|
||||
throw new Exception(Exception::INSTALLATION_NOT_FOUND);
|
||||
@@ -843,7 +875,7 @@ App::put('/v1/functions/:functionId')
|
||||
|
||||
// Git disconnect logic. Disconnecting only when providerRepositoryId is empty, allowing for continue updates without disconnecting git
|
||||
if ($isConnected && ($providerRepositoryId !== null && empty($providerRepositoryId))) {
|
||||
$repositories = $dbForConsole->find('repositories', [
|
||||
$repositories = $dbForPlatform->find('repositories', [
|
||||
Query::equal('projectInternalId', [$project->getInternalId()]),
|
||||
Query::equal('resourceInternalId', [$function->getInternalId()]),
|
||||
Query::equal('resourceType', ['function']),
|
||||
@@ -851,7 +883,7 @@ App::put('/v1/functions/:functionId')
|
||||
]);
|
||||
|
||||
foreach ($repositories as $repository) {
|
||||
$dbForConsole->deleteDocument('repositories', $repository->getId());
|
||||
$dbForPlatform->deleteDocument('repositories', $repository->getId());
|
||||
}
|
||||
|
||||
$providerRepositoryId = '';
|
||||
@@ -867,7 +899,7 @@ App::put('/v1/functions/:functionId')
|
||||
if (!$isConnected && !empty($providerRepositoryId)) {
|
||||
$teamId = $project->getAttribute('teamId', '');
|
||||
|
||||
$repository = $dbForConsole->createDocument('repositories', new Document([
|
||||
$repository = $dbForPlatform->createDocument('repositories', new Document([
|
||||
'$id' => ID::unique(),
|
||||
'$permissions' => [
|
||||
Permission::read(Role::team(ID::custom($teamId))),
|
||||
@@ -949,12 +981,12 @@ App::put('/v1/functions/:functionId')
|
||||
}
|
||||
|
||||
// Inform scheduler if function is still active
|
||||
$schedule = $dbForConsole->getDocument('schedules', $function->getAttribute('scheduleId'));
|
||||
$schedule = $dbForPlatform->getDocument('schedules', $function->getAttribute('scheduleId'));
|
||||
$schedule
|
||||
->setAttribute('resourceUpdatedAt', DateTime::now())
|
||||
->setAttribute('schedule', $function->getAttribute('schedule'))
|
||||
->setAttribute('active', !empty($function->getAttribute('schedule')) && !empty($function->getAttribute('deployment')));
|
||||
Authorization::skip(fn () => $dbForConsole->updateDocument('schedules', $schedule->getId(), $schedule));
|
||||
Authorization::skip(fn () => $dbForPlatform->updateDocument('schedules', $schedule->getId(), $schedule));
|
||||
|
||||
$queueForEvents->setParam('functionId', $function->getId());
|
||||
|
||||
@@ -1067,8 +1099,8 @@ App::patch('/v1/functions/:functionId/deployments/:deploymentId')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('queueForEvents')
|
||||
->inject('dbForConsole')
|
||||
->action(function (string $functionId, string $deploymentId, Response $response, Database $dbForProject, Event $queueForEvents, Database $dbForConsole) {
|
||||
->inject('dbForPlatform')
|
||||
->action(function (string $functionId, string $deploymentId, Response $response, Database $dbForProject, Event $queueForEvents, Database $dbForPlatform) {
|
||||
|
||||
$function = $dbForProject->getDocument('functions', $functionId);
|
||||
$deployment = $dbForProject->getDocument('deployments', $deploymentId);
|
||||
@@ -1096,12 +1128,12 @@ App::patch('/v1/functions/:functionId/deployments/:deploymentId')
|
||||
])));
|
||||
|
||||
// Inform scheduler if function is still active
|
||||
$schedule = $dbForConsole->getDocument('schedules', $function->getAttribute('scheduleId'));
|
||||
$schedule = $dbForPlatform->getDocument('schedules', $function->getAttribute('scheduleId'));
|
||||
$schedule
|
||||
->setAttribute('resourceUpdatedAt', DateTime::now())
|
||||
->setAttribute('schedule', $function->getAttribute('schedule'))
|
||||
->setAttribute('active', !empty($function->getAttribute('schedule')) && !empty($function->getAttribute('deployment')));
|
||||
Authorization::skip(fn () => $dbForConsole->updateDocument('schedules', $schedule->getId(), $schedule));
|
||||
Authorization::skip(fn () => $dbForPlatform->updateDocument('schedules', $schedule->getId(), $schedule));
|
||||
|
||||
$queueForEvents
|
||||
->setParam('functionId', $function->getId())
|
||||
@@ -1129,8 +1161,8 @@ App::delete('/v1/functions/:functionId')
|
||||
->inject('dbForProject')
|
||||
->inject('queueForDeletes')
|
||||
->inject('queueForEvents')
|
||||
->inject('dbForConsole')
|
||||
->action(function (string $functionId, Response $response, Database $dbForProject, Delete $queueForDeletes, Event $queueForEvents, Database $dbForConsole) {
|
||||
->inject('dbForPlatform')
|
||||
->action(function (string $functionId, Response $response, Database $dbForProject, Delete $queueForDeletes, Event $queueForEvents, Database $dbForPlatform) {
|
||||
|
||||
$function = $dbForProject->getDocument('functions', $functionId);
|
||||
|
||||
@@ -1143,11 +1175,11 @@ App::delete('/v1/functions/:functionId')
|
||||
}
|
||||
|
||||
// Inform scheduler to no longer run function
|
||||
$schedule = $dbForConsole->getDocument('schedules', $function->getAttribute('scheduleId'));
|
||||
$schedule = $dbForPlatform->getDocument('schedules', $function->getAttribute('scheduleId'));
|
||||
$schedule
|
||||
->setAttribute('resourceUpdatedAt', DateTime::now())
|
||||
->setAttribute('active', false);
|
||||
Authorization::skip(fn () => $dbForConsole->updateDocument('schedules', $schedule->getId(), $schedule));
|
||||
Authorization::skip(fn () => $dbForPlatform->updateDocument('schedules', $schedule->getId(), $schedule));
|
||||
|
||||
$queueForDeletes
|
||||
->setType(DELETE_TYPE_DOCUMENT)
|
||||
@@ -1757,13 +1789,13 @@ App::post('/v1/functions/:functionId/executions')
|
||||
->inject('request')
|
||||
->inject('project')
|
||||
->inject('dbForProject')
|
||||
->inject('dbForConsole')
|
||||
->inject('dbForPlatform')
|
||||
->inject('user')
|
||||
->inject('queueForEvents')
|
||||
->inject('queueForUsage')
|
||||
->inject('queueForFunctions')
|
||||
->inject('geoRecord')
|
||||
->action(function (string $functionId, string $body, mixed $async, string $path, string $method, mixed $headers, ?string $scheduledAt, Response $response, Request $request, Document $project, Database $dbForProject, Database $dbForConsole, Document $user, Event $queueForEvents, Usage $queueForUsage, Func $queueForFunctions, array $geoRecord) {
|
||||
->action(function (string $functionId, string $body, mixed $async, string $path, string $method, mixed $headers, ?string $scheduledAt, Response $response, Request $request, Document $project, Database $dbForProject, Database $dbForPlatform, Document $user, Event $queueForEvents, Usage $queueForUsage, Func $queueForFunctions, array $geoRecord) {
|
||||
$async = \strval($async) === 'true' || \strval($async) === '1';
|
||||
|
||||
if (!$async && !is_null($scheduledAt)) {
|
||||
@@ -1942,7 +1974,7 @@ App::post('/v1/functions/:functionId/executions')
|
||||
'userId' => $user->getId()
|
||||
];
|
||||
|
||||
$schedule = $dbForConsole->createDocument('schedules', new Document([
|
||||
$schedule = $dbForPlatform->createDocument('schedules', new Document([
|
||||
'region' => System::getEnv('_APP_REGION', 'default'),
|
||||
'resourceType' => ScheduleExecutions::getSupportedResource(),
|
||||
'resourceId' => $execution->getId(),
|
||||
@@ -2277,9 +2309,9 @@ App::delete('/v1/functions/:functionId/executions/:executionId')
|
||||
->param('executionId', '', new UID(), 'Execution ID.')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('dbForConsole')
|
||||
->inject('dbForPlatform')
|
||||
->inject('queueForEvents')
|
||||
->action(function (string $functionId, string $executionId, Response $response, Database $dbForProject, Database $dbForConsole, Event $queueForEvents) {
|
||||
->action(function (string $functionId, string $executionId, Response $response, Database $dbForProject, Database $dbForPlatform, Event $queueForEvents) {
|
||||
$function = $dbForProject->getDocument('functions', $functionId);
|
||||
|
||||
if ($function->isEmpty()) {
|
||||
@@ -2305,7 +2337,7 @@ App::delete('/v1/functions/:functionId/executions/:executionId')
|
||||
}
|
||||
|
||||
if ($status === 'scheduled') {
|
||||
$schedule = $dbForConsole->findOne('schedules', [
|
||||
$schedule = $dbForPlatform->findOne('schedules', [
|
||||
Query::equal('resourceId', [$execution->getId()]),
|
||||
Query::equal('resourceType', [ScheduleExecutions::getSupportedResource()]),
|
||||
Query::equal('active', [true]),
|
||||
@@ -2316,7 +2348,7 @@ App::delete('/v1/functions/:functionId/executions/:executionId')
|
||||
->setAttribute('resourceUpdatedAt', DateTime::now())
|
||||
->setAttribute('active', false);
|
||||
|
||||
Authorization::skip(fn () => $dbForConsole->updateDocument('schedules', $schedule->getId(), $schedule));
|
||||
Authorization::skip(fn () => $dbForPlatform->updateDocument('schedules', $schedule->getId(), $schedule));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2349,8 +2381,8 @@ App::post('/v1/functions/:functionId/variables')
|
||||
->param('value', null, new Text(8192, 0), 'Variable value. Max length: 8192 chars.', false)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('dbForConsole')
|
||||
->action(function (string $functionId, string $key, string $value, Response $response, Database $dbForProject, Database $dbForConsole) {
|
||||
->inject('dbForPlatform')
|
||||
->action(function (string $functionId, string $key, string $value, Response $response, Database $dbForProject, Database $dbForPlatform) {
|
||||
$function = $dbForProject->getDocument('functions', $functionId);
|
||||
|
||||
if ($function->isEmpty()) {
|
||||
@@ -2383,12 +2415,12 @@ App::post('/v1/functions/:functionId/variables')
|
||||
$dbForProject->updateDocument('functions', $function->getId(), $function->setAttribute('live', false));
|
||||
|
||||
// Inform scheduler to pull the latest changes
|
||||
$schedule = $dbForConsole->getDocument('schedules', $function->getAttribute('scheduleId'));
|
||||
$schedule = $dbForPlatform->getDocument('schedules', $function->getAttribute('scheduleId'));
|
||||
$schedule
|
||||
->setAttribute('resourceUpdatedAt', DateTime::now())
|
||||
->setAttribute('schedule', $function->getAttribute('schedule'))
|
||||
->setAttribute('active', !empty($function->getAttribute('schedule')) && !empty($function->getAttribute('deployment')));
|
||||
Authorization::skip(fn () => $dbForConsole->updateDocument('schedules', $schedule->getId(), $schedule));
|
||||
Authorization::skip(fn () => $dbForPlatform->updateDocument('schedules', $schedule->getId(), $schedule));
|
||||
|
||||
$response
|
||||
->setStatusCode(Response::STATUS_CODE_CREATED)
|
||||
@@ -2483,8 +2515,8 @@ App::put('/v1/functions/:functionId/variables/:variableId')
|
||||
->param('value', null, new Text(8192, 0), 'Variable value. Max length: 8192 chars.', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('dbForConsole')
|
||||
->action(function (string $functionId, string $variableId, string $key, ?string $value, Response $response, Database $dbForProject, Database $dbForConsole) {
|
||||
->inject('dbForPlatform')
|
||||
->action(function (string $functionId, string $variableId, string $key, ?string $value, Response $response, Database $dbForProject, Database $dbForPlatform) {
|
||||
|
||||
$function = $dbForProject->getDocument('functions', $functionId);
|
||||
|
||||
@@ -2515,12 +2547,12 @@ App::put('/v1/functions/:functionId/variables/:variableId')
|
||||
$dbForProject->updateDocument('functions', $function->getId(), $function->setAttribute('live', false));
|
||||
|
||||
// Inform scheduler to pull the latest changes
|
||||
$schedule = $dbForConsole->getDocument('schedules', $function->getAttribute('scheduleId'));
|
||||
$schedule = $dbForPlatform->getDocument('schedules', $function->getAttribute('scheduleId'));
|
||||
$schedule
|
||||
->setAttribute('resourceUpdatedAt', DateTime::now())
|
||||
->setAttribute('schedule', $function->getAttribute('schedule'))
|
||||
->setAttribute('active', !empty($function->getAttribute('schedule')) && !empty($function->getAttribute('deployment')));
|
||||
Authorization::skip(fn () => $dbForConsole->updateDocument('schedules', $schedule->getId(), $schedule));
|
||||
Authorization::skip(fn () => $dbForPlatform->updateDocument('schedules', $schedule->getId(), $schedule));
|
||||
|
||||
$response->dynamic($variable, Response::MODEL_VARIABLE);
|
||||
});
|
||||
@@ -2542,8 +2574,8 @@ App::delete('/v1/functions/:functionId/variables/:variableId')
|
||||
->param('variableId', '', new UID(), 'Variable unique ID.', false)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('dbForConsole')
|
||||
->action(function (string $functionId, string $variableId, Response $response, Database $dbForProject, Database $dbForConsole) {
|
||||
->inject('dbForPlatform')
|
||||
->action(function (string $functionId, string $variableId, Response $response, Database $dbForProject, Database $dbForPlatform) {
|
||||
$function = $dbForProject->getDocument('functions', $functionId);
|
||||
|
||||
if ($function->isEmpty()) {
|
||||
@@ -2564,12 +2596,12 @@ App::delete('/v1/functions/:functionId/variables/:variableId')
|
||||
$dbForProject->updateDocument('functions', $function->getId(), $function->setAttribute('live', false));
|
||||
|
||||
// Inform scheduler to pull the latest changes
|
||||
$schedule = $dbForConsole->getDocument('schedules', $function->getAttribute('scheduleId'));
|
||||
$schedule = $dbForPlatform->getDocument('schedules', $function->getAttribute('scheduleId'));
|
||||
$schedule
|
||||
->setAttribute('resourceUpdatedAt', DateTime::now())
|
||||
->setAttribute('schedule', $function->getAttribute('schedule'))
|
||||
->setAttribute('active', !empty($function->getAttribute('schedule')) && !empty($function->getAttribute('deployment')));
|
||||
Authorization::skip(fn () => $dbForConsole->updateDocument('schedules', $schedule->getId(), $schedule));
|
||||
Authorization::skip(fn () => $dbForPlatform->updateDocument('schedules', $schedule->getId(), $schedule));
|
||||
|
||||
$response->noContent();
|
||||
});
|
||||
|
||||
@@ -2627,11 +2627,11 @@ App::post('/v1/messaging/messages/email')
|
||||
->param('scheduledAt', null, new DatetimeValidator(requireDateInFuture: true), 'Scheduled delivery time for message in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. DateTime value must be in future.', true)
|
||||
->inject('queueForEvents')
|
||||
->inject('dbForProject')
|
||||
->inject('dbForConsole')
|
||||
->inject('dbForPlatform')
|
||||
->inject('project')
|
||||
->inject('queueForMessaging')
|
||||
->inject('response')
|
||||
->action(function (string $messageId, string $subject, string $content, array $topics, array $users, array $targets, array $cc, array $bcc, array $attachments, bool $draft, bool $html, ?string $scheduledAt, Event $queueForEvents, Database $dbForProject, Database $dbForConsole, Document $project, Messaging $queueForMessaging, Response $response) {
|
||||
->action(function (string $messageId, string $subject, string $content, array $topics, array $users, array $targets, array $cc, array $bcc, array $attachments, bool $draft, bool $html, ?string $scheduledAt, Event $queueForEvents, Database $dbForProject, Database $dbForPlatform, Document $project, Messaging $queueForMessaging, Response $response) {
|
||||
$messageId = $messageId == 'unique()'
|
||||
? ID::unique()
|
||||
: $messageId;
|
||||
@@ -2720,7 +2720,7 @@ App::post('/v1/messaging/messages/email')
|
||||
->setMessageId($message->getId());
|
||||
break;
|
||||
case MessageStatus::SCHEDULED:
|
||||
$schedule = $dbForConsole->createDocument('schedules', new Document([
|
||||
$schedule = $dbForPlatform->createDocument('schedules', new Document([
|
||||
'region' => System::getEnv('_APP_REGION', 'default'),
|
||||
'resourceType' => 'message',
|
||||
'resourceId' => $message->getId(),
|
||||
@@ -2775,11 +2775,11 @@ App::post('/v1/messaging/messages/sms')
|
||||
->param('scheduledAt', null, new DatetimeValidator(requireDateInFuture: true), 'Scheduled delivery time for message in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. DateTime value must be in future.', true)
|
||||
->inject('queueForEvents')
|
||||
->inject('dbForProject')
|
||||
->inject('dbForConsole')
|
||||
->inject('dbForPlatform')
|
||||
->inject('project')
|
||||
->inject('queueForMessaging')
|
||||
->inject('response')
|
||||
->action(function (string $messageId, string $content, array $topics, array $users, array $targets, bool $draft, ?string $scheduledAt, Event $queueForEvents, Database $dbForProject, Database $dbForConsole, Document $project, Messaging $queueForMessaging, Response $response) {
|
||||
->action(function (string $messageId, string $content, array $topics, array $users, array $targets, bool $draft, ?string $scheduledAt, Event $queueForEvents, Database $dbForProject, Database $dbForPlatform, Document $project, Messaging $queueForMessaging, Response $response) {
|
||||
$messageId = $messageId == 'unique()'
|
||||
? ID::unique()
|
||||
: $messageId;
|
||||
@@ -2837,7 +2837,7 @@ App::post('/v1/messaging/messages/sms')
|
||||
->setMessageId($message->getId());
|
||||
break;
|
||||
case MessageStatus::SCHEDULED:
|
||||
$schedule = $dbForConsole->createDocument('schedules', new Document([
|
||||
$schedule = $dbForPlatform->createDocument('schedules', new Document([
|
||||
'region' => System::getEnv('_APP_REGION', 'default'),
|
||||
'resourceType' => 'message',
|
||||
'resourceId' => $message->getId(),
|
||||
@@ -2901,11 +2901,11 @@ App::post('/v1/messaging/messages/push')
|
||||
->param('scheduledAt', null, new DatetimeValidator(requireDateInFuture: true), 'Scheduled delivery time for message in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. DateTime value must be in future.', true)
|
||||
->inject('queueForEvents')
|
||||
->inject('dbForProject')
|
||||
->inject('dbForConsole')
|
||||
->inject('dbForPlatform')
|
||||
->inject('project')
|
||||
->inject('queueForMessaging')
|
||||
->inject('response')
|
||||
->action(function (string $messageId, string $title, string $body, array $topics, array $users, array $targets, ?array $data, string $action, string $image, string $icon, string $sound, string $color, string $tag, string $badge, bool $draft, ?string $scheduledAt, Event $queueForEvents, Database $dbForProject, Database $dbForConsole, Document $project, Messaging $queueForMessaging, Response $response) {
|
||||
->action(function (string $messageId, string $title, string $body, array $topics, array $users, array $targets, ?array $data, string $action, string $image, string $icon, string $sound, string $color, string $tag, string $badge, bool $draft, ?string $scheduledAt, Event $queueForEvents, Database $dbForProject, Database $dbForPlatform, Document $project, Messaging $queueForMessaging, Response $response) {
|
||||
$messageId = $messageId == 'unique()'
|
||||
? ID::unique()
|
||||
: $messageId;
|
||||
@@ -3014,7 +3014,7 @@ App::post('/v1/messaging/messages/push')
|
||||
->setMessageId($message->getId());
|
||||
break;
|
||||
case MessageStatus::SCHEDULED:
|
||||
$schedule = $dbForConsole->createDocument('schedules', new Document([
|
||||
$schedule = $dbForPlatform->createDocument('schedules', new Document([
|
||||
'region' => System::getEnv('_APP_REGION', 'default'),
|
||||
'resourceType' => 'message',
|
||||
'resourceId' => $message->getId(),
|
||||
@@ -3310,11 +3310,11 @@ App::patch('/v1/messaging/messages/email/:messageId')
|
||||
->param('attachments', null, new ArrayList(new CompoundUID()), 'Array of compound ID strings of bucket IDs and file IDs to be attached to the email. They should be formatted as <BUCKET_ID>:<FILE_ID>.', true)
|
||||
->inject('queueForEvents')
|
||||
->inject('dbForProject')
|
||||
->inject('dbForConsole')
|
||||
->inject('dbForPlatform')
|
||||
->inject('project')
|
||||
->inject('queueForMessaging')
|
||||
->inject('response')
|
||||
->action(function (string $messageId, ?array $topics, ?array $users, ?array $targets, ?string $subject, ?string $content, ?bool $draft, ?bool $html, ?array $cc, ?array $bcc, ?string $scheduledAt, ?array $attachments, Event $queueForEvents, Database $dbForProject, Database $dbForConsole, Document $project, Messaging $queueForMessaging, Response $response) {
|
||||
->action(function (string $messageId, ?array $topics, ?array $users, ?array $targets, ?string $subject, ?string $content, ?bool $draft, ?bool $html, ?array $cc, ?array $bcc, ?string $scheduledAt, ?array $attachments, Event $queueForEvents, Database $dbForProject, Database $dbForPlatform, Document $project, Messaging $queueForMessaging, Response $response) {
|
||||
$message = $dbForProject->getDocument('messages', $messageId);
|
||||
|
||||
if ($message->isEmpty()) {
|
||||
@@ -3366,7 +3366,7 @@ App::patch('/v1/messaging/messages/email/:messageId')
|
||||
}
|
||||
|
||||
if (\is_null($currentScheduledAt) && !\is_null($scheduledAt)) {
|
||||
$schedule = $dbForConsole->createDocument('schedules', new Document([
|
||||
$schedule = $dbForPlatform->createDocument('schedules', new Document([
|
||||
'region' => System::getEnv('_APP_REGION', 'default'),
|
||||
'resourceType' => 'message',
|
||||
'resourceId' => $message->getId(),
|
||||
@@ -3381,7 +3381,7 @@ App::patch('/v1/messaging/messages/email/:messageId')
|
||||
}
|
||||
|
||||
if (!\is_null($currentScheduledAt)) {
|
||||
$schedule = $dbForConsole->getDocument('schedules', $message->getAttribute('scheduleId'));
|
||||
$schedule = $dbForPlatform->getDocument('schedules', $message->getAttribute('scheduleId'));
|
||||
$scheduledStatus = ($status ?? $message->getAttribute('status')) === MessageStatus::SCHEDULED;
|
||||
|
||||
if ($schedule->isEmpty()) {
|
||||
@@ -3396,7 +3396,7 @@ App::patch('/v1/messaging/messages/email/:messageId')
|
||||
$schedule->setAttribute('schedule', $scheduledAt);
|
||||
}
|
||||
|
||||
$dbForConsole->updateDocument('schedules', $schedule->getId(), $schedule);
|
||||
$dbForPlatform->updateDocument('schedules', $schedule->getId(), $schedule);
|
||||
}
|
||||
|
||||
if (!\is_null($scheduledAt)) {
|
||||
@@ -3506,11 +3506,11 @@ App::patch('/v1/messaging/messages/sms/:messageId')
|
||||
->param('scheduledAt', null, new DatetimeValidator(requireDateInFuture: true), 'Scheduled delivery time for message in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. DateTime value must be in future.', true)
|
||||
->inject('queueForEvents')
|
||||
->inject('dbForProject')
|
||||
->inject('dbForConsole')
|
||||
->inject('dbForPlatform')
|
||||
->inject('project')
|
||||
->inject('queueForMessaging')
|
||||
->inject('response')
|
||||
->action(function (string $messageId, ?array $topics, ?array $users, ?array $targets, ?string $content, ?bool $draft, ?string $scheduledAt, Event $queueForEvents, Database $dbForProject, Database $dbForConsole, Document $project, Messaging $queueForMessaging, Response $response) {
|
||||
->action(function (string $messageId, ?array $topics, ?array $users, ?array $targets, ?string $content, ?bool $draft, ?string $scheduledAt, Event $queueForEvents, Database $dbForProject, Database $dbForPlatform, Document $project, Messaging $queueForMessaging, Response $response) {
|
||||
$message = $dbForProject->getDocument('messages', $messageId);
|
||||
|
||||
if ($message->isEmpty()) {
|
||||
@@ -3562,7 +3562,7 @@ App::patch('/v1/messaging/messages/sms/:messageId')
|
||||
}
|
||||
|
||||
if (\is_null($currentScheduledAt) && !\is_null($scheduledAt)) {
|
||||
$schedule = $dbForConsole->createDocument('schedules', new Document([
|
||||
$schedule = $dbForPlatform->createDocument('schedules', new Document([
|
||||
'region' => System::getEnv('_APP_REGION', 'default'),
|
||||
'resourceType' => 'message',
|
||||
'resourceId' => $message->getId(),
|
||||
@@ -3577,7 +3577,7 @@ App::patch('/v1/messaging/messages/sms/:messageId')
|
||||
}
|
||||
|
||||
if (!\is_null($currentScheduledAt)) {
|
||||
$schedule = $dbForConsole->getDocument('schedules', $message->getAttribute('scheduleId'));
|
||||
$schedule = $dbForPlatform->getDocument('schedules', $message->getAttribute('scheduleId'));
|
||||
$scheduledStatus = ($status ?? $message->getAttribute('status')) === MessageStatus::SCHEDULED;
|
||||
|
||||
if ($schedule->isEmpty()) {
|
||||
@@ -3592,7 +3592,7 @@ App::patch('/v1/messaging/messages/sms/:messageId')
|
||||
$schedule->setAttribute('schedule', $scheduledAt);
|
||||
}
|
||||
|
||||
$dbForConsole->updateDocument('schedules', $schedule->getId(), $schedule);
|
||||
$dbForPlatform->updateDocument('schedules', $schedule->getId(), $schedule);
|
||||
}
|
||||
|
||||
if (!\is_null($scheduledAt)) {
|
||||
@@ -3671,11 +3671,11 @@ App::patch('/v1/messaging/messages/push/:messageId')
|
||||
->param('scheduledAt', null, new DatetimeValidator(requireDateInFuture: true), 'Scheduled delivery time for message in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. DateTime value must be in future.', true)
|
||||
->inject('queueForEvents')
|
||||
->inject('dbForProject')
|
||||
->inject('dbForConsole')
|
||||
->inject('dbForPlatform')
|
||||
->inject('project')
|
||||
->inject('queueForMessaging')
|
||||
->inject('response')
|
||||
->action(function (string $messageId, ?array $topics, ?array $users, ?array $targets, ?string $title, ?string $body, ?array $data, ?string $action, ?string $image, ?string $icon, ?string $sound, ?string $color, ?string $tag, ?int $badge, ?bool $draft, ?string $scheduledAt, Event $queueForEvents, Database $dbForProject, Database $dbForConsole, Document $project, Messaging $queueForMessaging, Response $response) {
|
||||
->action(function (string $messageId, ?array $topics, ?array $users, ?array $targets, ?string $title, ?string $body, ?array $data, ?string $action, ?string $image, ?string $icon, ?string $sound, ?string $color, ?string $tag, ?int $badge, ?bool $draft, ?string $scheduledAt, Event $queueForEvents, Database $dbForProject, Database $dbForPlatform, Document $project, Messaging $queueForMessaging, Response $response) {
|
||||
$message = $dbForProject->getDocument('messages', $messageId);
|
||||
|
||||
if ($message->isEmpty()) {
|
||||
@@ -3727,7 +3727,7 @@ App::patch('/v1/messaging/messages/push/:messageId')
|
||||
}
|
||||
|
||||
if (\is_null($currentScheduledAt) && !\is_null($scheduledAt)) {
|
||||
$schedule = $dbForConsole->createDocument('schedules', new Document([
|
||||
$schedule = $dbForPlatform->createDocument('schedules', new Document([
|
||||
'region' => System::getEnv('_APP_REGION', 'default'),
|
||||
'resourceType' => 'message',
|
||||
'resourceId' => $message->getId(),
|
||||
@@ -3742,7 +3742,7 @@ App::patch('/v1/messaging/messages/push/:messageId')
|
||||
}
|
||||
|
||||
if (!\is_null($currentScheduledAt)) {
|
||||
$schedule = $dbForConsole->getDocument('schedules', $message->getAttribute('scheduleId'));
|
||||
$schedule = $dbForPlatform->getDocument('schedules', $message->getAttribute('scheduleId'));
|
||||
$scheduledStatus = ($status ?? $message->getAttribute('status')) === MessageStatus::SCHEDULED;
|
||||
|
||||
if ($schedule->isEmpty()) {
|
||||
@@ -3757,7 +3757,7 @@ App::patch('/v1/messaging/messages/push/:messageId')
|
||||
$schedule->setAttribute('schedule', $scheduledAt);
|
||||
}
|
||||
|
||||
$dbForConsole->updateDocument('schedules', $schedule->getId(), $schedule);
|
||||
$dbForPlatform->updateDocument('schedules', $schedule->getId(), $schedule);
|
||||
}
|
||||
|
||||
if (!\is_null($scheduledAt)) {
|
||||
@@ -3894,10 +3894,10 @@ App::delete('/v1/messaging/messages/:messageId')
|
||||
->label('sdk.response.model', Response::MODEL_NONE)
|
||||
->param('messageId', '', new UID(), 'Message ID.')
|
||||
->inject('dbForProject')
|
||||
->inject('dbForConsole')
|
||||
->inject('dbForPlatform')
|
||||
->inject('queueForEvents')
|
||||
->inject('response')
|
||||
->action(function (string $messageId, Database $dbForProject, Database $dbForConsole, Event $queueForEvents, Response $response) {
|
||||
->action(function (string $messageId, Database $dbForProject, Database $dbForPlatform, Event $queueForEvents, Response $response) {
|
||||
$message = $dbForProject->getDocument('messages', $messageId);
|
||||
|
||||
if ($message->isEmpty()) {
|
||||
@@ -3920,7 +3920,7 @@ App::delete('/v1/messaging/messages/:messageId')
|
||||
|
||||
if (!empty($scheduleId)) {
|
||||
try {
|
||||
$dbForConsole->deleteDocument('schedules', $scheduleId);
|
||||
$dbForPlatform->deleteDocument('schedules', $scheduleId);
|
||||
} catch (Exception) {
|
||||
// Ignore
|
||||
}
|
||||
|
||||
@@ -1,17 +1,12 @@
|
||||
<?php
|
||||
|
||||
use Appwrite\Auth\OAuth2\Firebase as OAuth2Firebase;
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Event\Migration;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Permission;
|
||||
use Appwrite\Role;
|
||||
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\Exception\Query as QueryException;
|
||||
use Utopia\Database\Helpers\ID;
|
||||
@@ -22,9 +17,7 @@ use Utopia\Migration\Sources\Appwrite;
|
||||
use Utopia\Migration\Sources\Firebase;
|
||||
use Utopia\Migration\Sources\NHost;
|
||||
use Utopia\Migration\Sources\Supabase;
|
||||
use Utopia\System\System;
|
||||
use Utopia\Validator\ArrayList;
|
||||
use Utopia\Validator\Host;
|
||||
use Utopia\Validator\Integer;
|
||||
use Utopia\Validator\Text;
|
||||
use Utopia\Validator\URL;
|
||||
@@ -87,112 +80,9 @@ App::post('/v1/migrations/appwrite')
|
||||
->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.[migrationId].create')
|
||||
->label('audits.event', 'migration.create')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
|
||||
->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('queueForEvents')
|
||||
->inject('queueForMigrations')
|
||||
->inject('request')
|
||||
->action(function (array $resources, string $projectId, Response $response, Database $dbForProject, Database $dbForConsole, Document $project, Document $user, Event $queueForEvents, Migration $queueForMigrations, Request $request) {
|
||||
$firebase = new OAuth2Firebase(
|
||||
System::getEnv('_APP_MIGRATIONS_FIREBASE_CLIENT_ID', ''),
|
||||
System::getEnv('_APP_MIGRATIONS_FIREBASE_CLIENT_SECRET', ''),
|
||||
$request->getProtocol() . '://' . $request->getHostname() . '/v1/migrations/firebase/redirect'
|
||||
);
|
||||
|
||||
$identity = $dbForConsole->findOne('identities', [
|
||||
Query::equal('provider', ['firebase']),
|
||||
Query::equal('userInternalId', [$user->getInternalId()]),
|
||||
]);
|
||||
if ($identity->isEmpty()) {
|
||||
throw new Exception(Exception::USER_IDENTITY_NOT_FOUND);
|
||||
}
|
||||
|
||||
$accessToken = $identity->getAttribute('providerAccessToken');
|
||||
$refreshToken = $identity->getAttribute('providerRefreshToken');
|
||||
$accessTokenExpiry = $identity->getAttribute('providerAccessTokenExpiry');
|
||||
|
||||
$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.');
|
||||
}
|
||||
|
||||
$identity = $identity
|
||||
->setAttribute('providerAccessToken', $accessToken)
|
||||
->setAttribute('providerRefreshToken', $refreshToken)
|
||||
->setAttribute('providerAccessTokenExpiry', DateTime::addSeconds(new \DateTime(), (int)$firebase->getAccessTokenExpiry('')));
|
||||
|
||||
$dbForConsole->updateDocument('identities', $identity->getId(), $identity);
|
||||
}
|
||||
|
||||
if ($identity->getAttribute('secrets')) {
|
||||
$serviceAccount = $identity->getAttribute('secrets');
|
||||
} else {
|
||||
$firebase->cleanupServiceAccounts($accessToken, $projectId);
|
||||
$serviceAccount = $firebase->createServiceAccount($accessToken, $projectId);
|
||||
$identity = $identity
|
||||
->setAttribute('secrets', json_encode($serviceAccount));
|
||||
|
||||
$dbForConsole->updateDocument('identities', $identity->getId(), $identity);
|
||||
}
|
||||
|
||||
$migration = $dbForProject->createDocument('migrations', new Document([
|
||||
'$id' => ID::unique(),
|
||||
'status' => 'pending',
|
||||
'stage' => 'init',
|
||||
'source' => Firebase::getName(),
|
||||
'destination' => Appwrite::getName(),
|
||||
'credentials' => [
|
||||
'serviceAccount' => json_encode($serviceAccount),
|
||||
],
|
||||
'resources' => $resources,
|
||||
'statusCounters' => '{}',
|
||||
'resourceData' => '{}',
|
||||
'errors' => []
|
||||
]));
|
||||
|
||||
$queueForEvents->setParam('migrationId', $migration->getId());
|
||||
|
||||
// Trigger Transfer
|
||||
$queueForMigrations
|
||||
->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)')
|
||||
->desc('Migrate Firebase data')
|
||||
->label('scope', 'migrations.write')
|
||||
->label('event', 'migrations.[migrationId].create')
|
||||
->label('audits.event', 'migration.create')
|
||||
@@ -547,368 +437,6 @@ App::get('/v1/migrations/firebase/report')
|
||||
->dynamic(new Document($report), Response::MODEL_MIGRATION_REPORT);
|
||||
});
|
||||
|
||||
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_ADMIN])
|
||||
->label('sdk.namespace', 'migrations')
|
||||
->label('sdk.method', 'getFirebaseReportOAuth')
|
||||
->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) {
|
||||
$firebase = new OAuth2Firebase(
|
||||
System::getEnv('_APP_MIGRATIONS_FIREBASE_CLIENT_ID', ''),
|
||||
System::getEnv('_APP_MIGRATIONS_FIREBASE_CLIENT_SECRET', ''),
|
||||
$request->getProtocol() . '://' . $request->getHostname() . '/v1/migrations/firebase/redirect'
|
||||
);
|
||||
|
||||
$identity = $dbForConsole->findOne('identities', [
|
||||
Query::equal('provider', ['firebase']),
|
||||
Query::equal('userInternalId', [$user->getInternalId()]),
|
||||
]);
|
||||
|
||||
if ($identity->isEmpty()) {
|
||||
throw new Exception(Exception::USER_IDENTITY_NOT_FOUND);
|
||||
}
|
||||
|
||||
$accessToken = $identity->getAttribute('providerAccessToken');
|
||||
$refreshToken = $identity->getAttribute('providerRefreshToken');
|
||||
$accessTokenExpiry = $identity->getAttribute('providerAccessTokenExpiry');
|
||||
|
||||
if (empty($accessToken) || empty($refreshToken) || empty($accessTokenExpiry)) {
|
||||
throw new Exception(Exception::USER_IDENTITY_NOT_FOUND);
|
||||
}
|
||||
|
||||
if (System::getEnv('_APP_MIGRATIONS_FIREBASE_CLIENT_ID', '') === '' || System::getEnv('_APP_MIGRATIONS_FIREBASE_CLIENT_SECRET', '') === '') {
|
||||
throw new Exception(Exception::USER_IDENTITY_NOT_FOUND);
|
||||
}
|
||||
|
||||
$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.');
|
||||
}
|
||||
|
||||
$identity = $identity
|
||||
->setAttribute('providerAccessToken', $accessToken)
|
||||
->setAttribute('providerRefreshToken', $refreshToken)
|
||||
->setAttribute('providerAccessTokenExpiry', DateTime::addSeconds(new \DateTime(), (int)$firebase->getAccessTokenExpiry('')));
|
||||
|
||||
$dbForConsole->updateDocument('identities', $identity->getId(), $identity);
|
||||
}
|
||||
|
||||
// Get Service Account
|
||||
if ($identity->getAttribute('secrets')) {
|
||||
$serviceAccount = $identity->getAttribute('secrets');
|
||||
} else {
|
||||
$firebase->cleanupServiceAccounts($accessToken, $projectId);
|
||||
$serviceAccount = $firebase->createServiceAccount($accessToken, $projectId);
|
||||
$identity = $identity
|
||||
->setAttribute('secrets', json_encode($serviceAccount));
|
||||
|
||||
$dbForConsole->updateDocument('identities', $identity->getId(), $identity);
|
||||
}
|
||||
|
||||
$firebase = new Firebase($serviceAccount);
|
||||
|
||||
try {
|
||||
$report = $firebase->report($resources);
|
||||
} catch (\Throwable $e) {
|
||||
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Source Error: ' . $e->getMessage());
|
||||
}
|
||||
|
||||
$response
|
||||
->setStatusCode(Response::STATUS_CODE_OK)
|
||||
->dynamic(new Document($report), Response::MODEL_MIGRATION_REPORT);
|
||||
});
|
||||
|
||||
App::get('/v1/migrations/firebase/connect')
|
||||
->desc('Authorize with Firebase')
|
||||
->groups(['api', 'migrations'])
|
||||
->label('scope', 'migrations.write')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
|
||||
->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')
|
||||
->label('sdk.hide', true)
|
||||
->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(
|
||||
System::getEnv('_APP_MIGRATIONS_FIREBASE_CLIENT_ID', ''),
|
||||
System::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. This is a temporary code that the will be later exchanged for an access token.', 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(
|
||||
System::getEnv('_APP_MIGRATIONS_FIREBASE_CLIENT_ID', ''),
|
||||
System::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);
|
||||
$email = $oauth2->getUserEmail($accessToken);
|
||||
$oauth2ID = $oauth2->getUserID($accessToken);
|
||||
|
||||
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.');
|
||||
}
|
||||
|
||||
// Makes sure this email is not already used in another identity
|
||||
$identity = $dbForConsole->findOne('identities', [
|
||||
Query::equal('providerEmail', [$email]),
|
||||
]);
|
||||
|
||||
if (!$identity->isEmpty()) {
|
||||
if ($identity->getAttribute('userInternalId', '') !== $user->getInternalId()) {
|
||||
throw new Exception(Exception::USER_EMAIL_ALREADY_EXISTS);
|
||||
}
|
||||
}
|
||||
|
||||
if (!$identity->isEmpty()) {
|
||||
$identity = $identity
|
||||
->setAttribute('providerAccessToken', $accessToken)
|
||||
->setAttribute('providerRefreshToken', $refreshToken)
|
||||
->setAttribute('providerAccessTokenExpiry', DateTime::addSeconds(new \DateTime(), (int)$accessTokenExpiry));
|
||||
|
||||
$dbForConsole->updateDocument('identities', $identity->getId(), $identity);
|
||||
} else {
|
||||
$identity = $dbForConsole->createDocument('identities', new Document([
|
||||
'$id' => ID::unique(),
|
||||
'$permissions' => [
|
||||
Permission::read(Role::any()),
|
||||
Permission::update(Role::user($user->getId())),
|
||||
Permission::delete(Role::user($user->getId())),
|
||||
],
|
||||
'userInternalId' => $user->getInternalId(),
|
||||
'userId' => $user->getId(),
|
||||
'provider' => 'firebase',
|
||||
'providerUid' => $oauth2ID,
|
||||
'providerEmail' => $email,
|
||||
'providerAccessToken' => $accessToken,
|
||||
'providerRefreshToken' => $refreshToken,
|
||||
'providerAccessTokenExpiry' => DateTime::addSeconds(new \DateTime(), (int)$accessTokenExpiry),
|
||||
]));
|
||||
}
|
||||
} 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', 'migrations.read')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
|
||||
->label('sdk.namespace', 'migrations')
|
||||
->label('sdk.method', 'listFirebaseProjects')
|
||||
->label('sdk.description', '')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_MIGRATION_FIREBASE_PROJECT_LIST)
|
||||
->inject('user')
|
||||
->inject('response')
|
||||
->inject('project')
|
||||
->inject('dbForConsole')
|
||||
->inject('request')
|
||||
->action(function (Document $user, Response $response, Document $project, Database $dbForConsole, Request $request) {
|
||||
$firebase = new OAuth2Firebase(
|
||||
System::getEnv('_APP_MIGRATIONS_FIREBASE_CLIENT_ID', ''),
|
||||
System::getEnv('_APP_MIGRATIONS_FIREBASE_CLIENT_SECRET', ''),
|
||||
$request->getProtocol() . '://' . $request->getHostname() . '/v1/migrations/firebase/redirect'
|
||||
);
|
||||
|
||||
$identity = $dbForConsole->findOne('identities', [
|
||||
Query::equal('provider', ['firebase']),
|
||||
Query::equal('userInternalId', [$user->getInternalId()]),
|
||||
]);
|
||||
|
||||
if ($identity->isEmpty()) {
|
||||
throw new Exception(Exception::USER_IDENTITY_NOT_FOUND);
|
||||
}
|
||||
|
||||
$accessToken = $identity->getAttribute('providerAccessToken');
|
||||
$refreshToken = $identity->getAttribute('providerRefreshToken');
|
||||
$accessTokenExpiry = $identity->getAttribute('providerAccessTokenExpiry');
|
||||
|
||||
if (empty($accessToken) || empty($refreshToken) || empty($accessTokenExpiry)) {
|
||||
throw new Exception(Exception::USER_IDENTITY_NOT_FOUND);
|
||||
}
|
||||
|
||||
if (System::getEnv('_APP_MIGRATIONS_FIREBASE_CLIENT_ID', '') === '' || System::getEnv('_APP_MIGRATIONS_FIREBASE_CLIENT_SECRET', '') === '') {
|
||||
throw new Exception(Exception::USER_IDENTITY_NOT_FOUND);
|
||||
}
|
||||
|
||||
try {
|
||||
$isExpired = new \DateTime($accessTokenExpiry) < new \DateTime('now');
|
||||
if ($isExpired) {
|
||||
try {
|
||||
$firebase->refreshTokens($refreshToken);
|
||||
} catch (\Throwable $e) {
|
||||
throw new Exception(Exception::USER_IDENTITY_NOT_FOUND);
|
||||
}
|
||||
|
||||
$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.');
|
||||
}
|
||||
|
||||
$identity = $identity
|
||||
->setAttribute('providerAccessToken', $accessToken)
|
||||
->setAttribute('providerRefreshToken', $refreshToken)
|
||||
->setAttribute('providerAccessTokenExpiry', DateTime::addSeconds(new \DateTime(), (int)$firebase->getAccessTokenExpiry('')));
|
||||
|
||||
$dbForConsole->updateDocument('identities', $identity->getId(), $identity);
|
||||
}
|
||||
|
||||
$projects = $firebase->getProjects($accessToken);
|
||||
|
||||
$output = [];
|
||||
foreach ($projects as $project) {
|
||||
$output[] = [
|
||||
'displayName' => $project['displayName'],
|
||||
'projectId' => $project['projectId'],
|
||||
];
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
throw new Exception(Exception::USER_IDENTITY_NOT_FOUND);
|
||||
}
|
||||
|
||||
$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', 'migrations.write')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
|
||||
->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) {
|
||||
$identity = $dbForConsole->findOne('identities', [
|
||||
Query::equal('provider', ['firebase']),
|
||||
Query::equal('userInternalId', [$user->getInternalId()]),
|
||||
]);
|
||||
|
||||
if ($identity->isEmpty()) {
|
||||
throw new Exception(Exception::GENERAL_ACCESS_FORBIDDEN, 'Not authenticated with Firebase'); //TODO: Replace with USER_IDENTITY_NOT_FOUND
|
||||
}
|
||||
|
||||
$dbForConsole->deleteDocument('identities', $identity->getId());
|
||||
|
||||
$response->noContent();
|
||||
});
|
||||
|
||||
App::get('/v1/migrations/supabase/report')
|
||||
->groups(['api', 'migrations'])
|
||||
->desc('Generate a report on Supabase Data')
|
||||
|
||||
@@ -325,8 +325,8 @@ App::post('/v1/project/variables')
|
||||
->inject('project')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('dbForConsole')
|
||||
->action(function (string $key, string $value, Document $project, Response $response, Database $dbForProject, Database $dbForConsole) {
|
||||
->inject('dbForPlatform')
|
||||
->action(function (string $key, string $value, Document $project, Response $response, Database $dbForProject, Database $dbForPlatform) {
|
||||
$variableId = ID::unique();
|
||||
|
||||
$variable = new Document([
|
||||
@@ -429,8 +429,8 @@ App::put('/v1/project/variables/:variableId')
|
||||
->inject('project')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('dbForConsole')
|
||||
->action(function (string $variableId, string $key, ?string $value, Document $project, Response $response, Database $dbForProject, Database $dbForConsole) {
|
||||
->inject('dbForPlatform')
|
||||
->action(function (string $variableId, string $key, ?string $value, Document $project, Response $response, Database $dbForProject, Database $dbForPlatform) {
|
||||
$variable = $dbForProject->getDocument('variables', $variableId);
|
||||
if ($variable === false || $variable->isEmpty() || $variable->getAttribute('resourceType') !== 'project') {
|
||||
throw new Exception(Exception::VARIABLE_NOT_FOUND);
|
||||
|
||||
+277
-245
File diff suppressed because it is too large
Load Diff
@@ -11,6 +11,7 @@ use Utopia\App;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Exception\Query as QueryException;
|
||||
use Utopia\Database\Helpers\ID;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\Database\Validator\Query\Cursor;
|
||||
use Utopia\Database\Validator\UID;
|
||||
@@ -42,9 +43,9 @@ App::post('/v1/proxy/rules')
|
||||
->inject('project')
|
||||
->inject('queueForCertificates')
|
||||
->inject('queueForEvents')
|
||||
->inject('dbForConsole')
|
||||
->inject('dbForPlatform')
|
||||
->inject('dbForProject')
|
||||
->action(function (string $domain, string $resourceType, string $resourceId, Response $response, Document $project, Certificate $queueForCertificates, Event $queueForEvents, Database $dbForConsole, Database $dbForProject) {
|
||||
->action(function (string $domain, string $resourceType, string $resourceId, Response $response, Document $project, Certificate $queueForCertificates, Event $queueForEvents, Database $dbForPlatform, Database $dbForProject) {
|
||||
$mainDomain = System::getEnv('_APP_DOMAIN', '');
|
||||
if ($domain === $mainDomain) {
|
||||
throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'You cannot assign your main domain to specific resource. Please use subdomain or a different domain.');
|
||||
@@ -59,8 +60,15 @@ App::post('/v1/proxy/rules')
|
||||
throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'This domain name is not allowed. Please pick another one.');
|
||||
}
|
||||
|
||||
$ruleId = md5($domain);
|
||||
$document = $dbForConsole->getDocument('rules', $ruleId);
|
||||
// TODO: @christyjacob remove once we migrate the rules in 1.7.x
|
||||
if (System::getEnv('_APP_RULES_FORMAT') === 'md5') {
|
||||
$document = $dbForPlatform->getDocument('rules', md5($domain));
|
||||
} else {
|
||||
$document = $dbForPlatform->findOne('rules', [
|
||||
Query::equal('domain', [$domain]),
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
if (!$document->isEmpty()) {
|
||||
if ($document->getAttribute('projectId') === $project->getId()) {
|
||||
@@ -101,7 +109,9 @@ App::post('/v1/proxy/rules')
|
||||
throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'Domain may not start with http:// or https://.');
|
||||
}
|
||||
|
||||
$ruleId = md5($domain->get());
|
||||
// TODO: @christyjacob remove once we migrate the rules in 1.7.x
|
||||
$ruleId = System::getEnv('_APP_RULES_FORMAT') === 'md5' ? md5($domain->get()) : ID::unique();
|
||||
|
||||
$rule = new Document([
|
||||
'$id' => $ruleId,
|
||||
'projectId' => $project->getId(),
|
||||
@@ -135,7 +145,7 @@ App::post('/v1/proxy/rules')
|
||||
}
|
||||
|
||||
$rule->setAttribute('status', $status);
|
||||
$rule = $dbForConsole->createDocument('rules', $rule);
|
||||
$rule = $dbForPlatform->createDocument('rules', $rule);
|
||||
|
||||
$queueForEvents->setParam('ruleId', $rule->getId());
|
||||
|
||||
@@ -161,8 +171,8 @@ App::get('/v1/proxy/rules')
|
||||
->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true)
|
||||
->inject('response')
|
||||
->inject('project')
|
||||
->inject('dbForConsole')
|
||||
->action(function (array $queries, string $search, Response $response, Document $project, Database $dbForConsole) {
|
||||
->inject('dbForPlatform')
|
||||
->action(function (array $queries, string $search, Response $response, Document $project, Database $dbForPlatform) {
|
||||
try {
|
||||
$queries = Query::parseQueries($queries);
|
||||
} catch (QueryException $e) {
|
||||
@@ -191,7 +201,7 @@ App::get('/v1/proxy/rules')
|
||||
}
|
||||
|
||||
$ruleId = $cursor->getValue();
|
||||
$cursorDocument = $dbForConsole->getDocument('rules', $ruleId);
|
||||
$cursorDocument = $dbForPlatform->getDocument('rules', $ruleId);
|
||||
|
||||
if ($cursorDocument->isEmpty()) {
|
||||
throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "Rule '{$ruleId}' for the 'cursor' value not found.");
|
||||
@@ -202,16 +212,16 @@ App::get('/v1/proxy/rules')
|
||||
|
||||
$filterQueries = Query::groupByType($queries)['filters'];
|
||||
|
||||
$rules = $dbForConsole->find('rules', $queries);
|
||||
$rules = $dbForPlatform->find('rules', $queries);
|
||||
foreach ($rules as $rule) {
|
||||
$certificate = $dbForConsole->getDocument('certificates', $rule->getAttribute('certificateId', ''));
|
||||
$certificate = $dbForPlatform->getDocument('certificates', $rule->getAttribute('certificateId', ''));
|
||||
$rule->setAttribute('logs', $certificate->getAttribute('logs', ''));
|
||||
$rule->setAttribute('renewAt', $certificate->getAttribute('renewDate', ''));
|
||||
}
|
||||
|
||||
$response->dynamic(new Document([
|
||||
'rules' => $rules,
|
||||
'total' => $dbForConsole->count('rules', $filterQueries, APP_LIMIT_COUNT),
|
||||
'total' => $dbForPlatform->count('rules', $filterQueries, APP_LIMIT_COUNT),
|
||||
]), Response::MODEL_PROXY_RULE_LIST);
|
||||
});
|
||||
|
||||
@@ -229,15 +239,15 @@ App::get('/v1/proxy/rules/:ruleId')
|
||||
->param('ruleId', '', new UID(), 'Rule ID.')
|
||||
->inject('response')
|
||||
->inject('project')
|
||||
->inject('dbForConsole')
|
||||
->action(function (string $ruleId, Response $response, Document $project, Database $dbForConsole) {
|
||||
$rule = $dbForConsole->getDocument('rules', $ruleId);
|
||||
->inject('dbForPlatform')
|
||||
->action(function (string $ruleId, Response $response, Document $project, Database $dbForPlatform) {
|
||||
$rule = $dbForPlatform->getDocument('rules', $ruleId);
|
||||
|
||||
if ($rule->isEmpty() || $rule->getAttribute('projectInternalId') !== $project->getInternalId()) {
|
||||
throw new Exception(Exception::RULE_NOT_FOUND);
|
||||
}
|
||||
|
||||
$certificate = $dbForConsole->getDocument('certificates', $rule->getAttribute('certificateId', ''));
|
||||
$certificate = $dbForPlatform->getDocument('certificates', $rule->getAttribute('certificateId', ''));
|
||||
$rule->setAttribute('logs', $certificate->getAttribute('logs', ''));
|
||||
$rule->setAttribute('renewAt', $certificate->getAttribute('renewDate', ''));
|
||||
|
||||
@@ -260,17 +270,17 @@ App::delete('/v1/proxy/rules/:ruleId')
|
||||
->param('ruleId', '', new UID(), 'Rule ID.')
|
||||
->inject('response')
|
||||
->inject('project')
|
||||
->inject('dbForConsole')
|
||||
->inject('dbForPlatform')
|
||||
->inject('queueForDeletes')
|
||||
->inject('queueForEvents')
|
||||
->action(function (string $ruleId, Response $response, Document $project, Database $dbForConsole, Delete $queueForDeletes, Event $queueForEvents) {
|
||||
$rule = $dbForConsole->getDocument('rules', $ruleId);
|
||||
->action(function (string $ruleId, Response $response, Document $project, Database $dbForPlatform, Delete $queueForDeletes, Event $queueForEvents) {
|
||||
$rule = $dbForPlatform->getDocument('rules', $ruleId);
|
||||
|
||||
if ($rule->isEmpty() || $rule->getAttribute('projectInternalId') !== $project->getInternalId()) {
|
||||
throw new Exception(Exception::RULE_NOT_FOUND);
|
||||
}
|
||||
|
||||
$dbForConsole->deleteDocument('rules', $rule->getId());
|
||||
$dbForPlatform->deleteDocument('rules', $rule->getId());
|
||||
|
||||
$queueForDeletes
|
||||
->setType(DELETE_TYPE_DOCUMENT)
|
||||
@@ -299,10 +309,10 @@ App::patch('/v1/proxy/rules/:ruleId/verification')
|
||||
->inject('queueForCertificates')
|
||||
->inject('queueForEvents')
|
||||
->inject('project')
|
||||
->inject('dbForConsole')
|
||||
->inject('dbForPlatform')
|
||||
->inject('log')
|
||||
->action(function (string $ruleId, Response $response, Certificate $queueForCertificates, Event $queueForEvents, Document $project, Database $dbForConsole, Log $log) {
|
||||
$rule = $dbForConsole->getDocument('rules', $ruleId);
|
||||
->action(function (string $ruleId, Response $response, Certificate $queueForCertificates, Event $queueForEvents, Document $project, Database $dbForPlatform, Log $log) {
|
||||
$rule = $dbForPlatform->getDocument('rules', $ruleId);
|
||||
|
||||
if ($rule->isEmpty() || $rule->getAttribute('projectInternalId') !== $project->getInternalId()) {
|
||||
throw new Exception(Exception::RULE_NOT_FOUND);
|
||||
@@ -332,7 +342,7 @@ App::patch('/v1/proxy/rules/:ruleId/verification')
|
||||
throw new Exception(Exception::RULE_VERIFICATION_FAILED);
|
||||
}
|
||||
|
||||
$dbForConsole->updateDocument('rules', $rule->getId(), $rule->setAttribute('status', 'verifying'));
|
||||
$dbForPlatform->updateDocument('rules', $rule->getId(), $rule->setAttribute('status', 'verifying'));
|
||||
|
||||
// Issue a TLS certificate when domain is verified
|
||||
$queueForCertificates
|
||||
@@ -343,7 +353,7 @@ App::patch('/v1/proxy/rules/:ruleId/verification')
|
||||
|
||||
$queueForEvents->setParam('ruleId', $rule->getId());
|
||||
|
||||
$certificate = $dbForConsole->getDocument('certificates', $rule->getAttribute('certificateId', ''));
|
||||
$certificate = $dbForPlatform->getDocument('certificates', $rule->getAttribute('certificateId', ''));
|
||||
$rule->setAttribute('logs', $certificate->getAttribute('logs', ''));
|
||||
|
||||
$response->dynamic($rule, Response::MODEL_PROXY_RULE);
|
||||
|
||||
@@ -6,6 +6,7 @@ use Appwrite\Auth\Auth;
|
||||
use Appwrite\ClamAV\Network;
|
||||
use Appwrite\Event\Delete;
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Event\Usage;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\OpenSSL\OpenSSL;
|
||||
use Appwrite\Utopia\Database\Validator\CustomId;
|
||||
@@ -74,13 +75,14 @@ App::post('/v1/storage/buckets')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('queueForEvents')
|
||||
->action(function (string $bucketId, string $name, ?array $permissions, bool $fileSecurity, bool $enabled, int $maximumFileSize, array $allowedFileExtensions, ?string $compression, bool $encryption, bool $antivirus, Response $response, Database $dbForProject, Event $queueForEvents) {
|
||||
->action(function (string $bucketId, string $name, ?array $permissions, bool $fileSecurity, bool $enabled, int $maximumFileSize, array $allowedFileExtensions, ?string $compression, ?bool $encryption, bool $antivirus, Response $response, Database $dbForProject, Event $queueForEvents) {
|
||||
|
||||
$bucketId = $bucketId === 'unique()' ? ID::unique() : $bucketId;
|
||||
|
||||
// Map aggregate permissions into the multiple permissions they represent.
|
||||
$permissions = Permission::aggregate($permissions);
|
||||
$compression ??= Compression::NONE;
|
||||
$encryption ??= true;
|
||||
try {
|
||||
$files = (Config::getParam('collections', [])['buckets'] ?? [])['files'] ?? [];
|
||||
if (empty($files)) {
|
||||
@@ -260,7 +262,7 @@ App::put('/v1/storage/buckets/:bucketId')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('queueForEvents')
|
||||
->action(function (string $bucketId, string $name, ?array $permissions, bool $fileSecurity, bool $enabled, ?int $maximumFileSize, array $allowedFileExtensions, ?string $compression, bool $encryption, bool $antivirus, Response $response, Database $dbForProject, Event $queueForEvents) {
|
||||
->action(function (string $bucketId, string $name, ?array $permissions, bool $fileSecurity, bool $enabled, ?int $maximumFileSize, array $allowedFileExtensions, ?string $compression, ?bool $encryption, bool $antivirus, Response $response, Database $dbForProject, Event $queueForEvents) {
|
||||
$bucket = $dbForProject->getDocument('buckets', $bucketId);
|
||||
|
||||
if ($bucket->isEmpty()) {
|
||||
@@ -885,7 +887,8 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/preview')
|
||||
->inject('mode')
|
||||
->inject('deviceForFiles')
|
||||
->inject('deviceForLocal')
|
||||
->action(function (string $bucketId, string $fileId, int $width, int $height, string $gravity, int $quality, int $borderWidth, string $borderColor, int $borderRadius, float $opacity, int $rotation, string $background, string $output, Request $request, Response $response, Document $project, Database $dbForProject, string $mode, Device $deviceForFiles, Device $deviceForLocal) {
|
||||
->inject('queueForUsage')
|
||||
->action(function (string $bucketId, string $fileId, int $width, int $height, string $gravity, int $quality, int $borderWidth, string $borderColor, int $borderRadius, float $opacity, int $rotation, string $background, string $output, Request $request, Response $response, Document $project, Database $dbForProject, string $mode, Device $deviceForFiles, Device $deviceForLocal, Usage $queueForUsage) {
|
||||
|
||||
if (!\extension_loaded('imagick')) {
|
||||
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Imagick extension is missing');
|
||||
@@ -1013,6 +1016,11 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/preview')
|
||||
|
||||
$contentType = (\array_key_exists($output, $outputs)) ? $outputs[$output] : $outputs['jpg'];
|
||||
|
||||
$queueForUsage
|
||||
->addMetric(METRIC_FILES_TRANSFORMATIONS, 1)
|
||||
->addMetric(str_replace('{bucketInternalId}', $bucket->getInternalId(), METRIC_BUCKET_ID_FILES_TRANSFORMATIONS), 1)
|
||||
;
|
||||
|
||||
$response
|
||||
->addHeader('Cache-Control', 'private, max-age=2592000') // 30 days
|
||||
->setContentType($contentType)
|
||||
|
||||
@@ -804,8 +804,11 @@ App::get('/v1/teams/:teamId/memberships')
|
||||
}, $membershipsPrivacy);
|
||||
|
||||
$memberships = array_map(function ($membership) use ($dbForProject, $team, $membershipsPrivacy) {
|
||||
$user = !empty(array_filter($membershipsPrivacy))
|
||||
? $dbForProject->getDocument('users', $membership->getAttribute('userId'))
|
||||
: new Document();
|
||||
|
||||
if ($membershipsPrivacy['mfa']) {
|
||||
$user = $dbForProject->getDocument('users', $membership->getAttribute('userId'));
|
||||
$mfa = $user->getAttribute('mfa', false);
|
||||
|
||||
if ($mfa) {
|
||||
@@ -887,9 +890,11 @@ App::get('/v1/teams/:teamId/memberships/:membershipId')
|
||||
return $privacy || $isPrivilegedUser || $isAppUser;
|
||||
}, $membershipsPrivacy);
|
||||
|
||||
if ($membershipsPrivacy['mfa']) {
|
||||
$user = $dbForProject->getDocument('users', $membership->getAttribute('userId'));
|
||||
$user = !empty(array_filter($membershipsPrivacy))
|
||||
? $dbForProject->getDocument('users', $membership->getAttribute('userId'))
|
||||
: new Document();
|
||||
|
||||
if ($membershipsPrivacy['mfa']) {
|
||||
$mfa = $user->getAttribute('mfa', false);
|
||||
|
||||
if ($mfa) {
|
||||
@@ -1054,7 +1059,8 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status')
|
||||
throw new Exception(Exception::TEAM_INVITE_MISMATCH, 'Invite does not belong to current user (' . $user->getAttribute('email') . ')');
|
||||
}
|
||||
|
||||
if ($user->isEmpty()) {
|
||||
$hasSession = !$user->isEmpty();
|
||||
if (!$hasSession) {
|
||||
$user->setAttributes($dbForProject->getDocument('users', $userId)->getArrayCopy()); // Get user
|
||||
}
|
||||
|
||||
@@ -1073,50 +1079,75 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status')
|
||||
|
||||
Authorization::skip(fn () => $dbForProject->updateDocument('users', $user->getId(), $user->setAttribute('emailVerification', true)));
|
||||
|
||||
// Log user in
|
||||
// Create session for the user if not logged in
|
||||
if (!$hasSession) {
|
||||
Authorization::setRole(Role::user($user->getId())->toString());
|
||||
|
||||
Authorization::setRole(Role::user($user->getId())->toString());
|
||||
$detector = new Detector($request->getUserAgent('UNKNOWN'));
|
||||
$authDuration = $project->getAttribute('auths', [])['duration'] ?? Auth::TOKEN_EXPIRATION_LOGIN_LONG;
|
||||
$expire = DateTime::addSeconds(new \DateTime(), $authDuration);
|
||||
$secret = Auth::tokenGenerator();
|
||||
$session = new Document(array_merge([
|
||||
'$id' => ID::unique(),
|
||||
'$permissions' => [
|
||||
Permission::read(Role::user($user->getId())),
|
||||
Permission::update(Role::user($user->getId())),
|
||||
Permission::delete(Role::user($user->getId())),
|
||||
],
|
||||
'userId' => $user->getId(),
|
||||
'userInternalId' => $user->getInternalId(),
|
||||
'provider' => Auth::SESSION_PROVIDER_EMAIL,
|
||||
'providerUid' => $user->getAttribute('email'),
|
||||
'secret' => Auth::hash($secret), // One way hash encryption to protect DB leak
|
||||
'userAgent' => $request->getUserAgent('UNKNOWN'),
|
||||
'ip' => $request->getIP(),
|
||||
'factors' => ['email'],
|
||||
'countryCode' => \strtolower($geoRecord['countryCode']),
|
||||
'continentCode' => strtolower($geoRecord['continentCode']),
|
||||
'latitude' => $geoRecord['latitude'],
|
||||
'longitude' => $geoRecord['longitude'],
|
||||
'timeZone' => $geoRecord['timeZone'],
|
||||
'weatherCode' => $geoRecord['weatherCode'],
|
||||
'postalCode' => $geoRecord['postalCode'],
|
||||
'isp' => $geoRecord['isp'],
|
||||
'autonomousSystemNumber' => $geoRecord['autonomousSystemNumber'],
|
||||
'autonomousSystemOrganization' => $geoRecord['autonomousSystemOrganization'],
|
||||
'connectionType' => $geoRecord['connectionType'],
|
||||
'userType' => $geoRecord['userType'],
|
||||
'organization' => $geoRecord['organization'],
|
||||
'expire' => DateTime::addSeconds(new \DateTime(), $authDuration)
|
||||
], $detector->getOS(), $detector->getClient(), $detector->getDevice()));
|
||||
|
||||
$detector = new Detector($request->getUserAgent('UNKNOWN'));
|
||||
$authDuration = $project->getAttribute('auths', [])['duration'] ?? Auth::TOKEN_EXPIRATION_LOGIN_LONG;
|
||||
$expire = DateTime::addSeconds(new \DateTime(), $authDuration);
|
||||
$secret = Auth::tokenGenerator();
|
||||
$session = new Document(array_merge([
|
||||
'$id' => ID::unique(),
|
||||
'userId' => $user->getId(),
|
||||
'userInternalId' => $user->getInternalId(),
|
||||
'provider' => Auth::SESSION_PROVIDER_EMAIL,
|
||||
'providerUid' => $user->getAttribute('email'),
|
||||
'secret' => Auth::hash($secret), // One way hash encryption to protect DB leak
|
||||
'userAgent' => $request->getUserAgent('UNKNOWN'),
|
||||
'ip' => $request->getIP(),
|
||||
'factors' => ['email'],
|
||||
'countryCode' => \strtolower($geoRecord['countryCode']),
|
||||
'continentCode' => strtolower($geoRecord['continentCode']),
|
||||
'latitude' => $geoRecord['latitude'],
|
||||
'longitude' => $geoRecord['longitude'],
|
||||
'timeZone' => $geoRecord['timeZone'],
|
||||
'weatherCode' => $geoRecord['weatherCode'],
|
||||
'postalCode' => $geoRecord['postalCode'],
|
||||
'isp' => $geoRecord['isp'],
|
||||
'autonomousSystemNumber' => $geoRecord['autonomousSystemNumber'],
|
||||
'autonomousSystemOrganization' => $geoRecord['autonomousSystemOrganization'],
|
||||
'connectionType' => $geoRecord['connectionType'],
|
||||
'userType' => $geoRecord['userType'],
|
||||
'organization' => $geoRecord['organization'],
|
||||
'expire' => DateTime::addSeconds(new \DateTime(), $authDuration)
|
||||
], $detector->getOS(), $detector->getClient(), $detector->getDevice()));
|
||||
$session = $dbForProject->createDocument('sessions', $session);
|
||||
|
||||
$session = $dbForProject->createDocument('sessions', $session
|
||||
->setAttribute('$permissions', [
|
||||
Permission::read(Role::user($user->getId())),
|
||||
Permission::update(Role::user($user->getId())),
|
||||
Permission::delete(Role::user($user->getId())),
|
||||
]));
|
||||
Authorization::setRole(Role::user($userId)->toString());
|
||||
|
||||
$dbForProject->purgeCachedDocument('users', $user->getId());
|
||||
if (!Config::getParam('domainVerification')) {
|
||||
$response->addHeader('X-Fallback-Cookies', \json_encode([Auth::$cookieName => Auth::encodeSession($user->getId(), $secret)]));
|
||||
}
|
||||
|
||||
Authorization::setRole(Role::user($userId)->toString());
|
||||
$response
|
||||
->addCookie(
|
||||
name: Auth::$cookieName . '_legacy',
|
||||
value: Auth::encodeSession($user->getId(), $secret),
|
||||
expire: (new \DateTime($expire))->getTimestamp(),
|
||||
path: '/',
|
||||
domain: Config::getParam('cookieDomain'),
|
||||
secure: ('https' === $protocol),
|
||||
httponly: true
|
||||
)
|
||||
->addCookie(
|
||||
name: Auth::$cookieName,
|
||||
value: Auth::encodeSession($user->getId(), $secret),
|
||||
expire: (new \DateTime($expire))->getTimestamp(),
|
||||
path: '/',
|
||||
domain: Config::getParam('cookieDomain'),
|
||||
secure: ('https' === $protocol),
|
||||
httponly: true,
|
||||
sameSite: Config::getParam('cookieSamesite')
|
||||
)
|
||||
;
|
||||
}
|
||||
|
||||
$membership = $dbForProject->updateDocument('memberships', $membership->getId(), $membership);
|
||||
|
||||
@@ -1130,22 +1161,11 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status')
|
||||
->setParam('membershipId', $membership->getId())
|
||||
;
|
||||
|
||||
if (!Config::getParam('domainVerification')) {
|
||||
$response
|
||||
->addHeader('X-Fallback-Cookies', \json_encode([Auth::$cookieName => Auth::encodeSession($user->getId(), $secret)]))
|
||||
;
|
||||
}
|
||||
|
||||
$response
|
||||
->addCookie(Auth::$cookieName . '_legacy', Auth::encodeSession($user->getId(), $secret), (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, null)
|
||||
->addCookie(Auth::$cookieName, Auth::encodeSession($user->getId(), $secret), (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, Config::getParam('cookieSamesite'))
|
||||
;
|
||||
|
||||
$response->dynamic(
|
||||
$membership
|
||||
->setAttribute('teamName', $team->getAttribute('name'))
|
||||
->setAttribute('userName', $user->getAttribute('name'))
|
||||
->setAttribute('userEmail', $user->getAttribute('email')),
|
||||
->setAttribute('teamName', $team->getAttribute('name'))
|
||||
->setAttribute('userName', $user->getAttribute('name'))
|
||||
->setAttribute('userEmail', $user->getAttribute('email')),
|
||||
Response::MODEL_MEMBERSHIP
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1795,6 +1795,7 @@ App::post('/v1/users/:userId/sessions')
|
||||
'provider' => Auth::SESSION_PROVIDER_SERVER,
|
||||
'secret' => Auth::hash($secret), // One way hash encryption to protect DB leak
|
||||
'userAgent' => $request->getUserAgent('UNKNOWN'),
|
||||
'factors' => ['server'],
|
||||
'ip' => $request->getIP(),
|
||||
'countryCode' => \strtolower($geoRecord['countryCode']),
|
||||
'continentCode' => strtolower($geoRecord['continentCode']),
|
||||
@@ -1819,8 +1820,11 @@ App::post('/v1/users/:userId/sessions')
|
||||
$countryName = $locale->getText('countries.' . strtolower($session->getAttribute('countryCode')), $locale->getText('locale.country.unknown'));
|
||||
|
||||
$session = $dbForProject->createDocument('sessions', $session);
|
||||
|
||||
$dbForProject->purgeCachedDocument('users', $user->getId());
|
||||
|
||||
$session
|
||||
->setAttribute('secret', $secret)
|
||||
->setAttribute('secret', Auth::encodeSession($user->getId(), $secret))
|
||||
->setAttribute('countryName', $countryName);
|
||||
|
||||
$queueForEvents
|
||||
|
||||
+64
-64
@@ -42,7 +42,7 @@ use Utopia\VCS\Exception\RepositoryNotFound;
|
||||
|
||||
use function Swoole\Coroutine\batch;
|
||||
|
||||
$createGitDeployments = function (GitHub $github, string $providerInstallationId, array $repositories, string $providerBranch, string $providerBranchUrl, string $providerRepositoryName, string $providerRepositoryUrl, string $providerRepositoryOwner, string $providerCommitHash, string $providerCommitAuthor, string $providerCommitAuthorUrl, string $providerCommitMessage, string $providerCommitUrl, string $providerPullRequestId, bool $external, Database $dbForConsole, Build $queueForBuilds, callable $getProjectDB, Request $request) {
|
||||
$createGitDeployments = function (GitHub $github, string $providerInstallationId, array $repositories, string $providerBranch, string $providerBranchUrl, string $providerRepositoryName, string $providerRepositoryUrl, string $providerRepositoryOwner, string $providerCommitHash, string $providerCommitAuthor, string $providerCommitAuthorUrl, string $providerCommitMessage, string $providerCommitUrl, string $providerPullRequestId, bool $external, Database $dbForPlatform, Build $queueForBuilds, callable $getProjectDB, Request $request) {
|
||||
$errors = [];
|
||||
foreach ($repositories as $resource) {
|
||||
try {
|
||||
@@ -53,7 +53,7 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId
|
||||
}
|
||||
|
||||
$projectId = $resource->getAttribute('projectId');
|
||||
$project = Authorization::skip(fn () => $dbForConsole->getDocument('projects', $projectId));
|
||||
$project = Authorization::skip(fn () => $dbForPlatform->getDocument('projects', $projectId));
|
||||
$dbForProject = $getProjectDB($project);
|
||||
|
||||
$functionId = $resource->getAttribute('resourceId');
|
||||
@@ -104,7 +104,7 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId
|
||||
$latestCommentId = '';
|
||||
|
||||
if (!empty($providerPullRequestId) && $function->getAttribute('providerSilentMode', false) === false) {
|
||||
$latestComment = Authorization::skip(fn () => $dbForConsole->findOne('vcsComments', [
|
||||
$latestComment = Authorization::skip(fn () => $dbForPlatform->findOne('vcsComments', [
|
||||
Query::equal('providerRepositoryId', [$providerRepositoryId]),
|
||||
Query::equal('providerPullRequestId', [$providerPullRequestId]),
|
||||
Query::orderDesc('$createdAt'),
|
||||
@@ -125,7 +125,7 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId
|
||||
if (!empty($latestCommentId)) {
|
||||
$teamId = $project->getAttribute('teamId', '');
|
||||
|
||||
$latestComment = Authorization::skip(fn () => $dbForConsole->createDocument('vcsComments', new Document([
|
||||
$latestComment = Authorization::skip(fn () => $dbForPlatform->createDocument('vcsComments', new Document([
|
||||
'$id' => ID::unique(),
|
||||
'$permissions' => [
|
||||
Permission::read(Role::team(ID::custom($teamId))),
|
||||
@@ -146,7 +146,7 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId
|
||||
}
|
||||
}
|
||||
} elseif (!empty($providerBranch)) {
|
||||
$latestComments = Authorization::skip(fn () => $dbForConsole->find('vcsComments', [
|
||||
$latestComments = Authorization::skip(fn () => $dbForPlatform->find('vcsComments', [
|
||||
Query::equal('providerRepositoryId', [$providerRepositoryId]),
|
||||
Query::equal('providerBranch', [$providerBranch]),
|
||||
Query::orderDesc('$createdAt'),
|
||||
@@ -319,8 +319,8 @@ App::get('/v1/vcs/github/callback')
|
||||
->inject('project')
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
->inject('dbForConsole')
|
||||
->action(function (string $providerInstallationId, string $setupAction, string $state, string $code, GitHub $github, Document $user, Document $project, Request $request, Response $response, Database $dbForConsole) {
|
||||
->inject('dbForPlatform')
|
||||
->action(function (string $providerInstallationId, string $setupAction, string $state, string $code, GitHub $github, Document $user, Document $project, Request $request, Response $response, Database $dbForPlatform) {
|
||||
if (empty($state)) {
|
||||
$error = 'Installation requests from organisation members for the Appwrite GitHub App are currently unsupported. To proceed with the installation, login to the Appwrite Console and install the GitHub App.';
|
||||
throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, $error);
|
||||
@@ -339,7 +339,7 @@ App::get('/v1/vcs/github/callback')
|
||||
$redirectSuccess = $state['success'] ?? '';
|
||||
$redirectFailure = $state['failure'] ?? '';
|
||||
|
||||
$project = $dbForConsole->getDocument('projects', $projectId);
|
||||
$project = $dbForPlatform->getDocument('projects', $projectId);
|
||||
|
||||
if ($project->isEmpty()) {
|
||||
$error = 'Project with the ID from state could not be found.';
|
||||
@@ -368,7 +368,7 @@ App::get('/v1/vcs/github/callback')
|
||||
$oauth2ID = $oauth2->getUserID($accessToken);
|
||||
|
||||
// Makes sure this email is not already used in another identity
|
||||
$identity = $dbForConsole->findOne('identities', [
|
||||
$identity = $dbForPlatform->findOne('identities', [
|
||||
Query::equal('providerEmail', [$email]),
|
||||
]);
|
||||
if (!$identity->isEmpty()) {
|
||||
@@ -381,9 +381,9 @@ App::get('/v1/vcs/github/callback')
|
||||
->setAttribute('providerRefreshToken', $refreshToken)
|
||||
->setAttribute('providerAccessTokenExpiry', DateTime::addSeconds(new \DateTime(), (int)$accessTokenExpiry));
|
||||
|
||||
$dbForConsole->updateDocument('identities', $identity->getId(), $identity);
|
||||
$dbForPlatform->updateDocument('identities', $identity->getId(), $identity);
|
||||
} else {
|
||||
$identity = $dbForConsole->createDocument('identities', new Document([
|
||||
$identity = $dbForPlatform->createDocument('identities', new Document([
|
||||
'$id' => ID::unique(),
|
||||
'$permissions' => [
|
||||
Permission::read(Role::any()),
|
||||
@@ -411,7 +411,7 @@ App::get('/v1/vcs/github/callback')
|
||||
|
||||
$projectInternalId = $project->getInternalId();
|
||||
|
||||
$installation = $dbForConsole->findOne('installations', [
|
||||
$installation = $dbForPlatform->findOne('installations', [
|
||||
Query::equal('providerInstallationId', [$providerInstallationId]),
|
||||
Query::equal('projectInternalId', [$projectInternalId])
|
||||
]);
|
||||
@@ -436,12 +436,12 @@ App::get('/v1/vcs/github/callback')
|
||||
'personal' => $personalSlug === $owner
|
||||
]);
|
||||
|
||||
$installation = $dbForConsole->createDocument('installations', $installation);
|
||||
$installation = $dbForPlatform->createDocument('installations', $installation);
|
||||
} else {
|
||||
$installation = $installation
|
||||
->setAttribute('organization', $owner)
|
||||
->setAttribute('personal', $personalSlug === $owner);
|
||||
$installation = $dbForConsole->updateDocument('installations', $installation->getId(), $installation);
|
||||
$installation = $dbForPlatform->updateDocument('installations', $installation->getId(), $installation);
|
||||
}
|
||||
} else {
|
||||
$error = 'Installation of the Appwrite GitHub App on organization accounts is restricted to organization owners. As a member of the organization, you do not have the necessary permissions to install this GitHub App. Please contact the organization owner to create the installation from the Appwrite console.';
|
||||
@@ -480,9 +480,9 @@ App::get('/v1/vcs/github/installations/:installationId/providerRepositories/:pro
|
||||
->inject('gitHub')
|
||||
->inject('response')
|
||||
->inject('project')
|
||||
->inject('dbForConsole')
|
||||
->action(function (string $installationId, string $providerRepositoryId, string $providerRootDirectory, GitHub $github, Response $response, Document $project, Database $dbForConsole) {
|
||||
$installation = $dbForConsole->getDocument('installations', $installationId);
|
||||
->inject('dbForPlatform')
|
||||
->action(function (string $installationId, string $providerRepositoryId, string $providerRootDirectory, GitHub $github, Response $response, Document $project, Database $dbForPlatform) {
|
||||
$installation = $dbForPlatform->getDocument('installations', $installationId);
|
||||
|
||||
if ($installation->isEmpty()) {
|
||||
throw new Exception(Exception::INSTALLATION_NOT_FOUND);
|
||||
@@ -541,9 +541,9 @@ App::post('/v1/vcs/github/installations/:installationId/providerRepositories/:pr
|
||||
->inject('gitHub')
|
||||
->inject('response')
|
||||
->inject('project')
|
||||
->inject('dbForConsole')
|
||||
->action(function (string $installationId, string $providerRepositoryId, string $providerRootDirectory, GitHub $github, Response $response, Document $project, Database $dbForConsole) {
|
||||
$installation = $dbForConsole->getDocument('installations', $installationId);
|
||||
->inject('dbForPlatform')
|
||||
->action(function (string $installationId, string $providerRepositoryId, string $providerRootDirectory, GitHub $github, Response $response, Document $project, Database $dbForPlatform) {
|
||||
$installation = $dbForPlatform->getDocument('installations', $installationId);
|
||||
|
||||
if ($installation->isEmpty()) {
|
||||
throw new Exception(Exception::INSTALLATION_NOT_FOUND);
|
||||
@@ -612,13 +612,13 @@ App::get('/v1/vcs/github/installations/:installationId/providerRepositories')
|
||||
->inject('gitHub')
|
||||
->inject('response')
|
||||
->inject('project')
|
||||
->inject('dbForConsole')
|
||||
->action(function (string $installationId, string $search, GitHub $github, Response $response, Document $project, Database $dbForConsole) {
|
||||
->inject('dbForPlatform')
|
||||
->action(function (string $installationId, string $search, GitHub $github, Response $response, Document $project, Database $dbForPlatform) {
|
||||
if (empty($search)) {
|
||||
$search = "";
|
||||
}
|
||||
|
||||
$installation = $dbForConsole->getDocument('installations', $installationId);
|
||||
$installation = $dbForPlatform->getDocument('installations', $installationId);
|
||||
|
||||
if ($installation->isEmpty()) {
|
||||
throw new Exception(Exception::INSTALLATION_NOT_FOUND);
|
||||
@@ -709,9 +709,9 @@ App::post('/v1/vcs/github/installations/:installationId/providerRepositories')
|
||||
->inject('user')
|
||||
->inject('response')
|
||||
->inject('project')
|
||||
->inject('dbForConsole')
|
||||
->action(function (string $installationId, string $name, bool $private, GitHub $github, Document $user, Response $response, Document $project, Database $dbForConsole) {
|
||||
$installation = $dbForConsole->getDocument('installations', $installationId);
|
||||
->inject('dbForPlatform')
|
||||
->action(function (string $installationId, string $name, bool $private, GitHub $github, Document $user, Response $response, Document $project, Database $dbForPlatform) {
|
||||
$installation = $dbForPlatform->getDocument('installations', $installationId);
|
||||
|
||||
if ($installation->isEmpty()) {
|
||||
throw new Exception(Exception::INSTALLATION_NOT_FOUND);
|
||||
@@ -720,7 +720,7 @@ App::post('/v1/vcs/github/installations/:installationId/providerRepositories')
|
||||
if ($installation->getAttribute('personal', false) === true) {
|
||||
$oauth2 = new OAuth2Github(System::getEnv('_APP_VCS_GITHUB_CLIENT_ID', ''), System::getEnv('_APP_VCS_GITHUB_CLIENT_SECRET', ''), "");
|
||||
|
||||
$identity = $dbForConsole->findOne('identities', [
|
||||
$identity = $dbForPlatform->findOne('identities', [
|
||||
Query::equal('provider', ['github']),
|
||||
Query::equal('userInternalId', [$user->getInternalId()]),
|
||||
]);
|
||||
@@ -750,7 +750,7 @@ App::post('/v1/vcs/github/installations/:installationId/providerRepositories')
|
||||
->setAttribute('providerRefreshToken', $refreshToken)
|
||||
->setAttribute('providerAccessTokenExpiry', DateTime::addSeconds(new \DateTime(), (int)$oauth2->getAccessTokenExpiry('')));
|
||||
|
||||
$dbForConsole->updateDocument('identities', $identity->getId(), $identity);
|
||||
$dbForPlatform->updateDocument('identities', $identity->getId(), $identity);
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -808,9 +808,9 @@ App::get('/v1/vcs/github/installations/:installationId/providerRepositories/:pro
|
||||
->inject('gitHub')
|
||||
->inject('response')
|
||||
->inject('project')
|
||||
->inject('dbForConsole')
|
||||
->action(function (string $installationId, string $providerRepositoryId, GitHub $github, Response $response, Document $project, Database $dbForConsole) {
|
||||
$installation = $dbForConsole->getDocument('installations', $installationId);
|
||||
->inject('dbForPlatform')
|
||||
->action(function (string $installationId, string $providerRepositoryId, GitHub $github, Response $response, Document $project, Database $dbForPlatform) {
|
||||
$installation = $dbForPlatform->getDocument('installations', $installationId);
|
||||
|
||||
if ($installation->isEmpty()) {
|
||||
throw new Exception(Exception::INSTALLATION_NOT_FOUND);
|
||||
@@ -857,9 +857,9 @@ App::get('/v1/vcs/github/installations/:installationId/providerRepositories/:pro
|
||||
->inject('gitHub')
|
||||
->inject('response')
|
||||
->inject('project')
|
||||
->inject('dbForConsole')
|
||||
->action(function (string $installationId, string $providerRepositoryId, GitHub $github, Response $response, Document $project, Database $dbForConsole) {
|
||||
$installation = $dbForConsole->getDocument('installations', $installationId);
|
||||
->inject('dbForPlatform')
|
||||
->action(function (string $installationId, string $providerRepositoryId, GitHub $github, Response $response, Document $project, Database $dbForPlatform) {
|
||||
$installation = $dbForPlatform->getDocument('installations', $installationId);
|
||||
|
||||
if ($installation->isEmpty()) {
|
||||
throw new Exception(Exception::INSTALLATION_NOT_FOUND);
|
||||
@@ -897,11 +897,11 @@ App::post('/v1/vcs/github/events')
|
||||
->inject('gitHub')
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
->inject('dbForConsole')
|
||||
->inject('dbForPlatform')
|
||||
->inject('getProjectDB')
|
||||
->inject('queueForBuilds')
|
||||
->action(
|
||||
function (GitHub $github, Request $request, Response $response, Database $dbForConsole, callable $getProjectDB, Build $queueForBuilds) use ($createGitDeployments) {
|
||||
function (GitHub $github, Request $request, Response $response, Database $dbForPlatform, callable $getProjectDB, Build $queueForBuilds) use ($createGitDeployments) {
|
||||
$payload = $request->getRawPayload();
|
||||
$signatureRemote = $request->getHeader('x-hub-signature-256', '');
|
||||
$signatureLocal = System::getEnv('_APP_VCS_GITHUB_WEBHOOK_SECRET', '');
|
||||
@@ -935,36 +935,36 @@ App::post('/v1/vcs/github/events')
|
||||
$github->initializeVariables($providerInstallationId, $privateKey, $githubAppId);
|
||||
|
||||
//find functionId from functions table
|
||||
$repositories = Authorization::skip(fn () => $dbForConsole->find('repositories', [
|
||||
$repositories = Authorization::skip(fn () => $dbForPlatform->find('repositories', [
|
||||
Query::equal('providerRepositoryId', [$providerRepositoryId]),
|
||||
Query::limit(100),
|
||||
]));
|
||||
|
||||
// create new deployment only on push and not when branch is created
|
||||
if (!$providerBranchCreated) {
|
||||
$createGitDeployments($github, $providerInstallationId, $repositories, $providerBranch, $providerBranchUrl, $providerRepositoryName, $providerRepositoryUrl, $providerRepositoryOwner, $providerCommitHash, $providerCommitAuthor, $providerCommitAuthorUrl, $providerCommitMessage, $providerCommitUrl, '', false, $dbForConsole, $queueForBuilds, $getProjectDB, $request);
|
||||
$createGitDeployments($github, $providerInstallationId, $repositories, $providerBranch, $providerBranchUrl, $providerRepositoryName, $providerRepositoryUrl, $providerRepositoryOwner, $providerCommitHash, $providerCommitAuthor, $providerCommitAuthorUrl, $providerCommitMessage, $providerCommitUrl, '', false, $dbForPlatform, $queueForBuilds, $getProjectDB, $request);
|
||||
}
|
||||
} elseif ($event == $github::EVENT_INSTALLATION) {
|
||||
if ($parsedPayload["action"] == "deleted") {
|
||||
// TODO: Use worker for this job instead (update function as well)
|
||||
$providerInstallationId = $parsedPayload["installationId"];
|
||||
|
||||
$installations = $dbForConsole->find('installations', [
|
||||
$installations = $dbForPlatform->find('installations', [
|
||||
Query::equal('providerInstallationId', [$providerInstallationId]),
|
||||
Query::limit(1000)
|
||||
]);
|
||||
|
||||
foreach ($installations as $installation) {
|
||||
$repositories = Authorization::skip(fn () => $dbForConsole->find('repositories', [
|
||||
$repositories = Authorization::skip(fn () => $dbForPlatform->find('repositories', [
|
||||
Query::equal('installationInternalId', [$installation->getInternalId()]),
|
||||
Query::limit(1000)
|
||||
]));
|
||||
|
||||
foreach ($repositories as $repository) {
|
||||
Authorization::skip(fn () => $dbForConsole->deleteDocument('repositories', $repository->getId()));
|
||||
Authorization::skip(fn () => $dbForPlatform->deleteDocument('repositories', $repository->getId()));
|
||||
}
|
||||
|
||||
$dbForConsole->deleteDocument('installations', $installation->getId());
|
||||
$dbForPlatform->deleteDocument('installations', $installation->getId());
|
||||
}
|
||||
}
|
||||
} elseif ($event == $github::EVENT_PULL_REQUEST) {
|
||||
@@ -993,12 +993,12 @@ App::post('/v1/vcs/github/events')
|
||||
$providerCommitAuthor = $commitDetails["commitAuthor"] ?? '';
|
||||
$providerCommitMessage = $commitDetails["commitMessage"] ?? '';
|
||||
|
||||
$repositories = Authorization::skip(fn () => $dbForConsole->find('repositories', [
|
||||
$repositories = Authorization::skip(fn () => $dbForPlatform->find('repositories', [
|
||||
Query::equal('providerRepositoryId', [$providerRepositoryId]),
|
||||
Query::orderDesc('$createdAt')
|
||||
]));
|
||||
|
||||
$createGitDeployments($github, $providerInstallationId, $repositories, $providerBranch, $providerBranchUrl, $providerRepositoryName, $providerRepositoryUrl, $providerRepositoryOwner, $providerCommitHash, $providerCommitAuthor, $providerCommitAuthorUrl, $providerCommitMessage, $providerCommitUrl, $providerPullRequestId, $external, $dbForConsole, $queueForBuilds, $getProjectDB, $request);
|
||||
$createGitDeployments($github, $providerInstallationId, $repositories, $providerBranch, $providerBranchUrl, $providerRepositoryName, $providerRepositoryUrl, $providerRepositoryOwner, $providerCommitHash, $providerCommitAuthor, $providerCommitAuthorUrl, $providerCommitMessage, $providerCommitUrl, $providerPullRequestId, $external, $dbForPlatform, $queueForBuilds, $getProjectDB, $request);
|
||||
} elseif ($parsedPayload["action"] == "closed") {
|
||||
// Allowed external contributions cleanup
|
||||
|
||||
@@ -1007,7 +1007,7 @@ App::post('/v1/vcs/github/events')
|
||||
$external = $parsedPayload["external"] ?? true;
|
||||
|
||||
if ($external) {
|
||||
$repositories = Authorization::skip(fn () => $dbForConsole->find('repositories', [
|
||||
$repositories = Authorization::skip(fn () => $dbForPlatform->find('repositories', [
|
||||
Query::equal('providerRepositoryId', [$providerRepositoryId]),
|
||||
Query::orderDesc('$createdAt')
|
||||
]));
|
||||
@@ -1018,7 +1018,7 @@ App::post('/v1/vcs/github/events')
|
||||
if (\in_array($providerPullRequestId, $providerPullRequestIds)) {
|
||||
$providerPullRequestIds = \array_diff($providerPullRequestIds, [$providerPullRequestId]);
|
||||
$repository = $repository->setAttribute('providerPullRequestIds', $providerPullRequestIds);
|
||||
$repository = Authorization::skip(fn () => $dbForConsole->updateDocument('repositories', $repository->getId(), $repository));
|
||||
$repository = Authorization::skip(fn () => $dbForPlatform->updateDocument('repositories', $repository->getId(), $repository));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1045,8 +1045,8 @@ App::get('/v1/vcs/installations')
|
||||
->inject('response')
|
||||
->inject('project')
|
||||
->inject('dbForProject')
|
||||
->inject('dbForConsole')
|
||||
->action(function (array $queries, string $search, Response $response, Document $project, Database $dbForProject, Database $dbForConsole) {
|
||||
->inject('dbForPlatform')
|
||||
->action(function (array $queries, string $search, Response $response, Document $project, Database $dbForProject, Database $dbForPlatform) {
|
||||
try {
|
||||
$queries = Query::parseQueries($queries);
|
||||
} catch (QueryException $e) {
|
||||
@@ -1075,7 +1075,7 @@ App::get('/v1/vcs/installations')
|
||||
}
|
||||
|
||||
$installationId = $cursor->getValue();
|
||||
$cursorDocument = $dbForConsole->getDocument('installations', $installationId);
|
||||
$cursorDocument = $dbForPlatform->getDocument('installations', $installationId);
|
||||
|
||||
if ($cursorDocument->isEmpty()) {
|
||||
throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "Installation '{$installationId}' for the 'cursor' value not found.");
|
||||
@@ -1086,8 +1086,8 @@ App::get('/v1/vcs/installations')
|
||||
|
||||
$filterQueries = Query::groupByType($queries)['filters'];
|
||||
|
||||
$results = $dbForConsole->find('installations', $queries);
|
||||
$total = $dbForConsole->count('installations', $filterQueries, APP_LIMIT_COUNT);
|
||||
$results = $dbForPlatform->find('installations', $queries);
|
||||
$total = $dbForPlatform->count('installations', $filterQueries, APP_LIMIT_COUNT);
|
||||
|
||||
$response->dynamic(new Document([
|
||||
'installations' => $results,
|
||||
@@ -1109,9 +1109,9 @@ App::get('/v1/vcs/installations/:installationId')
|
||||
->param('installationId', '', new Text(256), 'Installation Id')
|
||||
->inject('response')
|
||||
->inject('project')
|
||||
->inject('dbForConsole')
|
||||
->action(function (string $installationId, Response $response, Document $project, Database $dbForConsole) {
|
||||
$installation = $dbForConsole->getDocument('installations', $installationId);
|
||||
->inject('dbForPlatform')
|
||||
->action(function (string $installationId, Response $response, Document $project, Database $dbForPlatform) {
|
||||
$installation = $dbForPlatform->getDocument('installations', $installationId);
|
||||
|
||||
if ($installation === false || $installation->isEmpty()) {
|
||||
throw new Exception(Exception::INSTALLATION_NOT_FOUND);
|
||||
@@ -1137,16 +1137,16 @@ App::delete('/v1/vcs/installations/:installationId')
|
||||
->param('installationId', '', new Text(256), 'Installation Id')
|
||||
->inject('response')
|
||||
->inject('project')
|
||||
->inject('dbForConsole')
|
||||
->inject('dbForPlatform')
|
||||
->inject('queueForDeletes')
|
||||
->action(function (string $installationId, Response $response, Document $project, Database $dbForConsole, Delete $queueForDeletes) {
|
||||
$installation = $dbForConsole->getDocument('installations', $installationId);
|
||||
->action(function (string $installationId, Response $response, Document $project, Database $dbForPlatform, Delete $queueForDeletes) {
|
||||
$installation = $dbForPlatform->getDocument('installations', $installationId);
|
||||
|
||||
if ($installation->isEmpty()) {
|
||||
throw new Exception(Exception::INSTALLATION_NOT_FOUND);
|
||||
}
|
||||
|
||||
if (!$dbForConsole->deleteDocument('installations', $installation->getId())) {
|
||||
if (!$dbForPlatform->deleteDocument('installations', $installation->getId())) {
|
||||
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Failed to remove installation from DB');
|
||||
}
|
||||
|
||||
@@ -1174,17 +1174,17 @@ App::patch('/v1/vcs/github/installations/:installationId/repositories/:repositor
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
->inject('project')
|
||||
->inject('dbForConsole')
|
||||
->inject('dbForPlatform')
|
||||
->inject('getProjectDB')
|
||||
->inject('queueForBuilds')
|
||||
->action(function (string $installationId, string $repositoryId, string $providerPullRequestId, GitHub $github, Request $request, Response $response, Document $project, Database $dbForConsole, callable $getProjectDB, Build $queueForBuilds) use ($createGitDeployments) {
|
||||
$installation = $dbForConsole->getDocument('installations', $installationId);
|
||||
->action(function (string $installationId, string $repositoryId, string $providerPullRequestId, GitHub $github, Request $request, Response $response, Document $project, Database $dbForPlatform, callable $getProjectDB, Build $queueForBuilds) use ($createGitDeployments) {
|
||||
$installation = $dbForPlatform->getDocument('installations', $installationId);
|
||||
|
||||
if ($installation->isEmpty()) {
|
||||
throw new Exception(Exception::INSTALLATION_NOT_FOUND);
|
||||
}
|
||||
|
||||
$repository = Authorization::skip(fn () => $dbForConsole->getDocument('repositories', $repositoryId, [
|
||||
$repository = Authorization::skip(fn () => $dbForPlatform->getDocument('repositories', $repositoryId, [
|
||||
Query::equal('projectInternalId', [$project->getInternalId()])
|
||||
]));
|
||||
|
||||
@@ -1201,7 +1201,7 @@ App::patch('/v1/vcs/github/installations/:installationId/repositories/:repositor
|
||||
|
||||
// TODO: Delete from array when PR is closed
|
||||
|
||||
$repository = Authorization::skip(fn () => $dbForConsole->updateDocument('repositories', $repository->getId(), $repository));
|
||||
$repository = Authorization::skip(fn () => $dbForPlatform->updateDocument('repositories', $repository->getId(), $repository));
|
||||
|
||||
$privateKey = System::getEnv('_APP_VCS_GITHUB_PRIVATE_KEY');
|
||||
$githubAppId = System::getEnv('_APP_VCS_GITHUB_APP_ID');
|
||||
@@ -1225,7 +1225,7 @@ App::patch('/v1/vcs/github/installations/:installationId/repositories/:repositor
|
||||
$providerBranch = \explode(':', $pullRequestResponse['head']['label'])[1] ?? '';
|
||||
$providerCommitHash = $pullRequestResponse['head']['sha'] ?? '';
|
||||
|
||||
$createGitDeployments($github, $providerInstallationId, $repositories, $providerBranch, $providerCommitHash, $providerPullRequestId, true, $dbForConsole, $queueForBuilds, $getProjectDB, $request);
|
||||
$createGitDeployments($github, $providerInstallationId, $repositories, $providerBranch, $providerCommitHash, $providerPullRequestId, true, $dbForPlatform, $queueForBuilds, $getProjectDB, $request);
|
||||
|
||||
$response->noContent();
|
||||
});
|
||||
|
||||
+94
-51
@@ -28,6 +28,7 @@ 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\Authorization;
|
||||
use Utopia\Domains\Domain;
|
||||
use Utopia\DSN\DSN;
|
||||
@@ -44,13 +45,26 @@ Config::setParam('domainVerification', false);
|
||||
Config::setParam('cookieDomain', 'localhost');
|
||||
Config::setParam('cookieSamesite', Response::COOKIE_SAMESITE_NONE);
|
||||
|
||||
function router(App $utopia, Database $dbForConsole, callable $getProjectDB, SwooleRequest $swooleRequest, Request $request, Response $response, Event $queueForEvents, Usage $queueForUsage, Func $queueForFunctions, array $geoRecord, callable $isResourceBlocked)
|
||||
function router(App $utopia, Database $dbForPlatform, callable $getProjectDB, SwooleRequest $swooleRequest, Request $request, Response $response, Event $queueForEvents, Usage $queueForUsage, Func $queueForFunctions, array $geoRecord, callable $isResourceBlocked, string $previewHostname)
|
||||
{
|
||||
$utopia->getRoute()?->label('error', __DIR__ . '/../views/general/error.phtml');
|
||||
|
||||
$host = $request->getHostname() ?? '';
|
||||
if (!empty($previewHostname)) {
|
||||
$host = $previewHostname;
|
||||
}
|
||||
|
||||
$route = Authorization::skip(fn () => $dbForConsole->getDocument('rules', md5($host)));
|
||||
// TODO: @christyjacob remove once we migrate the rules in 1.7.x
|
||||
if (System::getEnv('_APP_RULES_FORMAT') === 'md5') {
|
||||
$route = Authorization::skip(fn () => $dbForPlatform->getDocument('rules', md5($host)));
|
||||
} else {
|
||||
$route = Authorization::skip(
|
||||
fn () => $dbForPlatform->find('rules', [
|
||||
Query::equal('domain', [$host]),
|
||||
Query::limit(1)
|
||||
])
|
||||
)[0] ?? new Document();
|
||||
}
|
||||
|
||||
if ($route->isEmpty()) {
|
||||
if ($host === System::getEnv('_APP_DOMAIN_FUNCTIONS', '')) {
|
||||
@@ -62,7 +76,7 @@ function router(App $utopia, Database $dbForConsole, callable $getProjectDB, Swo
|
||||
}
|
||||
|
||||
if (System::getEnv('_APP_OPTIONS_ROUTER_PROTECTION', 'disabled') === 'enabled') {
|
||||
if ($host !== 'localhost' && $host !== APP_HOSTNAME_INTERNAL) { // localhost allowed for proxy, APP_HOSTNAME_INTERNAL allowed for migrations
|
||||
if ($host !== 'localhost' && $host !== APP_HOSTNAME_INTERNAL && $host !== System::getEnv('_APP_CONSOLE_DOMAIN', '')) {
|
||||
throw new AppwriteException(AppwriteException::GENERAL_ACCESS_FORBIDDEN, 'Router protection does not allow accessing Appwrite over this domain. Please add it as custom domain to your project or disable _APP_OPTIONS_ROUTER_PROTECTION environment variable.');
|
||||
}
|
||||
}
|
||||
@@ -74,8 +88,17 @@ function router(App $utopia, Database $dbForConsole, callable $getProjectDB, Swo
|
||||
|
||||
$projectId = $route->getAttribute('projectId');
|
||||
$project = Authorization::skip(
|
||||
fn () => $dbForConsole->getDocument('projects', $projectId)
|
||||
fn () => $dbForPlatform->getDocument('projects', $projectId)
|
||||
);
|
||||
|
||||
if (!$project->isEmpty() && $project->getId() !== 'console') {
|
||||
$accessedAt = $project->getAttribute('accessedAt', '');
|
||||
if (DateTime::formatTz(DateTime::addSeconds(new \DateTime(), -APP_PROJECT_ACCESS)) > $accessedAt) {
|
||||
$project->setAttribute('accessedAt', DateTime::now());
|
||||
Authorization::skip(fn () => $dbForPlatform->updateDocument('projects', $project->getId(), $project));
|
||||
}
|
||||
}
|
||||
|
||||
if (array_key_exists('proxy', $project->getAttribute('services', []))) {
|
||||
$status = $project->getAttribute('services', [])['proxy'];
|
||||
if (!$status) {
|
||||
@@ -120,7 +143,7 @@ function router(App $utopia, Database $dbForConsole, callable $getProjectDB, Swo
|
||||
|
||||
$requestHeaders = $request->getHeaders();
|
||||
|
||||
$project = Authorization::skip(fn () => $dbForConsole->getDocument('projects', $projectId));
|
||||
$project = Authorization::skip(fn () => $dbForPlatform->getDocument('projects', $projectId));
|
||||
|
||||
$dbForProject = $getProjectDB($project);
|
||||
|
||||
@@ -334,26 +357,6 @@ function router(App $utopia, Database $dbForConsole, callable $getProjectDB, Swo
|
||||
throw $th;
|
||||
}
|
||||
} finally {
|
||||
$fileSize = 0;
|
||||
$file = $request->getFiles('file');
|
||||
if (!empty($file)) {
|
||||
$fileSize = (\is_array($file['size']) && isset($file['size'][0])) ? $file['size'][0] : $file['size'];
|
||||
}
|
||||
|
||||
$queueForUsage
|
||||
->addMetric(METRIC_NETWORK_REQUESTS, 1)
|
||||
->addMetric(METRIC_NETWORK_INBOUND, $request->getSize() + $fileSize)
|
||||
->addMetric(METRIC_NETWORK_OUTBOUND, $response->getSize())
|
||||
->addMetric(METRIC_EXECUTIONS, 1)
|
||||
->addMetric(str_replace('{functionInternalId}', $function->getInternalId(), METRIC_FUNCTION_ID_EXECUTIONS), 1)
|
||||
->addMetric(METRIC_EXECUTIONS_COMPUTE, (int)($execution->getAttribute('duration') * 1000)) // per project
|
||||
->addMetric(str_replace('{functionInternalId}', $function->getInternalId(), METRIC_FUNCTION_ID_EXECUTIONS_COMPUTE), (int)($execution->getAttribute('duration') * 1000)) // per function
|
||||
->addMetric(METRIC_EXECUTIONS_MB_SECONDS, (int)(($spec['memory'] ?? APP_FUNCTION_MEMORY_DEFAULT) * $execution->getAttribute('duration', 0) * ($spec['cpus'] ?? APP_FUNCTION_CPUS_DEFAULT)))
|
||||
->addMetric(str_replace('{functionInternalId}', $function->getInternalId(), METRIC_FUNCTION_ID_EXECUTIONS_MB_SECONDS), (int)(($spec['memory'] ?? APP_FUNCTION_MEMORY_DEFAULT) * $execution->getAttribute('duration', 0) * ($spec['cpus'] ?? APP_FUNCTION_CPUS_DEFAULT)))
|
||||
->setProject($project)
|
||||
->trigger()
|
||||
;
|
||||
|
||||
$queueForFunctions
|
||||
->setType(Func::TYPE_ASYNC_WRITE)
|
||||
->setExecution($execution)
|
||||
@@ -388,6 +391,26 @@ function router(App $utopia, Database $dbForConsole, callable $getProjectDB, Swo
|
||||
->setStatusCode($execution['responseStatusCode'] ?? 200)
|
||||
->send($body);
|
||||
|
||||
$fileSize = 0;
|
||||
$file = $request->getFiles('file');
|
||||
if (!empty($file)) {
|
||||
$fileSize = (\is_array($file['size']) && isset($file['size'][0])) ? $file['size'][0] : $file['size'];
|
||||
}
|
||||
|
||||
$queueForUsage
|
||||
->addMetric(METRIC_NETWORK_REQUESTS, 1)
|
||||
->addMetric(METRIC_NETWORK_INBOUND, $request->getSize() + $fileSize)
|
||||
->addMetric(METRIC_NETWORK_OUTBOUND, $response->getSize())
|
||||
->addMetric(METRIC_EXECUTIONS, 1)
|
||||
->addMetric(str_replace('{functionInternalId}', $function->getInternalId(), METRIC_FUNCTION_ID_EXECUTIONS), 1)
|
||||
->addMetric(METRIC_EXECUTIONS_COMPUTE, (int)($execution->getAttribute('duration') * 1000)) // per project
|
||||
->addMetric(str_replace('{functionInternalId}', $function->getInternalId(), METRIC_FUNCTION_ID_EXECUTIONS_COMPUTE), (int)($execution->getAttribute('duration') * 1000)) // per function
|
||||
->addMetric(METRIC_EXECUTIONS_MB_SECONDS, (int)(($spec['memory'] ?? APP_FUNCTION_MEMORY_DEFAULT) * $execution->getAttribute('duration', 0) * ($spec['cpus'] ?? APP_FUNCTION_CPUS_DEFAULT)))
|
||||
->addMetric(str_replace('{functionInternalId}', $function->getInternalId(), METRIC_FUNCTION_ID_EXECUTIONS_MB_SECONDS), (int)(($spec['memory'] ?? APP_FUNCTION_MEMORY_DEFAULT) * $execution->getAttribute('duration', 0) * ($spec['cpus'] ?? APP_FUNCTION_CPUS_DEFAULT)))
|
||||
->setProject($project)
|
||||
->trigger()
|
||||
;
|
||||
|
||||
return true;
|
||||
} elseif ($type === 'api') {
|
||||
$utopia->getRoute()?->label('error', '');
|
||||
@@ -433,7 +456,7 @@ App::init()
|
||||
->inject('response')
|
||||
->inject('console')
|
||||
->inject('project')
|
||||
->inject('dbForConsole')
|
||||
->inject('dbForPlatform')
|
||||
->inject('getProjectDB')
|
||||
->inject('locale')
|
||||
->inject('localeCodes')
|
||||
@@ -444,15 +467,16 @@ App::init()
|
||||
->inject('queueForCertificates')
|
||||
->inject('queueForFunctions')
|
||||
->inject('isResourceBlocked')
|
||||
->action(function (App $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Document $console, Document $project, Database $dbForConsole, callable $getProjectDB, Locale $locale, array $localeCodes, array $clients, array $geoRecord, Usage $queueForUsage, Event $queueForEvents, Certificate $queueForCertificates, Func $queueForFunctions, callable $isResourceBlocked) {
|
||||
->inject('previewHostname')
|
||||
->action(function (App $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Document $console, Document $project, Database $dbForPlatform, callable $getProjectDB, Locale $locale, array $localeCodes, array $clients, array $geoRecord, Usage $queueForUsage, Event $queueForEvents, Certificate $queueForCertificates, Func $queueForFunctions, callable $isResourceBlocked, string $previewHostname) {
|
||||
/*
|
||||
* Appwrite Router
|
||||
*/
|
||||
$host = $request->getHostname() ?? '';
|
||||
$mainDomain = System::getEnv('_APP_DOMAIN', '');
|
||||
// Only run Router when external domain
|
||||
if ($host !== $mainDomain) {
|
||||
if (router($utopia, $dbForConsole, $getProjectDB, $swooleRequest, $request, $response, $queueForEvents, $queueForUsage, $queueForFunctions, $geoRecord, $isResourceBlocked)) {
|
||||
if ($host !== $mainDomain || !empty($previewHostname)) {
|
||||
if (router($utopia, $dbForPlatform, $getProjectDB, $swooleRequest, $request, $response, $queueForEvents, $queueForUsage, $queueForFunctions, $geoRecord, $isResourceBlocked, $previewHostname)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -500,18 +524,31 @@ App::init()
|
||||
if (!empty($envDomain) && $envDomain !== 'localhost') {
|
||||
$mainDomain = $envDomain;
|
||||
} else {
|
||||
$domainDocument = $dbForConsole->getDocument('rules', md5($envDomain));
|
||||
// TODO: @christyjacob remove once we migrate the rules in 1.7.x
|
||||
if (System::getEnv('_APP_RULES_FORMAT') === 'md5') {
|
||||
$domainDocument = $dbForPlatform->getDocument('rules', md5($envDomain));
|
||||
} else {
|
||||
$domainDocument = $dbForPlatform->findOne('rules', [Query::orderAsc('$id')]);
|
||||
}
|
||||
$mainDomain = !$domainDocument->isEmpty() ? $domainDocument->getAttribute('domain') : $domain->get();
|
||||
}
|
||||
|
||||
if ($mainDomain !== $domain->get()) {
|
||||
Console::warning($domain->get() . ' is not a main domain. Skipping SSL certificate generation.');
|
||||
} else {
|
||||
$domainDocument = $dbForConsole->getDocument('rules', md5($domain->get()));
|
||||
// TODO: @christyjacob remove once we migrate the rules in 1.7.x
|
||||
if (System::getEnv('_APP_RULES_FORMAT') === 'md5') {
|
||||
$domainDocument = $dbForPlatform->getDocument('rules', md5($domain->get()));
|
||||
} else {
|
||||
$domainDocument = $dbForPlatform->findOne('rules', [
|
||||
Query::equal('domain', [$domain->get()])
|
||||
]);
|
||||
}
|
||||
|
||||
if ($domainDocument->isEmpty()) {
|
||||
$domainDocument = new Document([
|
||||
'$id' => md5($domain->get()),
|
||||
// TODO: @christyjacob remove once we migrate the rules in 1.7.x
|
||||
'$id' => System::getEnv('_APP_RULES_FORMAT') === 'md5' ? md5($domain->get()) : ID::unique(),
|
||||
'domain' => $domain->get(),
|
||||
'resourceType' => 'api',
|
||||
'status' => 'verifying',
|
||||
@@ -519,7 +556,7 @@ App::init()
|
||||
'projectInternalId' => 'console'
|
||||
]);
|
||||
|
||||
$domainDocument = $dbForConsole->createDocument('rules', $domainDocument);
|
||||
$domainDocument = $dbForPlatform->createDocument('rules', $domainDocument);
|
||||
|
||||
Console::info('Issuing a TLS certificate for the main domain (' . $domain->get() . ') in a few seconds...');
|
||||
|
||||
@@ -655,22 +692,23 @@ App::options()
|
||||
->inject('swooleRequest')
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
->inject('dbForConsole')
|
||||
->inject('dbForPlatform')
|
||||
->inject('getProjectDB')
|
||||
->inject('queueForEvents')
|
||||
->inject('queueForUsage')
|
||||
->inject('queueForFunctions')
|
||||
->inject('geoRecord')
|
||||
->inject('isResourceBlocked')
|
||||
->action(function (App $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Database $dbForConsole, callable $getProjectDB, Event $queueForEvents, Usage $queueForUsage, Func $queueForFunctions, array $geoRecord, callable $isResourceBlocked) {
|
||||
->inject('previewHostname')
|
||||
->action(function (App $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Database $dbForPlatform, callable $getProjectDB, Event $queueForEvents, Usage $queueForUsage, Func $queueForFunctions, array $geoRecord, callable $isResourceBlocked, string $previewHostname) {
|
||||
/*
|
||||
* Appwrite Router
|
||||
*/
|
||||
$host = $request->getHostname() ?? '';
|
||||
$mainDomain = System::getEnv('_APP_DOMAIN', '');
|
||||
// Only run Router when external domain
|
||||
if ($host !== $mainDomain) {
|
||||
if (router($utopia, $dbForConsole, $getProjectDB, $swooleRequest, $request, $response, $queueForEvents, $queueForUsage, $queueForFunctions, $geoRecord, $isResourceBlocked)) {
|
||||
if ($host !== $mainDomain || !empty($previewHostname)) {
|
||||
if (router($utopia, $dbForPlatform, $getProjectDB, $swooleRequest, $request, $response, $queueForEvents, $queueForUsage, $queueForFunctions, $geoRecord, $isResourceBlocked, $previewHostname)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -785,7 +823,7 @@ App::error()
|
||||
|
||||
$adapter = new Sentry($projectId, $key, $host);
|
||||
$logger = new Logger($adapter);
|
||||
$logger->setSample(0.04);
|
||||
$logger->setSample(0.01);
|
||||
$publish = true;
|
||||
} else {
|
||||
throw new \Exception('Invalid experimental logging provider');
|
||||
@@ -825,6 +863,8 @@ App::error()
|
||||
|
||||
if (isset($user) && !$user->isEmpty()) {
|
||||
$log->setUser(new User($user->getId()));
|
||||
} else {
|
||||
$log->setUser(new User('guest-' . hash('sha256', $request->getIP())));
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -835,7 +875,7 @@ App::error()
|
||||
}
|
||||
|
||||
$log->setNamespace("http");
|
||||
$log->setServer(\gethostname());
|
||||
$log->setServer(System::getEnv('_APP_LOGGING_SERVICE_IDENTIFIER', \gethostname()));
|
||||
$log->setVersion($version);
|
||||
$log->setType(Log::TYPE_ERROR);
|
||||
$log->setMessage($error->getMessage());
|
||||
@@ -856,6 +896,7 @@ App::error()
|
||||
|
||||
$action = $route->getLabel("sdk.namespace", "UNKNOWN_NAMESPACE") . '.' . $route->getLabel("sdk.method", "UNKNOWN_METHOD");
|
||||
$log->setAction($action);
|
||||
$log->addTag('service', $action);
|
||||
|
||||
$isProduction = System::getEnv('_APP_ENV', 'development') === 'production';
|
||||
$log->setEnvironment($isProduction ? Log::ENVIRONMENT_PRODUCTION : Log::ENVIRONMENT_STAGING);
|
||||
@@ -950,22 +991,23 @@ App::get('/robots.txt')
|
||||
->inject('swooleRequest')
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
->inject('dbForConsole')
|
||||
->inject('dbForPlatform')
|
||||
->inject('getProjectDB')
|
||||
->inject('queueForEvents')
|
||||
->inject('queueForUsage')
|
||||
->inject('queueForFunctions')
|
||||
->inject('geoRecord')
|
||||
->inject('isResourceBlocked')
|
||||
->action(function (App $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Database $dbForConsole, callable $getProjectDB, Event $queueForEvents, Usage $queueForUsage, Func $queueForFunctions, array $geoRecord, callable $isResourceBlocked) {
|
||||
->inject('previewHostname')
|
||||
->action(function (App $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Database $dbForPlatform, callable $getProjectDB, Event $queueForEvents, Usage $queueForUsage, Func $queueForFunctions, array $geoRecord, callable $isResourceBlocked, string $previewHostname) {
|
||||
$host = $request->getHostname() ?? '';
|
||||
$mainDomain = System::getEnv('_APP_DOMAIN', '');
|
||||
|
||||
if ($host === $mainDomain || $host === 'localhost') {
|
||||
if (($host === $mainDomain || $host === 'localhost') && empty($previewHostname)) {
|
||||
$template = new View(__DIR__ . '/../views/general/robots.phtml');
|
||||
$response->text($template->render(false));
|
||||
} else {
|
||||
router($utopia, $dbForConsole, $getProjectDB, $swooleRequest, $request, $response, $queueForEvents, $queueForUsage, $queueForFunctions, $geoRecord, $isResourceBlocked);
|
||||
router($utopia, $dbForPlatform, $getProjectDB, $swooleRequest, $request, $response, $queueForEvents, $queueForUsage, $queueForFunctions, $geoRecord, $isResourceBlocked, $previewHostname);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -977,22 +1019,23 @@ App::get('/humans.txt')
|
||||
->inject('swooleRequest')
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
->inject('dbForConsole')
|
||||
->inject('dbForPlatform')
|
||||
->inject('getProjectDB')
|
||||
->inject('queueForEvents')
|
||||
->inject('queueForUsage')
|
||||
->inject('queueForFunctions')
|
||||
->inject('geoRecord')
|
||||
->inject('isResourceBlocked')
|
||||
->action(function (App $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Database $dbForConsole, callable $getProjectDB, Event $queueForEvents, Usage $queueForUsage, Func $queueForFunctions, array $geoRecord, callable $isResourceBlocked) {
|
||||
->inject('previewHostname')
|
||||
->action(function (App $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Database $dbForPlatform, callable $getProjectDB, Event $queueForEvents, Usage $queueForUsage, Func $queueForFunctions, array $geoRecord, callable $isResourceBlocked, string $previewHostname) {
|
||||
$host = $request->getHostname() ?? '';
|
||||
$mainDomain = System::getEnv('_APP_DOMAIN', '');
|
||||
|
||||
if ($host === $mainDomain || $host === 'localhost') {
|
||||
if (($host === $mainDomain || $host === 'localhost') && empty($previewHostname)) {
|
||||
$template = new View(__DIR__ . '/../views/general/humans.phtml');
|
||||
$response->text($template->render(false));
|
||||
} else {
|
||||
router($utopia, $dbForConsole, $getProjectDB, $swooleRequest, $request, $response, $queueForEvents, $queueForUsage, $queueForFunctions, $geoRecord, $isResourceBlocked);
|
||||
router($utopia, $dbForPlatform, $getProjectDB, $swooleRequest, $request, $response, $queueForEvents, $queueForUsage, $queueForFunctions, $geoRecord, $isResourceBlocked, $previewHostname);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1056,9 +1099,9 @@ App::get('/v1/ping')
|
||||
->label('event', 'projects.[projectId].ping')
|
||||
->inject('response')
|
||||
->inject('project')
|
||||
->inject('dbForConsole')
|
||||
->inject('dbForPlatform')
|
||||
->inject('queueForEvents')
|
||||
->action(function (Response $response, Document $project, Database $dbForConsole, Event $queueForEvents) {
|
||||
->action(function (Response $response, Document $project, Database $dbForPlatform, Event $queueForEvents) {
|
||||
if ($project->isEmpty()) {
|
||||
throw new AppwriteException(AppwriteException::PROJECT_NOT_FOUND);
|
||||
}
|
||||
@@ -1070,8 +1113,8 @@ App::get('/v1/ping')
|
||||
->setAttribute('pingCount', $pingCount)
|
||||
->setAttribute('pingedAt', $pingedAt);
|
||||
|
||||
Authorization::skip(function () use ($dbForConsole, $project) {
|
||||
$dbForConsole->updateDocument('projects', $project->getId(), $project);
|
||||
Authorization::skip(function () use ($dbForPlatform, $project) {
|
||||
$dbForPlatform->updateDocument('projects', $project->getId(), $project);
|
||||
});
|
||||
|
||||
$queueForEvents
|
||||
|
||||
@@ -162,15 +162,15 @@ App::post('/v1/mock/api-key-unprefixed')
|
||||
->label('docs', false)
|
||||
->param('projectId', '', new UID(), 'Project ID.')
|
||||
->inject('response')
|
||||
->inject('dbForConsole')
|
||||
->action(function (string $projectId, Response $response, Database $dbForConsole) {
|
||||
->inject('dbForPlatform')
|
||||
->action(function (string $projectId, Response $response, Database $dbForPlatform) {
|
||||
$isDevelopment = System::getEnv('_APP_ENV', 'development') === 'development';
|
||||
|
||||
if (!$isDevelopment) {
|
||||
throw new Exception(Exception::GENERAL_NOT_IMPLEMENTED);
|
||||
}
|
||||
|
||||
$project = $dbForConsole->getDocument('projects', $projectId);
|
||||
$project = $dbForPlatform->getDocument('projects', $projectId);
|
||||
|
||||
if ($project->isEmpty()) {
|
||||
throw new Exception(Exception::PROJECT_NOT_FOUND);
|
||||
@@ -195,9 +195,9 @@ App::post('/v1/mock/api-key-unprefixed')
|
||||
'secret' => \bin2hex(\random_bytes(128)),
|
||||
]);
|
||||
|
||||
$key = $dbForConsole->createDocument('keys', $key);
|
||||
$key = $dbForPlatform->createDocument('keys', $key);
|
||||
|
||||
$dbForConsole->purgeCachedDocument('projects', $project->getId());
|
||||
$dbForPlatform->purgeCachedDocument('projects', $project->getId());
|
||||
|
||||
$response
|
||||
->setStatusCode(Response::STATUS_CODE_CREATED)
|
||||
@@ -214,15 +214,15 @@ App::get('/v1/mock/github/callback')
|
||||
->inject('gitHub')
|
||||
->inject('project')
|
||||
->inject('response')
|
||||
->inject('dbForConsole')
|
||||
->action(function (string $providerInstallationId, string $projectId, GitHub $github, Document $project, Response $response, Database $dbForConsole) {
|
||||
->inject('dbForPlatform')
|
||||
->action(function (string $providerInstallationId, string $projectId, GitHub $github, Document $project, Response $response, Database $dbForPlatform) {
|
||||
$isDevelopment = System::getEnv('_APP_ENV', 'development') === 'development';
|
||||
|
||||
if (!$isDevelopment) {
|
||||
throw new Exception(Exception::GENERAL_NOT_IMPLEMENTED);
|
||||
}
|
||||
|
||||
$project = $dbForConsole->getDocument('projects', $projectId);
|
||||
$project = $dbForPlatform->getDocument('projects', $projectId);
|
||||
|
||||
if ($project->isEmpty()) {
|
||||
$error = 'Project with the ID from state could not be found.';
|
||||
@@ -256,7 +256,7 @@ App::get('/v1/mock/github/callback')
|
||||
'personal' => false
|
||||
]);
|
||||
|
||||
$installation = $dbForConsole->createDocument('installations', $installation);
|
||||
$installation = $dbForPlatform->createDocument('installations', $installation);
|
||||
}
|
||||
|
||||
$response->json([
|
||||
|
||||
@@ -19,7 +19,6 @@ use Appwrite\Extend\Exception as AppwriteException;
|
||||
use Appwrite\Utopia\Request;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Utopia\Abuse\Abuse;
|
||||
use Utopia\Abuse\Adapters\Database\TimeLimit;
|
||||
use Utopia\App;
|
||||
use Utopia\Cache\Adapter\Filesystem;
|
||||
use Utopia\Cache\Cache;
|
||||
@@ -184,14 +183,15 @@ App::init()
|
||||
->groups(['api'])
|
||||
->inject('utopia')
|
||||
->inject('request')
|
||||
->inject('dbForConsole')
|
||||
->inject('dbForPlatform')
|
||||
->inject('dbForProject')
|
||||
->inject('project')
|
||||
->inject('user')
|
||||
->inject('session')
|
||||
->inject('servers')
|
||||
->inject('mode')
|
||||
->inject('team')
|
||||
->action(function (App $utopia, Request $request, Database $dbForConsole, Document $project, Document $user, ?Document $session, array $servers, string $mode, Document $team) {
|
||||
->action(function (App $utopia, Request $request, Database $dbForPlatform, Database $dbForProject, Document $project, Document $user, ?Document $session, array $servers, string $mode, Document $team) {
|
||||
$route = $utopia->getRoute();
|
||||
|
||||
if ($project->isEmpty()) {
|
||||
@@ -283,8 +283,8 @@ App::init()
|
||||
$accessedAt = $key->getAttribute('accessedAt', '');
|
||||
if (DateTime::formatTz(DateTime::addSeconds(new \DateTime(), -APP_KEY_ACCESS)) > $accessedAt) {
|
||||
$key->setAttribute('accessedAt', DateTime::now());
|
||||
$dbForConsole->updateDocument('keys', $key->getId(), $key);
|
||||
$dbForConsole->purgeCachedDocument('projects', $project->getId());
|
||||
$dbForPlatform->updateDocument('keys', $key->getId(), $key);
|
||||
$dbForPlatform->purgeCachedDocument('projects', $project->getId());
|
||||
}
|
||||
|
||||
$sdkValidator = new WhiteList($servers, true);
|
||||
@@ -297,8 +297,8 @@ App::init()
|
||||
|
||||
/** Update access time as well */
|
||||
$key->setAttribute('accessedAt', Datetime::now());
|
||||
$dbForConsole->updateDocument('keys', $key->getId(), $key);
|
||||
$dbForConsole->purgeCachedDocument('projects', $project->getId());
|
||||
$dbForPlatform->updateDocument('keys', $key->getId(), $key);
|
||||
$dbForPlatform->purgeCachedDocument('projects', $project->getId());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -335,6 +335,33 @@ App::init()
|
||||
Authorization::setRole($authRole);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update project last activity
|
||||
*/
|
||||
if (!$project->isEmpty() && $project->getId() !== 'console') {
|
||||
$accessedAt = $project->getAttribute('accessedAt', '');
|
||||
if (DateTime::formatTz(DateTime::addSeconds(new \DateTime(), -APP_PROJECT_ACCESS)) > $accessedAt) {
|
||||
$project->setAttribute('accessedAt', DateTime::now());
|
||||
Authorization::skip(fn () => $dbForPlatform->updateDocument('projects', $project->getId(), $project));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update user last activity
|
||||
*/
|
||||
if (!empty($user->getId())) {
|
||||
$accessedAt = $user->getAttribute('accessedAt', '');
|
||||
if (DateTime::formatTz(DateTime::addSeconds(new \DateTime(), -APP_USER_ACCESS)) > $accessedAt) {
|
||||
$user->setAttribute('accessedAt', DateTime::now());
|
||||
|
||||
if (APP_MODE_ADMIN !== $mode) {
|
||||
$dbForProject->updateDocument('users', $user->getId(), $user);
|
||||
} else {
|
||||
$dbForPlatform->updateDocument('users', $user->getId(), $user);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Do not allow access to disabled services */
|
||||
$service = $route->getLabel('sdk.namespace', '');
|
||||
if (!empty($service)) {
|
||||
@@ -392,8 +419,9 @@ App::init()
|
||||
->inject('queueForBuilds')
|
||||
->inject('queueForUsage')
|
||||
->inject('dbForProject')
|
||||
->inject('timelimit')
|
||||
->inject('mode')
|
||||
->action(function (App $utopia, Request $request, Response $response, Document $project, Document $user, Connection $queue, Event $queueForEvents, Messaging $queueForMessaging, Audit $queueForAudits, Delete $queueForDeletes, EventDatabase $queueForDatabase, Build $queueForBuilds, Usage $queueForUsage, Database $dbForProject, string $mode) use ($usageDatabaseListener, $eventDatabaseListener) {
|
||||
->action(function (App $utopia, Request $request, Response $response, Document $project, Document $user, Connection $queue, Event $queueForEvents, Messaging $queueForMessaging, Audit $queueForAudits, Delete $queueForDeletes, EventDatabase $queueForDatabase, Build $queueForBuilds, Usage $queueForUsage, Database $dbForProject, callable $timelimit, string $mode) use ($usageDatabaseListener, $eventDatabaseListener) {
|
||||
|
||||
$route = $utopia->getRoute();
|
||||
|
||||
@@ -416,7 +444,7 @@ App::init()
|
||||
foreach ($abuseKeyLabel as $abuseKey) {
|
||||
$start = $request->getContentRangeStart();
|
||||
$end = $request->getContentRangeEnd();
|
||||
$timeLimit = new TimeLimit($abuseKey, $route->getLabel('abuse-limit', 0), $route->getLabel('abuse-time', 3600), $dbForProject);
|
||||
$timeLimit = $timelimit($abuseKey, $route->getLabel('abuse-limit', 0), $route->getLabel('abuse-time', 3600));
|
||||
$timeLimit
|
||||
->setParam('{projectId}', $project->getId())
|
||||
->setParam('{userId}', $user->getId())
|
||||
@@ -444,7 +472,7 @@ App::init()
|
||||
$abuse = new Abuse($timeLimit);
|
||||
$remaining = $timeLimit->remaining();
|
||||
$limit = $timeLimit->limit();
|
||||
$time = (new \DateTime($timeLimit->time()))->getTimestamp() + $route->getLabel('abuse-time', 3600);
|
||||
$time = $timeLimit->time() + $route->getLabel('abuse-time', 3600);
|
||||
|
||||
if ($limit && ($remaining < $closestLimit || is_null($closestLimit))) {
|
||||
$closestLimit = $remaining;
|
||||
@@ -641,9 +669,7 @@ App::shutdown()
|
||||
->inject('queueForWebhooks')
|
||||
->inject('queueForRealtime')
|
||||
->inject('dbForProject')
|
||||
->inject('mode')
|
||||
->inject('dbForConsole')
|
||||
->action(function (App $utopia, Request $request, Response $response, Document $project, Document $user, Event $queueForEvents, Audit $queueForAudits, Usage $queueForUsage, Delete $queueForDeletes, EventDatabase $queueForDatabase, Build $queueForBuilds, Messaging $queueForMessaging, Func $queueForFunctions, Event $queueForWebhooks, Realtime $queueForRealtime, Database $dbForProject, string $mode, Database $dbForConsole) use ($parseLabel) {
|
||||
->action(function (App $utopia, Request $request, Response $response, Document $project, Document $user, Event $queueForEvents, Audit $queueForAudits, Usage $queueForUsage, Delete $queueForDeletes, EventDatabase $queueForDatabase, Build $queueForBuilds, Messaging $queueForMessaging, Func $queueForFunctions, Event $queueForWebhooks, Realtime $queueForRealtime, Database $dbForProject) use ($parseLabel) {
|
||||
|
||||
$responsePayload = $response->getPayload();
|
||||
|
||||
@@ -763,8 +789,6 @@ App::shutdown()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
if ($project->getId() !== 'console') {
|
||||
if (!Auth::isPrivilegedUser(Authorization::getRoles())) {
|
||||
$fileSize = 0;
|
||||
@@ -783,33 +807,6 @@ App::shutdown()
|
||||
->setProject($project)
|
||||
->trigger();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update project last activity
|
||||
*/
|
||||
if (!$project->isEmpty() && $project->getId() !== 'console') {
|
||||
$accessedAt = $project->getAttribute('accessedAt', '');
|
||||
if (DateTime::formatTz(DateTime::addSeconds(new \DateTime(), -APP_PROJECT_ACCESS)) > $accessedAt) {
|
||||
$project->setAttribute('accessedAt', DateTime::now());
|
||||
Authorization::skip(fn () => $dbForConsole->updateDocument('projects', $project->getId(), $project));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update user last activity
|
||||
*/
|
||||
if (!$user->isEmpty()) {
|
||||
$accessedAt = $user->getAttribute('accessedAt', '');
|
||||
if (DateTime::formatTz(DateTime::addSeconds(new \DateTime(), -APP_USER_ACCESS)) > $accessedAt) {
|
||||
$user->setAttribute('accessedAt', DateTime::now());
|
||||
|
||||
if (APP_MODE_ADMIN !== $mode) {
|
||||
$dbForProject->updateDocument('users', $user->getId(), $user);
|
||||
} else {
|
||||
$dbForConsole->updateDocument('users', $user->getId(), $user);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
App::init()
|
||||
|
||||
+222
-73
@@ -9,16 +9,19 @@ use Swoole\Http\Request as SwooleRequest;
|
||||
use Swoole\Http\Response as SwooleResponse;
|
||||
use Swoole\Http\Server;
|
||||
use Swoole\Process;
|
||||
use Utopia\Abuse\Adapters\Database\TimeLimit;
|
||||
use Swoole\Table;
|
||||
use Utopia\App;
|
||||
use Utopia\Audit\Audit;
|
||||
use Utopia\CLI\Console;
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\DateTime;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Exception\Duplicate;
|
||||
use Utopia\Database\Helpers\ID;
|
||||
use Utopia\Database\Helpers\Permission;
|
||||
use Utopia\Database\Helpers\Role;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Logger\Log;
|
||||
use Utopia\Logger\Log\User;
|
||||
@@ -26,6 +29,12 @@ use Utopia\Pools\Group;
|
||||
use Utopia\Swoole\Files;
|
||||
use Utopia\System\System;
|
||||
|
||||
const DOMAIN_SYNC_TIMER = 30; // 30 seconds
|
||||
|
||||
$domains = new Table(1_000_000); // 1 million rows
|
||||
$domains->column('value', Table::TYPE_INT, 1);
|
||||
$domains->create();
|
||||
|
||||
$http = new Server(
|
||||
host: "0.0.0.0",
|
||||
port: System::getEnv('PORT', 80),
|
||||
@@ -33,15 +42,17 @@ $http = new Server(
|
||||
);
|
||||
|
||||
$payloadSize = 12 * (1024 * 1024); // 12MB - adding slight buffer for headers and other data that might be sent with the payload - update later with valid testing
|
||||
$workerNumber = swoole_cpu_num() * intval(System::getEnv('_APP_WORKER_PER_CORE', 6));
|
||||
$totalWorkers = intval(System::getEnv('_APP_CPU_NUM', swoole_cpu_num())) * intval(System::getEnv('_APP_WORKER_PER_CORE', 6));
|
||||
|
||||
$http
|
||||
->set([
|
||||
'worker_num' => $workerNumber,
|
||||
'worker_num' => $totalWorkers,
|
||||
'dispatch_func' => 'dispatch',
|
||||
'open_http2_protocol' => true,
|
||||
'http_compression' => false,
|
||||
'package_max_length' => $payloadSize,
|
||||
'buffer_output_size' => $payloadSize,
|
||||
'task_worker_num' => 1, // required for the task to fetch domains background
|
||||
]);
|
||||
|
||||
$http->on(Constant::EVENT_WORKER_START, function ($server, $workerId) {
|
||||
@@ -56,6 +67,93 @@ $http->on(Constant::EVENT_AFTER_RELOAD, function ($server, $workerId) {
|
||||
Console::success('Reload completed...');
|
||||
});
|
||||
|
||||
/**
|
||||
* Assigns HTTP requests to worker threads by analyzing its payload/content.
|
||||
*
|
||||
* Routes requests as 'safe' or 'risky' based on specific content patterns (like POST actions or certain domains)
|
||||
* to optimize load distribution between the workers. Utilizes `$safeThreadsPercent` to manage risk by assigning
|
||||
* riskier tasks to a dedicated worker subset. Prefers idle workers, with fallback to random selection if necessary.
|
||||
* doc: https://openswoole.com/docs/modules/swoole-server/configuration#dispatch_func
|
||||
*
|
||||
* @param Server $server Swoole server instance.
|
||||
* @param int $fd client ID
|
||||
* @param int $type the type of data and its current state
|
||||
* @param string|null $data Request content for categorization.
|
||||
* @global int $totalThreads Total number of workers.
|
||||
* @return int Chosen worker ID for the request.
|
||||
*/
|
||||
function dispatch(Server $server, int $fd, int $type, $data = null): int
|
||||
{
|
||||
global $totalWorkers, $domains;
|
||||
|
||||
// If data is not set we can send request to any worker
|
||||
// first we try to pick idle worker, if not we randomly pick a worker
|
||||
if ($data === null) {
|
||||
for ($i = 0; $i < $totalWorkers; $i++) {
|
||||
if ($server->getWorkerStatus($i) === SWOOLE_WORKER_IDLE) {
|
||||
return $i;
|
||||
}
|
||||
}
|
||||
return rand(0, $totalWorkers - 1);
|
||||
}
|
||||
|
||||
$riskyWorkersPercent = intval(System::getEnv('_APP_RISKY_WORKERS_PERCENT', 80)) / 100; // Decimal form 0 to 1
|
||||
|
||||
// Each worker has numeric ID, starting from 0 and incrementing
|
||||
// From 0 to riskyWorkers, we consider safe workers
|
||||
// From riskyWorkers to totalWorkers, we consider risky workers
|
||||
$riskyWorkers = (int) floor($totalWorkers * $riskyWorkersPercent); // Absolute amount of risky workers
|
||||
|
||||
$domain = '';
|
||||
// max up to 3 as first line has request details and second line has host
|
||||
$lines = explode("\n", $data, 3);
|
||||
$request = $lines[0];
|
||||
if (count($lines) > 1) {
|
||||
$domain = trim(explode('Host: ', $lines[1])[1]);
|
||||
}
|
||||
|
||||
// Sync executions are considered risky
|
||||
$risky = false;
|
||||
if (str_starts_with($request, 'POST') && str_contains($request, '/executions')) {
|
||||
$risky = true;
|
||||
} elseif (str_ends_with($domain, System::getEnv('_APP_DOMAIN_FUNCTIONS'))) {
|
||||
$risky = true;
|
||||
} elseif ($domains->get(md5($domain), 'value') === 1) {
|
||||
// executions request coming from custom domain
|
||||
$risky = true;
|
||||
}
|
||||
|
||||
if ($risky) {
|
||||
// If risky request, only consider risky workers
|
||||
for ($j = $riskyWorkers; $j < $totalWorkers; $j++) {
|
||||
/** Reference https://openswoole.com/docs/modules/swoole-server-getWorkerStatus#description */
|
||||
if ($server->getWorkerStatus($j) === SWOOLE_WORKER_IDLE) {
|
||||
// If idle worker found, give to him
|
||||
return $j;
|
||||
}
|
||||
}
|
||||
|
||||
// If no idle workers, give to random risky worker
|
||||
$worker = rand($riskyWorkers, $totalWorkers - 1);
|
||||
Console::warning("swoole_dispatch: Risky branch: did not find a idle worker, picking random worker {$worker}");
|
||||
return $worker;
|
||||
}
|
||||
|
||||
// If safe request, give to any idle worker
|
||||
// Its fine to pick risky worker here, because it's idle. Idle is never actually risky
|
||||
for ($i = 0; $i < $totalWorkers; $i++) {
|
||||
if ($server->getWorkerStatus($i) === SWOOLE_WORKER_IDLE) {
|
||||
return $i;
|
||||
}
|
||||
}
|
||||
|
||||
// If no idle worker found, give to random safe worker
|
||||
// We avoid risky workers here, as it could be in work - not idle. Thats exactly when they are risky.
|
||||
$worker = rand(0, $riskyWorkers - 1);
|
||||
Console::warning("swoole_dispatch: Non-risky branch: did not find a idle worker, picking random worker {$worker}");
|
||||
return $worker;
|
||||
}
|
||||
|
||||
include __DIR__ . '/controllers/general.php';
|
||||
|
||||
$http->on(Constant::EVENT_START, function (Server $http) use ($payloadSize, $register) {
|
||||
@@ -74,8 +172,8 @@ $http->on(Constant::EVENT_START, function (Server $http) use ($payloadSize, $reg
|
||||
do {
|
||||
try {
|
||||
$attempts++;
|
||||
$dbForConsole = $app->getResource('dbForConsole');
|
||||
/** @var Utopia\Database\Database $dbForConsole */
|
||||
$dbForPlatform = $app->getResource('dbForPlatform');
|
||||
/** @var Utopia\Database\Database $dbForPlatform */
|
||||
break; // leave the do-while if successful
|
||||
} catch (\Throwable $e) {
|
||||
Console::warning("Database not ready. Retrying connection ({$attempts})...");
|
||||
@@ -89,22 +187,17 @@ $http->on(Constant::EVENT_START, function (Server $http) use ($payloadSize, $reg
|
||||
Console::success('[Setup] - Server database init started...');
|
||||
|
||||
try {
|
||||
Console::success('[Setup] - Creating database: appwrite...');
|
||||
$dbForConsole->create();
|
||||
} catch (\Throwable $e) {
|
||||
Console::success('[Setup] - Creating console database...');
|
||||
$dbForPlatform->create();
|
||||
} catch (Duplicate) {
|
||||
Console::success('[Setup] - Skip: metadata table already exists');
|
||||
}
|
||||
|
||||
if ($dbForConsole->getCollection(Audit::COLLECTION)->isEmpty()) {
|
||||
$audit = new Audit($dbForConsole);
|
||||
if ($dbForPlatform->getCollection(Audit::COLLECTION)->isEmpty()) {
|
||||
$audit = new Audit($dbForPlatform);
|
||||
$audit->setup();
|
||||
}
|
||||
|
||||
if ($dbForConsole->getCollection(TimeLimit::COLLECTION)->isEmpty()) {
|
||||
$adapter = new TimeLimit("", 0, 1, $dbForConsole);
|
||||
$adapter->setup();
|
||||
}
|
||||
|
||||
/** @var array $collections */
|
||||
$collections = Config::getParam('collections', []);
|
||||
$consoleCollections = $collections['console'];
|
||||
@@ -112,45 +205,21 @@ $http->on(Constant::EVENT_START, function (Server $http) use ($payloadSize, $reg
|
||||
if (($collection['$collection'] ?? '') !== Database::METADATA) {
|
||||
continue;
|
||||
}
|
||||
if (!$dbForConsole->getCollection($key)->isEmpty()) {
|
||||
if (!$dbForPlatform->getCollection($key)->isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Console::success('[Setup] - Creating collection: ' . $collection['$id'] . '...');
|
||||
Console::success('[Setup] - Creating console collection: ' . $collection['$id'] . '...');
|
||||
|
||||
$attributes = [];
|
||||
$indexes = [];
|
||||
$attributes = \array_map(fn ($attribute) => new Document($attribute), $collection['attributes']);
|
||||
$indexes = \array_map(fn (array $index) => new Document($index), $collection['indexes']);
|
||||
|
||||
foreach ($collection['attributes'] as $attribute) {
|
||||
$attributes[] = new Document([
|
||||
'$id' => ID::custom($attribute['$id']),
|
||||
'type' => $attribute['type'],
|
||||
'size' => $attribute['size'],
|
||||
'required' => $attribute['required'],
|
||||
'signed' => $attribute['signed'],
|
||||
'array' => $attribute['array'],
|
||||
'filters' => $attribute['filters'],
|
||||
'default' => $attribute['default'] ?? null,
|
||||
'format' => $attribute['format'] ?? ''
|
||||
]);
|
||||
}
|
||||
|
||||
foreach ($collection['indexes'] as $index) {
|
||||
$indexes[] = new Document([
|
||||
'$id' => ID::custom($index['$id']),
|
||||
'type' => $index['type'],
|
||||
'attributes' => $index['attributes'],
|
||||
'lengths' => $index['lengths'],
|
||||
'orders' => $index['orders'],
|
||||
]);
|
||||
}
|
||||
|
||||
$dbForConsole->createCollection($key, $attributes, $indexes);
|
||||
$dbForPlatform->createCollection($key, $attributes, $indexes);
|
||||
}
|
||||
|
||||
if ($dbForConsole->getDocument('buckets', 'default')->isEmpty() && !$dbForConsole->exists($dbForConsole->getDatabase(), 'bucket_1')) {
|
||||
if ($dbForPlatform->getDocument('buckets', 'default')->isEmpty() && !$dbForPlatform->exists($dbForPlatform->getDatabase(), 'bucket_1')) {
|
||||
Console::success('[Setup] - Creating default bucket...');
|
||||
$dbForConsole->createDocument('buckets', new Document([
|
||||
$dbForPlatform->createDocument('buckets', new Document([
|
||||
'$id' => ID::custom('default'),
|
||||
'$collection' => ID::custom('buckets'),
|
||||
'name' => 'Default',
|
||||
@@ -170,7 +239,7 @@ $http->on(Constant::EVENT_START, function (Server $http) use ($payloadSize, $reg
|
||||
'search' => 'buckets Default',
|
||||
]));
|
||||
|
||||
$bucket = $dbForConsole->getDocument('buckets', 'default');
|
||||
$bucket = $dbForPlatform->getDocument('buckets', 'default');
|
||||
|
||||
Console::success('[Setup] - Creating files collection for default bucket...');
|
||||
$files = $collections['buckets']['files'] ?? [];
|
||||
@@ -178,34 +247,53 @@ $http->on(Constant::EVENT_START, function (Server $http) use ($payloadSize, $reg
|
||||
throw new Exception('Files collection is not configured.');
|
||||
}
|
||||
|
||||
$attributes = [];
|
||||
$indexes = [];
|
||||
$attributes = \array_map(fn ($attribute) => new Document($attribute), $files['attributes']);
|
||||
$indexes = \array_map(fn (array $index) => new Document($index), $files['indexes']);
|
||||
|
||||
foreach ($files['attributes'] as $attribute) {
|
||||
$attributes[] = new Document([
|
||||
'$id' => ID::custom($attribute['$id']),
|
||||
'type' => $attribute['type'],
|
||||
'size' => $attribute['size'],
|
||||
'required' => $attribute['required'],
|
||||
'signed' => $attribute['signed'],
|
||||
'array' => $attribute['array'],
|
||||
'filters' => $attribute['filters'],
|
||||
'default' => $attribute['default'] ?? null,
|
||||
'format' => $attribute['format'] ?? ''
|
||||
]);
|
||||
$dbForPlatform->createCollection('bucket_' . $bucket->getInternalId(), $attributes, $indexes);
|
||||
}
|
||||
|
||||
$projectCollections = $collections['projects'];
|
||||
$sharedTables = \explode(',', System::getEnv('_APP_DATABASE_SHARED_TABLES', ''));
|
||||
$sharedTablesV1 = \explode(',', System::getEnv('_APP_DATABASE_SHARED_TABLES_V1', ''));
|
||||
$sharedTablesV2 = \array_diff($sharedTables, $sharedTablesV1);
|
||||
|
||||
$cache = $app->getResource('cache');
|
||||
|
||||
foreach ($sharedTablesV2 as $hostname) {
|
||||
$adapter = $pools
|
||||
->get($hostname)
|
||||
->pop()
|
||||
->getResource();
|
||||
|
||||
$dbForProject = (new Database($adapter, $cache))
|
||||
->setDatabase('appwrite')
|
||||
->setSharedTables(true)
|
||||
->setTenant(null)
|
||||
->setNamespace(System::getEnv('_APP_DATABASE_SHARED_NAMESPACE', ''));
|
||||
|
||||
try {
|
||||
Console::success('[Setup] - Creating project database: ' . $hostname . '...');
|
||||
$dbForProject->create();
|
||||
} catch (Duplicate) {
|
||||
Console::success('[Setup] - Skip: metadata table already exists');
|
||||
}
|
||||
|
||||
foreach ($files['indexes'] as $index) {
|
||||
$indexes[] = new Document([
|
||||
'$id' => ID::custom($index['$id']),
|
||||
'type' => $index['type'],
|
||||
'attributes' => $index['attributes'],
|
||||
'lengths' => $index['lengths'],
|
||||
'orders' => $index['orders'],
|
||||
]);
|
||||
}
|
||||
foreach ($projectCollections as $key => $collection) {
|
||||
if (($collection['$collection'] ?? '') !== Database::METADATA) {
|
||||
continue;
|
||||
}
|
||||
if (!$dbForProject->getCollection($key)->isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$dbForConsole->createCollection('bucket_' . $bucket->getInternalId(), $attributes, $indexes);
|
||||
$attributes = \array_map(fn ($attribute) => new Document($attribute), $collection['attributes']);
|
||||
$indexes = \array_map(fn (array $index) => new Document($index), $collection['indexes']);
|
||||
|
||||
Console::success('[Setup] - Creating project collection: ' . $collection['$id'] . '...');
|
||||
|
||||
$dbForProject->createCollection($key, $attributes, $indexes);
|
||||
}
|
||||
}
|
||||
|
||||
$pools->reclaim();
|
||||
@@ -216,6 +304,9 @@ $http->on(Constant::EVENT_START, function (Server $http) use ($payloadSize, $reg
|
||||
Console::success('Server started successfully (max payload is ' . number_format($payloadSize) . ' bytes)');
|
||||
Console::info("Master pid {$http->master_pid}, manager pid {$http->manager_pid}");
|
||||
|
||||
// Start the task that starts fetching custom domains
|
||||
$http->task([], 0);
|
||||
|
||||
// listen ctrl + c
|
||||
Process::signal(2, function () use ($http) {
|
||||
Console::log('Stop by Ctrl+C');
|
||||
@@ -223,7 +314,7 @@ $http->on(Constant::EVENT_START, function (Server $http) use ($payloadSize, $reg
|
||||
});
|
||||
});
|
||||
|
||||
$http->on('request', function (SwooleRequest $swooleRequest, SwooleResponse $swooleResponse) use ($register) {
|
||||
$http->on(Constant::EVENT_REQUEST, function (SwooleRequest $swooleRequest, SwooleResponse $swooleResponse) use ($register) {
|
||||
App::setResource('swooleRequest', fn () => $swooleRequest);
|
||||
App::setResource('swooleResponse', fn () => $swooleResponse);
|
||||
|
||||
@@ -272,10 +363,12 @@ $http->on('request', function (SwooleRequest $swooleRequest, SwooleResponse $swo
|
||||
|
||||
if (isset($user) && !$user->isEmpty()) {
|
||||
$log->setUser(new User($user->getId()));
|
||||
} else {
|
||||
$log->setUser(new User('guest-' . hash('sha256', $request->getIP())));
|
||||
}
|
||||
|
||||
$log->setNamespace("http");
|
||||
$log->setServer(\gethostname());
|
||||
$log->setServer(System::getEnv('_APP_LOGGING_SERVICE_IDENTIFIER', \gethostname()));
|
||||
$log->setVersion($version);
|
||||
$log->setType(Log::TYPE_ERROR);
|
||||
$log->setMessage($th->getMessage());
|
||||
@@ -295,6 +388,7 @@ $http->on('request', function (SwooleRequest $swooleRequest, SwooleResponse $swo
|
||||
|
||||
$action = $route->getLabel("sdk.namespace", "UNKNOWN_NAMESPACE") . '.' . $route->getLabel("sdk.method", "UNKNOWN_METHOD");
|
||||
$log->setAction($action);
|
||||
$log->addTag('service', $action);
|
||||
|
||||
$isProduction = System::getEnv('_APP_ENV', 'development') === 'production';
|
||||
$log->setEnvironment($isProduction ? Log::ENVIRONMENT_PRODUCTION : Log::ENVIRONMENT_STAGING);
|
||||
@@ -333,4 +427,59 @@ $http->on('request', function (SwooleRequest $swooleRequest, SwooleResponse $swo
|
||||
}
|
||||
});
|
||||
|
||||
// Fetch domains every `DOMAIN_SYNC_TIMER` seconds and update in the memory
|
||||
$http->on('Task', function () use ($register, $domains) {
|
||||
$lastSyncUpdate = null;
|
||||
$pools = $register->get('pools');
|
||||
App::setResource('pools', fn () => $pools);
|
||||
$app = new App('UTC');
|
||||
|
||||
/** @var Utopia\Database\Database $dbForPlatform */
|
||||
$dbForPlatform = $app->getResource('dbForPlatform');
|
||||
|
||||
Console::loop(function () use ($dbForPlatform, $domains, &$lastSyncUpdate) {
|
||||
try {
|
||||
$time = DateTime::now();
|
||||
$limit = 1000;
|
||||
$sum = $limit;
|
||||
$latestDocument = null;
|
||||
|
||||
while ($sum === $limit) {
|
||||
$queries = [Query::limit($limit)];
|
||||
if ($latestDocument !== null) {
|
||||
$queries[] = Query::cursorAfter($latestDocument);
|
||||
}
|
||||
if ($lastSyncUpdate != null) {
|
||||
$queries[] = Query::greaterThanEqual('$updatedAt', $lastSyncUpdate);
|
||||
}
|
||||
$queries[] = Query::equal('resourceType', ['function']);
|
||||
$results = [];
|
||||
try {
|
||||
$results = Authorization::skip(fn () => $dbForPlatform->find('rules', $queries));
|
||||
} catch (Throwable $th) {
|
||||
Console::error($th->getMessage());
|
||||
}
|
||||
|
||||
$sum = count($results);
|
||||
foreach ($results as $document) {
|
||||
$domain = $document->getAttribute('domain');
|
||||
if (str_ends_with($domain, System::getEnv('_APP_DOMAIN_FUNCTIONS'))) {
|
||||
continue;
|
||||
}
|
||||
$domains->set(md5($domain), ['value' => 1]);
|
||||
}
|
||||
$latestDocument = !empty(array_key_last($results)) ? $results[array_key_last($results)] : null;
|
||||
}
|
||||
$lastSyncUpdate = $time;
|
||||
if ($sum > 0) {
|
||||
Console::log("Sync domains tick: {$sum} domains were updated");
|
||||
}
|
||||
} catch (Throwable $th) {
|
||||
Console::error($th->getMessage());
|
||||
}
|
||||
}, DOMAIN_SYNC_TIMER, 0, function ($error) {
|
||||
Console::error($error);
|
||||
});
|
||||
});
|
||||
|
||||
$http->start();
|
||||
|
||||
+82
-41
@@ -49,6 +49,7 @@ use Appwrite\Utopia\Request;
|
||||
use MaxMind\Db\Reader;
|
||||
use PHPMailer\PHPMailer\PHPMailer;
|
||||
use Swoole\Database\PDOProxy;
|
||||
use Utopia\Abuse\Adapters\TimeLimit\Redis as TimeLimitRedis;
|
||||
use Utopia\App;
|
||||
use Utopia\Cache\Adapter\Redis as RedisCache;
|
||||
use Utopia\Cache\Adapter\Sharding;
|
||||
@@ -124,7 +125,7 @@ const APP_USER_ACCESS = 24 * 60 * 60; // 24 hours
|
||||
const APP_PROJECT_ACCESS = 24 * 60 * 60; // 24 hours
|
||||
const APP_CACHE_UPDATE = 24 * 60 * 60; // 24 hours
|
||||
const APP_CACHE_BUSTER = 4318;
|
||||
const APP_VERSION_STABLE = '1.6.0';
|
||||
const APP_VERSION_STABLE = '1.6.1';
|
||||
const APP_DATABASE_ATTRIBUTE_EMAIL = 'email';
|
||||
const APP_DATABASE_ATTRIBUTE_ENUM = 'enum';
|
||||
const APP_DATABASE_ATTRIBUTE_IP = 'ip';
|
||||
@@ -232,6 +233,11 @@ const API_KEY_DYNAMIC = 'dynamic';
|
||||
// Usage metrics
|
||||
const METRIC_TEAMS = 'teams';
|
||||
const METRIC_USERS = 'users';
|
||||
const METRIC_WEBHOOKS_SENT = 'webhooks.events.sent';
|
||||
const METRIC_WEBHOOKS_FAILED = 'webhooks.events.failed';
|
||||
const METRIC_WEBHOOK_ID_SENT = '{webhookInternalId}.webhooks.events.sent';
|
||||
const METRIC_WEBHOOK_ID_FAILED = '{webhookInternalId}.webhooks.events.failed';
|
||||
|
||||
|
||||
const METRIC_AUTH_METHOD_PHONE = 'auth.method.phone';
|
||||
const METRIC_AUTH_METHOD_PHONE_COUNTRY_CODE = METRIC_AUTH_METHOD_PHONE . '.{countryCode}';
|
||||
@@ -254,9 +260,15 @@ const METRIC_DOCUMENTS = 'documents';
|
||||
const METRIC_DATABASE_ID_DOCUMENTS = '{databaseInternalId}.documents';
|
||||
const METRIC_DATABASE_ID_COLLECTION_ID_DOCUMENTS = '{databaseInternalId}.{collectionInternalId}.documents';
|
||||
const METRIC_DATABASE_ID_COLLECTION_ID_STORAGE = '{databaseInternalId}.{collectionInternalId}.databases.storage';
|
||||
const METRIC_DATABASES_OPERATIONS_READS = 'databases.operations.reads';
|
||||
const METRIC_DATABASE_ID_OPERATIONS_READS = '{databaseInternalId}.databases.operations.reads';
|
||||
const METRIC_DATABASES_OPERATIONS_WRITES = 'databases.operations.writes';
|
||||
const METRIC_DATABASE_ID_OPERATIONS_WRITES = '{databaseInternalId}.databases.operations.writes';
|
||||
const METRIC_BUCKETS = 'buckets';
|
||||
const METRIC_FILES = 'files';
|
||||
const METRIC_FILES_STORAGE = 'files.storage';
|
||||
const METRIC_FILES_TRANSFORMATIONS = 'files.transformations';
|
||||
const METRIC_BUCKET_ID_FILES_TRANSFORMATIONS = '{bucketInternalId}.files.transformations';
|
||||
const METRIC_BUCKET_ID_FILES = '{bucketInternalId}.files';
|
||||
const METRIC_BUCKET_ID_FILES_STORAGE = '{bucketInternalId}.files.storage';
|
||||
const METRIC_FUNCTIONS = 'functions';
|
||||
@@ -854,31 +866,31 @@ $register->set('pools', function () {
|
||||
$connections = [
|
||||
'console' => [
|
||||
'type' => 'database',
|
||||
'dsns' => System::getEnv('_APP_CONNECTIONS_DB_CONSOLE', $fallbackForDB),
|
||||
'dsns' => $fallbackForDB,
|
||||
'multiple' => false,
|
||||
'schemes' => ['mariadb', 'mysql'],
|
||||
],
|
||||
'database' => [
|
||||
'type' => 'database',
|
||||
'dsns' => System::getEnv('_APP_CONNECTIONS_DB_PROJECT', $fallbackForDB),
|
||||
'dsns' => $fallbackForDB,
|
||||
'multiple' => true,
|
||||
'schemes' => ['mariadb', 'mysql'],
|
||||
],
|
||||
'queue' => [
|
||||
'type' => 'queue',
|
||||
'dsns' => System::getEnv('_APP_CONNECTIONS_QUEUE', $fallbackForRedis),
|
||||
'dsns' => $fallbackForRedis,
|
||||
'multiple' => false,
|
||||
'schemes' => ['redis'],
|
||||
],
|
||||
'pubsub' => [
|
||||
'type' => 'pubsub',
|
||||
'dsns' => System::getEnv('_APP_CONNECTIONS_PUBSUB', $fallbackForRedis),
|
||||
'dsns' => $fallbackForRedis,
|
||||
'multiple' => false,
|
||||
'schemes' => ['redis'],
|
||||
],
|
||||
'cache' => [
|
||||
'type' => 'cache',
|
||||
'dsns' => System::getEnv('_APP_CONNECTIONS_CACHE', $fallbackForRedis),
|
||||
'dsns' => $fallbackForRedis,
|
||||
'multiple' => true,
|
||||
'schemes' => ['redis'],
|
||||
],
|
||||
@@ -890,7 +902,7 @@ $register->set('pools', function () {
|
||||
$multiprocessing = System::getEnv('_APP_SERVER_MULTIPROCESS', 'disabled') === 'enabled';
|
||||
|
||||
if ($multiprocessing) {
|
||||
$workerCount = swoole_cpu_num() * intval(System::getEnv('_APP_WORKER_PER_CORE', 6));
|
||||
$workerCount = intval(System::getEnv('_APP_CPU_NUM', swoole_cpu_num())) * intval(System::getEnv('_APP_WORKER_PER_CORE', 6));
|
||||
} else {
|
||||
$workerCount = 1;
|
||||
}
|
||||
@@ -950,12 +962,12 @@ $register->set('pools', function () {
|
||||
});
|
||||
},
|
||||
'redis' => function () use ($dsnHost, $dsnPort, $dsnPass) {
|
||||
$redis = new Redis();
|
||||
$redis = new \Redis();
|
||||
@$redis->pconnect($dsnHost, (int)$dsnPort);
|
||||
if ($dsnPass) {
|
||||
$redis->auth($dsnPass);
|
||||
}
|
||||
$redis->setOption(Redis::OPT_READ_TIMEOUT, -1);
|
||||
$redis->setOption(\Redis::OPT_READ_TIMEOUT, -1);
|
||||
|
||||
return $redis;
|
||||
},
|
||||
@@ -1215,12 +1227,12 @@ App::setResource('clients', function ($request, $console, $project) {
|
||||
return \array_unique($clients);
|
||||
}, ['request', 'console', 'project']);
|
||||
|
||||
App::setResource('user', function ($mode, $project, $console, $request, $response, $dbForProject, $dbForConsole) {
|
||||
App::setResource('user', function ($mode, $project, $console, $request, $response, $dbForProject, $dbForPlatform) {
|
||||
/** @var Appwrite\Utopia\Request $request */
|
||||
/** @var Appwrite\Utopia\Response $response */
|
||||
/** @var Utopia\Database\Document $project */
|
||||
/** @var Utopia\Database\Database $dbForProject */
|
||||
/** @var Utopia\Database\Database $dbForConsole */
|
||||
/** @var Utopia\Database\Database $dbForPlatform */
|
||||
/** @var string $mode */
|
||||
|
||||
Authorization::setDefaultStatus(true);
|
||||
@@ -1269,13 +1281,13 @@ App::setResource('user', function ($mode, $project, $console, $request, $respons
|
||||
$user = new Document([]);
|
||||
} else {
|
||||
if ($project->getId() === 'console') {
|
||||
$user = $dbForConsole->getDocument('users', Auth::$unique);
|
||||
$user = $dbForPlatform->getDocument('users', Auth::$unique);
|
||||
} else {
|
||||
$user = $dbForProject->getDocument('users', Auth::$unique);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$user = $dbForConsole->getDocument('users', Auth::$unique);
|
||||
$user = $dbForPlatform->getDocument('users', Auth::$unique);
|
||||
}
|
||||
|
||||
if (
|
||||
@@ -1318,14 +1330,14 @@ App::setResource('user', function ($mode, $project, $console, $request, $respons
|
||||
}
|
||||
|
||||
$dbForProject->setMetadata('user', $user->getId());
|
||||
$dbForConsole->setMetadata('user', $user->getId());
|
||||
$dbForPlatform->setMetadata('user', $user->getId());
|
||||
|
||||
return $user;
|
||||
}, ['mode', 'project', 'console', 'request', 'response', 'dbForProject', 'dbForConsole']);
|
||||
}, ['mode', 'project', 'console', 'request', 'response', 'dbForProject', 'dbForPlatform']);
|
||||
|
||||
App::setResource('project', function ($dbForConsole, $request, $console) {
|
||||
App::setResource('project', function ($dbForPlatform, $request, $console) {
|
||||
/** @var Appwrite\Utopia\Request $request */
|
||||
/** @var Utopia\Database\Database $dbForConsole */
|
||||
/** @var Utopia\Database\Database $dbForPlatform */
|
||||
/** @var Utopia\Database\Document $console */
|
||||
|
||||
$projectId = $request->getParam('project', $request->getHeader('x-appwrite-project', ''));
|
||||
@@ -1334,10 +1346,10 @@ App::setResource('project', function ($dbForConsole, $request, $console) {
|
||||
return $console;
|
||||
}
|
||||
|
||||
$project = Authorization::skip(fn () => $dbForConsole->getDocument('projects', $projectId));
|
||||
$project = Authorization::skip(fn () => $dbForPlatform->getDocument('projects', $projectId));
|
||||
|
||||
return $project;
|
||||
}, ['dbForConsole', 'request', 'console']);
|
||||
}, ['dbForPlatform', 'request', 'console']);
|
||||
|
||||
App::setResource('session', function (Document $user) {
|
||||
if ($user->isEmpty()) {
|
||||
@@ -1402,9 +1414,9 @@ App::setResource('console', function () {
|
||||
]);
|
||||
}, []);
|
||||
|
||||
App::setResource('dbForProject', function (Group $pools, Database $dbForConsole, Cache $cache, Document $project) {
|
||||
App::setResource('dbForProject', function (Group $pools, Database $dbForPlatform, Cache $cache, Document $project) {
|
||||
if ($project->isEmpty() || $project->getId() === 'console') {
|
||||
return $dbForConsole;
|
||||
return $dbForPlatform;
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -1427,14 +1439,9 @@ App::setResource('dbForProject', function (Group $pools, Database $dbForConsole,
|
||||
->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS)
|
||||
->setMaxQueryValues(APP_DATABASE_QUERY_MAX_VALUES);
|
||||
|
||||
try {
|
||||
$dsn = new DSN($project->getAttribute('database'));
|
||||
} catch (\InvalidArgumentException) {
|
||||
// TODO: Temporary until all projects are using shared tables
|
||||
$dsn = new DSN('mysql://' . $project->getAttribute('database'));
|
||||
}
|
||||
$sharedTables = \explode(',', System::getEnv('_APP_DATABASE_SHARED_TABLES', ''));
|
||||
|
||||
if ($dsn->getHost() === System::getEnv('_APP_DATABASE_SHARED_TABLES', '')) {
|
||||
if (\in_array($dsn->getHost(), $sharedTables)) {
|
||||
$database
|
||||
->setSharedTables(true)
|
||||
->setTenant($project->getInternalId())
|
||||
@@ -1447,9 +1454,9 @@ App::setResource('dbForProject', function (Group $pools, Database $dbForConsole,
|
||||
}
|
||||
|
||||
return $database;
|
||||
}, ['pools', 'dbForConsole', 'cache', 'project']);
|
||||
}, ['pools', 'dbForPlatform', 'cache', 'project']);
|
||||
|
||||
App::setResource('dbForConsole', function (Group $pools, Cache $cache) {
|
||||
App::setResource('dbForPlatform', function (Group $pools, Cache $cache) {
|
||||
$dbAdapter = $pools
|
||||
->get('console')
|
||||
->pop()
|
||||
@@ -1467,12 +1474,12 @@ App::setResource('dbForConsole', function (Group $pools, Cache $cache) {
|
||||
return $database;
|
||||
}, ['pools', 'cache']);
|
||||
|
||||
App::setResource('getProjectDB', function (Group $pools, Database $dbForConsole, $cache) {
|
||||
App::setResource('getProjectDB', function (Group $pools, Database $dbForPlatform, $cache) {
|
||||
$databases = []; // TODO: @Meldiron This should probably be responsibility of utopia-php/pools
|
||||
|
||||
return function (Document $project) use ($pools, $dbForConsole, $cache, &$databases) {
|
||||
return function (Document $project) use ($pools, $dbForPlatform, $cache, &$databases) {
|
||||
if ($project->isEmpty() || $project->getId() === 'console') {
|
||||
return $dbForConsole;
|
||||
return $dbForPlatform;
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -1489,7 +1496,9 @@ App::setResource('getProjectDB', function (Group $pools, Database $dbForConsole,
|
||||
->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS)
|
||||
->setMaxQueryValues(APP_DATABASE_QUERY_MAX_VALUES);
|
||||
|
||||
if ($dsn->getHost() === System::getEnv('_APP_DATABASE_SHARED_TABLES', '')) {
|
||||
$sharedTables = \explode(',', System::getEnv('_APP_DATABASE_SHARED_TABLES', ''));
|
||||
|
||||
if (\in_array($dsn->getHost(), $sharedTables)) {
|
||||
$database
|
||||
->setSharedTables(true)
|
||||
->setTenant($project->getInternalId())
|
||||
@@ -1519,7 +1528,7 @@ App::setResource('getProjectDB', function (Group $pools, Database $dbForConsole,
|
||||
|
||||
return $database;
|
||||
};
|
||||
}, ['pools', 'dbForConsole', 'cache']);
|
||||
}, ['pools', 'dbForPlatform', 'cache']);
|
||||
|
||||
App::setResource('cache', function (Group $pools) {
|
||||
$list = Config::getParam('pools-cache', []);
|
||||
@@ -1536,6 +1545,27 @@ App::setResource('cache', function (Group $pools) {
|
||||
return new Cache(new Sharding($adapters));
|
||||
}, ['pools']);
|
||||
|
||||
App::setResource('redis', function () {
|
||||
$host = System::getEnv('_APP_REDIS_HOST', 'localhost');
|
||||
$port = System::getEnv('_APP_REDIS_PORT', 6379);
|
||||
$pass = System::getEnv('_APP_REDIS_PASS', '');
|
||||
|
||||
$redis = new \Redis();
|
||||
@$redis->pconnect($host, (int)$port);
|
||||
if ($pass) {
|
||||
$redis->auth($pass);
|
||||
}
|
||||
$redis->setOption(\Redis::OPT_READ_TIMEOUT, -1);
|
||||
|
||||
return $redis;
|
||||
});
|
||||
|
||||
App::setResource('timelimit', function (\Redis $redis) {
|
||||
return function (string $key, int $limit, int $time) use ($redis) {
|
||||
return new TimeLimitRedis($key, $limit, $time, $redis);
|
||||
};
|
||||
}, ['redis']);
|
||||
|
||||
App::setResource('deviceForLocal', function () {
|
||||
return new Local();
|
||||
});
|
||||
@@ -1860,7 +1890,7 @@ App::setResource('plan', function (array $plan = []) {
|
||||
return [];
|
||||
});
|
||||
|
||||
App::setResource('team', function (Document $project, Database $dbForConsole, App $utopia, Request $request) {
|
||||
App::setResource('team', function (Document $project, Database $dbForPlatform, App $utopia, Request $request) {
|
||||
$teamInternalId = '';
|
||||
if ($project->getId() !== 'console') {
|
||||
$teamInternalId = $project->getAttribute('teamInternalId', '');
|
||||
@@ -1870,25 +1900,36 @@ App::setResource('team', function (Document $project, Database $dbForConsole, Ap
|
||||
if (str_starts_with($path, '/v1/projects/:projectId')) {
|
||||
$uri = $request->getURI();
|
||||
$pid = explode('/', $uri)[3];
|
||||
$p = Authorization::skip(fn () => $dbForConsole->getDocument('projects', $pid));
|
||||
$p = Authorization::skip(fn () => $dbForPlatform->getDocument('projects', $pid));
|
||||
$teamInternalId = $p->getAttribute('teamInternalId', '');
|
||||
} elseif ($path === '/v1/projects') {
|
||||
$teamId = $request->getParam('teamId', '');
|
||||
$team = Authorization::skip(fn () => $dbForConsole->getDocument('teams', $teamId));
|
||||
$team = Authorization::skip(fn () => $dbForPlatform->getDocument('teams', $teamId));
|
||||
return $team;
|
||||
}
|
||||
}
|
||||
|
||||
$team = Authorization::skip(function () use ($dbForConsole, $teamInternalId) {
|
||||
return $dbForConsole->findOne('teams', [
|
||||
$team = Authorization::skip(function () use ($dbForPlatform, $teamInternalId) {
|
||||
return $dbForPlatform->findOne('teams', [
|
||||
Query::equal('$internalId', [$teamInternalId]),
|
||||
]);
|
||||
});
|
||||
|
||||
return $team;
|
||||
}, ['project', 'dbForConsole', 'utopia', 'request']);
|
||||
}, ['project', 'dbForPlatform', 'utopia', 'request']);
|
||||
|
||||
App::setResource(
|
||||
'isResourceBlocked',
|
||||
fn () => fn (Document $project, string $resourceType, ?string $resourceId) => false
|
||||
);
|
||||
|
||||
App::setResource('previewHostname', function (Request $request) {
|
||||
if (App::isDevelopment()) {
|
||||
$host = $request->getQuery('appwrite-hostname') ?? '';
|
||||
if (!empty($host)) {
|
||||
return $host;
|
||||
}
|
||||
}
|
||||
|
||||
return '';
|
||||
}, ['request']);
|
||||
|
||||
+63
-13
@@ -13,7 +13,7 @@ use Swoole\Runtime;
|
||||
use Swoole\Table;
|
||||
use Swoole\Timer;
|
||||
use Utopia\Abuse\Abuse;
|
||||
use Utopia\Abuse\Adapters\Database\TimeLimit;
|
||||
use Utopia\Abuse\Adapters\TimeLimit\Redis as TimeLimitRedis;
|
||||
use Utopia\App;
|
||||
use Utopia\Cache\Adapter\Sharding;
|
||||
use Utopia\Cache\Cache;
|
||||
@@ -29,6 +29,7 @@ use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\DSN\DSN;
|
||||
use Utopia\Logger\Log;
|
||||
use Utopia\System\System;
|
||||
use Utopia\Telemetry\Adapter\None as NoTelemetry;
|
||||
use Utopia\WebSocket\Adapter;
|
||||
use Utopia\WebSocket\Server;
|
||||
|
||||
@@ -92,7 +93,9 @@ if (!function_exists('getProjectDB')) {
|
||||
|
||||
$database = new Database($adapter, getCache());
|
||||
|
||||
if ($dsn->getHost() === System::getEnv('_APP_DATABASE_SHARED_TABLES', '')) {
|
||||
$sharedTables = \explode(',', System::getEnv('_APP_DATABASE_SHARED_TABLES', ''));
|
||||
|
||||
if (\in_array($dsn->getHost(), $sharedTables)) {
|
||||
$database
|
||||
->setSharedTables(true)
|
||||
->setTenant($project->getInternalId())
|
||||
@@ -135,6 +138,32 @@ if (!function_exists('getCache')) {
|
||||
}
|
||||
}
|
||||
|
||||
// Allows overriding
|
||||
if (!function_exists('getRedis')) {
|
||||
function getRedis(): \Redis
|
||||
{
|
||||
$host = System::getEnv('_APP_REDIS_HOST', 'localhost');
|
||||
$port = System::getEnv('_APP_REDIS_PORT', 6379);
|
||||
$pass = System::getEnv('_APP_REDIS_PASS', '');
|
||||
|
||||
$redis = new \Redis();
|
||||
@$redis->pconnect($host, (int)$port);
|
||||
if ($pass) {
|
||||
$redis->auth($pass);
|
||||
}
|
||||
$redis->setOption(\Redis::OPT_READ_TIMEOUT, -1);
|
||||
|
||||
return $redis;
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('getTimelimit')) {
|
||||
function getTimelimit(): TimeLimitRedis
|
||||
{
|
||||
return new TimeLimitRedis("", 0, 1, getRedis());
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('getRealtime')) {
|
||||
function getRealtime(): Realtime
|
||||
{
|
||||
@@ -142,6 +171,13 @@ if (!function_exists('getRealtime')) {
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('getTelemetry')) {
|
||||
function getTelemetry(int $workerId): Utopia\Telemetry\Adapter
|
||||
{
|
||||
return new NoTelemetry();
|
||||
}
|
||||
}
|
||||
|
||||
$realtime = getRealtime();
|
||||
|
||||
/**
|
||||
@@ -157,7 +193,7 @@ $stats->create();
|
||||
|
||||
$containerId = uniqid();
|
||||
$statsDocument = null;
|
||||
$workerNumber = swoole_cpu_num() * intval(System::getEnv('_APP_WORKER_PER_CORE', 6));
|
||||
$workerNumber = intval(System::getEnv('_APP_CPU_NUM', swoole_cpu_num())) * intval(System::getEnv('_APP_WORKER_PER_CORE', 6));
|
||||
|
||||
$adapter = new Adapter\Swoole(port: System::getEnv('PORT', 80));
|
||||
$adapter
|
||||
@@ -174,7 +210,7 @@ $logError = function (Throwable $error, string $action) use ($register) {
|
||||
|
||||
$log = new Log();
|
||||
$log->setNamespace("realtime");
|
||||
$log->setServer(gethostname());
|
||||
$log->setServer(System::getEnv('_APP_LOGGING_SERVICE_IDENTIFIER', \gethostname()));
|
||||
$log->setVersion($version);
|
||||
$log->setType(Log::TYPE_ERROR);
|
||||
$log->setMessage($error->getMessage());
|
||||
@@ -274,6 +310,12 @@ $server->onStart(function () use ($stats, $register, $containerId, &$statsDocume
|
||||
$server->onWorkerStart(function (int $workerId) use ($server, $register, $stats, $realtime, $logError) {
|
||||
Console::success('Worker ' . $workerId . ' started successfully');
|
||||
|
||||
$telemetry = getTelemetry($workerId);
|
||||
$register->set('telemetry', fn () => $telemetry);
|
||||
$register->set('telemetry.connectionCounter', fn () => $telemetry->createUpDownCounter('realtime.server.open_connections'));
|
||||
$register->set('telemetry.connectionCreatedCounter', fn () => $telemetry->createCounter('realtime.server.connection.created'));
|
||||
$register->set('telemetry.messageSentCounter', fn () => $telemetry->createCounter('realtime.server.message.sent'));
|
||||
|
||||
$attempts = 0;
|
||||
$start = time();
|
||||
|
||||
@@ -416,6 +458,7 @@ $server->onWorkerStart(function (int $workerId) use ($server, $register, $stats,
|
||||
);
|
||||
|
||||
if (($num = count($receivers)) > 0) {
|
||||
$register->get('telemetry.messageSentCounter')->add($num);
|
||||
$stats->incr($event['project'], 'messages', $num);
|
||||
}
|
||||
});
|
||||
@@ -464,7 +507,7 @@ $server->onOpen(function (int $connection, SwooleRequest $request) use ($server,
|
||||
throw new AppwriteException(AppwriteException::GENERAL_API_DISABLED);
|
||||
}
|
||||
|
||||
$dbForProject = getProjectDB($project);
|
||||
$timelimit = $app->getResource('timelimit');
|
||||
$console = $app->getResource('console'); /** @var Document $console */
|
||||
$user = $app->getResource('user'); /** @var Document $user */
|
||||
|
||||
@@ -473,12 +516,12 @@ $server->onOpen(function (int $connection, SwooleRequest $request) use ($server,
|
||||
*
|
||||
* Abuse limits are connecting 128 times per minute and ip address.
|
||||
*/
|
||||
$timeLimit = new TimeLimit('url:{url},ip:{ip}', 128, 60, $dbForProject);
|
||||
$timeLimit
|
||||
$timelimit = $timelimit('url:{url},ip:{ip}', 128, 60);
|
||||
$timelimit
|
||||
->setParam('{ip}', $request->getIP())
|
||||
->setParam('{url}', $request->getURI());
|
||||
|
||||
$abuse = new Abuse($timeLimit);
|
||||
$abuse = new Abuse($timelimit);
|
||||
|
||||
if (System::getEnv('_APP_OPTIONS_ABUSE', 'enabled') === 'enabled' && $abuse->check()) {
|
||||
throw new Exception(Exception::REALTIME_TOO_MANY_MESSAGES, 'Too many requests');
|
||||
@@ -519,6 +562,9 @@ $server->onOpen(function (int $connection, SwooleRequest $request) use ($server,
|
||||
]
|
||||
]));
|
||||
|
||||
$register->get('telemetry.connectionCounter')->add(1);
|
||||
$register->get('telemetry.connectionCreatedCounter')->add(1);
|
||||
|
||||
$stats->set($project->getId(), [
|
||||
'projectId' => $project->getId(),
|
||||
'teamId' => $project->getAttribute('teamId')
|
||||
@@ -573,7 +619,7 @@ $server->onMessage(function (int $connection, string $message) use ($server, $re
|
||||
*
|
||||
* Abuse limits are sending 32 times per minute and connection.
|
||||
*/
|
||||
$timeLimit = new TimeLimit('url:{url},connection:{connection}', 32, 60, $database);
|
||||
$timeLimit = getTimelimit('url:{url},connection:{connection}', 32, 60);
|
||||
|
||||
$timeLimit
|
||||
->setParam('{connection}', $connection)
|
||||
@@ -592,9 +638,12 @@ $server->onMessage(function (int $connection, string $message) use ($server, $re
|
||||
}
|
||||
|
||||
switch ($message['type']) {
|
||||
/**
|
||||
* This type is used to authenticate.
|
||||
*/
|
||||
case 'ping':
|
||||
$server->send([$connection], json_encode([
|
||||
'type' => 'pong'
|
||||
]));
|
||||
|
||||
break;
|
||||
case 'authentication':
|
||||
if (!array_key_exists('session', $message['data'])) {
|
||||
throw new Exception(Exception::REALTIME_MESSAGE_FORMAT_INVALID, 'Payload is not valid.');
|
||||
@@ -652,9 +701,10 @@ $server->onMessage(function (int $connection, string $message) use ($server, $re
|
||||
}
|
||||
});
|
||||
|
||||
$server->onClose(function (int $connection) use ($realtime, $stats) {
|
||||
$server->onClose(function (int $connection) use ($realtime, $stats, $register) {
|
||||
if (array_key_exists($connection, $realtime->connections)) {
|
||||
$stats->decr($realtime->connections[$connection]['projectId'], 'connectionsTotal');
|
||||
$register->get('telemetry.connectionCounter')->add(-1);
|
||||
}
|
||||
$realtime->unsubscribe($connection);
|
||||
|
||||
|
||||
@@ -167,7 +167,7 @@ $image = $this->getParam('image', '');
|
||||
appwrite-console:
|
||||
<<: *x-logging
|
||||
container_name: appwrite-console
|
||||
image: <?php echo $organization; ?>/console:5.0.12
|
||||
image: <?php echo $organization; ?>/console:5.2.27
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- appwrite
|
||||
|
||||
+98
-17
@@ -2,6 +2,7 @@
|
||||
|
||||
require_once __DIR__ . '/init.php';
|
||||
|
||||
use Appwrite\Certificates\LetsEncrypt;
|
||||
use Appwrite\Event\Audit;
|
||||
use Appwrite\Event\Build;
|
||||
use Appwrite\Event\Certificate;
|
||||
@@ -16,7 +17,7 @@ use Appwrite\Event\Usage;
|
||||
use Appwrite\Event\UsageDump;
|
||||
use Appwrite\Platform\Appwrite;
|
||||
use Swoole\Runtime;
|
||||
use Utopia\App;
|
||||
use Utopia\Abuse\Adapters\TimeLimit\Redis as TimeLimitRedis;
|
||||
use Utopia\Cache\Adapter\Sharding;
|
||||
use Utopia\Cache\Cache;
|
||||
use Utopia\CLI\Console;
|
||||
@@ -41,7 +42,7 @@ Runtime::enableCoroutine(SWOOLE_HOOK_ALL);
|
||||
|
||||
Server::setResource('register', fn () => $register);
|
||||
|
||||
Server::setResource('dbForConsole', function (Cache $cache, Registry $register) {
|
||||
Server::setResource('dbForPlatform', function (Cache $cache, Registry $register) {
|
||||
$pools = $register->get('pools');
|
||||
$database = $pools
|
||||
->get('console')
|
||||
@@ -54,7 +55,7 @@ Server::setResource('dbForConsole', function (Cache $cache, Registry $register)
|
||||
return $adapter;
|
||||
}, ['cache', 'register']);
|
||||
|
||||
Server::setResource('project', function (Message $message, Database $dbForConsole) {
|
||||
Server::setResource('project', function (Message $message, Database $dbForPlatform) {
|
||||
$payload = $message->getPayload() ?? [];
|
||||
$project = new Document($payload['project'] ?? []);
|
||||
|
||||
@@ -62,12 +63,12 @@ Server::setResource('project', function (Message $message, Database $dbForConsol
|
||||
return $project;
|
||||
}
|
||||
|
||||
return $dbForConsole->getDocument('projects', $project->getId());
|
||||
}, ['message', 'dbForConsole']);
|
||||
return $dbForPlatform->getDocument('projects', $project->getId());
|
||||
}, ['message', 'dbForPlatform']);
|
||||
|
||||
Server::setResource('dbForProject', function (Cache $cache, Registry $register, Message $message, Document $project, Database $dbForConsole) {
|
||||
Server::setResource('dbForProject', function (Cache $cache, Registry $register, Message $message, Document $project, Database $dbForPlatform) {
|
||||
if ($project->isEmpty() || $project->getId() === 'console') {
|
||||
return $dbForConsole;
|
||||
return $dbForPlatform;
|
||||
}
|
||||
|
||||
$pools = $register->get('pools');
|
||||
@@ -93,7 +94,9 @@ Server::setResource('dbForProject', function (Cache $cache, Registry $register,
|
||||
$dsn = new DSN('mysql://' . $project->getAttribute('database'));
|
||||
}
|
||||
|
||||
if ($dsn->getHost() === System::getEnv('_APP_DATABASE_SHARED_TABLES', '')) {
|
||||
$sharedTables = \explode(',', System::getEnv('_APP_DATABASE_SHARED_TABLES', ''));
|
||||
|
||||
if (\in_array($dsn->getHost(), $sharedTables)) {
|
||||
$database
|
||||
->setSharedTables(true)
|
||||
->setTenant($project->getInternalId())
|
||||
@@ -106,14 +109,14 @@ Server::setResource('dbForProject', function (Cache $cache, Registry $register,
|
||||
}
|
||||
|
||||
return $database;
|
||||
}, ['cache', 'register', 'message', 'project', 'dbForConsole']);
|
||||
}, ['cache', 'register', 'message', 'project', 'dbForPlatform']);
|
||||
|
||||
Server::setResource('getProjectDB', function (Group $pools, Database $dbForConsole, $cache) {
|
||||
Server::setResource('getProjectDB', function (Group $pools, Database $dbForPlatform, $cache) {
|
||||
$databases = []; // TODO: @Meldiron This should probably be responsibility of utopia-php/pools
|
||||
|
||||
return function (Document $project) use ($pools, $dbForConsole, $cache, &$databases): Database {
|
||||
return function (Document $project) use ($pools, $dbForPlatform, $cache, &$databases): Database {
|
||||
if ($project->isEmpty() || $project->getId() === 'console') {
|
||||
return $dbForConsole;
|
||||
return $dbForPlatform;
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -126,7 +129,9 @@ Server::setResource('getProjectDB', function (Group $pools, Database $dbForConso
|
||||
if (isset($databases[$dsn->getHost()])) {
|
||||
$database = $databases[$dsn->getHost()];
|
||||
|
||||
if ($dsn->getHost() === System::getEnv('_APP_DATABASE_SHARED_TABLES', '')) {
|
||||
$sharedTables = \explode(',', System::getEnv('_APP_DATABASE_SHARED_TABLES', ''));
|
||||
|
||||
if (\in_array($dsn->getHost(), $sharedTables)) {
|
||||
$database
|
||||
->setSharedTables(true)
|
||||
->setTenant($project->getInternalId())
|
||||
@@ -150,7 +155,9 @@ Server::setResource('getProjectDB', function (Group $pools, Database $dbForConso
|
||||
|
||||
$databases[$dsn->getHost()] = $database;
|
||||
|
||||
if ($dsn->getHost() === System::getEnv('_APP_DATABASE_SHARED_TABLES', '')) {
|
||||
$sharedTables = \explode(',', System::getEnv('_APP_DATABASE_SHARED_TABLES', ''));
|
||||
|
||||
if (\in_array($dsn->getHost(), $sharedTables)) {
|
||||
$database
|
||||
->setSharedTables(true)
|
||||
->setTenant($project->getInternalId())
|
||||
@@ -164,10 +171,10 @@ Server::setResource('getProjectDB', function (Group $pools, Database $dbForConso
|
||||
|
||||
return $database;
|
||||
};
|
||||
}, ['pools', 'dbForConsole', 'cache']);
|
||||
}, ['pools', 'dbForPlatform', 'cache']);
|
||||
|
||||
Server::setResource('abuseRetention', function () {
|
||||
return DateTime::addSeconds(new \DateTime(), -1 * System::getEnv('_APP_MAINTENANCE_RETENTION_ABUSE', 86400));
|
||||
return time() - (int) System::getEnv('_APP_MAINTENANCE_RETENTION_ABUSE', 86400);
|
||||
});
|
||||
|
||||
Server::setResource('auditRetention', function () {
|
||||
@@ -194,6 +201,27 @@ Server::setResource('cache', function (Registry $register) {
|
||||
return new Cache(new Sharding($adapters));
|
||||
}, ['register']);
|
||||
|
||||
Server::setResource('redis', function () {
|
||||
$host = System::getEnv('_APP_REDIS_HOST', 'localhost');
|
||||
$port = System::getEnv('_APP_REDIS_PORT', 6379);
|
||||
$pass = System::getEnv('_APP_REDIS_PASS', '');
|
||||
|
||||
$redis = new \Redis();
|
||||
@$redis->pconnect($host, (int)$port);
|
||||
if ($pass) {
|
||||
$redis->auth($pass);
|
||||
}
|
||||
$redis->setOption(\Redis::OPT_READ_TIMEOUT, -1);
|
||||
|
||||
return $redis;
|
||||
});
|
||||
|
||||
Server::setResource('timelimit', function (\Redis $redis) {
|
||||
return function (string $key, int $limit, int $time) use ($redis) {
|
||||
return new TimeLimitRedis($key, $limit, $time, $redis);
|
||||
};
|
||||
}, ['redis']);
|
||||
|
||||
Server::setResource('log', fn () => new Log());
|
||||
|
||||
Server::setResource('queueForUsage', function (Connection $queue) {
|
||||
@@ -277,6 +305,59 @@ Server::setResource(
|
||||
fn () => fn (Document $project, string $resourceType, ?string $resourceId) => false
|
||||
);
|
||||
|
||||
Server::setResource('certificates', function () {
|
||||
$email = System::getEnv('_APP_EMAIL_CERTIFICATES', System::getEnv('_APP_SYSTEM_SECURITY_EMAIL_ADDRESS'));
|
||||
if (empty($email)) {
|
||||
throw new Exception('You must set a valid security email address (_APP_EMAIL_CERTIFICATES) to issue a LetsEncrypt SSL certificate.');
|
||||
}
|
||||
|
||||
return new LetsEncrypt($email);
|
||||
});
|
||||
|
||||
Server::setResource('logError', function (Registry $register, Document $project) {
|
||||
return function (Throwable $error, string $namespace, string $action, ?array $extras) use ($register, $project) {
|
||||
$logger = $register->get('logger');
|
||||
|
||||
if ($logger) {
|
||||
$version = System::getEnv('_APP_VERSION', 'UNKNOWN');
|
||||
|
||||
$log = new Log();
|
||||
$log->setNamespace($namespace);
|
||||
$log->setServer(System::getEnv('_APP_LOGGING_SERVICE_IDENTIFIER', \gethostname()));
|
||||
$log->setVersion($version);
|
||||
$log->setType(Log::TYPE_ERROR);
|
||||
$log->setMessage($error->getMessage());
|
||||
|
||||
$log->addTag('code', $error->getCode());
|
||||
$log->addTag('verboseType', get_class($error));
|
||||
$log->addTag('projectId', $project->getId() ?? '');
|
||||
|
||||
$log->addExtra('file', $error->getFile());
|
||||
$log->addExtra('line', $error->getLine());
|
||||
$log->addExtra('trace', $error->getTraceAsString());
|
||||
|
||||
|
||||
foreach ($extras as $key => $value) {
|
||||
$log->addExtra($key, $value);
|
||||
}
|
||||
|
||||
$log->setAction($action);
|
||||
|
||||
$isProduction = System::getEnv('_APP_ENV', 'development') === 'production';
|
||||
$log->setEnvironment($isProduction ? Log::ENVIRONMENT_PRODUCTION : Log::ENVIRONMENT_STAGING);
|
||||
|
||||
try {
|
||||
$responseCode = $logger->addLog($log);
|
||||
Console::info('Error log pushed with status code: ' . $responseCode);
|
||||
} catch (Throwable $th) {
|
||||
Console::error('Error pushing log: ' . $th->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
Console::warning("Failed: {$error->getMessage()}");
|
||||
Console::warning($error->getTraceAsString());
|
||||
};
|
||||
}, ['register', 'project']);
|
||||
|
||||
$pools = $register->get('pools');
|
||||
$platform = new Appwrite();
|
||||
@@ -335,7 +416,7 @@ $worker
|
||||
|
||||
if ($logger) {
|
||||
$log->setNamespace("appwrite-worker");
|
||||
$log->setServer(\gethostname());
|
||||
$log->setServer(System::getEnv('_APP_LOGGING_SERVICE_IDENTIFIER', \gethostname()));
|
||||
$log->setVersion($version);
|
||||
$log->setType(Log::TYPE_ERROR);
|
||||
$log->setMessage($error->getMessage());
|
||||
|
||||
Reference in New Issue
Block a user