mirror of
https://github.com/appwrite/appwrite.git
synced 2026-05-26 13:51:13 +00:00
Merge remote-tracking branch 'refs/remotes/origin/main' into feat-eldad4-coroutines
# Conflicts: # app/cli.php # app/console # app/controllers/api/projects.php # app/controllers/general.php # app/init.php # app/realtime.php # app/worker.php # composer.json # composer.lock
This commit is contained in:
@@ -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
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
[submodule "app/console"]
|
||||
path = app/console
|
||||
url = https://github.com/appwrite/console
|
||||
branch = 4.3.5
|
||||
branch = 4.3.14
|
||||
|
||||
@@ -29,6 +29,7 @@ $context = new Dependency();
|
||||
$register = new Dependency();
|
||||
$logError = new Dependency();
|
||||
$queueForDeletes = new Dependency();
|
||||
$queueForFunctions = new Dependency();
|
||||
$queueForCertificates = new Dependency();
|
||||
|
||||
$context
|
||||
@@ -41,6 +42,13 @@ $register
|
||||
return $global;
|
||||
});
|
||||
|
||||
$queueForFunctions
|
||||
->setName('queueForFunctions')
|
||||
->inject('queue')
|
||||
->setCallback(function (Connection $queue) {
|
||||
return new Func($queue);
|
||||
});
|
||||
|
||||
|
||||
$queueForDeletes
|
||||
->setName('queueForDeletes')
|
||||
@@ -100,6 +108,7 @@ $container->set($context);
|
||||
$container->set($logError);
|
||||
$container->set($register);
|
||||
$container->set($queueForDeletes);
|
||||
$container->set($queueForFunctions);
|
||||
$container->set($queueForCertificates);
|
||||
|
||||
$platform = new Appwrite();
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -468,7 +468,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,
|
||||
|
||||
@@ -87,8 +87,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)
|
||||
});
|
||||
@@ -1520,7 +1520,7 @@ Http::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()));
|
||||
@@ -2639,6 +2639,7 @@ Http::patch('/v1/account/password')
|
||||
->label('sdk.response.model', Response::MODEL_USER)
|
||||
->label('sdk.offline.model', '/account')
|
||||
->label('sdk.offline.key', 'current')
|
||||
->label('abuse-limit', 10)
|
||||
->param('password', '', fn ($project, $passwordsDictionary) => new PasswordDictionary($passwordsDictionary, $project->getAttribute('auths', [])['passwordDictionary'] ?? false), 'New user password. Must be at least 8 chars.', false, ['project', 'passwordsDictionary'])
|
||||
->param('oldPassword', '', new Password(), 'Current user password. Must be at least 8 chars.', true)
|
||||
->inject('requestTimestamp')
|
||||
|
||||
@@ -884,7 +884,7 @@ Http::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,
|
||||
|
||||
@@ -115,35 +115,8 @@ Http::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) {
|
||||
@@ -156,39 +129,60 @@ Http::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;
|
||||
$dsn = $schema . '://' . System::getEnv('_APP_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 (
|
||||
Http::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;
|
||||
try {
|
||||
$project = $dbForConsole->createDocument('projects', new Document([
|
||||
'$id' => $projectId,
|
||||
'$permissions' => [
|
||||
Permission::read(Role::team(ID::custom($teamId))),
|
||||
Permission::update(Role::team(ID::custom($teamId), 'owner')),
|
||||
Permission::update(Role::team(ID::custom($teamId), 'developer')),
|
||||
Permission::delete(Role::team(ID::custom($teamId), 'owner')),
|
||||
Permission::delete(Role::team(ID::custom($teamId), 'developer')),
|
||||
],
|
||||
'name' => $name,
|
||||
'teamInternalId' => $team->getInternalId(),
|
||||
'teamId' => $team->getId(),
|
||||
'region' => $region,
|
||||
'description' => $description,
|
||||
'logo' => $logo,
|
||||
'url' => $url,
|
||||
'version' => APP_VERSION_STABLE,
|
||||
'legalName' => $legalName,
|
||||
'legalCountry' => $legalCountry,
|
||||
'legalState' => $legalState,
|
||||
'legalCity' => $legalCity,
|
||||
'legalAddress' => $legalAddress,
|
||||
'legalTaxId' => ID::custom($legalTaxId),
|
||||
'services' => new stdClass(),
|
||||
'platforms' => null,
|
||||
'oAuthProviders' => [],
|
||||
'webhooks' => null,
|
||||
'keys' => null,
|
||||
'auths' => $auths,
|
||||
'search' => implode(' ', [$projectId, $name]),
|
||||
'database' => $dsn,
|
||||
]));
|
||||
} catch (Duplicate) {
|
||||
throw new Exception(Exception::PROJECT_ALREADY_EXISTS);
|
||||
}
|
||||
|
||||
if (!empty($namespace)) {
|
||||
$dsn .= '&namespace=' . $namespace;
|
||||
}
|
||||
try {
|
||||
$dsn = new DSN($dsn);
|
||||
} catch (\InvalidArgumentException) {
|
||||
// TODO: Temporary until all projects are using shared tables
|
||||
$dsn = new DSN('mysql://' . $dsn);
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -251,7 +245,7 @@ Http::post('/v1/projects')
|
||||
$dbForProject = new Database($adapter, $cache);
|
||||
$dbForProject->setAuthorization($authorization);
|
||||
|
||||
if ($dsn->getHost() === DATABASE_SHARED_TABLES) {
|
||||
if ($dsn->getHost() === System::getEnv('_APP_DATABASE_SHARED_TABLES', '')) {
|
||||
$dbForProject
|
||||
->setSharedTables(true)
|
||||
->setTenant($project->getInternalId())
|
||||
|
||||
@@ -64,7 +64,7 @@ Http::post('/v1/storage/buckets')
|
||||
->param('permissions', null, new Permissions(APP_LIMIT_ARRAY_PARAMS_SIZE), 'An array of permission strings. By default, no user is granted with any permissions. [Learn more about permissions](https://appwrite.io/docs/permissions).', true)
|
||||
->param('fileSecurity', false, new Boolean(true), 'Enables configuring permissions for individual file. A user needs one of file or bucket level permissions to access a file. [Learn more about permissions](https://appwrite.io/docs/permissions).', true)
|
||||
->param('enabled', true, new Boolean(true), 'Is bucket enabled? When set to \'disabled\', users cannot access the files in this bucket but Server SDKs with and API key can still access the bucket. No files are lost when this is toggled.', true)
|
||||
->param('maximumFileSize', fn (array $plan) => empty($plan['fileSize']) ? (int) System::getEnv('_APP_STORAGE_LIMIT', 0) : $plan['fileSize'] * 1024 * 1024, fn (array $plan) => new Range(1, empty($plan['fileSize']) ? (int) System::getEnv('_APP_STORAGE_LIMIT', 0) : $plan['fileSize'] * 1024 * 1024 * 1024), 'Maximum file size allowed in bytes. Maximum allowed value is ' . Storage::human(System::getEnv('_APP_STORAGE_LIMIT', 0), 0) . '.', true, ['plan'])
|
||||
->param('maximumFileSize', fn (array $plan) => empty($plan['fileSize']) ? (int) System::getEnv('_APP_STORAGE_LIMIT', 0) : $plan['fileSize'] * 1024 * 1024, fn (array $plan) => new Range(1, empty($plan['fileSize']) ? (int) System::getEnv('_APP_STORAGE_LIMIT', 0) : $plan['fileSize'] * 1024 * 1024), 'Maximum file size allowed in bytes. Maximum allowed value is ' . Storage::human(System::getEnv('_APP_STORAGE_LIMIT', 0), 0) . '.', true, ['plan'])
|
||||
->param('allowedFileExtensions', [], new ArrayList(new Text(64), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Allowed file extensions. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' extensions are allowed, each 64 characters long.', true)
|
||||
->param('compression', Compression::NONE, new WhiteList([Compression::NONE, Compression::GZIP, Compression::ZSTD]), 'Compression algorithm choosen for compression. Can be one of ' . Compression::NONE . ', [' . Compression::GZIP . '](https://en.wikipedia.org/wiki/Gzip), or [' . Compression::ZSTD . '](https://en.wikipedia.org/wiki/Zstd), For file size above ' . Storage::human(APP_STORAGE_READ_BUFFER, 0) . ' compression is skipped even if it\'s enabled', true)
|
||||
->param('encryption', true, new Boolean(true), 'Is encryption enabled? For file size above ' . Storage::human(APP_STORAGE_READ_BUFFER, 0) . ' encryption is skipped even if it\'s enabled', true)
|
||||
@@ -241,7 +241,7 @@ Http::put('/v1/storage/buckets/:bucketId')
|
||||
->param('permissions', null, new Permissions(APP_LIMIT_ARRAY_PARAMS_SIZE), 'An array of permission strings. By default, the current permissions are inherited. [Learn more about permissions](https://appwrite.io/docs/permissions).', true)
|
||||
->param('fileSecurity', false, new Boolean(true), 'Enables configuring permissions for individual file. A user needs one of file or bucket level permissions to access a file. [Learn more about permissions](https://appwrite.io/docs/permissions).', true)
|
||||
->param('enabled', true, new Boolean(true), 'Is bucket enabled? When set to \'disabled\', users cannot access the files in this bucket but Server SDKs with and API key can still access the bucket. No files are lost when this is toggled.', true)
|
||||
->param('maximumFileSize', null, new Range(1, (int) System::getEnv('_APP_STORAGE_LIMIT', 0)), 'Maximum file size allowed in bytes. Maximum allowed value is ' . Storage::human((int)System::getEnv('_APP_STORAGE_LIMIT', 0), 0) . '.', true)
|
||||
->param('maximumFileSize', fn (array $plan) => empty($plan['fileSize']) ? (int) System::getEnv('_APP_STORAGE_LIMIT', 0) : $plan['fileSize'] * 1024 * 1024, fn (array $plan) => new Range(1, empty($plan['fileSize']) ? (int) System::getEnv('_APP_STORAGE_LIMIT', 0) : $plan['fileSize'] * 1024 * 1024), 'Maximum file size allowed in bytes. Maximum allowed value is ' . Storage::human(System::getEnv('_APP_STORAGE_LIMIT', 0), 0) . '.', true, ['plan'])
|
||||
->param('allowedFileExtensions', [], new ArrayList(new Text(64), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Allowed file extensions. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' extensions are allowed, each 64 characters long.', true)
|
||||
->param('compression', Compression::NONE, new WhiteList([Compression::NONE, Compression::GZIP, Compression::ZSTD]), 'Compression algorithm choosen for compression. Can be one of ' . Compression::NONE . ', [' . Compression::GZIP . '](https://en.wikipedia.org/wiki/Gzip), or [' . Compression::ZSTD . '](https://en.wikipedia.org/wiki/Zstd), For file size above ' . Storage::human(APP_STORAGE_READ_BUFFER, 0) . ' compression is skipped even if it\'s enabled', true)
|
||||
->param('encryption', true, new Boolean(true), 'Is encryption enabled? For file size above ' . Storage::human(APP_STORAGE_READ_BUFFER, 0) . ' encryption is skipped even if it\'s enabled', true)
|
||||
|
||||
@@ -1785,7 +1785,7 @@ Http::post('/v1/users/:userId/sessions')
|
||||
throw new Exception(Exception::USER_NOT_FOUND);
|
||||
}
|
||||
|
||||
$secret = Auth::codeGenerator();
|
||||
$secret = Auth::tokenGenerator(Auth::TOKEN_LENGTH_SESSION);
|
||||
$detector = new Detector($request->getUserAgent('UNKNOWN'));
|
||||
$record = $geodb->get($request->getIP());
|
||||
|
||||
@@ -1802,6 +1802,7 @@ Http::post('/v1/users/:userId/sessions')
|
||||
'userAgent' => $request->getUserAgent('UNKNOWN'),
|
||||
'ip' => $request->getIP(),
|
||||
'countryCode' => ($record) ? \strtolower($record['country']['iso_code']) : '--',
|
||||
'expire' => $expire,
|
||||
],
|
||||
$detector->getOS(),
|
||||
$detector->getClient(),
|
||||
@@ -1813,7 +1814,6 @@ Http::post('/v1/users/:userId/sessions')
|
||||
$session = $dbForProject->createDocument('sessions', $session);
|
||||
$session
|
||||
->setAttribute('secret', $secret)
|
||||
->setAttribute('expire', $expire)
|
||||
->setAttribute('countryName', $countryName);
|
||||
|
||||
$queueForEvents
|
||||
|
||||
@@ -597,7 +597,7 @@ Http::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');
|
||||
@@ -648,7 +648,7 @@ Http::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')
|
||||
@@ -666,7 +666,8 @@ Http::error()
|
||||
->inject('log')
|
||||
->inject('authorization')
|
||||
->inject('connections')
|
||||
->action(function (Throwable $error, Document $user, ?Route $route, Request $request, Response $response, Document $project, ?Logger $logger, Log $log, Authorization $authorization, Connections $connections) {
|
||||
->inject('queueForUsage')
|
||||
->action(function (Throwable $error, Document $user, ?Route $route, Request $request, Response $response, Document $project, ?Logger $logger, Log $log, Authorization $authorization, Connections $connections, Usage $queueForUsage) {
|
||||
$version = System::getEnv('_APP_VERSION', 'UNKNOWN');
|
||||
|
||||
if (is_null($route)) {
|
||||
@@ -759,6 +760,26 @@ Http::error()
|
||||
}
|
||||
}
|
||||
|
||||
if ($publish && $project->getId() !== 'console') {
|
||||
if (!Auth::isPrivilegedUser(Authorization::getRoles())) {
|
||||
$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());
|
||||
}
|
||||
|
||||
$queueForUsage
|
||||
->setProject($project)
|
||||
->trigger();
|
||||
}
|
||||
|
||||
|
||||
if ($logger && ($publish || $error->getCode() === 0)) {
|
||||
if (isset($user) && !$user->isEmpty()) {
|
||||
$log->setUser(new User($user->getId()));
|
||||
|
||||
+1698
-1708
File diff suppressed because it is too large
Load Diff
+2
-1
@@ -60,7 +60,7 @@
|
||||
"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": "dev-feat-framework-v2 as 0.9.99",
|
||||
"utopia-php/platform": "dev-feat-framework-v2 as 0.5.99",
|
||||
@@ -70,6 +70,7 @@
|
||||
"utopia-php/queue": "dev-feat-coroutine-and-di as 0.7.99",
|
||||
"utopia-php/registry": "0.5.*",
|
||||
"utopia-php/storage": "dev-feat-framework-v2-v2 as 0.18.99",
|
||||
"utopia-php/swoole": "0.8.*",
|
||||
"utopia-php/system": "0.8.*",
|
||||
"utopia-php/vcs": "0.6.*",
|
||||
"utopia-php/websocket": "0.1.*",
|
||||
|
||||
Generated
+262
-537
File diff suppressed because it is too large
Load Diff
+18
-22
@@ -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
|
||||
@@ -649,6 +660,7 @@ services:
|
||||
- _APP_LOGGING_CONFIG
|
||||
- _APP_MIGRATIONS_FIREBASE_CLIENT_ID
|
||||
- _APP_MIGRATIONS_FIREBASE_CLIENT_SECRET
|
||||
- _APP_DATABASE_SHARED_TABLES
|
||||
|
||||
appwrite-task-maintenance:
|
||||
entrypoint: maintenance
|
||||
@@ -687,6 +699,7 @@ services:
|
||||
- _APP_MAINTENANCE_RETENTION_USAGE_HOURLY
|
||||
- _APP_MAINTENANCE_RETENTION_SCHEDULES
|
||||
- _APP_MAINTENANCE_DELAY
|
||||
- _APP_DATABASE_SHARED_TABLES
|
||||
|
||||
appwrite-worker-usage:
|
||||
entrypoint: worker-usage
|
||||
@@ -718,6 +731,7 @@ services:
|
||||
- _APP_LOGGING_PROVIDER
|
||||
- _APP_LOGGING_CONFIG
|
||||
- _APP_USAGE_AGGREGATION_INTERVAL
|
||||
- _APP_DATABASE_SHARED_TABLES
|
||||
|
||||
appwrite-worker-usage-dump:
|
||||
entrypoint: worker-usage-dump
|
||||
@@ -749,6 +763,7 @@ services:
|
||||
- _APP_LOGGING_PROVIDER
|
||||
- _APP_LOGGING_CONFIG
|
||||
- _APP_USAGE_AGGREGATION_INTERVAL
|
||||
- _APP_DATABASE_SHARED_TABLES
|
||||
|
||||
appwrite-task-scheduler-functions:
|
||||
entrypoint: schedule-functions
|
||||
@@ -777,6 +792,7 @@ services:
|
||||
- _APP_DB_SCHEMA
|
||||
- _APP_DB_USER
|
||||
- _APP_DB_PASS
|
||||
- _APP_DATABASE_SHARED_TABLES
|
||||
|
||||
appwrite-task-scheduler-messages:
|
||||
entrypoint: schedule-messages
|
||||
@@ -805,6 +821,7 @@ services:
|
||||
- _APP_DB_SCHEMA
|
||||
- _APP_DB_USER
|
||||
- _APP_DB_PASS
|
||||
- _APP_DATABASE_SHARED_TABLES
|
||||
|
||||
appwrite-assistant:
|
||||
container_name: appwrite-assistant
|
||||
@@ -903,20 +920,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
|
||||
@@ -934,14 +938,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
|
||||
|
||||
@@ -1 +1 @@
|
||||
Verify an authenticator app after adding it using the [add authenticator](/docs/references/cloud/client-web/account#createMfaAuthenticator) method. add
|
||||
Verify an authenticator app after adding it using the [add authenticator](/docs/references/cloud/client-web/account#createMfaAuthenticator) method.
|
||||
@@ -235,7 +235,7 @@ abstract class Migration
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates colletion from the config collection.
|
||||
* Creates collection from the config collection.
|
||||
*
|
||||
* @param string $id
|
||||
* @param string|null $name
|
||||
@@ -262,6 +262,7 @@ abstract class Migration
|
||||
'type' => $attribute['type'],
|
||||
'size' => $attribute['size'],
|
||||
'required' => $attribute['required'],
|
||||
'default' => $attribute['default'] ?? null,
|
||||
'signed' => $attribute['signed'],
|
||||
'array' => $attribute['array'],
|
||||
'filters' => $attribute['filters'],
|
||||
|
||||
@@ -336,6 +336,32 @@ class V20 extends Migration
|
||||
Console::warning("Purge cache from {$id}: {$th->getMessage()}");
|
||||
}
|
||||
|
||||
break;
|
||||
case 'topics':
|
||||
try {
|
||||
$this->projectDB->updateAttributeDefault($id, 'emailTotal', 0);
|
||||
} catch (Throwable $th) {
|
||||
Console::warning("'topics' from {$id}: {$th->getMessage()}");
|
||||
}
|
||||
|
||||
try {
|
||||
$this->projectDB->updateAttributeDefault($id, 'pushTotal', 0);
|
||||
} catch (Throwable $th) {
|
||||
Console::warning("'topics' from {$id}: {$th->getMessage()}");
|
||||
}
|
||||
|
||||
try {
|
||||
$this->projectDB->updateAttributeDefault($id, 'smsTotal', 0);
|
||||
} catch (Throwable $th) {
|
||||
Console::warning("'topics' from {$id}: {$th->getMessage()}");
|
||||
}
|
||||
|
||||
try {
|
||||
$this->projectDB->purgeCachedCollection($id);
|
||||
} catch (Throwable $th) {
|
||||
Console::warning("Purge cache from {$id}: {$th->getMessage()}");
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
@@ -91,6 +91,7 @@ class Migrate extends Action
|
||||
// TODO: Iterate through all project DBs
|
||||
/** @var Database $projectDB */
|
||||
$projectDB = $getProjectDB($project);
|
||||
$projectDB->disableValidation();
|
||||
$migration
|
||||
->setProject($project, $projectDB, $dbForConsole)
|
||||
->setPDO($register->get('db', true))
|
||||
|
||||
@@ -500,14 +500,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))) {
|
||||
@@ -556,7 +556,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);
|
||||
|
||||
@@ -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']),
|
||||
|
||||
@@ -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']);
|
||||
|
||||
@@ -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
|
||||
{
|
||||
/**
|
||||
@@ -164,7 +166,7 @@ class FunctionsCustomClientTest extends Scope
|
||||
$this->assertEquals(202, $execution['headers']['status-code']);
|
||||
|
||||
// Wait for the first scheduled execution to be created
|
||||
sleep(65);
|
||||
sleep(90);
|
||||
|
||||
$executions = $this->client->call(Client::METHOD_GET, '/functions/' . $function['body']['$id'] . '/executions', [
|
||||
'content-type' => 'application/json',
|
||||
|
||||
@@ -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, [
|
||||
|
||||
@@ -455,7 +455,7 @@ class HealthCustomServerTest extends Scope
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertEquals('/CN=www.google.com', $response['body']['name']);
|
||||
$this->assertEquals('www.google.com', $response['body']['subjectSN']);
|
||||
$this->assertEquals('Google Trust Services LLC', $response['body']['issuerOrganisation']);
|
||||
$this->assertStringContainsString('Google Trust Services', $response['body']['issuerOrganisation']);
|
||||
$this->assertIsInt($response['body']['validFrom']);
|
||||
$this->assertIsInt($response['body']['validTo']);
|
||||
|
||||
|
||||
@@ -511,6 +511,55 @@ trait MessagingBase
|
||||
];
|
||||
}
|
||||
|
||||
public function testSubscriberTargetSubQuery()
|
||||
{
|
||||
$response = $this->client->call(Client::METHOD_POST, '/messaging/topics', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey'],
|
||||
], [
|
||||
'topicId' => 'sub-query-test',
|
||||
'name' => 'sub-query-test',
|
||||
]);
|
||||
|
||||
$this->assertEquals(201, $response['headers']['status-code']);
|
||||
|
||||
$topic = $response['body'];
|
||||
|
||||
$prefix = uniqid();
|
||||
|
||||
for ($i = 1; $i <= 101; $i++) {
|
||||
$response = $this->client->call(Client::METHOD_POST, '/users', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey'],
|
||||
], [
|
||||
'userId' => "$prefix-$i",
|
||||
'email' => "$prefix-$i@example.com",
|
||||
'password' => 'password',
|
||||
'name' => "User $prefix $i",
|
||||
]);
|
||||
|
||||
$this->assertEquals(201, $response['headers']['status-code']);
|
||||
$user = $response['body'];
|
||||
$targets = $user['targets'] ?? [];
|
||||
|
||||
$this->assertGreaterThan(0, count($targets));
|
||||
|
||||
$target = $targets[0];
|
||||
|
||||
$response = $this->client->call(Client::METHOD_POST, '/messaging/topics/' . $topic['$id'] . '/subscribers', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'subscriberId' => $user['$id'],
|
||||
'targetId' => $target['$id'],
|
||||
]);
|
||||
|
||||
$this->assertEquals(201, $response['headers']['status-code']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testCreateSubscriber
|
||||
*/
|
||||
|
||||
@@ -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']);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -225,7 +225,7 @@ trait StorageBase
|
||||
'bucketId' => ID::unique(),
|
||||
'name' => 'Test Bucket 2',
|
||||
'fileSecurity' => true,
|
||||
'maximumFileSize' => 200000000, //200MB
|
||||
'maximumFileSize' => 6000000000, //6GB
|
||||
'allowedFileExtensions' => ["jpg", "png"],
|
||||
'permissions' => [
|
||||
Permission::read(Role::any()),
|
||||
|
||||
@@ -290,6 +290,28 @@ trait UsersBase
|
||||
$this->assertArrayNotHasKey('secret', $token['body']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testCreateUser
|
||||
*/
|
||||
public function testCreateSession(array $data): void
|
||||
{
|
||||
/**
|
||||
* Test for SUCCESS
|
||||
*/
|
||||
$response = $this->client->call(Client::METHOD_POST, '/users/' . $data['userId'] . '/sessions', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()));
|
||||
|
||||
$this->assertEquals(201, $response['headers']['status-code']);
|
||||
|
||||
$session = $response['body'];
|
||||
$this->assertEquals($data['userId'], $session['userId']);
|
||||
$this->assertNotEmpty($session['secret']);
|
||||
$this->assertNotEmpty($session['expire']);
|
||||
$this->assertEquals('server', $session['provider']);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Tests all optional parameters of createUser (email, phone, anonymous..)
|
||||
@@ -986,7 +1008,7 @@ trait UsersBase
|
||||
'password' => 'password'
|
||||
]);
|
||||
|
||||
$this->assertEquals($session['headers']['status-code'], 401);
|
||||
$this->assertEquals(401, $session['headers']['status-code']);
|
||||
|
||||
$user = $this->client->call(Client::METHOD_PATCH, '/users/' . $data['userId'] . '/password', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
|
||||
Reference in New Issue
Block a user