diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 2cc4c700f..7bc39392e 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -145,3 +145,6 @@ jobs: - name: Run ${{matrix.service}} Tests run: docker compose exec -T appwrite test /usr/src/code/tests/e2e/Services/${{matrix.service}} --debug + + - name: Run ${{matrix.service}} Shared Tables Tests + run: _APP_DATABASE_SHARED_TABLES=database_db_main docker compose exec -T appwrite test /usr/src/code/tests/e2e/Services/${{matrix.service}} --debug diff --git a/Dockerfile b/Dockerfile index 1d2ac91ae..1d82930c1 100755 --- a/Dockerfile +++ b/Dockerfile @@ -29,7 +29,7 @@ ENV VITE_APPWRITE_GROWTH_ENDPOINT=$VITE_APPWRITE_GROWTH_ENDPOINT RUN npm ci RUN npm run build -FROM appwrite/base:0.9.0 as final +FROM appwrite/base:0.9.1 as final LABEL maintainer="team@appwrite.io" diff --git a/app/cli.php b/app/cli.php index da7d23c18..87bb711da 100644 --- a/app/cli.php +++ b/app/cli.php @@ -109,7 +109,7 @@ CLI::setResource('getProjectDB', function (Group $pools, Database $dbForConsole, if (isset($databases[$dsn->getHost()])) { $database = $databases[$dsn->getHost()]; - if ($dsn->getHost() === DATABASE_SHARED_TABLES) { + if ($dsn->getHost() === System::getEnv('_APP_DATABASE_SHARED_TABLES', '')) { $database ->setSharedTables(true) ->setTenant($project->getInternalId()) @@ -133,7 +133,7 @@ CLI::setResource('getProjectDB', function (Group $pools, Database $dbForConsole, $databases[$dsn->getHost()] = $database; - if ($dsn->getHost() === DATABASE_SHARED_TABLES) { + if ($dsn->getHost() === System::getEnv('_APP_DATABASE_SHARED_TABLES', '')) { $database ->setSharedTables(true) ->setTenant($project->getInternalId()) @@ -202,7 +202,7 @@ CLI::setResource('logError', function (Registry $register) { }, ['register']); $platform = new Appwrite(); -$platform->init(Service::TYPE_CLI); +$platform->init(Service::TYPE_TASK); $cli = $platform->getCli(); diff --git a/app/config/platforms.php b/app/config/platforms.php index d06c25a79..f0ce427c7 100644 --- a/app/config/platforms.php +++ b/app/config/platforms.php @@ -15,7 +15,7 @@ return [ [ 'key' => 'web', 'name' => 'Web', - 'version' => '14.0.2', + 'version' => '15.0.0', 'url' => 'https://github.com/appwrite/sdk-for-web', 'package' => 'https://www.npmjs.com/package/appwrite', 'enabled' => true, @@ -138,7 +138,7 @@ return [ [ 'key' => 'react-native', 'name' => 'React Native', - 'version' => '0.3.2', + 'version' => '0.4.0', 'url' => 'https://github.com/appwrite/sdk-for-react-native', 'package' => 'https://npmjs.com/package/react-native-appwrite', 'enabled' => true, @@ -267,7 +267,7 @@ return [ [ 'key' => 'deno', 'name' => 'Deno', - 'version' => '10.0.2', + 'version' => '11.0.0', 'url' => 'https://github.com/appwrite/sdk-for-deno', 'package' => 'https://deno.land/x/appwrite', 'enabled' => true, diff --git a/app/config/variables.php b/app/config/variables.php index b61a26778..77e8a3676 100644 --- a/app/config/variables.php +++ b/app/config/variables.php @@ -450,7 +450,7 @@ return [ ], [ 'name' => '_APP_SMS_FROM', - 'description' => 'Phone number used for sending out messages. Must start with a leading \'+\' and maximum of 15 digits without spaces (+123456789).', + 'description' => 'Phone number used for sending out messages. If using Twilio, this may be a Messaging Service SID, starting with MG. Otherwise, the number must start with a leading \'+\' and maximum of 15 digits without spaces (+123456789). ', 'introduction' => '0.15.0', 'default' => '', 'required' => false, diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index e1ddeb6b6..9461af661 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -86,8 +86,8 @@ $createSession = function (string $userId, string $secret, Request $request, Res $factor = (match ($verifiedToken->getAttribute('type')) { Auth::TOKEN_TYPE_MAGIC_URL, Auth::TOKEN_TYPE_OAUTH2, - Auth::TOKEN_TYPE_EMAIL => 'email', - Auth::TOKEN_TYPE_PHONE => 'phone', + Auth::TOKEN_TYPE_EMAIL => Type::EMAIL, + Auth::TOKEN_TYPE_PHONE => Type::PHONE, Auth::TOKEN_TYPE_GENERIC => 'token', default => throw new Exception(Exception::USER_INVALID_TOKEN) }); @@ -1506,7 +1506,7 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect') 'secret' => Auth::hash($secret), // One way hash encryption to protect DB leak 'userAgent' => $request->getUserAgent('UNKNOWN'), 'ip' => $request->getIP(), - 'factors' => ['email'], + 'factors' => [TYPE::EMAIL, 'oauth2'], // include a special oauth2 factor to bypass MFA checks 'countryCode' => ($record) ? \strtolower($record['country']['iso_code']) : '--', 'expire' => DateTime::addSeconds(new \DateTime(), $duration) ], $detector->getOS(), $detector->getClient(), $detector->getDevice())); diff --git a/app/controllers/api/health.php b/app/controllers/api/health.php index 4b4ce8f30..f4581df8e 100644 --- a/app/controllers/api/health.php +++ b/app/controllers/api/health.php @@ -852,7 +852,7 @@ App::get('/v1/health/queue/failed/:name') Event::FUNCTIONS_QUEUE_NAME, Event::USAGE_QUEUE_NAME, Event::USAGE_DUMP_QUEUE_NAME, - Event::WEBHOOK_CLASS_NAME, + Event::WEBHOOK_QUEUE_NAME, Event::CERTIFICATES_QUEUE_NAME, Event::BUILDS_QUEUE_NAME, Event::MESSAGING_QUEUE_NAME, diff --git a/app/controllers/api/projects.php b/app/controllers/api/projects.php index f872ef311..91f19a235 100644 --- a/app/controllers/api/projects.php +++ b/app/controllers/api/projects.php @@ -111,35 +111,8 @@ App::post('/v1/projects') $projectId = ($projectId == 'unique()') ? ID::unique() : $projectId; - $backups['database_db_fra1_v14x_02'] = ['from' => '03:00', 'to' => '05:00']; - $backups['database_db_fra1_v14x_03'] = ['from' => '00:00', 'to' => '02:00']; - $backups['database_db_fra1_v14x_04'] = ['from' => '00:00', 'to' => '02:00']; - $backups['database_db_fra1_v14x_05'] = ['from' => '00:00', 'to' => '02:00']; - $backups['database_db_fra1_v14x_06'] = ['from' => '00:00', 'to' => '02:00']; - $backups['database_db_fra1_v14x_07'] = ['from' => '00:00', 'to' => '02:00']; - $databases = Config::getParam('pools-database', []); - /** - * Remove databases from the list that are currently undergoing an backup - */ - if (count($databases) > 1) { - $now = new \DateTime(); - - foreach ($databases as $index => $database) { - if (empty($backups[$database])) { - continue; - } - $backup = $backups[$database]; - $from = \DateTime::createFromFormat('H:i', $backup['from']); - $to = \DateTime::createFromFormat('H:i', $backup['to']); - if ($now >= $from && $now <= $to) { - unset($databases[$index]); - break; - } - } - } - $databaseOverride = System::getEnv('_APP_DATABASE_OVERRIDE'); $index = \array_search($databaseOverride, $databases); if ($index !== false) { @@ -152,37 +125,12 @@ App::post('/v1/projects') throw new Exception(Exception::PROJECT_RESERVED_PROJECT, "'console' is a reserved project."); } - // TODO: 1 in 5 projects use shared tables. Temporary until all projects are using shared tables. - if ( - ( - !\mt_rand(0, 4) - && System::getEnv('_APP_DATABASE_SHARED_TABLES', 'enabled') === 'enabled' - && System::getEnv('_APP_EDITION', 'self-hosted') !== 'self-hosted' - ) || - ( - $dsn === DATABASE_SHARED_TABLES - ) - ) { + // TODO: Temporary until all projects are using shared tables. + if ($dsn === System::getEnv('_APP_DATABASE_SHARED_TABLES', '')) { $schema = 'appwrite'; $database = 'appwrite'; $namespace = System::getEnv('_APP_DATABASE_SHARED_NAMESPACE', ''); - $dsn = $schema . '://' . DATABASE_SHARED_TABLES . '?database=' . $database; - - if (!empty($namespace)) { - $dsn .= '&namespace=' . $namespace; - } - } - - // TODO: Allow overriding in development mode. Temporary until all projects are using shared tables. - if ( - App::isDevelopment() - && System::getEnv('_APP_EDITION', 'self-hosted') !== 'self-hosted' - && $request->getHeader('x-appwrited-share-tables', false) - ) { - $schema = 'appwrite'; - $database = 'appwrite'; - $namespace = System::getEnv('_APP_DATABASE_SHARED_NAMESPACE', ''); - $dsn = $schema . '://' . DATABASE_SHARED_TABLES . '?database=' . $database; + $dsn = $schema . '://' . System::getEnv('_APP_DATABASE_SHARED_TABLES', '') . '?database=' . $database; if (!empty($namespace)) { $dsn .= '&namespace=' . $namespace; @@ -236,7 +184,7 @@ App::post('/v1/projects') $adapter = $pools->get($dsn->getHost())->pop()->getResource(); $dbForProject = new Database($adapter, $cache); - if ($dsn->getHost() === DATABASE_SHARED_TABLES) { + if ($dsn->getHost() === System::getEnv('_APP_DATABASE_SHARED_TABLES', '')) { $dbForProject ->setSharedTables(true) ->setTenant($project->getInternalId()) diff --git a/app/controllers/general.php b/app/controllers/general.php index 15ba10da3..92ddec58f 100644 --- a/app/controllers/general.php +++ b/app/controllers/general.php @@ -584,7 +584,7 @@ App::init() ->addHeader('Server', 'Appwrite') ->addHeader('X-Content-Type-Options', 'nosniff') ->addHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, PATCH, DELETE') - ->addHeader('Access-Control-Allow-Headers', 'Origin, Cookie, Set-Cookie, X-Requested-With, Content-Type, Access-Control-Allow-Origin, Access-Control-Request-Headers, Accept, X-Appwrite-Project, X-Appwrite-Key, X-Appwrite-Locale, X-Appwrite-Mode, X-Appwrite-JWT, X-Appwrite-Response-Format, X-Appwrite-Timeout, X-Appwrite-Shared-Tables, X-SDK-Version, X-SDK-Name, X-SDK-Language, X-SDK-Platform, X-SDK-GraphQL, X-Appwrite-ID, X-Appwrite-Timestamp, Content-Range, Range, Cache-Control, Expires, Pragma, X-Forwarded-For, X-Forwarded-User-Agent') + ->addHeader('Access-Control-Allow-Headers', 'Origin, Cookie, Set-Cookie, X-Requested-With, Content-Type, Access-Control-Allow-Origin, Access-Control-Request-Headers, Accept, X-Appwrite-Project, X-Appwrite-Key, X-Appwrite-Locale, X-Appwrite-Mode, X-Appwrite-JWT, X-Appwrite-Response-Format, X-Appwrite-Timeout, X-SDK-Version, X-SDK-Name, X-SDK-Language, X-SDK-Platform, X-SDK-GraphQL, X-Appwrite-ID, X-Appwrite-Timestamp, Content-Range, Range, Cache-Control, Expires, Pragma, X-Forwarded-For, X-Forwarded-User-Agent') ->addHeader('Access-Control-Expose-Headers', 'X-Appwrite-Session, X-Fallback-Cookies') ->addHeader('Access-Control-Allow-Origin', $refDomain) ->addHeader('Access-Control-Allow-Credentials', 'true'); @@ -635,7 +635,7 @@ App::options() $response ->addHeader('Server', 'Appwrite') ->addHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, PATCH, DELETE') - ->addHeader('Access-Control-Allow-Headers', 'Origin, Cookie, Set-Cookie, X-Requested-With, Content-Type, Access-Control-Allow-Origin, Access-Control-Request-Headers, Accept, X-Appwrite-Project, X-Appwrite-Key, X-Appwrite-Locale, X-Appwrite-Mode, X-Appwrite-JWT, X-Appwrite-Response-Format, X-Appwrite-Timeout, X-Appwrite-Shared-Tables, X-SDK-Version, X-SDK-Name, X-SDK-Language, X-SDK-Platform, X-SDK-GraphQL, X-Appwrite-ID, X-Appwrite-Timestamp, Content-Range, Range, Cache-Control, Expires, Pragma, X-Appwrite-Session, X-Fallback-Cookies, X-Forwarded-For, X-Forwarded-User-Agent') + ->addHeader('Access-Control-Allow-Headers', 'Origin, Cookie, Set-Cookie, X-Requested-With, Content-Type, Access-Control-Allow-Origin, Access-Control-Request-Headers, Accept, X-Appwrite-Project, X-Appwrite-Key, X-Appwrite-Locale, X-Appwrite-Mode, X-Appwrite-JWT, X-Appwrite-Response-Format, X-Appwrite-Timeout, X-SDK-Version, X-SDK-Name, X-SDK-Language, X-SDK-Platform, X-SDK-GraphQL, X-Appwrite-ID, X-Appwrite-Timestamp, Content-Range, Range, Cache-Control, Expires, Pragma, X-Appwrite-Session, X-Fallback-Cookies, X-Forwarded-For, X-Forwarded-User-Agent') ->addHeader('Access-Control-Expose-Headers', 'X-Appwrite-Session, X-Fallback-Cookies') ->addHeader('Access-Control-Allow-Origin', $origin) ->addHeader('Access-Control-Allow-Credentials', 'true') diff --git a/app/init.php b/app/init.php index d6866f24a..5e9dfa2de 100644 --- a/app/init.php +++ b/app/init.php @@ -143,9 +143,6 @@ const APP_SOCIAL_STACKSHARE = 'https://stackshare.io/appwrite'; const APP_SOCIAL_YOUTUBE = 'https://www.youtube.com/c/appwrite?sub_confirmation=1'; const APP_HOSTNAME_INTERNAL = 'appwrite'; -// Databases -const DATABASE_SHARED_TABLES = 'database_db_fra1_self_hosted_16_0'; - // Database Reconnect const DATABASE_RECONNECT_SLEEP = 2; const DATABASE_RECONNECT_MAX_ATTEMPTS = 10; @@ -1404,7 +1401,7 @@ App::setResource('dbForProject', function (Group $pools, Database $dbForConsole, $dsn = new DSN('mysql://' . $project->getAttribute('database')); } - if ($dsn->getHost() === DATABASE_SHARED_TABLES) { + if ($dsn->getHost() === System::getEnv('_APP_DATABASE_SHARED_TABLES', '')) { $database ->setSharedTables(true) ->setTenant($project->getInternalId()) @@ -1457,7 +1454,7 @@ App::setResource('getProjectDB', function (Group $pools, Database $dbForConsole, ->setMetadata('project', $project->getId()) ->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS); - if ($dsn->getHost() === DATABASE_SHARED_TABLES) { + if ($dsn->getHost() === System::getEnv('_APP_DATABASE_SHARED_TABLES', '')) { $database ->setSharedTables(true) ->setTenant($project->getInternalId()) diff --git a/app/realtime.php b/app/realtime.php index 2904b1db9..cde432741 100644 --- a/app/realtime.php +++ b/app/realtime.php @@ -92,7 +92,7 @@ if (!function_exists("getProjectDB")) { $database = new Database($adapter, getCache()); - if ($dsn->getHost() === DATABASE_SHARED_TABLES) { + if ($dsn->getHost() === System::getEnv('_APP_DATABASE_SHARED_TABLES', '')) { $database ->setSharedTables(true) ->setTenant($project->getInternalId()) diff --git a/app/worker.php b/app/worker.php index 60358ad6b..bca92c433 100644 --- a/app/worker.php +++ b/app/worker.php @@ -93,7 +93,7 @@ Server::setResource('dbForProject', function (Cache $cache, Registry $register, $dsn = new DSN('mysql://' . $project->getAttribute('database')); } - if ($dsn->getHost() === DATABASE_SHARED_TABLES) { + if ($dsn->getHost() === System::getEnv('_APP_DATABASE_SHARED_TABLES', '')) { $database ->setSharedTables(true) ->setTenant($project->getInternalId()) @@ -126,7 +126,7 @@ Server::setResource('getProjectDB', function (Group $pools, Database $dbForConso if (isset($databases[$dsn->getHost()])) { $database = $databases[$dsn->getHost()]; - if ($dsn->getHost() === DATABASE_SHARED_TABLES) { + if ($dsn->getHost() === System::getEnv('_APP_DATABASE_SHARED_TABLES', '')) { $database ->setSharedTables(true) ->setTenant($project->getInternalId()) @@ -150,7 +150,7 @@ Server::setResource('getProjectDB', function (Group $pools, Database $dbForConso $databases[$dsn->getHost()] = $database; - if ($dsn->getHost() === DATABASE_SHARED_TABLES) { + if ($dsn->getHost() === System::getEnv('_APP_DATABASE_SHARED_TABLES', '')) { $database ->setSharedTables(true) ->setTenant($project->getInternalId()) @@ -284,11 +284,6 @@ if (!isset($args[1])) { \array_shift($args); $workerName = $args[0]; -$workerIndex = $args[1] ?? ''; - -if (!empty($workerIndex)) { - $workerName .= '_' . $workerIndex; -} if (\str_starts_with($workerName, 'databases')) { $queueName = System::getEnv('_APP_QUEUE_NAME', 'database_db_main'); diff --git a/composer.json b/composer.json index 205fe308f..192b31182 100644 --- a/composer.json +++ b/composer.json @@ -47,7 +47,7 @@ "utopia-php/abuse": "0.37.*", "utopia-php/analytics": "0.10.*", "utopia-php/audit": "0.39.*", - "utopia-php/cache": "0.9.*", + "utopia-php/cache": "0.10.*", "utopia-php/cli": "0.15.*", "utopia-php/config": "0.2.*", "utopia-php/database": "0.49.*", @@ -58,10 +58,10 @@ "utopia-php/image": "0.6.*", "utopia-php/locale": "0.4.*", "utopia-php/logger": "0.5.*", - "utopia-php/messaging": "0.11.*", + "utopia-php/messaging": "0.12.*", "utopia-php/migration": "0.4.*", "utopia-php/orchestration": "0.9.*", - "utopia-php/platform": "0.5.*", + "utopia-php/platform": "0.7.*", "utopia-php/pools": "0.5.*", "utopia-php/preloader": "0.2.*", "utopia-php/queue": "0.7.*", diff --git a/composer.lock b/composer.lock index bfe8fc073..aee6c86d5 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "53996479cd4ba0c73dbc72d46b240be0", + "content-hash": "e002600539435ca8eaaace6e73b4004d", "packages": [ { "name": "adhocore/jwt", @@ -1427,16 +1427,16 @@ }, { "name": "utopia-php/abuse", - "version": "0.37.0", + "version": "0.37.1", "source": { "type": "git", "url": "https://github.com/utopia-php/abuse.git", - "reference": "2de5c12886cbd516e511e559afdd9e615d871062" + "reference": "4dfcff4754c7804d1a70039792c0f2d59a5cc981" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/abuse/zipball/2de5c12886cbd516e511e559afdd9e615d871062", - "reference": "2de5c12886cbd516e511e559afdd9e615d871062", + "url": "https://api.github.com/repos/utopia-php/abuse/zipball/4dfcff4754c7804d1a70039792c0f2d59a5cc981", + "reference": "4dfcff4754c7804d1a70039792c0f2d59a5cc981", "shasum": "" }, "require": { @@ -1470,9 +1470,9 @@ ], "support": { "issues": "https://github.com/utopia-php/abuse/issues", - "source": "https://github.com/utopia-php/abuse/tree/0.37.0" + "source": "https://github.com/utopia-php/abuse/tree/0.37.1" }, - "time": "2024-03-06T21:20:27+00:00" + "time": "2024-06-05T18:03:59+00:00" }, { "name": "utopia-php/analytics", @@ -1522,16 +1522,16 @@ }, { "name": "utopia-php/audit", - "version": "0.39.0", + "version": "0.39.1", "source": { "type": "git", "url": "https://github.com/utopia-php/audit.git", - "reference": "f0bc15012e05cc0b9dde012ab27d25f193768a2c" + "reference": "7ea91e0ceea7b94293612fea94022b73315677c2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/audit/zipball/f0bc15012e05cc0b9dde012ab27d25f193768a2c", - "reference": "f0bc15012e05cc0b9dde012ab27d25f193768a2c", + "url": "https://api.github.com/repos/utopia-php/audit/zipball/7ea91e0ceea7b94293612fea94022b73315677c2", + "reference": "7ea91e0ceea7b94293612fea94022b73315677c2", "shasum": "" }, "require": { @@ -1563,22 +1563,22 @@ ], "support": { "issues": "https://github.com/utopia-php/audit/issues", - "source": "https://github.com/utopia-php/audit/tree/0.39.0" + "source": "https://github.com/utopia-php/audit/tree/0.39.1" }, - "time": "2024-03-06T21:20:37+00:00" + "time": "2024-06-05T19:28:22+00:00" }, { "name": "utopia-php/cache", - "version": "0.9.1", + "version": "0.10.0", "source": { "type": "git", "url": "https://github.com/utopia-php/cache.git", - "reference": "552b4c554bb14d0c529631ce304cdf4a2b9d06a6" + "reference": "313bcdfbb166f75c2c205a59d1467cead63a9626" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/cache/zipball/552b4c554bb14d0c529631ce304cdf4a2b9d06a6", - "reference": "552b4c554bb14d0c529631ce304cdf4a2b9d06a6", + "url": "https://api.github.com/repos/utopia-php/cache/zipball/313bcdfbb166f75c2c205a59d1467cead63a9626", + "reference": "313bcdfbb166f75c2c205a59d1467cead63a9626", "shasum": "" }, "require": { @@ -1613,9 +1613,9 @@ ], "support": { "issues": "https://github.com/utopia-php/cache/issues", - "source": "https://github.com/utopia-php/cache/tree/0.9.1" + "source": "https://github.com/utopia-php/cache/tree/0.10.0" }, - "time": "2024-03-19T17:07:20+00:00" + "time": "2024-06-05T16:40:43+00:00" }, { "name": "utopia-php/cli", @@ -1719,23 +1719,23 @@ }, { "name": "utopia-php/database", - "version": "0.49.10", + "version": "0.49.12", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "216209121bc97a2010f67a39c561fafe1e936bec" + "reference": "45def2f7c6bc5f631dbb67e5df0e8e7331af5f63" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/216209121bc97a2010f67a39c561fafe1e936bec", - "reference": "216209121bc97a2010f67a39c561fafe1e936bec", + "url": "https://api.github.com/repos/utopia-php/database/zipball/45def2f7c6bc5f631dbb67e5df0e8e7331af5f63", + "reference": "45def2f7c6bc5f631dbb67e5df0e8e7331af5f63", "shasum": "" }, "require": { "ext-mbstring": "*", "ext-pdo": "*", "php": ">=8.0", - "utopia-php/cache": "0.9.*", + "utopia-php/cache": "0.10.*", "utopia-php/framework": "0.33.*", "utopia-php/mongo": "0.3.*" }, @@ -1769,9 +1769,9 @@ ], "support": { "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/0.49.10" + "source": "https://github.com/utopia-php/database/tree/0.49.12" }, - "time": "2024-05-20T02:14:20+00:00" + "time": "2024-06-05T16:52:59+00:00" }, { "name": "utopia-php/domains", @@ -2119,16 +2119,16 @@ }, { "name": "utopia-php/messaging", - "version": "0.11.0", + "version": "0.12.0", "source": { "type": "git", "url": "https://github.com/utopia-php/messaging.git", - "reference": "b499c3ad11af711c28252c62d83f24e6106a2154" + "reference": "6e466d3511981291843c6ebf9ce3f44fc75e37b0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/messaging/zipball/b499c3ad11af711c28252c62d83f24e6106a2154", - "reference": "b499c3ad11af711c28252c62d83f24e6106a2154", + "url": "https://api.github.com/repos/utopia-php/messaging/zipball/6e466d3511981291843c6ebf9ce3f44fc75e37b0", + "reference": "6e466d3511981291843c6ebf9ce3f44fc75e37b0", "shasum": "" }, "require": { @@ -2164,9 +2164,9 @@ ], "support": { "issues": "https://github.com/utopia-php/messaging/issues", - "source": "https://github.com/utopia-php/messaging/tree/0.11.0" + "source": "https://github.com/utopia-php/messaging/tree/0.12.0" }, - "time": "2024-05-08T17:10:02+00:00" + "time": "2024-05-30T14:58:25+00:00" }, { "name": "utopia-php/migration", @@ -2327,16 +2327,16 @@ }, { "name": "utopia-php/platform", - "version": "0.5.2", + "version": "0.7.0", "source": { "type": "git", "url": "https://github.com/utopia-php/platform.git", - "reference": "b9feabc79b92dc2b05683a986ad43bce5c1583e3" + "reference": "beeea0f2c9bce14a6869fc5c87a1047cdecb5c52" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/platform/zipball/b9feabc79b92dc2b05683a986ad43bce5c1583e3", - "reference": "b9feabc79b92dc2b05683a986ad43bce5c1583e3", + "url": "https://api.github.com/repos/utopia-php/platform/zipball/beeea0f2c9bce14a6869fc5c87a1047cdecb5c52", + "reference": "beeea0f2c9bce14a6869fc5c87a1047cdecb5c52", "shasum": "" }, "require": { @@ -2344,7 +2344,8 @@ "ext-redis": "*", "php": ">=8.0", "utopia-php/cli": "0.15.*", - "utopia-php/framework": "0.33.*" + "utopia-php/framework": "0.33.*", + "utopia-php/queue": "0.7.*" }, "require-dev": { "laravel/pint": "1.2.*", @@ -2370,9 +2371,9 @@ ], "support": { "issues": "https://github.com/utopia-php/platform/issues", - "source": "https://github.com/utopia-php/platform/tree/0.5.2" + "source": "https://github.com/utopia-php/platform/tree/0.7.0" }, - "time": "2024-05-22T12:50:35+00:00" + "time": "2024-05-08T17:00:55+00:00" }, { "name": "utopia-php/pools", @@ -2755,22 +2756,22 @@ }, { "name": "utopia-php/vcs", - "version": "0.6.6", + "version": "0.6.7", "source": { "type": "git", "url": "https://github.com/utopia-php/vcs.git", - "reference": "e538264cfee5e3efdfe1771efba04750cf20b2c4" + "reference": "8d8ff1ac68e991b95adb6f91fcde8f9bb8f24974" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/vcs/zipball/e538264cfee5e3efdfe1771efba04750cf20b2c4", - "reference": "e538264cfee5e3efdfe1771efba04750cf20b2c4", + "url": "https://api.github.com/repos/utopia-php/vcs/zipball/8d8ff1ac68e991b95adb6f91fcde8f9bb8f24974", + "reference": "8d8ff1ac68e991b95adb6f91fcde8f9bb8f24974", "shasum": "" }, "require": { "adhocore/jwt": "^1.1", "php": ">=8.0", - "utopia-php/cache": "^0.9.0", + "utopia-php/cache": "^0.10.0", "utopia-php/framework": "0.*.*" }, "require-dev": { @@ -2798,9 +2799,9 @@ ], "support": { "issues": "https://github.com/utopia-php/vcs/issues", - "source": "https://github.com/utopia-php/vcs/tree/0.6.6" + "source": "https://github.com/utopia-php/vcs/tree/0.6.7" }, - "time": "2024-05-17T09:36:30+00:00" + "time": "2024-06-05T17:38:29+00:00" }, { "name": "utopia-php/websocket", @@ -3824,16 +3825,16 @@ }, { "name": "phpstan/phpdoc-parser", - "version": "1.29.0", + "version": "1.29.1", "source": { "type": "git", "url": "https://github.com/phpstan/phpdoc-parser.git", - "reference": "536889f2b340489d328f5ffb7b02bb6b183ddedc" + "reference": "fcaefacf2d5c417e928405b71b400d4ce10daaf4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/536889f2b340489d328f5ffb7b02bb6b183ddedc", - "reference": "536889f2b340489d328f5ffb7b02bb6b183ddedc", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/fcaefacf2d5c417e928405b71b400d4ce10daaf4", + "reference": "fcaefacf2d5c417e928405b71b400d4ce10daaf4", "shasum": "" }, "require": { @@ -3865,9 +3866,9 @@ "description": "PHPDoc parser with support for nullable, intersection and generic types", "support": { "issues": "https://github.com/phpstan/phpdoc-parser/issues", - "source": "https://github.com/phpstan/phpdoc-parser/tree/1.29.0" + "source": "https://github.com/phpstan/phpdoc-parser/tree/1.29.1" }, - "time": "2024-05-06T12:04:23+00:00" + "time": "2024-05-31T08:52:43+00:00" }, { "name": "phpunit/php-code-coverage", diff --git a/docker-compose.yml b/docker-compose.yml index dbc262f18..990da17d6 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -189,6 +189,7 @@ services: - _APP_CONSOLE_COUNTRIES_DENYLIST - _APP_EXPERIMENT_LOGGING_PROVIDER - _APP_EXPERIMENT_LOGGING_CONFIG + - _APP_DATABASE_SHARED_TABLES appwrite-realtime: entrypoint: realtime @@ -238,6 +239,7 @@ services: - _APP_USAGE_STATS - _APP_LOGGING_PROVIDER - _APP_LOGGING_CONFIG + - _APP_DATABASE_SHARED_TABLES appwrite-worker-audits: entrypoint: worker-audits @@ -267,6 +269,7 @@ services: - _APP_DB_PASS - _APP_LOGGING_PROVIDER - _APP_LOGGING_CONFIG + - _APP_DATABASE_SHARED_TABLES appwrite-worker-webhooks: entrypoint: worker-webhooks @@ -299,6 +302,7 @@ services: - _APP_LOGGING_PROVIDER - _APP_LOGGING_CONFIG - _APP_WEBHOOK_MAX_FAILED_ATTEMPTS + - _APP_DATABASE_SHARED_TABLES appwrite-worker-deletes: entrypoint: worker-deletes @@ -356,6 +360,7 @@ services: - _APP_LOGGING_CONFIG - _APP_EXECUTOR_SECRET - _APP_EXECUTOR_HOST + - _APP_DATABASE_SHARED_TABLES appwrite-worker-databases: entrypoint: worker-databases @@ -387,6 +392,7 @@ services: - _APP_LOGGING_CONFIG - _APP_WORKERS_NUM - _APP_QUEUE_NAME + - _APP_DATABASE_SHARED_TABLES appwrite-worker-builds: entrypoint: worker-builds @@ -452,6 +458,7 @@ services: - _APP_STORAGE_WASABI_SECRET - _APP_STORAGE_WASABI_REGION - _APP_STORAGE_WASABI_BUCKET + - _APP_DATABASE_SHARED_TABLES appwrite-worker-certificates: entrypoint: worker-certificates @@ -487,6 +494,7 @@ services: - _APP_DB_PASS - _APP_LOGGING_PROVIDER - _APP_LOGGING_CONFIG + - _APP_DATABASE_SHARED_TABLES appwrite-worker-functions: entrypoint: worker-functions @@ -526,6 +534,7 @@ services: - _APP_DOCKER_HUB_PASSWORD - _APP_LOGGING_CONFIG - _APP_LOGGING_PROVIDER + - _APP_DATABASE_SHARED_TABLES appwrite-worker-mails: entrypoint: worker-mails @@ -560,6 +569,7 @@ services: - _APP_LOGGING_CONFIG - _APP_DOMAIN - _APP_OPTIONS_FORCE_HTTPS + - _APP_DATABASE_SHARED_TABLES appwrite-worker-messaging: entrypoint: worker-messaging @@ -614,6 +624,7 @@ services: - _APP_STORAGE_WASABI_SECRET - _APP_STORAGE_WASABI_REGION - _APP_STORAGE_WASABI_BUCKET + - _APP_DATABASE_SHARED_TABLES appwrite-worker-migrations: entrypoint: worker-migrations @@ -648,6 +659,7 @@ services: - _APP_DB_PASS - _APP_LOGGING_PROVIDER - _APP_LOGGING_CONFIG + - _APP_DATABASE_SHARED_TABLES appwrite-task-maintenance: entrypoint: maintenance @@ -685,6 +697,7 @@ services: - _APP_MAINTENANCE_RETENTION_USAGE_HOURLY - _APP_MAINTENANCE_RETENTION_SCHEDULES - _APP_MAINTENANCE_DELAY + - _APP_DATABASE_SHARED_TABLES appwrite-worker-usage: entrypoint: worker-usage @@ -716,6 +729,7 @@ services: - _APP_LOGGING_PROVIDER - _APP_LOGGING_CONFIG - _APP_USAGE_AGGREGATION_INTERVAL + - _APP_DATABASE_SHARED_TABLES appwrite-worker-usage-dump: entrypoint: worker-usage-dump @@ -747,6 +761,7 @@ services: - _APP_LOGGING_PROVIDER - _APP_LOGGING_CONFIG - _APP_USAGE_AGGREGATION_INTERVAL + - _APP_DATABASE_SHARED_TABLES appwrite-task-scheduler-functions: entrypoint: schedule-functions @@ -774,6 +789,7 @@ services: - _APP_DB_SCHEMA - _APP_DB_USER - _APP_DB_PASS + - _APP_DATABASE_SHARED_TABLES appwrite-task-scheduler-messages: entrypoint: schedule-messages @@ -801,6 +817,7 @@ services: - _APP_DB_SCHEMA - _APP_DB_USER - _APP_DB_PASS + - _APP_DATABASE_SHARED_TABLES appwrite-assistant: container_name: appwrite-assistant @@ -899,20 +916,7 @@ services: - MYSQL_USER=${_APP_DB_USER} - MYSQL_PASSWORD=${_APP_DB_PASS} - MARIADB_AUTO_UPGRADE=1 - command: "mysqld --innodb-flush-method=fsync" # add ' --query_cache_size=0' for DB tests - # command: mv /var/lib/mysql/ib_logfile0 /var/lib/mysql/ib_logfile0.bu && mv /var/lib/mysql/ib_logfile1 /var/lib/mysql/ib_logfile1.bu - - # smtp: - # image: appwrite/smtp:1.2.0 - # container_name: appwrite-smtp - # restart: unless-stopped - # networks: - # - appwrite - # environment: - # - LOCAL_DOMAINS=@ - # - RELAY_FROM_HOSTS=192.168.0.0/16 ; *.yourdomain.com - # - SMARTHOST_HOST=smtp - # - SMARTHOST_PORT=587 + command: "mysqld --innodb-flush-method=fsync" redis: image: redis:7.2.4-alpine @@ -930,14 +934,6 @@ services: volumes: - appwrite-redis:/data:rw - # clamav: - # image: appwrite/clamav:1.2.0 - # container_name: appwrite-clamav - # networks: - # - appwrite - # volumes: - # - appwrite-uploads:/storage/uploads - # Dev Tools Start ------------------------------------------------------------------------------------------ # # The Appwrite Team uses the following tools to help debug, monitor and diagnose the Appwrite stack diff --git a/docs/references/account/update-mfa-authenticator.md b/docs/references/account/update-mfa-authenticator.md index 253826a89..23a325144 100644 --- a/docs/references/account/update-mfa-authenticator.md +++ b/docs/references/account/update-mfa-authenticator.md @@ -1 +1 @@ -Verify an authenticator app after adding it using the [add authenticator](/docs/references/cloud/client-web/account#createMfaAuthenticator) method. add \ No newline at end of file +Verify an authenticator app after adding it using the [add authenticator](/docs/references/cloud/client-web/account#createMfaAuthenticator) method. \ No newline at end of file diff --git a/src/Appwrite/Platform/Appwrite.php b/src/Appwrite/Platform/Appwrite.php index 05224799f..6b3eb077f 100644 --- a/src/Appwrite/Platform/Appwrite.php +++ b/src/Appwrite/Platform/Appwrite.php @@ -2,15 +2,13 @@ namespace Appwrite\Platform; -use Appwrite\Platform\Services\Tasks; -use Appwrite\Platform\Services\Workers; +use Appwrite\Platform\Modules\Core; use Utopia\Platform\Platform; class Appwrite extends Platform { public function __construct() { - $this->addService('tasks', new Tasks()); - $this->addService('workers', new Workers()); + parent::__construct(new Core()); } } diff --git a/src/Appwrite/Platform/Modules/Core.php b/src/Appwrite/Platform/Modules/Core.php new file mode 100644 index 000000000..859ca3b52 --- /dev/null +++ b/src/Appwrite/Platform/Modules/Core.php @@ -0,0 +1,17 @@ +addService('tasks', new Tasks()); + $this->addService('workers', new Workers()); + } + +} diff --git a/src/Appwrite/Platform/Services/Tasks.php b/src/Appwrite/Platform/Services/Tasks.php index ac1f99eec..31465d2f2 100644 --- a/src/Appwrite/Platform/Services/Tasks.php +++ b/src/Appwrite/Platform/Services/Tasks.php @@ -22,7 +22,7 @@ class Tasks extends Service { public function __construct() { - $this->type = self::TYPE_CLI; + $this->type = Service::TYPE_TASK; $this ->addAction(Doctor::getName(), new Doctor()) ->addAction(Install::getName(), new Install()) diff --git a/src/Appwrite/Platform/Services/Workers.php b/src/Appwrite/Platform/Services/Workers.php index 62a7fcf3f..0e79f4257 100644 --- a/src/Appwrite/Platform/Services/Workers.php +++ b/src/Appwrite/Platform/Services/Workers.php @@ -20,7 +20,7 @@ class Workers extends Service { public function __construct() { - $this->type = self::TYPE_WORKER; + $this->type = Service::TYPE_WORKER; $this ->addAction(Audits::getName(), new Audits()) ->addAction(Builds::getName(), new Builds()) diff --git a/src/Appwrite/Platform/Workers/Deletes.php b/src/Appwrite/Platform/Workers/Deletes.php index 49b41da49..d54f3f507 100644 --- a/src/Appwrite/Platform/Workers/Deletes.php +++ b/src/Appwrite/Platform/Workers/Deletes.php @@ -498,14 +498,14 @@ class Deletes extends Action $collections = $dbForProject->listCollections($limit); foreach ($collections as $collection) { - if ($dsn->getHost() !== DATABASE_SHARED_TABLES || !\in_array($collection->getId(), $projectCollectionIds)) { + if ($dsn->getHost() !== System::getEnv('_APP_DATABASE_SHARED_TABLES', '') || !\in_array($collection->getId(), $projectCollectionIds)) { $dbForProject->deleteCollection($collection->getId()); } else { $this->deleteByGroup($collection->getId(), [], database: $dbForProject); } } - if ($dsn->getHost() === DATABASE_SHARED_TABLES) { + if ($dsn->getHost() === System::getEnv('_APP_DATABASE_SHARED_TABLES', '')) { $collectionsIds = \array_map(fn ($collection) => $collection->getId(), $collections); if (empty(\array_diff($collectionsIds, $projectCollectionIds))) { @@ -554,7 +554,7 @@ class Deletes extends Action ], $dbForConsole); // Delete metadata table - if ($dsn->getHost() !== DATABASE_SHARED_TABLES) { + if ($dsn->getHost() !== System::getEnv('_APP_DATABASE_SHARED_TABLES', '')) { $dbForProject->deleteCollection('_metadata'); } else { $this->deleteByGroup('_metadata', [], $dbForProject); diff --git a/src/Appwrite/Platform/Workers/Messaging.php b/src/Appwrite/Platform/Workers/Messaging.php index 19d2c5a0f..6f642fabb 100644 --- a/src/Appwrite/Platform/Workers/Messaging.php +++ b/src/Appwrite/Platform/Workers/Messaging.php @@ -399,7 +399,10 @@ class Messaging extends Action 'credentials' => match ($host) { 'twilio' => [ 'accountSid' => $user, - 'authToken' => $password + 'authToken' => $password, + // Twilio Messaging Service SIDs always start with MG + // https://www.twilio.com/docs/messaging/services + 'messagingServiceSid' => \str_starts_with($from, 'MG') ? $from : null ], 'textmagic' => [ 'username' => $user, @@ -420,9 +423,14 @@ class Messaging extends Action ], default => null }, - 'options' => [ - 'from' => $from - ] + 'options' => match ($host) { + 'twilio' => [ + 'from' => \str_starts_with($from, 'MG') ? null : $from + ], + default => [ + 'from' => $from + ] + } ]); $adapter = $this->getSmsAdapter($provider); @@ -465,7 +473,7 @@ class Messaging extends Action return match ($provider->getAttribute('provider')) { 'mock' => new Mock('username', 'password'), - 'twilio' => new Twilio($credentials['accountSid'], $credentials['authToken']), + 'twilio' => new Twilio($credentials['accountSid'], $credentials['authToken'], null, isset($credentials['messagingServiceSid']) ? $credentials['messagingServiceSid'] : null), 'textmagic' => new TextMagic($credentials['username'], $credentials['apiKey']), 'telesign' => new Telesign($credentials['customerId'], $credentials['apiKey']), 'msg91' => new Msg91($credentials['senderId'], $credentials['authKey'], $credentials['templateId']), diff --git a/tests/e2e/General/HTTPTest.php b/tests/e2e/General/HTTPTest.php index 0bb5ca465..92bc52561 100644 --- a/tests/e2e/General/HTTPTest.php +++ b/tests/e2e/General/HTTPTest.php @@ -31,7 +31,7 @@ class HTTPTest extends Scope $this->assertEquals(204, $response['headers']['status-code']); $this->assertEquals('Appwrite', $response['headers']['server']); $this->assertEquals('GET, POST, PUT, PATCH, DELETE', $response['headers']['access-control-allow-methods']); - $this->assertEquals('Origin, Cookie, Set-Cookie, X-Requested-With, Content-Type, Access-Control-Allow-Origin, Access-Control-Request-Headers, Accept, X-Appwrite-Project, X-Appwrite-Key, X-Appwrite-Locale, X-Appwrite-Mode, X-Appwrite-JWT, X-Appwrite-Response-Format, X-Appwrite-Timeout, X-Appwrite-Shared-Tables, X-SDK-Version, X-SDK-Name, X-SDK-Language, X-SDK-Platform, X-SDK-GraphQL, X-Appwrite-ID, X-Appwrite-Timestamp, Content-Range, Range, Cache-Control, Expires, Pragma, X-Appwrite-Session, X-Fallback-Cookies, X-Forwarded-For, X-Forwarded-User-Agent', $response['headers']['access-control-allow-headers']); + $this->assertEquals('Origin, Cookie, Set-Cookie, X-Requested-With, Content-Type, Access-Control-Allow-Origin, Access-Control-Request-Headers, Accept, X-Appwrite-Project, X-Appwrite-Key, X-Appwrite-Locale, X-Appwrite-Mode, X-Appwrite-JWT, X-Appwrite-Response-Format, X-Appwrite-Timeout, X-SDK-Version, X-SDK-Name, X-SDK-Language, X-SDK-Platform, X-SDK-GraphQL, X-Appwrite-ID, X-Appwrite-Timestamp, Content-Range, Range, Cache-Control, Expires, Pragma, X-Appwrite-Session, X-Fallback-Cookies, X-Forwarded-For, X-Forwarded-User-Agent', $response['headers']['access-control-allow-headers']); $this->assertEquals('X-Appwrite-Session, X-Fallback-Cookies', $response['headers']['access-control-expose-headers']); $this->assertEquals('http://localhost', $response['headers']['access-control-allow-origin']); $this->assertEquals('true', $response['headers']['access-control-allow-credentials']); diff --git a/tests/e2e/Services/Functions/FunctionsCustomClientTest.php b/tests/e2e/Services/Functions/FunctionsCustomClientTest.php index 119c1a222..966f948ce 100644 --- a/tests/e2e/Services/Functions/FunctionsCustomClientTest.php +++ b/tests/e2e/Services/Functions/FunctionsCustomClientTest.php @@ -2,6 +2,7 @@ namespace Tests\E2E\Services\Functions; +use Appwrite\Tests\Retry; use CURLFile; use Tests\E2E\Client; use Tests\E2E\Scopes\ProjectCustom; @@ -42,6 +43,7 @@ class FunctionsCustomClientTest extends Scope return []; } + #[Retry(count: 2)] public function testCreateExecution(): array { /** diff --git a/tests/e2e/Services/Functions/FunctionsCustomServerTest.php b/tests/e2e/Services/Functions/FunctionsCustomServerTest.php index 7d68f7562..9031faf8d 100644 --- a/tests/e2e/Services/Functions/FunctionsCustomServerTest.php +++ b/tests/e2e/Services/Functions/FunctionsCustomServerTest.php @@ -988,7 +988,7 @@ class FunctionsCustomServerTest extends Scope $this->assertEquals($executions['body']['executions'][0]['logs'], ''); $this->assertStringContainsString('timed out', $executions['body']['executions'][0]['errors']); - sleep(70); //wait for scheduled execution to be created and time out + sleep(75); // Wait for scheduled execution to be created and time out $executions = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/executions', array_merge([ 'content-type' => 'application/json', @@ -999,12 +999,6 @@ class FunctionsCustomServerTest extends Scope $this->assertCount(2, $executions['body']['executions']); $this->assertIsArray($executions['body']['executions']); $this->assertEquals($executions['body']['executions'][1]['trigger'], 'schedule'); - $this->assertEquals($executions['body']['executions'][1]['status'], 'failed'); - $this->assertEquals($executions['body']['executions'][1]['responseStatusCode'], 500); - $this->assertLessThan(20, $executions['body']['executions'][1]['duration']); - $this->assertEquals($executions['body']['executions'][1]['responseBody'], ''); - $this->assertEquals($executions['body']['executions'][1]['logs'], ''); - $this->assertStringContainsString('timed out', $executions['body']['executions'][1]['errors']); // Cleanup : Delete function $response = $this->client->call(Client::METHOD_DELETE, '/functions/' . $functionId, [ diff --git a/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php b/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php index 8cdd32550..2af0c8f1c 100644 --- a/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php +++ b/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php @@ -9,7 +9,6 @@ use Tests\E2E\General\UsageTest; use Tests\E2E\Scopes\ProjectConsole; use Tests\E2E\Scopes\Scope; use Tests\E2E\Scopes\SideClient; -use Utopia\Database\Database; use Utopia\Database\DateTime; use Utopia\Database\Document; use Utopia\Database\Helpers\ID; @@ -3494,504 +3493,4 @@ class ProjectsConsoleClientTest extends Scope return $data; } - - public function testTenantIsolation(): void - { - // Create a team and a project - $team = $this->client->call(Client::METHOD_POST, '/teams', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'teamId' => ID::unique(), - 'name' => 'Amazing Team', - ]); - - $teamId = $team['body']['$id']; - - // Project-level isolation - $project1 = $this->client->call(Client::METHOD_POST, '/projects', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-shared-tables' => false - ], $this->getHeaders()), [ - 'projectId' => ID::unique(), - 'name' => 'Amazing Project', - 'teamId' => $teamId, - 'region' => 'default' - ]); - - // Application level isolation (shared tables) - $project2 = $this->client->call(Client::METHOD_POST, '/projects', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-shared-tables' => true - ], $this->getHeaders()), [ - 'projectId' => ID::unique(), - 'name' => 'Amazing Project', - 'teamId' => $teamId, - 'region' => 'default' - ]); - - // Project-level isolation - $project3 = $this->client->call(Client::METHOD_POST, '/projects', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-shared-tables' => false - ], $this->getHeaders()), [ - 'projectId' => ID::unique(), - 'name' => 'Amazing Project', - 'teamId' => $teamId, - 'region' => 'default' - ]); - - // Application level isolation (shared tables) - $project4 = $this->client->call(Client::METHOD_POST, '/projects', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-shared-tables' => true - ], $this->getHeaders()), [ - 'projectId' => ID::unique(), - 'name' => 'Amazing Project', - 'teamId' => $teamId, - 'region' => 'default' - ]); - - // Create and API key in each project - $key1 = $this->client->call(Client::METHOD_POST, '/projects/' . $project1['body']['$id'] . '/keys', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'name' => 'Key Test', - 'scopes' => ['databases.read', 'databases.write', 'collections.read', 'collections.write', 'attributes.read', 'attributes.write', 'indexes.read', 'indexes.write', 'documents.read', 'documents.write', 'users.read', 'users.write'], - ]); - - $key2 = $this->client->call(Client::METHOD_POST, '/projects/' . $project2['body']['$id'] . '/keys', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'name' => 'Key Test', - 'scopes' => ['databases.read', 'databases.write', 'collections.read', 'collections.write', 'attributes.read', 'attributes.write', 'indexes.read', 'indexes.write', 'documents.read', 'documents.write', 'users.read', 'users.write'], - ]); - - $key3 = $this->client->call(Client::METHOD_POST, '/projects/' . $project3['body']['$id'] . '/keys', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'name' => 'Key Test', - 'scopes' => ['databases.read', 'databases.write', 'collections.read', 'collections.write', 'attributes.read', 'attributes.write', 'indexes.read', 'indexes.write', 'documents.read', 'documents.write', 'users.read', 'users.write'], - ]); - - $key4 = $this->client->call(Client::METHOD_POST, '/projects/' . $project4['body']['$id'] . '/keys', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'name' => 'Key Test', - 'scopes' => ['databases.read', 'databases.write', 'collections.read', 'collections.write', 'attributes.read', 'attributes.write', 'indexes.read', 'indexes.write', 'documents.read', 'documents.write', 'users.read', 'users.write'], - ]); - - // Create a database in each project - $database1 = $this->client->call(Client::METHOD_POST, '/databases', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $project1['body']['$id'], - 'x-appwrite-key' => $key1['body']['secret'] - ], [ - 'databaseId' => ID::unique(), - 'name' => 'Amazing Database', - ]); - - $database2 = $this->client->call(Client::METHOD_POST, '/databases', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $project2['body']['$id'], - 'x-appwrite-key' => $key2['body']['secret'] - ], [ - 'databaseId' => ID::unique(), - 'name' => 'Amazing Database', - ]); - - $database3 = $this->client->call(Client::METHOD_POST, '/databases', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $project3['body']['$id'], - 'x-appwrite-key' => $key3['body']['secret'] - ], [ - 'databaseId' => ID::unique(), - 'name' => 'Amazing Database', - ]); - - $database4 = $this->client->call(Client::METHOD_POST, '/databases', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $project4['body']['$id'], - 'x-appwrite-key' => $key4['body']['secret'] - ], [ - 'databaseId' => ID::unique(), - 'name' => 'Amazing Database', - ]); - - // Create a collection in each project - $collection1 = $this->client->call(Client::METHOD_POST, '/databases/' . $database1['body']['$id'] . '/collections', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $project1['body']['$id'], - 'x-appwrite-key' => $key1['body']['secret'] - ], [ - 'databaseId' => $database1['body']['$id'], - 'collectionId' => ID::unique(), - 'name' => 'Amazing Collection', - ]); - - $collection2 = $this->client->call(Client::METHOD_POST, '/databases/' . $database2['body']['$id'] . '/collections', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $project2['body']['$id'], - 'x-appwrite-key' => $key2['body']['secret'] - ], [ - 'databaseId' => $database2['body']['$id'], - 'collectionId' => ID::unique(), - 'name' => 'Amazing Collection', - ]); - - $collection3 = $this->client->call(Client::METHOD_POST, '/databases/' . $database3['body']['$id'] . '/collections', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $project3['body']['$id'], - 'x-appwrite-key' => $key3['body']['secret'] - ], [ - 'databaseId' => $database3['body']['$id'], - 'collectionId' => ID::unique(), - 'name' => 'Amazing Collection', - ]); - - $collection4 = $this->client->call(Client::METHOD_POST, '/databases/' . $database4['body']['$id'] . '/collections', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $project4['body']['$id'], - 'x-appwrite-key' => $key4['body']['secret'] - ], [ - 'databaseId' => $database4['body']['$id'], - 'collectionId' => ID::unique(), - 'name' => 'Amazing Collection', - ]); - - // Create an attribute in each project - $attribute1 = $this->client->call(Client::METHOD_POST, '/databases/' . $database1['body']['$id'] . '/collections/' . $collection1['body']['$id'] . '/attributes/string', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $project1['body']['$id'], - 'x-appwrite-key' => $key1['body']['secret'] - ], [ - 'databaseId' => $database1['body']['$id'], - 'collectionId' => $collection1['body']['$id'], - 'key' => ID::unique(), - 'size' => 255, - 'required' => true - ]); - - $attribute2 = $this->client->call(Client::METHOD_POST, '/databases/' . $database2['body']['$id'] . '/collections/' . $collection2['body']['$id'] . '/attributes/string', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $project2['body']['$id'], - 'x-appwrite-key' => $key2['body']['secret'] - ], [ - 'databaseId' => $database2['body']['$id'], - 'collectionId' => $collection2['body']['$id'], - 'key' => ID::unique(), - 'size' => 255, - 'required' => true - ]); - - $attribute3 = $this->client->call(Client::METHOD_POST, '/databases/' . $database3['body']['$id'] . '/collections/' . $collection3['body']['$id'] . '/attributes/string', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $project3['body']['$id'], - 'x-appwrite-key' => $key3['body']['secret'] - ], [ - 'databaseId' => $database3['body']['$id'], - 'collectionId' => $collection3['body']['$id'], - 'key' => ID::unique(), - 'size' => 255, - 'required' => true - ]); - - $attribute4 = $this->client->call(Client::METHOD_POST, '/databases/' . $database4['body']['$id'] . '/collections/' . $collection4['body']['$id'] . '/attributes/string', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $project4['body']['$id'], - 'x-appwrite-key' => $key4['body']['secret'] - ], [ - 'databaseId' => $database4['body']['$id'], - 'collectionId' => $collection4['body']['$id'], - 'key' => ID::unique(), - 'size' => 255, - 'required' => true - ]); - - // Wait for attributes - \sleep(2); - - // Create an index in each project - $index1 = $this->client->call(Client::METHOD_POST, '/databases/' . $database1['body']['$id'] . '/collections/' . $collection1['body']['$id'] . '/indexes', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $project1['body']['$id'], - 'x-appwrite-key' => $key1['body']['secret'] - ], [ - 'databaseId' => $database1['body']['$id'], - 'collectionId' => $collection1['body']['$id'], - 'key' => ID::unique(), - 'type' => Database::INDEX_KEY, - 'attributes' => [$attribute1['body']['key']], - ]); - - $index2 = $this->client->call(Client::METHOD_POST, '/databases/' . $database2['body']['$id'] . '/collections/' . $collection2['body']['$id'] . '/indexes', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $project2['body']['$id'], - 'x-appwrite-key' => $key2['body']['secret'] - ], [ - 'databaseId' => $database2['body']['$id'], - 'collectionId' => $collection2['body']['$id'], - 'key' => ID::unique(), - 'type' => Database::INDEX_KEY, - 'attributes' => [$attribute2['body']['key']], - ]); - - $index3 = $this->client->call(Client::METHOD_POST, '/databases/' . $database3['body']['$id'] . '/collections/' . $collection3['body']['$id'] . '/indexes', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $project3['body']['$id'], - 'x-appwrite-key' => $key3['body']['secret'] - ], [ - 'databaseId' => $database3['body']['$id'], - 'collectionId' => $collection3['body']['$id'], - 'key' => ID::unique(), - 'type' => Database::INDEX_KEY, - 'attributes' => [$attribute3['body']['key']], - ]); - - $index4 = $this->client->call(Client::METHOD_POST, '/databases/' . $database4['body']['$id'] . '/collections/' . $collection4['body']['$id'] . '/indexes', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $project4['body']['$id'], - 'x-appwrite-key' => $key4['body']['secret'] - ], [ - 'databaseId' => $database4['body']['$id'], - 'collectionId' => $collection4['body']['$id'], - 'key' => ID::unique(), - 'type' => Database::INDEX_KEY, - 'attributes' => [$attribute4['body']['key']], - ]); - - // Wait for indexes - \sleep(2); - - // Assert that each project has only 1 database, 1 collection, 1 attribute and 1 index - $databasesProject1 = $this->client->call(Client::METHOD_GET, '/databases', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $project1['body']['$id'], - 'x-appwrite-key' => $key1['body']['secret'] - ]); - - $this->assertEquals(1, $databasesProject1['body']['total']); - $this->assertEquals(1, \count($databasesProject1['body']['databases'])); - - $databasesProject2 = $this->client->call(Client::METHOD_GET, '/databases', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $project2['body']['$id'], - 'x-appwrite-key' => $key2['body']['secret'] - ]); - - $this->assertEquals(1, $databasesProject2['body']['total']); - $this->assertEquals(1, \count($databasesProject2['body']['databases'])); - - $databasesProject3 = $this->client->call(Client::METHOD_GET, '/databases', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $project3['body']['$id'], - 'x-appwrite-key' => $key3['body']['secret'] - ]); - - $this->assertEquals(1, $databasesProject3['body']['total']); - $this->assertEquals(1, \count($databasesProject3['body']['databases'])); - - $databasesProject4 = $this->client->call(Client::METHOD_GET, '/databases', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $project4['body']['$id'], - 'x-appwrite-key' => $key4['body']['secret'] - ]); - - $this->assertEquals(1, $databasesProject4['body']['total']); - $this->assertEquals(1, \count($databasesProject4['body']['databases'])); - - $collectionsProject1 = $this->client->call(Client::METHOD_GET, '/databases/' . $database1['body']['$id'] . '/collections', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $project1['body']['$id'], - 'x-appwrite-key' => $key1['body']['secret'] - ]); - - $this->assertEquals(1, $collectionsProject1['body']['total']); - $this->assertEquals(1, \count($collectionsProject1['body']['collections'])); - - $collectionsProject2 = $this->client->call(Client::METHOD_GET, '/databases/' . $database2['body']['$id'] . '/collections', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $project2['body']['$id'], - 'x-appwrite-key' => $key2['body']['secret'] - ]); - - $this->assertEquals(1, $collectionsProject2['body']['total']); - $this->assertEquals(1, \count($collectionsProject2['body']['collections'])); - - $collectionsProject3 = $this->client->call(Client::METHOD_GET, '/databases/' . $database3['body']['$id'] . '/collections', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $project3['body']['$id'], - 'x-appwrite-key' => $key3['body']['secret'] - ]); - - $this->assertEquals(1, $collectionsProject3['body']['total']); - $this->assertEquals(1, \count($collectionsProject3['body']['collections'])); - - $collectionsProject4 = $this->client->call(Client::METHOD_GET, '/databases/' . $database4['body']['$id'] . '/collections', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $project4['body']['$id'], - 'x-appwrite-key' => $key4['body']['secret'] - ]); - - $this->assertEquals(1, $collectionsProject4['body']['total']); - $this->assertEquals(1, \count($collectionsProject4['body']['collections'])); - - $attributesProject1 = $this->client->call(Client::METHOD_GET, '/databases/' . $database1['body']['$id'] . '/collections/' . $collection1['body']['$id'] . '/attributes', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $project1['body']['$id'], - 'x-appwrite-key' => $key1['body']['secret'] - ]); - - $this->assertEquals(1, $attributesProject1['body']['total']); - $this->assertEquals(1, \count($attributesProject1['body']['attributes'])); - $this->assertEquals('available', $attributesProject1['body']['attributes'][0]['status']); - - $attributesProject2 = $this->client->call(Client::METHOD_GET, '/databases/' . $database2['body']['$id'] . '/collections/' . $collection2['body']['$id'] . '/attributes', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $project2['body']['$id'], - 'x-appwrite-key' => $key2['body']['secret'] - ]); - - $this->assertEquals(1, $attributesProject2['body']['total']); - $this->assertEquals(1, \count($attributesProject2['body']['attributes'])); - $this->assertEquals('available', $attributesProject2['body']['attributes'][0]['status']); - - $attributesProject3 = $this->client->call(Client::METHOD_GET, '/databases/' . $database3['body']['$id'] . '/collections/' . $collection3['body']['$id'] . '/attributes', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $project3['body']['$id'], - 'x-appwrite-key' => $key3['body']['secret'] - ]); - - $this->assertEquals(1, $attributesProject3['body']['total']); - $this->assertEquals(1, \count($attributesProject3['body']['attributes'])); - $this->assertEquals('available', $attributesProject3['body']['attributes'][0]['status']); - - $attributesProject4 = $this->client->call(Client::METHOD_GET, '/databases/' . $database4['body']['$id'] . '/collections/' . $collection4['body']['$id'] . '/attributes', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $project4['body']['$id'], - 'x-appwrite-key' => $key4['body']['secret'] - ]); - - $this->assertEquals(1, $attributesProject4['body']['total']); - $this->assertEquals(1, \count($attributesProject4['body']['attributes'])); - $this->assertEquals('available', $attributesProject4['body']['attributes'][0]['status']); - - $indexesProject1 = $this->client->call(Client::METHOD_GET, '/databases/' . $database1['body']['$id'] . '/collections/' . $collection1['body']['$id'] . '/indexes', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $project1['body']['$id'], - 'x-appwrite-key' => $key1['body']['secret'] - ]); - - $this->assertEquals(1, $indexesProject1['body']['total']); - $this->assertEquals(1, \count($indexesProject1['body']['indexes'])); - - $indexesProject2 = $this->client->call(Client::METHOD_GET, '/databases/' . $database2['body']['$id'] . '/collections/' . $collection2['body']['$id'] . '/indexes', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $project2['body']['$id'], - 'x-appwrite-key' => $key2['body']['secret'] - ]); - - $this->assertEquals(1, $indexesProject2['body']['total']); - $this->assertEquals(1, \count($indexesProject2['body']['indexes'])); - - $indexesProject3 = $this->client->call(Client::METHOD_GET, '/databases/' . $database3['body']['$id'] . '/collections/' . $collection3['body']['$id'] . '/indexes', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $project3['body']['$id'], - 'x-appwrite-key' => $key3['body']['secret'] - ]); - - $this->assertEquals(1, $indexesProject3['body']['total']); - $this->assertEquals(1, \count($indexesProject3['body']['indexes'])); - - $indexesProject4 = $this->client->call(Client::METHOD_GET, '/databases/' . $database4['body']['$id'] . '/collections/' . $collection4['body']['$id'] . '/indexes', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $project4['body']['$id'], - 'x-appwrite-key' => $key4['body']['secret'] - ]); - - $this->assertEquals(1, $indexesProject4['body']['total']); - $this->assertEquals(1, \count($indexesProject4['body']['indexes'])); - - // Attempt to read cross-type resources - $collectionProject2WithProject1Key = $this->client->call(Client::METHOD_GET, '/databases/' . $database2['body']['$id'] . '/collections/' . $collection2['body']['$id'], [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $project1['body']['$id'], - 'x-appwrite-key' => $key1['body']['secret'] - ]); - - $this->assertEquals(404, $collectionProject2WithProject1Key['headers']['status-code']); - - $collectionProject1WithProject2Key = $this->client->call(Client::METHOD_GET, '/databases/' . $database1['body']['$id'] . '/collections/' . $collection1['body']['$id'], [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $project2['body']['$id'], - 'x-appwrite-key' => $key2['body']['secret'] - ]); - - $this->assertEquals(404, $collectionProject1WithProject2Key['headers']['status-code']); - - // Attempt to read cross-tenant resources - $collectionProject3WithProject1Key = $this->client->call(Client::METHOD_GET, '/databases/' . $database3['body']['$id'] . '/collections/' . $collection3['body']['$id'], [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $project1['body']['$id'], - 'x-appwrite-key' => $key1['body']['secret'] - ]); - - $this->assertEquals(404, $collectionProject3WithProject1Key['headers']['status-code']); - - $collectionProject1WithProject3Key = $this->client->call(Client::METHOD_GET, '/databases/' . $database1['body']['$id'] . '/collections/' . $collection1['body']['$id'], [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $project3['body']['$id'], - 'x-appwrite-key' => $key3['body']['secret'] - ]); - - $this->assertEquals(404, $collectionProject1WithProject3Key['headers']['status-code']); - - // Assert that shared project resources can have the same ID as they're unique on tenant + ID not just ID - $collection5 = $this->client->call(Client::METHOD_POST, '/databases/' . $database2['body']['$id'] . '/collections', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $project2['body']['$id'], - 'x-appwrite-key' => $key2['body']['secret'] - ], [ - 'databaseId' => $database2['body']['$id'], - 'collectionId' => $collection4['body']['$id'], - 'name' => 'Amazing Collection', - ]); - - $this->assertEquals(201, $collection5['headers']['status-code']); - - // Assert that users across projects on shared tables can have the same email as they're unique on tenant + email not just email - $user1 = $this->client->call(Client::METHOD_POST, '/users', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $project2['body']['$id'], - 'x-appwrite-key' => $key2['body']['secret'] - ], [ - 'userId' => 'user', - 'email' => 'test@appwrite.io', - 'password' => 'password', - 'name' => 'Test User', - ]); - - $this->assertEquals(201, $user1['headers']['status-code']); - - $user2 = $this->client->call(Client::METHOD_POST, '/users', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $project4['body']['$id'], - 'x-appwrite-key' => $key4['body']['secret'] - ], [ - 'userId' => 'user', - 'email' => 'test@appwrite.io', - 'password' => 'password', - 'name' => 'Test User', - ]); - - $this->assertEquals(201, $user2['headers']['status-code']); - } }