diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 2cc4c700f7..7bc39392ef 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/.gitmodules b/.gitmodules index ffb7623bd7..653e6acbc0 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,4 +1,4 @@ [submodule "app/console"] path = app/console url = https://github.com/appwrite/console - branch = 4.3.5 + branch = 4.3.14 diff --git a/app/cli.php b/app/cli.php index 75c4c96555..adff98aeeb 100644 --- a/app/cli.php +++ b/app/cli.php @@ -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(); diff --git a/app/config/platforms.php b/app/config/platforms.php index d06c25a796..f0ce427c79 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 f7cd34b1e1..ff19b94a91 100644 --- a/app/config/variables.php +++ b/app/config/variables.php @@ -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, diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index 107c7d0d1f..5eadf92e33 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -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') diff --git a/app/controllers/api/health.php b/app/controllers/api/health.php index 79c83aa13a..24acaf96cc 100644 --- a/app/controllers/api/health.php +++ b/app/controllers/api/health.php @@ -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, diff --git a/app/controllers/api/projects.php b/app/controllers/api/projects.php index f23e40e581..0b9d462f80 100644 --- a/app/controllers/api/projects.php +++ b/app/controllers/api/projects.php @@ -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()) diff --git a/app/controllers/api/storage.php b/app/controllers/api/storage.php index 564c9314a0..2a538cb271 100644 --- a/app/controllers/api/storage.php +++ b/app/controllers/api/storage.php @@ -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) diff --git a/app/controllers/api/users.php b/app/controllers/api/users.php index 439da24ff4..ccf9461086 100644 --- a/app/controllers/api/users.php +++ b/app/controllers/api/users.php @@ -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 diff --git a/app/controllers/general.php b/app/controllers/general.php index f11943b9d6..9dab136c42 100644 --- a/app/controllers/general.php +++ b/app/controllers/general.php @@ -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())); diff --git a/app/init.php b/app/init.php index dfa80a600f..ea8b087faa 100644 --- a/app/init.php +++ b/app/init.php @@ -1,1710 +1,1700 @@ $value], JSON_PRESERVE_ZERO_FRACTION); -// }, -// function (mixed $value) { -// if (is_null($value)) { -// return; -// } -// -// return json_decode($value, true)['value']; -// } -//); -// -//Database::addFilter( -// 'enum', -// function (mixed $value, Document $attribute) { -// if ($attribute->isSet('elements')) { -// $attribute->removeAttribute('elements'); -// } -// -// return $value; -// }, -// function (mixed $value, Document $attribute) { -// $formatOptions = \json_decode($attribute->getAttribute('formatOptions', '[]'), true); -// if (isset($formatOptions['elements'])) { -// $attribute->setAttribute('elements', $formatOptions['elements']); -// } -// -// return $value; -// } -//); -// -//Database::addFilter( -// 'range', -// function (mixed $value, Document $attribute) { -// if ($attribute->isSet('min')) { -// $attribute->removeAttribute('min'); -// } -// if ($attribute->isSet('max')) { -// $attribute->removeAttribute('max'); -// } -// -// return $value; -// }, -// function (mixed $value, Document $attribute) { -// $formatOptions = json_decode($attribute->getAttribute('formatOptions', '[]'), true); -// if (isset($formatOptions['min']) || isset($formatOptions['max'])) { -// $attribute -// ->setAttribute('min', $formatOptions['min']) -// ->setAttribute('max', $formatOptions['max']) -// ; -// } -// -// return $value; -// } -//); -// -//Database::addFilter( -// 'subQueryAttributes', -// function (mixed $value) { -// return; -// }, -// function (mixed $value, Document $document, Database $database) { -// $attributes = $database->find('attributes', [ -// Query::equal('collectionInternalId', [$document->getInternalId()]), -// Query::equal('databaseInternalId', [$document->getAttribute('databaseInternalId')]), -// Query::limit($database->getLimitForAttributes()), -// ]); -// -// foreach ($attributes as $attribute) { -// if ($attribute->getAttribute('type') === Database::VAR_RELATIONSHIP) { -// $options = $attribute->getAttribute('options'); -// foreach ($options as $key => $value) { -// $attribute->setAttribute($key, $value); -// } -// $attribute->removeAttribute('options'); -// } -// } -// -// return $attributes; -// } -//); -// -//Database::addFilter( -// 'subQueryIndexes', -// function (mixed $value) { -// return; -// }, -// function (mixed $value, Document $document, Database $database) { -// return $database -// ->find('indexes', [ -// Query::equal('collectionInternalId', [$document->getInternalId()]), -// Query::equal('databaseInternalId', [$document->getAttribute('databaseInternalId')]), -// Query::limit($database->getLimitForIndexes()), -// ]); -// } -//); -// -//Database::addFilter( -// 'subQueryPlatforms', -// function (mixed $value) { -// return; -// }, -// function (mixed $value, Document $document, Database $database) { -// return $database -// ->find('platforms', [ -// Query::equal('projectInternalId', [$document->getInternalId()]), -// Query::limit(APP_LIMIT_SUBQUERY), -// ]); -// } -//); -// -//Database::addFilter( -// 'subQueryKeys', -// function (mixed $value) { -// return; -// }, -// function (mixed $value, Document $document, Database $database) { -// return $database -// ->find('keys', [ -// Query::equal('projectInternalId', [$document->getInternalId()]), -// Query::limit(APP_LIMIT_SUBQUERY), -// ]); -// } -//); -// -//Database::addFilter( -// 'subQueryWebhooks', -// function (mixed $value) { -// return; -// }, -// function (mixed $value, Document $document, Database $database) { -// return $database -// ->find('webhooks', [ -// Query::equal('projectInternalId', [$document->getInternalId()]), -// Query::limit(APP_LIMIT_SUBQUERY), -// ]); -// } -//); -// -//Database::addFilter( -// 'subQuerySessions', -// function (mixed $value) { -// return; -// }, -// function (mixed $value, Document $document, Database $database) { -// return $database->getAuthorization()->skip(fn () => $database->find('sessions', [ -// Query::equal('userInternalId', [$document->getInternalId()]), -// Query::limit(APP_LIMIT_SUBQUERY), -// ])); -// } -//); -// -//Database::addFilter( -// 'subQueryTokens', -// function (mixed $value) { -// return; -// }, -// function (mixed $value, Document $document, Database $database) { -// return $database->getAuthorization()->skip(fn () => $database -// ->find('tokens', [ -// Query::equal('userInternalId', [$document->getInternalId()]), -// Query::limit(APP_LIMIT_SUBQUERY), -// ])); -// } -//); -// -//Database::addFilter( -// 'subQueryChallenges', -// function (mixed $value) { -// return; -// }, -// function (mixed $value, Document $document, Database $database) { -// return $database->getAuthorization()->skip(fn () => $database -// ->find('challenges', [ -// Query::equal('userInternalId', [$document->getInternalId()]), -// Query::limit(APP_LIMIT_SUBQUERY), -// ])); -// } -//); -// -//Database::addFilter( -// 'subQueryAuthenticators', -// function (mixed $value) { -// return; -// }, -// function (mixed $value, Document $document, Database $database) { -// return $database->getAuthorization()->skip(fn () => $database -// ->find('authenticators', [ -// Query::equal('userInternalId', [$document->getInternalId()]), -// Query::limit(APP_LIMIT_SUBQUERY), -// ])); -// } -//); -// -//Database::addFilter( -// 'subQueryMemberships', -// function (mixed $value) { -// return; -// }, -// function (mixed $value, Document $document, Database $database) { -// return $database->getAuthorization()->skip(fn () => $database -// ->find('memberships', [ -// Query::equal('userInternalId', [$document->getInternalId()]), -// Query::limit(APP_LIMIT_SUBQUERY), -// ])); -// } -//); -// -//Database::addFilter( -// 'subQueryVariables', -// function (mixed $value) { -// return; -// }, -// function (mixed $value, Document $document, Database $database) { -// return $database -// ->find('variables', [ -// Query::equal('resourceInternalId', [$document->getInternalId()]), -// Query::equal('resourceType', ['function']), -// Query::limit(APP_LIMIT_SUBQUERY), -// ]); -// } -//); -// -//Database::addFilter( -// 'encrypt', -// function (mixed $value) { -// $key = System::getEnv('_APP_OPENSSL_KEY_V1'); -// $iv = OpenSSL::randomPseudoBytes(OpenSSL::cipherIVLength(OpenSSL::CIPHER_AES_128_GCM)); -// $tag = null; -// -// return json_encode([ -// 'data' => OpenSSL::encrypt($value, OpenSSL::CIPHER_AES_128_GCM, $key, 0, $iv, $tag), -// 'method' => OpenSSL::CIPHER_AES_128_GCM, -// 'iv' => \bin2hex($iv), -// 'tag' => \bin2hex($tag ?? ''), -// 'version' => '1', -// ]); -// }, -// function (mixed $value) { -// if (is_null($value)) { -// return; -// } -// $value = json_decode($value, true); -// $key = System::getEnv('_APP_OPENSSL_KEY_V' . $value['version']); -// -// return OpenSSL::decrypt($value['data'], $value['method'], $key, 0, hex2bin($value['iv']), hex2bin($value['tag'])); -// } -//); -// -//Database::addFilter( -// 'subQueryProjectVariables', -// function (mixed $value) { -// return; -// }, -// function (mixed $value, Document $document, Database $database) { -// return $database -// ->find('variables', [ -// Query::equal('resourceType', ['project']), -// Query::limit(APP_LIMIT_SUBQUERY) -// ]); -// } -//); -// -//Database::addFilter( -// 'userSearch', -// function (mixed $value, Document $user) { -// $searchValues = [ -// $user->getId(), -// $user->getAttribute('email', ''), -// $user->getAttribute('name', ''), -// $user->getAttribute('phone', '') -// ]; -// -// foreach ($user->getAttribute('labels', []) as $label) { -// $searchValues[] = 'label:' . $label; -// } -// -// $search = implode(' ', \array_filter($searchValues)); -// -// return $search; -// }, -// function (mixed $value) { -// return $value; -// } -//); -// -//Database::addFilter( -// 'subQueryTargets', -// function (mixed $value) { -// return; -// }, -// function (mixed $value, Document $document, Database $database) { -// return $database->getAuthorization()->skip(fn () => $database -// ->find('targets', [ -// Query::equal('userInternalId', [$document->getInternalId()]), -// Query::limit(APP_LIMIT_SUBQUERY) -// ])); -// } -//); -// -//Database::addFilter( -// 'subQueryTopicTargets', -// function (mixed $value) { -// return; -// }, -// function (mixed $value, Document $document, Database $database) { -// $targetIds = $database->getAuthorization()->skip(fn () => \array_map( -// fn ($document) => $document->getAttribute('targetInternalId'), -// $database->find('subscribers', [ -// Query::equal('topicInternalId', [$document->getInternalId()]), -// Query::limit(APP_LIMIT_SUBSCRIBERS_SUBQUERY) -// ]) -// )); -// if (\count($targetIds) > 0) { -// return $database->find('targets', [ -// Query::equal('$internalId', $targetIds) -// ]); -// } -// return []; -// } -//); -// -//Database::addFilter( -// 'providerSearch', -// function (mixed $value, Document $provider) { -// $searchValues = [ -// $provider->getId(), -// $provider->getAttribute('name', ''), -// $provider->getAttribute('provider', ''), -// $provider->getAttribute('type', '') -// ]; -// -// $search = \implode(' ', \array_filter($searchValues)); -// -// return $search; -// }, -// function (mixed $value) { -// return $value; -// } -//); -// -//Database::addFilter( -// 'topicSearch', -// function (mixed $value, Document $topic) { -// $searchValues = [ -// $topic->getId(), -// $topic->getAttribute('name', ''), -// $topic->getAttribute('description', ''), -// ]; -// -// $search = \implode(' ', \array_filter($searchValues)); -// -// return $search; -// }, -// function (mixed $value) { -// return $value; -// } -//); -// -//Database::addFilter( -// 'messageSearch', -// function (mixed $value, Document $message) { -// $searchValues = [ -// $message->getId(), -// $message->getAttribute('description', ''), -// $message->getAttribute('status', ''), -// ]; -// -// $data = \json_decode($message->getAttribute('data', []), true); -// $providerType = $message->getAttribute('providerType', ''); -// -// if ($providerType === MESSAGE_TYPE_EMAIL) { -// $searchValues = \array_merge($searchValues, [$data['subject'], MESSAGE_TYPE_EMAIL]); -// } elseif ($providerType === MESSAGE_TYPE_SMS) { -// $searchValues = \array_merge($searchValues, [$data['content'], MESSAGE_TYPE_SMS]); -// } else { -// $searchValues = \array_merge($searchValues, [$data['title'], MESSAGE_TYPE_PUSH]); -// } -// -// $search = \implode(' ', \array_filter($searchValues)); -// -// return $search; -// }, -// function (mixed $value) { -// return $value; -// } -//); -// -///** -// * DB Formats -// */ -//Structure::addFormat(APP_DATABASE_ATTRIBUTE_EMAIL, function () { -// return new Email(); -//}, Database::VAR_STRING); -// -//Structure::addFormat(APP_DATABASE_ATTRIBUTE_DATETIME, function () { -// return new DatetimeValidator(); -//}, Database::VAR_DATETIME); -// -//Structure::addFormat(APP_DATABASE_ATTRIBUTE_ENUM, function ($attribute) { -// $elements = $attribute['formatOptions']['elements']; -// return new WhiteList($elements, true); -//}, Database::VAR_STRING); -// -//Structure::addFormat(APP_DATABASE_ATTRIBUTE_IP, function () { -// return new IP(); -//}, Database::VAR_STRING); -// -//Structure::addFormat(APP_DATABASE_ATTRIBUTE_URL, function () { -// return new URL(); -//}, Database::VAR_STRING); -// -//Structure::addFormat(APP_DATABASE_ATTRIBUTE_INT_RANGE, function ($attribute) { -// $min = $attribute['formatOptions']['min'] ?? -INF; -// $max = $attribute['formatOptions']['max'] ?? INF; -// return new Range($min, $max, Range::TYPE_INTEGER); -//}, Database::VAR_INTEGER); -// -//Structure::addFormat(APP_DATABASE_ATTRIBUTE_FLOAT_RANGE, function ($attribute) { -// $min = $attribute['formatOptions']['min'] ?? -INF; -// $max = $attribute['formatOptions']['max'] ?? INF; -// return new Range($min, $max, Range::TYPE_FLOAT); -//}, Database::VAR_FLOAT); -// -///* -// * Registry -// */ -//$register->set('logger', function () { -// // Register error logger -// $providerName = System::getEnv('_APP_LOGGING_PROVIDER', ''); -// $providerConfig = System::getEnv('_APP_LOGGING_CONFIG', ''); -// -// if (empty($providerName) || empty($providerConfig)) { -// return; -// } -// -// if (!Logger::hasProvider($providerName)) { -// throw new Exception(Exception::GENERAL_SERVER_ERROR, "Logging provider not supported. Logging is disabled"); -// } -// -// // Old Sentry Format conversion. Fallback until the old syntax is completely deprecated. -// if (str_contains($providerConfig, ';') && strtolower($providerName) == 'sentry') { -// $configChunks = \explode(";", $providerConfig); -// -// $sentryKey = $configChunks[0]; -// $projectId = $configChunks[1]; -// -// $providerConfig = 'https://' . $sentryKey . '@sentry.io/' . $projectId; -// } -// -// $classname = '\\Utopia\\Logger\\Adapter\\' . \ucfirst($providerName); -// $adapter = new $classname($providerConfig); -// return new Logger($adapter); -//}); -//$register->set('pools', function () { -// $group = new Group(); -// -// $fallbackForDB = 'db_main=' . AppwriteURL::unparse([ -// 'scheme' => 'mariadb', -// 'host' => System::getEnv('_APP_DB_HOST', 'mariadb'), -// 'port' => System::getEnv('_APP_DB_PORT', '3306'), -// 'user' => System::getEnv('_APP_DB_USER', ''), -// 'pass' => System::getEnv('_APP_DB_PASS', ''), -// 'path' => System::getEnv('_APP_DB_SCHEMA', ''), -// ]); -// $fallbackForRedis = 'redis_main=' . AppwriteURL::unparse([ -// 'scheme' => 'redis', -// 'host' => System::getEnv('_APP_REDIS_HOST', 'redis'), -// 'port' => System::getEnv('_APP_REDIS_PORT', '6379'), -// 'user' => System::getEnv('_APP_REDIS_USER', ''), -// 'pass' => System::getEnv('_APP_REDIS_PASS', ''), -// ]); -// -// $connections = [ -// 'console' => [ -// 'type' => 'database', -// 'dsns' => System::getEnv('_APP_CONNECTIONS_DB_CONSOLE', $fallbackForDB), -// 'multiple' => false, -// 'schemes' => ['mariadb', 'mysql'], -// ], -// 'database' => [ -// 'type' => 'database', -// 'dsns' => System::getEnv('_APP_CONNECTIONS_DB_PROJECT', $fallbackForDB), -// 'multiple' => true, -// 'schemes' => ['mariadb', 'mysql'], -// ], -// 'queue' => [ -// 'type' => 'queue', -// 'dsns' => System::getEnv('_APP_CONNECTIONS_QUEUE', $fallbackForRedis), -// 'multiple' => false, -// 'schemes' => ['redis'], -// ], -// 'pubsub' => [ -// 'type' => 'pubsub', -// 'dsns' => System::getEnv('_APP_CONNECTIONS_PUBSUB', $fallbackForRedis), -// 'multiple' => false, -// 'schemes' => ['redis'], -// ], -// 'cache' => [ -// 'type' => 'cache', -// 'dsns' => System::getEnv('_APP_CONNECTIONS_CACHE', $fallbackForRedis), -// 'multiple' => true, -// 'schemes' => ['redis'], -// ], -// ]; -// -// $maxConnections = System::getEnv('_APP_CONNECTIONS_MAX', 151); -// $instanceConnections = $maxConnections / System::getEnv('_APP_POOL_CLIENTS', 14); -// -// $multiprocessing = System::getEnv('_APP_SERVER_MULTIPROCESS', 'disabled') === 'enabled'; -// -// if ($multiprocessing) { -// $workerCount = swoole_cpu_num() * intval(System::getEnv('_APP_WORKER_PER_CORE', 6)); -// } else { -// $workerCount = 1; -// } -// -// if ($workerCount > $instanceConnections) { -// throw new \Exception('Pool size is too small. Increase the number of allowed database connections or decrease the number of workers.', 500); -// } -// -// $poolSize = (int)($instanceConnections / $workerCount); -// -// foreach ($connections as $key => $connection) { -// $type = $connection['type'] ?? ''; -// $multiple = $connection['multiple'] ?? false; -// $schemes = $connection['schemes'] ?? []; -// $config = []; -// $dsns = explode(',', $connection['dsns'] ?? ''); -// foreach ($dsns as &$dsn) { -// $dsn = explode('=', $dsn); -// $name = ($multiple) ? $key . '_' . $dsn[0] : $key; -// $dsn = $dsn[1] ?? ''; -// $config[] = $name; -// if (empty($dsn)) { -// //throw new Exception(Exception::GENERAL_SERVER_ERROR, "Missing value for DSN connection in {$key}"); -// continue; -// } -// -// $dsn = new DSN($dsn); -// $dsnHost = $dsn->getHost(); -// $dsnPort = $dsn->getPort(); -// $dsnUser = $dsn->getUser(); -// $dsnPass = $dsn->getPassword(); -// $dsnScheme = $dsn->getScheme(); -// $dsnDatabase = $dsn->getPath(); -// -// if (!in_array($dsnScheme, $schemes)) { -// throw new Exception(Exception::GENERAL_SERVER_ERROR, "Invalid console database scheme"); -// } -// -// /** -// * Get Resource -// * -// * Creation could be reused across connection types like database, cache, queue, etc. -// * -// * Resource assignment to an adapter will happen below. -// */ -// $resource = match ($dsnScheme) { -// 'mysql', -// 'mariadb' => function () use ($dsnHost, $dsnPort, $dsnUser, $dsnPass, $dsnDatabase) { -// return new PDOProxy(function () use ($dsnHost, $dsnPort, $dsnUser, $dsnPass, $dsnDatabase) { -// return new PDO("mysql:host={$dsnHost};port={$dsnPort};dbname={$dsnDatabase};charset=utf8mb4", $dsnUser, $dsnPass, array( -// PDO::ATTR_TIMEOUT => 3, // Seconds -// PDO::ATTR_PERSISTENT => true, -// PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, -// PDO::ATTR_EMULATE_PREPARES => true, -// PDO::ATTR_STRINGIFY_FETCHES => true -// )); -// }); -// }, -// 'redis' => function () use ($dsnHost, $dsnPort, $dsnPass) { -// $redis = new Redis(); -// @$redis->pconnect($dsnHost, (int)$dsnPort); -// if ($dsnPass) { -// $redis->auth($dsnPass); -// } -// $redis->setOption(Redis::OPT_READ_TIMEOUT, -1); -// -// return $redis; -// }, -// default => throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Invalid scheme'), -// }; -// -// $pool = new Pool($name, $poolSize, function () use ($type, $resource, $dsn) { -// // Get Adapter -// switch ($type) { -// case 'database': -// $adapter = match ($dsn->getScheme()) { -// 'mariadb' => new MariaDB($resource()), -// 'mysql' => new MySQL($resource()), -// default => null -// }; -// -// $adapter->setDatabase($dsn->getPath()); -// break; -// case 'pubsub': -// $adapter = $resource(); -// break; -// case 'queue': -// $adapter = match ($dsn->getScheme()) { -// 'redis' => new Queue\Connection\Redis($dsn->getHost(), $dsn->getPort()), -// default => null -// }; -// break; -// case 'cache': -// $adapter = match ($dsn->getScheme()) { -// 'redis' => new RedisCache($resource()), -// default => null -// }; -// break; -// -// default: -// throw new Exception(Exception::GENERAL_SERVER_ERROR, "Server error: Missing adapter implementation."); -// } -// -// return $adapter; -// }); -// -// $group->add($pool); -// } -// -// Config::setParam('pools-' . $key, $config); -// } -// -// return $group; -//}); -// -//$register->set('db', function () { -// // This is usually for our workers or CLI commands scope -// $dbHost = System::getEnv('_APP_DB_HOST', ''); -// $dbPort = System::getEnv('_APP_DB_PORT', ''); -// $dbUser = System::getEnv('_APP_DB_USER', ''); -// $dbPass = System::getEnv('_APP_DB_PASS', ''); -// $dbScheme = System::getEnv('_APP_DB_SCHEMA', ''); -// -// return new PDO( -// "mysql:host={$dbHost};port={$dbPort};dbname={$dbScheme};charset=utf8mb4", -// $dbUser, -// $dbPass, -// SQL::getPDOAttributes() -// ); -//}); -// -//$register->set('smtp', function () { -// $mail = new PHPMailer(true); -// -// $mail->isSMTP(); -// -// $username = System::getEnv('_APP_SMTP_USERNAME'); -// $password = System::getEnv('_APP_SMTP_PASSWORD'); -// -// $mail->XMailer = 'Appwrite Mailer'; -// $mail->Host = System::getEnv('_APP_SMTP_HOST', 'smtp'); -// $mail->Port = System::getEnv('_APP_SMTP_PORT', 25); -// $mail->SMTPAuth = !empty($username) && !empty($password); -// $mail->Username = $username; -// $mail->Password = $password; -// $mail->SMTPSecure = System::getEnv('_APP_SMTP_SECURE', ''); -// $mail->SMTPAutoTLS = false; -// $mail->CharSet = 'UTF-8'; -// -// $from = \urldecode(System::getEnv('_APP_SYSTEM_EMAIL_NAME', APP_NAME . ' Server')); -// $email = System::getEnv('_APP_SYSTEM_EMAIL_ADDRESS', APP_EMAIL_TEAM); -// -// $mail->setFrom($email, $from); -// $mail->addReplyTo($email, $from); -// -// $mail->isHTML(true); -// -// return $mail; -//}); -//$register->set('geodb', function () { -// return new Reader(__DIR__ . '/assets/dbip/dbip-country-lite-2024-02.mmdb'); -//}); -//$register->set('passwordsDictionary', function () { -// $content = \file_get_contents(__DIR__ . '/assets/security/10k-common-passwords'); -// $content = explode("\n", $content); -// $content = array_flip($content); -// return $content; -//}); -//$register->set('promiseAdapter', function () { -// return new Swoole(); -//}); -//$register->set('hooks', function () { -// return new Hooks(); -//}); -///* -// * Localization -// */ -//Locale::$exceptions = false; -// -//$locales = Config::getParam('locale-codes', []); -// -//foreach ($locales as $locale) { -// $code = $locale['code']; -// -// $path = __DIR__ . '/config/locale/translations/' . $code . '.json'; -// -// if (!\file_exists($path)) { -// $path = __DIR__ . '/config/locale/translations/' . \substr($code, 0, 2) . '.json'; // if `ar-ae` doesn't exist, look for `ar` -// if (!\file_exists($path)) { -// $path = __DIR__ . '/config/locale/translations/en.json'; // if none translation exists, use default from `en.json` -// } -// } -// -// Locale::setLanguageFromJSON($code, $path); -//} -// -//\stream_context_set_default([ // Set global user agent and http settings -// 'http' => [ -// 'method' => 'GET', -// 'user_agent' => \sprintf( -// APP_USERAGENT, -// System::getEnv('_APP_VERSION', 'UNKNOWN'), -// System::getEnv('_APP_EMAIL_SECURITY', System::getEnv('_APP_SYSTEM_SECURITY_EMAIL_ADDRESS', APP_EMAIL_SECURITY)) -// ), -// 'timeout' => 2, -// ], -//]); -// -//// Runtime Execution -//Http::setResource('log', fn () => new Log()); -//Http::setResource('logger', function ($register) { -// return $register->get('logger'); -//}, ['register']); -// -//Http::setResource('hooks', function ($register) { -// return $register->get('hooks'); -//}, ['register']); -// -//Http::setResource('register', fn () => $register); -//Http::setResource('locale', fn () => new Locale(System::getEnv('_APP_LOCALE', 'en'))); -// -//Http::setResource('localeCodes', function () { -// return array_map(fn ($locale) => $locale['code'], Config::getParam('locale-codes', [])); -//}); -// -//Http::setResource('connections', function () { -// return new Connections(); -//}); -// -//// Queues -//Http::setResource('queue', function (Group $pools, Connections $connections) { -// $connection = $pools->get('queue')->pop(); -// $connections->add($connection); -// return $connection->getResource(); -//}, ['pools', 'connections']); -//Http::setResource('queueForMessaging', function (Connection $queue) { -// return new Messaging($queue); -//}, ['queue']); -//Http::setResource('queueForMails', function (Connection $queue) { -// return new Mail($queue); -//}, ['queue']); -//Http::setResource('queueForBuilds', function (Connection $queue) { -// return new Build($queue); -//}, ['queue']); -//Http::setResource('queueForDatabase', function (Connection $queue) { -// return new EventDatabase($queue); -//}, ['queue']); -//Http::setResource('queueForDeletes', function (Connection $queue) { -// return new Delete($queue); -//}, ['queue']); -//Http::setResource('queueForEvents', function (Connection $queue) { -// return new Event($queue); -//}, ['queue']); -//Http::setResource('queueForAudits', function (Connection $queue) { -// return new Audit($queue); -//}, ['queue']); -//Http::setResource('queueForFunctions', function (Connection $queue) { -// return new Func($queue); -//}, ['queue']); -//Http::setResource('queueForUsage', function (Connection $queue) { -// return new Usage($queue); -//}, ['queue']); -//Http::setResource('queueForCertificates', function (Connection $queue) { -// return new Certificate($queue); -//}, ['queue']); -//Http::setResource('queueForMigrations', function (Connection $queue) { -// return new Migration($queue); -//}, ['queue']); -//Http::setResource('clients', function ($request, $console, $project) { -// $console->setAttribute('platforms', [ // Always allow current host -// '$collection' => ID::custom('platforms'), -// 'name' => 'Current Host', -// 'type' => Origin::CLIENT_TYPE_WEB, -// 'hostname' => $request->getHostname(), -// ], Document::SET_TYPE_APPEND); -// -// $hostnames = explode(',', System::getEnv('_APP_CONSOLE_HOSTNAMES', '')); -// $validator = new Hostname(); -// foreach ($hostnames as $hostname) { -// $hostname = trim($hostname); -// if (!$validator->isValid($hostname)) { -// continue; -// } -// $console->setAttribute('platforms', [ -// '$collection' => ID::custom('platforms'), -// 'type' => Origin::CLIENT_TYPE_WEB, -// 'name' => $hostname, -// 'hostname' => $hostname, -// ], Document::SET_TYPE_APPEND); -// } -// -// /** -// * Get All verified client URLs for both console and current projects -// * + Filter for duplicated entries -// */ -// $clientsConsole = \array_map( -// fn ($node) => $node['hostname'], -// \array_filter( -// $console->getAttribute('platforms', []), -// fn ($node) => (isset($node['type']) && ($node['type'] === Origin::CLIENT_TYPE_WEB) && !empty($node['hostname'])) -// ) -// ); -// -// $clients = $clientsConsole; -// $platforms = $project->getAttribute('platforms', []); -// -// foreach ($platforms as $node) { -// if ( -// isset($node['type']) && -// ($node['type'] === Origin::CLIENT_TYPE_WEB || -// $node['type'] === Origin::CLIENT_TYPE_FLUTTER_WEB) && -// !empty($node['hostname']) -// ) { -// $clients[] = $node['hostname']; -// } -// } -// -// return \array_unique($clients); -//}, ['request', 'console', 'project']); -// -//Http::setResource('user', function (string $mode, Document $project, Document $console, Request $request, Response $response, Database $dbForProject, Database $dbForConsole, Authorization $auth) { -// -// $auth->setDefaultStatus(true); -// -// Auth::setCookieName('a_session_' . $project->getId()); -// -// if (APP_MODE_ADMIN === $mode) { -// Auth::setCookieName('a_session_' . $console->getId()); -// } -// -// $session = Auth::decodeSession( -// $request->getCookie( -// Auth::$cookieName, // Get sessions -// $request->getCookie(Auth::$cookieName . '_legacy', '') -// ) -// ); -// -// // Get session from header for SSR clients -// if (empty($session['id']) && empty($session['secret'])) { -// $sessionHeader = $request->getHeader('x-appwrite-session', ''); -// -// if (!empty($sessionHeader)) { -// $session = Auth::decodeSession($sessionHeader); -// } -// } -// -// // Get fallback session from old clients (no SameSite support) or clients who block 3rd-party cookies -// if ($response) { -// $response->addHeader('X-Debug-Fallback', 'false'); -// } -// -// if (empty($session['id']) && empty($session['secret'])) { -// if ($response) { -// $response->addHeader('X-Debug-Fallback', 'true'); -// } -// $fallback = $request->getHeader('x-fallback-cookies', ''); -// $fallback = \json_decode($fallback, true); -// $session = Auth::decodeSession(((isset($fallback[Auth::$cookieName])) ? $fallback[Auth::$cookieName] : '')); -// } -// -// Auth::$unique = $session['id'] ?? ''; -// Auth::$secret = $session['secret'] ?? ''; -// -// if (APP_MODE_ADMIN !== $mode) { -// if ($project->isEmpty()) { -// $user = new Document([]); -// } else { -// if ($project->getId() === 'console') { -// $user = $dbForConsole->getDocument('users', Auth::$unique); -// } else { -// $user = $dbForProject->getDocument('users', Auth::$unique); -// } -// } -// } else { -// $user = $dbForConsole->getDocument('users', Auth::$unique); -// } -// -// if ( -// $user->isEmpty() // Check a document has been found in the DB -// || !Auth::sessionVerify($user->getAttribute('sessions', []), Auth::$secret) -// ) { // Validate user has valid login token -// $user = new Document([]); -// } -// -// if (APP_MODE_ADMIN === $mode) { -// if ($user->find('teamId', $project->getAttribute('teamId'), 'memberships')) { -// $auth->setDefaultStatus(false); // Cancel security segmentation for admin users. -// } else { -// $user = new Document([]); -// } -// } -// -// $authJWT = $request->getHeader('x-appwrite-jwt', ''); -// -// if (!empty($authJWT) && !$project->isEmpty()) { // JWT authentication -// $jwt = new JWT(System::getEnv('_APP_OPENSSL_KEY_V1'), 'HS256', 900, 10); // Instantiate with key, algo, maxAge and leeway. -// -// try { -// $payload = $jwt->decode($authJWT); -// } catch (JWTException $error) { -// throw new Exception(Exception::USER_JWT_INVALID, 'Failed to verify JWT. ' . $error->getMessage()); -// } -// -// $jwtUserId = $payload['userId'] ?? ''; -// $jwtSessionId = $payload['sessionId'] ?? ''; -// -// if ($jwtUserId && $jwtSessionId) { -// $user = $dbForProject->getDocument('users', $jwtUserId); -// } -// -// if (empty($user->find('$id', $jwtSessionId, 'sessions'))) { // Match JWT to active token -// $user = new Document([]); -// } -// } -// -// $dbForProject->setMetadata('user', $user->getId()); -// $dbForConsole->setMetadata('user', $user->getId()); -// -// return $user; -//}, ['mode', 'project', 'console', 'request', 'response', 'dbForProject', 'dbForConsole', 'auth']); -// -//Http::setResource('project', function (Database $dbForConsole, Request $request, Document $console, Authorization $auth) { -// -// $projectId = $request->getParam('project', $request->getHeader('x-appwrite-project', '')); -// -// if (empty($projectId) || $projectId === 'console') { -// return $console; -// } -// -// $project = $auth->skip(fn () => $dbForConsole->getDocument('projects', $projectId)); -// -// return $project; -//}, ['dbForConsole', 'request', 'console', 'auth']); -// -//Http::setResource('session', function (Document $user) { -// if ($user->isEmpty()) { -// return; -// } -// -// $sessions = $user->getAttribute('sessions', []); -// $sessionId = Auth::sessionVerify($user->getAttribute('sessions'), Auth::$secret); -// -// if (!$sessionId) { -// return; -// } -// -// foreach ($sessions as $session) {/** @var Document $session */ -// if ($sessionId === $session->getId()) { -// return $session; -// } -// } -// -// return; -//}, ['user']); -// -//Http::setResource('console', function () { -// return new Document([ -// '$id' => ID::custom('console'), -// '$internalId' => ID::custom('console'), -// 'name' => 'Appwrite', -// '$collection' => ID::custom('projects'), -// 'description' => 'Appwrite core engine', -// 'logo' => '', -// 'teamId' => -1, -// 'webhooks' => [], -// 'keys' => [], -// 'platforms' => [ -// [ -// '$collection' => ID::custom('platforms'), -// 'name' => 'Localhost', -// 'type' => Origin::CLIENT_TYPE_WEB, -// 'hostname' => 'localhost', -// ], // Current host is added on app init -// ], -// 'legalName' => '', -// 'legalCountry' => '', -// 'legalState' => '', -// 'legalCity' => '', -// 'legalAddress' => '', -// 'legalTaxId' => '', -// 'auths' => [ -// 'invites' => System::getEnv('_APP_CONSOLE_INVITES', 'enabled') === 'enabled', -// 'limit' => (System::getEnv('_APP_CONSOLE_WHITELIST_ROOT', 'enabled') === 'enabled') ? 1 : 0, // limit signup to 1 user -// 'duration' => Auth::TOKEN_EXPIRATION_LOGIN_LONG, // 1 Year in seconds -// ], -// 'authWhitelistEmails' => (!empty(System::getEnv('_APP_CONSOLE_WHITELIST_EMAILS', null))) ? \explode(',', System::getEnv('_APP_CONSOLE_WHITELIST_EMAILS', null)) : [], -// 'authWhitelistIPs' => (!empty(System::getEnv('_APP_CONSOLE_WHITELIST_IPS', null))) ? \explode(',', System::getEnv('_APP_CONSOLE_WHITELIST_IPS', null)) : [], -// 'oAuthProviders' => [ -// 'githubEnabled' => true, -// 'githubSecret' => System::getEnv('_APP_CONSOLE_GITHUB_SECRET', ''), -// 'githubAppid' => System::getEnv('_APP_CONSOLE_GITHUB_APP_ID', '') -// ], -// ]); -//}, []); -// -//Http::setResource('dbForProject', function (Group $pools, Database $dbForConsole, Cache $cache, Document $project, Authorization $auth, Connections $connections) { -// if ($project->isEmpty() || $project->getId() === 'console') { -// return $dbForConsole; -// } -// -// try { -// $dsn = new DSN($project->getAttribute('database')); -// } catch (\InvalidArgumentException) { -// // TODO: Temporary until all projects are using shared tables -// $dsn = new DSN('mysql://' . $project->getAttribute('database')); -// } -// -// $connection = $pools->get($dsn->getHost())->pop(); -// $connections->add($connection); -// $dbAdapter = $connection->getResource(); -// -// $database = new Database($dbAdapter, $cache); -// $database->setAuthorization($auth); -// -// $database -// ->setMetadata('host', \gethostname()) -// ->setMetadata('project', $project->getId()) -// ->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS); -// -// try { -// $dsn = new DSN($project->getAttribute('database')); -// } catch (\InvalidArgumentException) { -// // TODO: Temporary until all projects are using shared tables -// $dsn = new DSN('mysql://' . $project->getAttribute('database')); -// } -// -// if ($dsn->getHost() === DATABASE_SHARED_TABLES) { -// $database -// ->setSharedTables(true) -// ->setTenant($project->getInternalId()) -// ->setNamespace($dsn->getParam('namespace')); -// } else { -// $database -// ->setSharedTables(false) -// ->setTenant(null) -// ->setNamespace('_' . $project->getInternalId()); -// } -// -// return $database; -//}, ['pools', 'dbForConsole', 'cache', 'project', 'auth', 'connections']); -// -//Http::setResource('dbForConsole', function (Group $pools, Cache $cache, Authorization $auth, Connections $connections) { -// $connection = $pools->get('console')->pop(); -// $connections->add($connection); -// $dbAdapter = $connection->getResource(); -// -// $database = new Database($dbAdapter, $cache); -// $database->setAuthorization($auth); -// -// $database -// ->setNamespace('_console') -// ->setMetadata('host', \gethostname()) -// ->setMetadata('project', 'console') -// ->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS); -// -// return $database; -//}, ['pools', 'cache', 'auth', 'connections']); -// -//Http::setResource('getProjectDB', function (Group $pools, Database $dbForConsole, $cache, Authorization $auth, Connections $connections) { -// $databases = []; // TODO: @Meldiron This should probably be responsibility of utopia-php/pools -// -// $getProjectDB = function (Document $project) use ($pools, $dbForConsole, $cache, &$databases, $auth, $connections) { -// if ($project->isEmpty() || $project->getId() === 'console') { -// return $dbForConsole; -// } -// -// try { -// $dsn = new DSN($project->getAttribute('database')); -// } catch (\InvalidArgumentException) { -// // TODO: Temporary until all projects are using shared tables -// $dsn = new DSN('mysql://' . $project->getAttribute('database')); -// } -// -// $configure = (function (Database $database) use ($project, $dsn) { -// $database -// ->setMetadata('host', \gethostname()) -// ->setMetadata('project', $project->getId()) -// ->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS); -// -// if ($dsn->getHost() === DATABASE_SHARED_TABLES) { -// $database -// ->setSharedTables(true) -// ->setTenant($project->getInternalId()) -// ->setNamespace($dsn->getParam('namespace')); -// } else { -// $database -// ->setSharedTables(false) -// ->setTenant(null) -// ->setNamespace('_' . $project->getInternalId()); -// } -// }); -// -// if (isset($databases[$dsn->getHost()])) { -// $database = $databases[$dsn->getHost()]; -// $configure($database); -// return $database; -// } -// -// $connection = $pools->get($dsn->getHost())->pop(); -// $connections->add($connection); -// $dbAdapter = $connection->getResource(); -// -// $database = new Database($dbAdapter, $cache); -// $database->setAuthorization($auth); -// $databases[$dsn->getHost()] = $database; -// $configure($database); -// -// return $database; -// }; -// -// return $getProjectDB; -//}, ['pools', 'dbForConsole', 'cache', 'auth', 'connections']); -// -//Http::setResource('cache', function (Group $pools, Connections $connections) { -// $list = Config::getParam('pools-cache', []); -// $adapters = []; -// -// foreach ($list as $value) { -// $connection = $pools->get($value)->pop(); -// $connections->add($connection); -// $adapters[] = $connection->getResource(); -// } -// -// return new Cache(new Sharding($adapters)); -//}, ['pools', 'connections']); -// -//Http::setResource('deviceForLocal', function () { -// return new Local(); -//}); -// -//Http::setResource('deviceForFiles', function ($project) { -// return getDevice(APP_STORAGE_UPLOADS . '/app-' . $project->getId()); -//}, ['project']); -// -//Http::setResource('deviceForFunctions', function ($project) { -// return getDevice(APP_STORAGE_FUNCTIONS . '/app-' . $project->getId()); -//}, ['project']); -// -//Http::setResource('deviceForBuilds', function ($project) { -// return getDevice(APP_STORAGE_BUILDS . '/app-' . $project->getId()); -//}, ['project']); -// -//function getDevice($root): Device -//{ -// $connection = System::getEnv('_APP_CONNECTIONS_STORAGE', ''); -// -// if (!empty($connection)) { -// $acl = 'private'; -// $device = Storage::DEVICE_LOCAL; -// $accessKey = ''; -// $accessSecret = ''; -// $bucket = ''; -// $region = ''; -// -// try { -// $dsn = new DSN($connection); -// $device = $dsn->getScheme(); -// $accessKey = $dsn->getUser() ?? ''; -// $accessSecret = $dsn->getPassword() ?? ''; -// $bucket = $dsn->getPath() ?? ''; -// $region = $dsn->getParam('region'); -// } catch (\Throwable $e) { -// Console::warning($e->getMessage() . 'Invalid DSN. Defaulting to Local device.'); -// } -// -// switch ($device) { -// case Storage::DEVICE_S3: -// return new S3($root, $accessKey, $accessSecret, $bucket, $region, $acl); -// case STORAGE::DEVICE_DO_SPACES: -// $device = new DOSpaces($root, $accessKey, $accessSecret, $bucket, $region, $acl); -// $device->setHttpVersion(S3::HTTP_VERSION_1_1); -// return $device; -// case Storage::DEVICE_BACKBLAZE: -// return new Backblaze($root, $accessKey, $accessSecret, $bucket, $region, $acl); -// case Storage::DEVICE_LINODE: -// return new Linode($root, $accessKey, $accessSecret, $bucket, $region, $acl); -// case Storage::DEVICE_WASABI: -// return new Wasabi($root, $accessKey, $accessSecret, $bucket, $region, $acl); -// case Storage::DEVICE_LOCAL: -// default: -// return new Local($root); -// } -// } else { -// switch (strtolower(System::getEnv('_APP_STORAGE_DEVICE', Storage::DEVICE_LOCAL) ?? '')) { -// case Storage::DEVICE_LOCAL: -// default: -// return new Local($root); -// case Storage::DEVICE_S3: -// $s3AccessKey = System::getEnv('_APP_STORAGE_S3_ACCESS_KEY', ''); -// $s3SecretKey = System::getEnv('_APP_STORAGE_S3_SECRET', ''); -// $s3Region = System::getEnv('_APP_STORAGE_S3_REGION', ''); -// $s3Bucket = System::getEnv('_APP_STORAGE_S3_BUCKET', ''); -// $s3Acl = 'private'; -// return new S3($root, $s3AccessKey, $s3SecretKey, $s3Bucket, $s3Region, $s3Acl); -// case Storage::DEVICE_DO_SPACES: -// $doSpacesAccessKey = System::getEnv('_APP_STORAGE_DO_SPACES_ACCESS_KEY', ''); -// $doSpacesSecretKey = System::getEnv('_APP_STORAGE_DO_SPACES_SECRET', ''); -// $doSpacesRegion = System::getEnv('_APP_STORAGE_DO_SPACES_REGION', ''); -// $doSpacesBucket = System::getEnv('_APP_STORAGE_DO_SPACES_BUCKET', ''); -// $doSpacesAcl = 'private'; -// $device = new DOSpaces($root, $doSpacesAccessKey, $doSpacesSecretKey, $doSpacesBucket, $doSpacesRegion, $doSpacesAcl); -// $device->setHttpVersion(S3::HTTP_VERSION_1_1); -// return $device; -// case Storage::DEVICE_BACKBLAZE: -// $backblazeAccessKey = System::getEnv('_APP_STORAGE_BACKBLAZE_ACCESS_KEY', ''); -// $backblazeSecretKey = System::getEnv('_APP_STORAGE_BACKBLAZE_SECRET', ''); -// $backblazeRegion = System::getEnv('_APP_STORAGE_BACKBLAZE_REGION', ''); -// $backblazeBucket = System::getEnv('_APP_STORAGE_BACKBLAZE_BUCKET', ''); -// $backblazeAcl = 'private'; -// return new Backblaze($root, $backblazeAccessKey, $backblazeSecretKey, $backblazeBucket, $backblazeRegion, $backblazeAcl); -// case Storage::DEVICE_LINODE: -// $linodeAccessKey = System::getEnv('_APP_STORAGE_LINODE_ACCESS_KEY', ''); -// $linodeSecretKey = System::getEnv('_APP_STORAGE_LINODE_SECRET', ''); -// $linodeRegion = System::getEnv('_APP_STORAGE_LINODE_REGION', ''); -// $linodeBucket = System::getEnv('_APP_STORAGE_LINODE_BUCKET', ''); -// $linodeAcl = 'private'; -// return new Linode($root, $linodeAccessKey, $linodeSecretKey, $linodeBucket, $linodeRegion, $linodeAcl); -// case Storage::DEVICE_WASABI: -// $wasabiAccessKey = System::getEnv('_APP_STORAGE_WASABI_ACCESS_KEY', ''); -// $wasabiSecretKey = System::getEnv('_APP_STORAGE_WASABI_SECRET', ''); -// $wasabiRegion = System::getEnv('_APP_STORAGE_WASABI_REGION', ''); -// $wasabiBucket = System::getEnv('_APP_STORAGE_WASABI_BUCKET', ''); -// $wasabiAcl = 'private'; -// return new Wasabi($root, $wasabiAccessKey, $wasabiSecretKey, $wasabiBucket, $wasabiRegion, $wasabiAcl); -// } -// } -//} -// -//Http::setResource('mode', function ($request) { -// /** @var Appwrite\Utopia\Request $request */ -// -// /** -// * Defines the mode for the request: -// * - 'default' => Requests for Client and Server Side -// * - 'admin' => Request from the Console on non-console projects -// */ -// return $request->getParam('mode', $request->getHeader('x-appwrite-mode', APP_MODE_DEFAULT)); -//}, ['request']); -// -//Http::setResource('geodb', function ($register) { -// /** @var Utopia\Registry\Registry $register */ -// return $register->get('geodb'); -//}, ['register']); -// -//Http::setResource('passwordsDictionary', function ($register) { -// /** @var Utopia\Registry\Registry $register */ -// return $register->get('passwordsDictionary'); -//}, ['register']); -// -// -//Http::setResource('servers', function () { -// $platforms = Config::getParam('platforms'); -// $server = $platforms[APP_PLATFORM_SERVER]; -// -// $languages = array_map(function ($language) { -// return strtolower($language['name']); -// }, $server['sdks']); -// -// return $languages; -//}); -// -//Http::setResource('promiseAdapter', function ($register) { -// return $register->get('promiseAdapter'); -//}, ['register']); -// -//Http::setResource('schema', function (Http $utopia, Database $dbForProject, Authorization $auth) { -// -// $complexity = function (int $complexity, array $args) { -// $queries = Query::parseQueries($args['queries'] ?? []); -// $query = Query::getByType($queries, [Query::TYPE_LIMIT])[0] ?? null; -// $limit = $query ? $query->getValue() : APP_LIMIT_LIST_DEFAULT; -// -// return $complexity * $limit; -// }; -// -// $attributes = function (int $limit, int $offset) use ($dbForProject, $auth) { -// $attrs = $auth->skip(fn () => $dbForProject->find('attributes', [ -// Query::limit($limit), -// Query::offset($offset), -// ])); -// -// return \array_map(function ($attr) { -// return $attr->getArrayCopy(); -// }, $attrs); -// }; -// -// $urls = [ -// 'list' => function (string $databaseId, string $collectionId, array $args) { -// return "/v1/databases/$databaseId/collections/$collectionId/documents"; -// }, -// 'create' => function (string $databaseId, string $collectionId, array $args) { -// return "/v1/databases/$databaseId/collections/$collectionId/documents"; -// }, -// 'read' => function (string $databaseId, string $collectionId, array $args) { -// return "/v1/databases/$databaseId/collections/$collectionId/documents/{$args['documentId']}"; -// }, -// 'update' => function (string $databaseId, string $collectionId, array $args) { -// return "/v1/databases/$databaseId/collections/$collectionId/documents/{$args['documentId']}"; -// }, -// 'delete' => function (string $databaseId, string $collectionId, array $args) { -// return "/v1/databases/$databaseId/collections/$collectionId/documents/{$args['documentId']}"; -// }, -// ]; -// -// $params = [ -// 'list' => function (string $databaseId, string $collectionId, array $args) { -// return [ 'queries' => $args['queries']]; -// }, -// 'create' => function (string $databaseId, string $collectionId, array $args) { -// $id = $args['id'] ?? 'unique()'; -// $permissions = $args['permissions'] ?? null; -// -// unset($args['id']); -// unset($args['permissions']); -// -// // Order must be the same as the route params -// return [ -// 'databaseId' => $databaseId, -// 'documentId' => $id, -// 'collectionId' => $collectionId, -// 'data' => $args, -// 'permissions' => $permissions, -// ]; -// }, -// 'update' => function (string $databaseId, string $collectionId, array $args) { -// $documentId = $args['id']; -// $permissions = $args['permissions'] ?? null; -// -// unset($args['id']); -// unset($args['permissions']); -// -// // Order must be the same as the route params -// return [ -// 'databaseId' => $databaseId, -// 'collectionId' => $collectionId, -// 'documentId' => $documentId, -// 'data' => $args, -// 'permissions' => $permissions, -// ]; -// }, -// ]; -// -// return Schema::build( -// $utopia, -// $complexity, -// $attributes, -// $urls, -// $params, -// ); -//}, ['utopia', 'dbForProject', 'auth']); -// -//Http::setResource('contributors', function () { -// $path = 'app/config/contributors.json'; -// $list = (file_exists($path)) ? json_decode(file_get_contents($path), true) : []; -// return $list; -//}); -// -//Http::setResource('employees', function () { -// $path = 'app/config/employees.json'; -// $list = (file_exists($path)) ? json_decode(file_get_contents($path), true) : []; -// return $list; -//}); -// -//Http::setResource('heroes', function () { -// $path = 'app/config/heroes.json'; -// $list = (file_exists($path)) ? json_decode(file_get_contents($path), true) : []; -// return $list; -//}); -// -//Http::setResource('gitHub', function (Cache $cache) { -// return new VcsGitHub($cache); -//}, ['cache']); -// -//Http::setResource('requestTimestamp', function ($request) { -// //TODO: Move this to the Request class itself -// $timestampHeader = $request->getHeader('x-appwrite-timestamp'); -// $requestTimestamp = null; -// if (!empty($timestampHeader)) { -// try { -// $requestTimestamp = new \DateTime($timestampHeader); -// } catch (\Throwable $e) { -// throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'Invalid X-Appwrite-Timestamp header value'); -// } -// } -// return $requestTimestamp; -//}, ['request']); -// -//Http::setResource('plan', function (array $plan = []) { -// return []; -//}); -// -//Http::setResource('auth', fn () => new Authorization()); -// -//Http::setResource('pools', function ($register) { -// return $register->get('pools'); -//}, ['pools']); +/** + * Init + * + * Initializes both Appwrite API entry point, queue workers, and CLI tasks. + * Set configuration, framework resources & app constants + * + */ + +if (\file_exists(__DIR__ . '/../vendor/autoload.php')) { + require_once __DIR__ . '/../vendor/autoload.php'; +} + +\ini_set('memory_limit', '512M'); +\ini_set('display_errors', 1); +\ini_set('display_startup_errors', 1); +\ini_set('default_socket_timeout', -1); +\error_reporting(E_ALL); + +use Ahc\Jwt\JWT; +use Ahc\Jwt\JWTException; +use Appwrite\Auth\Auth; +use Appwrite\Event\Audit; +use Appwrite\Event\Build; +use Appwrite\Event\Certificate; +use Appwrite\Event\Database as EventDatabase; +use Appwrite\Event\Delete; +use Appwrite\Event\Event; +use Appwrite\Event\Func; +use Appwrite\Event\Mail; +use Appwrite\Event\Messaging; +use Appwrite\Event\Migration; +use Appwrite\Event\Usage; +use Appwrite\Extend\Exception; +use Appwrite\GraphQL\Promises\Adapter\Swoole; +use Appwrite\GraphQL\Schema; +use Appwrite\Hooks\Hooks; +use Appwrite\Network\Validator\Email; +use Appwrite\Network\Validator\Origin; +use Appwrite\OpenSSL\OpenSSL; +use Appwrite\URL\URL as AppwriteURL; +use MaxMind\Db\Reader; +use PHPMailer\PHPMailer\PHPMailer; +use Swoole\Database\PDOProxy; +use Utopia\App; +use Utopia\Cache\Adapter\Redis as RedisCache; +use Utopia\Cache\Adapter\Sharding; +use Utopia\Cache\Cache; +use Utopia\CLI\Console; +use Utopia\Config\Config; +use Utopia\Database\Adapter\MariaDB; +use Utopia\Database\Adapter\MySQL; +use Utopia\Database\Adapter\SQL; +use Utopia\Database\Database; +use Utopia\Database\Document; +use Utopia\Database\Helpers\ID; +use Utopia\Database\Query; +use Utopia\Database\Validator\Authorization; +use Utopia\Database\Validator\Datetime as DatetimeValidator; +use Utopia\Database\Validator\Structure; +use Utopia\Domains\Validator\PublicDomain; +use Utopia\DSN\DSN; +use Utopia\Locale\Locale; +use Utopia\Logger\Log; +use Utopia\Logger\Logger; +use Utopia\Pools\Group; +use Utopia\Pools\Pool; +use Utopia\Queue; +use Utopia\Queue\Connection; +use Utopia\Registry\Registry; +use Utopia\Storage\Device; +use Utopia\Storage\Device\Backblaze; +use Utopia\Storage\Device\DOSpaces; +use Utopia\Storage\Device\Linode; +use Utopia\Storage\Device\Local; +use Utopia\Storage\Device\S3; +use Utopia\Storage\Device\Wasabi; +use Utopia\Storage\Storage; +use Utopia\System\System; +use Utopia\Validator\Hostname; +use Utopia\Validator\IP; +use Utopia\Validator\Range; +use Utopia\Validator\URL; +use Utopia\Validator\WhiteList; +use Utopia\VCS\Adapter\Git\GitHub as VcsGitHub; + +const APP_NAME = 'Appwrite'; +const APP_DOMAIN = 'appwrite.io'; +const APP_EMAIL_TEAM = 'team@localhost.test'; // Default email address +const APP_EMAIL_SECURITY = ''; // Default security email address +const APP_USERAGENT = APP_NAME . '-Server v%s. Please report abuse at %s'; +const APP_MODE_DEFAULT = 'default'; +const APP_MODE_ADMIN = 'admin'; +const APP_PAGING_LIMIT = 12; +const APP_LIMIT_COUNT = 5000; +const APP_LIMIT_USERS = 10_000; +const APP_LIMIT_USER_PASSWORD_HISTORY = 20; +const APP_LIMIT_USER_SESSIONS_MAX = 100; +const APP_LIMIT_USER_SESSIONS_DEFAULT = 10; +const APP_LIMIT_ANTIVIRUS = 20_000_000; //20MB +const APP_LIMIT_ENCRYPTION = 20_000_000; //20MB +const APP_LIMIT_COMPRESSION = 20_000_000; //20MB +const APP_LIMIT_ARRAY_PARAMS_SIZE = 100; // Default maximum of how many elements can there be in API parameter that expects array value +const APP_LIMIT_ARRAY_LABELS_SIZE = 1000; // Default maximum of how many labels elements can there be in API parameter that expects array value +const APP_LIMIT_ARRAY_ELEMENT_SIZE = 4096; // Default maximum length of element in array parameter represented by maximum URL length. +const APP_LIMIT_SUBQUERY = 1000; +const APP_LIMIT_SUBSCRIBERS_SUBQUERY = 1_000_000; +const APP_LIMIT_WRITE_RATE_DEFAULT = 60; // Default maximum write rate per rate period +const APP_LIMIT_WRITE_RATE_PERIOD_DEFAULT = 60; // Default maximum write rate period in seconds +const APP_LIMIT_LIST_DEFAULT = 25; // Default maximum number of items to return in list API calls +const APP_KEY_ACCCESS = 24 * 60 * 60; // 24 hours +const APP_USER_ACCCESS = 24 * 60 * 60; // 24 hours +const APP_CACHE_UPDATE = 24 * 60 * 60; // 24 hours +const APP_CACHE_BUSTER = 4314; +const APP_VERSION_STABLE = '1.5.7'; +const APP_DATABASE_ATTRIBUTE_EMAIL = 'email'; +const APP_DATABASE_ATTRIBUTE_ENUM = 'enum'; +const APP_DATABASE_ATTRIBUTE_IP = 'ip'; +const APP_DATABASE_ATTRIBUTE_DATETIME = 'datetime'; +const APP_DATABASE_ATTRIBUTE_URL = 'url'; +const APP_DATABASE_ATTRIBUTE_INT_RANGE = 'intRange'; +const APP_DATABASE_ATTRIBUTE_FLOAT_RANGE = 'floatRange'; +const APP_DATABASE_ATTRIBUTE_STRING_MAX_LENGTH = 1_073_741_824; // 2^32 bits / 4 bits per char +const APP_DATABASE_TIMEOUT_MILLISECONDS = 15_000; +const APP_STORAGE_UPLOADS = '/storage/uploads'; +const APP_STORAGE_FUNCTIONS = '/storage/functions'; +const APP_STORAGE_BUILDS = '/storage/builds'; +const APP_STORAGE_CACHE = '/storage/cache'; +const APP_STORAGE_CERTIFICATES = '/storage/certificates'; +const APP_STORAGE_CONFIG = '/storage/config'; +const APP_STORAGE_READ_BUFFER = 20 * (1000 * 1000); //20MB other names `APP_STORAGE_MEMORY_LIMIT`, `APP_STORAGE_MEMORY_BUFFER`, `APP_STORAGE_READ_LIMIT`, `APP_STORAGE_BUFFER_LIMIT` +const APP_SOCIAL_TWITTER = 'https://twitter.com/appwrite'; +const APP_SOCIAL_TWITTER_HANDLE = 'appwrite'; +const APP_SOCIAL_FACEBOOK = 'https://www.facebook.com/appwrite.io'; +const APP_SOCIAL_LINKEDIN = 'https://www.linkedin.com/company/appwrite'; +const APP_SOCIAL_INSTAGRAM = 'https://www.instagram.com/appwrite.io'; +const APP_SOCIAL_GITHUB = 'https://github.com/appwrite'; +const APP_SOCIAL_DISCORD = 'https://appwrite.io/discord'; +const APP_SOCIAL_DISCORD_CHANNEL = '564160730845151244'; +const APP_SOCIAL_DEV = 'https://dev.to/appwrite'; +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'; + +// Database Reconnect +const DATABASE_RECONNECT_SLEEP = 2; +const DATABASE_RECONNECT_MAX_ATTEMPTS = 10; + +// Database Worker Types +const DATABASE_TYPE_CREATE_ATTRIBUTE = 'createAttribute'; +const DATABASE_TYPE_CREATE_INDEX = 'createIndex'; +const DATABASE_TYPE_DELETE_ATTRIBUTE = 'deleteAttribute'; +const DATABASE_TYPE_DELETE_INDEX = 'deleteIndex'; +const DATABASE_TYPE_DELETE_COLLECTION = 'deleteCollection'; +const DATABASE_TYPE_DELETE_DATABASE = 'deleteDatabase'; + +// Build Worker Types +const BUILD_TYPE_DEPLOYMENT = 'deployment'; +const BUILD_TYPE_RETRY = 'retry'; + +// Deletion Types +const DELETE_TYPE_DATABASES = 'databases'; +const DELETE_TYPE_DOCUMENT = 'document'; +const DELETE_TYPE_COLLECTIONS = 'collections'; +const DELETE_TYPE_PROJECTS = 'projects'; +const DELETE_TYPE_FUNCTIONS = 'functions'; +const DELETE_TYPE_DEPLOYMENTS = 'deployments'; +const DELETE_TYPE_USERS = 'users'; +const DELETE_TYPE_TEAMS = 'teams'; +const DELETE_TYPE_EXECUTIONS = 'executions'; +const DELETE_TYPE_AUDIT = 'audit'; +const DELETE_TYPE_ABUSE = 'abuse'; +const DELETE_TYPE_USAGE = 'usage'; +const DELETE_TYPE_REALTIME = 'realtime'; +const DELETE_TYPE_BUCKETS = 'buckets'; +const DELETE_TYPE_INSTALLATIONS = 'installations'; +const DELETE_TYPE_RULES = 'rules'; +const DELETE_TYPE_SESSIONS = 'sessions'; +const DELETE_TYPE_CACHE_BY_TIMESTAMP = 'cacheByTimeStamp'; +const DELETE_TYPE_CACHE_BY_RESOURCE = 'cacheByResource'; +const DELETE_TYPE_SCHEDULES = 'schedules'; +const DELETE_TYPE_TOPIC = 'topic'; +const DELETE_TYPE_TARGET = 'target'; +const DELETE_TYPE_EXPIRED_TARGETS = 'invalid_targets'; +const DELETE_TYPE_SESSION_TARGETS = 'session_targets'; + +// Message types +const MESSAGE_SEND_TYPE_INTERNAL = 'internal'; +const MESSAGE_SEND_TYPE_EXTERNAL = 'external'; +// Mail Types +const MAIL_TYPE_VERIFICATION = 'verification'; +const MAIL_TYPE_MAGIC_SESSION = 'magicSession'; +const MAIL_TYPE_RECOVERY = 'recovery'; +const MAIL_TYPE_INVITATION = 'invitation'; +const MAIL_TYPE_CERTIFICATE = 'certificate'; +// Auth Types +const APP_AUTH_TYPE_SESSION = 'Session'; +const APP_AUTH_TYPE_JWT = 'JWT'; +const APP_AUTH_TYPE_KEY = 'Key'; +const APP_AUTH_TYPE_ADMIN = 'Admin'; +// Response related +const MAX_OUTPUT_CHUNK_SIZE = 10 * 1024 * 1024; // 10MB +// Function headers +const FUNCTION_ALLOWLIST_HEADERS_REQUEST = ['content-type', 'agent', 'content-length', 'host']; +const FUNCTION_ALLOWLIST_HEADERS_RESPONSE = ['content-type', 'content-length']; +// Message types +const MESSAGE_TYPE_EMAIL = 'email'; +const MESSAGE_TYPE_SMS = 'sms'; +const MESSAGE_TYPE_PUSH = 'push'; +// Usage metrics +const METRIC_TEAMS = 'teams'; +const METRIC_USERS = 'users'; +const METRIC_MESSAGES = 'messages'; +const METRIC_MESSAGES_COUNTRY_CODE = '{countryCode}.messages'; +const METRIC_SESSIONS = 'sessions'; +const METRIC_DATABASES = 'databases'; +const METRIC_COLLECTIONS = 'collections'; +const METRIC_DATABASE_ID_COLLECTIONS = '{databaseInternalId}.collections'; +const METRIC_DOCUMENTS = 'documents'; +const METRIC_DATABASE_ID_DOCUMENTS = '{databaseInternalId}.documents'; +const METRIC_DATABASE_ID_COLLECTION_ID_DOCUMENTS = '{databaseInternalId}.{collectionInternalId}.documents'; +const METRIC_BUCKETS = 'buckets'; +const METRIC_FILES = 'files'; +const METRIC_FILES_STORAGE = 'files.storage'; +const METRIC_BUCKET_ID_FILES = '{bucketInternalId}.files'; +const METRIC_BUCKET_ID_FILES_STORAGE = '{bucketInternalId}.files.storage'; +const METRIC_FUNCTIONS = 'functions'; +const METRIC_DEPLOYMENTS = 'deployments'; +const METRIC_DEPLOYMENTS_STORAGE = 'deployments.storage'; +const METRIC_BUILDS = 'builds'; +const METRIC_BUILDS_STORAGE = 'builds.storage'; +const METRIC_BUILDS_COMPUTE = 'builds.compute'; +const METRIC_FUNCTION_ID_BUILDS = '{functionInternalId}.builds'; +const METRIC_FUNCTION_ID_BUILDS_STORAGE = '{functionInternalId}.builds.storage'; +const METRIC_FUNCTION_ID_BUILDS_COMPUTE = '{functionInternalId}.builds.compute'; +const METRIC_FUNCTION_ID_DEPLOYMENTS = '{resourceType}.{resourceInternalId}.deployments'; +const METRIC_FUNCTION_ID_DEPLOYMENTS_STORAGE = '{resourceType}.{resourceInternalId}.deployments.storage'; +const METRIC_EXECUTIONS = 'executions'; +const METRIC_EXECUTIONS_COMPUTE = 'executions.compute'; +const METRIC_FUNCTION_ID_EXECUTIONS = '{functionInternalId}.executions'; +const METRIC_FUNCTION_ID_EXECUTIONS_COMPUTE = '{functionInternalId}.executions.compute'; +const METRIC_NETWORK_REQUESTS = 'network.requests'; +const METRIC_NETWORK_INBOUND = 'network.inbound'; +const METRIC_NETWORK_OUTBOUND = 'network.outbound'; + +$register = new Registry(); + +App::setMode(System::getEnv('_APP_ENV', App::MODE_TYPE_PRODUCTION)); + +if (!App::isProduction()) { + // Allow specific domains to skip public domain validation in dev environment + // Useful for existing tests involving webhooks + PublicDomain::allow(['request-catcher']); +} + +/* + * ENV vars + */ +Config::load('events', __DIR__ . '/config/events.php'); +Config::load('auth', __DIR__ . '/config/auth.php'); +Config::load('apis', __DIR__ . '/config/apis.php'); // List of APIs +Config::load('errors', __DIR__ . '/config/errors.php'); +Config::load('oAuthProviders', __DIR__ . '/config/oAuthProviders.php'); +Config::load('platforms', __DIR__ . '/config/platforms.php'); +Config::load('collections', __DIR__ . '/config/collections.php'); +Config::load('runtimes', __DIR__ . '/config/runtimes.php'); +Config::load('runtimes-v2', __DIR__ . '/config/runtimes-v2.php'); +Config::load('usage', __DIR__ . '/config/usage.php'); +Config::load('roles', __DIR__ . '/config/roles.php'); // User roles and scopes +Config::load('scopes', __DIR__ . '/config/scopes.php'); // User roles and scopes +Config::load('services', __DIR__ . '/config/services.php'); // List of services +Config::load('variables', __DIR__ . '/config/variables.php'); // List of env variables +Config::load('regions', __DIR__ . '/config/regions.php'); // List of available regions +Config::load('avatar-browsers', __DIR__ . '/config/avatars/browsers.php'); +Config::load('avatar-credit-cards', __DIR__ . '/config/avatars/credit-cards.php'); +Config::load('avatar-flags', __DIR__ . '/config/avatars/flags.php'); +Config::load('locale-codes', __DIR__ . '/config/locale/codes.php'); +Config::load('locale-currencies', __DIR__ . '/config/locale/currencies.php'); +Config::load('locale-eu', __DIR__ . '/config/locale/eu.php'); +Config::load('locale-languages', __DIR__ . '/config/locale/languages.php'); +Config::load('locale-phones', __DIR__ . '/config/locale/phones.php'); +Config::load('locale-countries', __DIR__ . '/config/locale/countries.php'); +Config::load('locale-continents', __DIR__ . '/config/locale/continents.php'); +Config::load('locale-templates', __DIR__ . '/config/locale/templates.php'); +Config::load('storage-logos', __DIR__ . '/config/storage/logos.php'); +Config::load('storage-mimes', __DIR__ . '/config/storage/mimes.php'); +Config::load('storage-inputs', __DIR__ . '/config/storage/inputs.php'); +Config::load('storage-outputs', __DIR__ . '/config/storage/outputs.php'); + +/** + * New DB Filters + */ +Database::addFilter( + 'casting', + function (mixed $value) { + return json_encode(['value' => $value], JSON_PRESERVE_ZERO_FRACTION); + }, + function (mixed $value) { + if (is_null($value)) { + return; + } + + return json_decode($value, true)['value']; + } +); + +Database::addFilter( + 'enum', + function (mixed $value, Document $attribute) { + if ($attribute->isSet('elements')) { + $attribute->removeAttribute('elements'); + } + + return $value; + }, + function (mixed $value, Document $attribute) { + $formatOptions = \json_decode($attribute->getAttribute('formatOptions', '[]'), true); + if (isset($formatOptions['elements'])) { + $attribute->setAttribute('elements', $formatOptions['elements']); + } + + return $value; + } +); + +Database::addFilter( + 'range', + function (mixed $value, Document $attribute) { + if ($attribute->isSet('min')) { + $attribute->removeAttribute('min'); + } + if ($attribute->isSet('max')) { + $attribute->removeAttribute('max'); + } + + return $value; + }, + function (mixed $value, Document $attribute) { + $formatOptions = json_decode($attribute->getAttribute('formatOptions', '[]'), true); + if (isset($formatOptions['min']) || isset($formatOptions['max'])) { + $attribute + ->setAttribute('min', $formatOptions['min']) + ->setAttribute('max', $formatOptions['max']) + ; + } + + return $value; + } +); + +Database::addFilter( + 'subQueryAttributes', + function (mixed $value) { + return; + }, + function (mixed $value, Document $document, Database $database) { + $attributes = $database->find('attributes', [ + Query::equal('collectionInternalId', [$document->getInternalId()]), + Query::equal('databaseInternalId', [$document->getAttribute('databaseInternalId')]), + Query::limit($database->getLimitForAttributes()), + ]); + + foreach ($attributes as $attribute) { + if ($attribute->getAttribute('type') === Database::VAR_RELATIONSHIP) { + $options = $attribute->getAttribute('options'); + foreach ($options as $key => $value) { + $attribute->setAttribute($key, $value); + } + $attribute->removeAttribute('options'); + } + } + + return $attributes; + } +); + +Database::addFilter( + 'subQueryIndexes', + function (mixed $value) { + return; + }, + function (mixed $value, Document $document, Database $database) { + return $database + ->find('indexes', [ + Query::equal('collectionInternalId', [$document->getInternalId()]), + Query::equal('databaseInternalId', [$document->getAttribute('databaseInternalId')]), + Query::limit($database->getLimitForIndexes()), + ]); + } +); + +Database::addFilter( + 'subQueryPlatforms', + function (mixed $value) { + return; + }, + function (mixed $value, Document $document, Database $database) { + return $database + ->find('platforms', [ + Query::equal('projectInternalId', [$document->getInternalId()]), + Query::limit(APP_LIMIT_SUBQUERY), + ]); + } +); + +Database::addFilter( + 'subQueryKeys', + function (mixed $value) { + return; + }, + function (mixed $value, Document $document, Database $database) { + return $database + ->find('keys', [ + Query::equal('projectInternalId', [$document->getInternalId()]), + Query::limit(APP_LIMIT_SUBQUERY), + ]); + } +); + +Database::addFilter( + 'subQueryWebhooks', + function (mixed $value) { + return; + }, + function (mixed $value, Document $document, Database $database) { + return $database + ->find('webhooks', [ + Query::equal('projectInternalId', [$document->getInternalId()]), + Query::limit(APP_LIMIT_SUBQUERY), + ]); + } +); + +Database::addFilter( + 'subQuerySessions', + function (mixed $value) { + return; + }, + function (mixed $value, Document $document, Database $database) { + return Authorization::skip(fn () => $database->find('sessions', [ + Query::equal('userInternalId', [$document->getInternalId()]), + Query::limit(APP_LIMIT_SUBQUERY), + ])); + } +); + +Database::addFilter( + 'subQueryTokens', + function (mixed $value) { + return; + }, + function (mixed $value, Document $document, Database $database) { + return Authorization::skip(fn () => $database + ->find('tokens', [ + Query::equal('userInternalId', [$document->getInternalId()]), + Query::limit(APP_LIMIT_SUBQUERY), + ])); + } +); + +Database::addFilter( + 'subQueryChallenges', + function (mixed $value) { + return; + }, + function (mixed $value, Document $document, Database $database) { + return Authorization::skip(fn () => $database + ->find('challenges', [ + Query::equal('userInternalId', [$document->getInternalId()]), + Query::limit(APP_LIMIT_SUBQUERY), + ])); + } +); + +Database::addFilter( + 'subQueryAuthenticators', + function (mixed $value) { + return; + }, + function (mixed $value, Document $document, Database $database) { + return Authorization::skip(fn () => $database + ->find('authenticators', [ + Query::equal('userInternalId', [$document->getInternalId()]), + Query::limit(APP_LIMIT_SUBQUERY), + ])); + } +); + +Database::addFilter( + 'subQueryMemberships', + function (mixed $value) { + return; + }, + function (mixed $value, Document $document, Database $database) { + return Authorization::skip(fn () => $database + ->find('memberships', [ + Query::equal('userInternalId', [$document->getInternalId()]), + Query::limit(APP_LIMIT_SUBQUERY), + ])); + } +); + +Database::addFilter( + 'subQueryVariables', + function (mixed $value) { + return; + }, + function (mixed $value, Document $document, Database $database) { + return $database + ->find('variables', [ + Query::equal('resourceInternalId', [$document->getInternalId()]), + Query::equal('resourceType', ['function']), + Query::limit(APP_LIMIT_SUBQUERY), + ]); + } +); + +Database::addFilter( + 'encrypt', + function (mixed $value) { + $key = System::getEnv('_APP_OPENSSL_KEY_V1'); + $iv = OpenSSL::randomPseudoBytes(OpenSSL::cipherIVLength(OpenSSL::CIPHER_AES_128_GCM)); + $tag = null; + + return json_encode([ + 'data' => OpenSSL::encrypt($value, OpenSSL::CIPHER_AES_128_GCM, $key, 0, $iv, $tag), + 'method' => OpenSSL::CIPHER_AES_128_GCM, + 'iv' => \bin2hex($iv), + 'tag' => \bin2hex($tag ?? ''), + 'version' => '1', + ]); + }, + function (mixed $value) { + if (is_null($value)) { + return; + } + $value = json_decode($value, true); + $key = System::getEnv('_APP_OPENSSL_KEY_V' . $value['version']); + + return OpenSSL::decrypt($value['data'], $value['method'], $key, 0, hex2bin($value['iv']), hex2bin($value['tag'])); + } +); + +Database::addFilter( + 'subQueryProjectVariables', + function (mixed $value) { + return; + }, + function (mixed $value, Document $document, Database $database) { + return $database + ->find('variables', [ + Query::equal('resourceType', ['project']), + Query::limit(APP_LIMIT_SUBQUERY) + ]); + } +); + +Database::addFilter( + 'userSearch', + function (mixed $value, Document $user) { + $searchValues = [ + $user->getId(), + $user->getAttribute('email', ''), + $user->getAttribute('name', ''), + $user->getAttribute('phone', '') + ]; + + foreach ($user->getAttribute('labels', []) as $label) { + $searchValues[] = 'label:' . $label; + } + + $search = implode(' ', \array_filter($searchValues)); + + return $search; + }, + function (mixed $value) { + return $value; + } +); + +Database::addFilter( + 'subQueryTargets', + function (mixed $value) { + return; + }, + function (mixed $value, Document $document, Database $database) { + return Authorization::skip(fn () => $database + ->find('targets', [ + Query::equal('userInternalId', [$document->getInternalId()]), + Query::limit(APP_LIMIT_SUBQUERY) + ])); + } +); + +Database::addFilter( + 'subQueryTopicTargets', + function (mixed $value) { + return; + }, + function (mixed $value, Document $document, Database $database) { + $targetIds = Authorization::skip(fn () => \array_map( + fn ($document) => $document->getAttribute('targetInternalId'), + $database->find('subscribers', [ + Query::equal('topicInternalId', [$document->getInternalId()]), + Query::limit(APP_LIMIT_SUBSCRIBERS_SUBQUERY) + ]) + )); + if (\count($targetIds) > 0) { + return $database->skipValidation(fn () => $database->find('targets', [ + Query::equal('$internalId', $targetIds) + ])); + } + return []; + } +); + +Database::addFilter( + 'providerSearch', + function (mixed $value, Document $provider) { + $searchValues = [ + $provider->getId(), + $provider->getAttribute('name', ''), + $provider->getAttribute('provider', ''), + $provider->getAttribute('type', '') + ]; + + $search = \implode(' ', \array_filter($searchValues)); + + return $search; + }, + function (mixed $value) { + return $value; + } +); + +Database::addFilter( + 'topicSearch', + function (mixed $value, Document $topic) { + $searchValues = [ + $topic->getId(), + $topic->getAttribute('name', ''), + $topic->getAttribute('description', ''), + ]; + + $search = \implode(' ', \array_filter($searchValues)); + + return $search; + }, + function (mixed $value) { + return $value; + } +); + +Database::addFilter( + 'messageSearch', + function (mixed $value, Document $message) { + $searchValues = [ + $message->getId(), + $message->getAttribute('description', ''), + $message->getAttribute('status', ''), + ]; + + $data = \json_decode($message->getAttribute('data', []), true); + $providerType = $message->getAttribute('providerType', ''); + + if ($providerType === MESSAGE_TYPE_EMAIL) { + $searchValues = \array_merge($searchValues, [$data['subject'], MESSAGE_TYPE_EMAIL]); + } elseif ($providerType === MESSAGE_TYPE_SMS) { + $searchValues = \array_merge($searchValues, [$data['content'], MESSAGE_TYPE_SMS]); + } else { + $searchValues = \array_merge($searchValues, [$data['title'], MESSAGE_TYPE_PUSH]); + } + + $search = \implode(' ', \array_filter($searchValues)); + + return $search; + }, + function (mixed $value) { + return $value; + } +); + +/** + * DB Formats + */ +Structure::addFormat(APP_DATABASE_ATTRIBUTE_EMAIL, function () { + return new Email(); +}, Database::VAR_STRING); + +Structure::addFormat(APP_DATABASE_ATTRIBUTE_DATETIME, function () { + return new DatetimeValidator(); +}, Database::VAR_DATETIME); + +Structure::addFormat(APP_DATABASE_ATTRIBUTE_ENUM, function ($attribute) { + $elements = $attribute['formatOptions']['elements']; + return new WhiteList($elements, true); +}, Database::VAR_STRING); + +Structure::addFormat(APP_DATABASE_ATTRIBUTE_IP, function () { + return new IP(); +}, Database::VAR_STRING); + +Structure::addFormat(APP_DATABASE_ATTRIBUTE_URL, function () { + return new URL(); +}, Database::VAR_STRING); + +Structure::addFormat(APP_DATABASE_ATTRIBUTE_INT_RANGE, function ($attribute) { + $min = $attribute['formatOptions']['min'] ?? -INF; + $max = $attribute['formatOptions']['max'] ?? INF; + return new Range($min, $max, Range::TYPE_INTEGER); +}, Database::VAR_INTEGER); + +Structure::addFormat(APP_DATABASE_ATTRIBUTE_FLOAT_RANGE, function ($attribute) { + $min = $attribute['formatOptions']['min'] ?? -INF; + $max = $attribute['formatOptions']['max'] ?? INF; + return new Range($min, $max, Range::TYPE_FLOAT); +}, Database::VAR_FLOAT); + +/* + * Registry + */ +$register->set('logger', function () { + // Register error logger + $providerName = System::getEnv('_APP_LOGGING_PROVIDER', ''); + $providerConfig = System::getEnv('_APP_LOGGING_CONFIG', ''); + + if (empty($providerName) || empty($providerConfig)) { + return; + } + + if (!Logger::hasProvider($providerName)) { + throw new Exception(Exception::GENERAL_SERVER_ERROR, "Logging provider not supported. Logging is disabled"); + } + + // Old Sentry Format conversion. Fallback until the old syntax is completely deprecated. + if (str_contains($providerConfig, ';') && strtolower($providerName) == 'sentry') { + $configChunks = \explode(";", $providerConfig); + + $sentryKey = $configChunks[0]; + $projectId = $configChunks[1]; + + $providerConfig = 'https://' . $sentryKey . '@sentry.io/' . $projectId; + } + + $classname = '\\Utopia\\Logger\\Adapter\\' . \ucfirst($providerName); + $adapter = new $classname($providerConfig); + return new Logger($adapter); +}); +$register->set('pools', function () { + $group = new Group(); + + $fallbackForDB = 'db_main=' . AppwriteURL::unparse([ + 'scheme' => 'mariadb', + 'host' => System::getEnv('_APP_DB_HOST', 'mariadb'), + 'port' => System::getEnv('_APP_DB_PORT', '3306'), + 'user' => System::getEnv('_APP_DB_USER', ''), + 'pass' => System::getEnv('_APP_DB_PASS', ''), + 'path' => System::getEnv('_APP_DB_SCHEMA', ''), + ]); + $fallbackForRedis = 'redis_main=' . AppwriteURL::unparse([ + 'scheme' => 'redis', + 'host' => System::getEnv('_APP_REDIS_HOST', 'redis'), + 'port' => System::getEnv('_APP_REDIS_PORT', '6379'), + 'user' => System::getEnv('_APP_REDIS_USER', ''), + 'pass' => System::getEnv('_APP_REDIS_PASS', ''), + ]); + + $connections = [ + 'console' => [ + 'type' => 'database', + 'dsns' => System::getEnv('_APP_CONNECTIONS_DB_CONSOLE', $fallbackForDB), + 'multiple' => false, + 'schemes' => ['mariadb', 'mysql'], + ], + 'database' => [ + 'type' => 'database', + 'dsns' => System::getEnv('_APP_CONNECTIONS_DB_PROJECT', $fallbackForDB), + 'multiple' => true, + 'schemes' => ['mariadb', 'mysql'], + ], + 'queue' => [ + 'type' => 'queue', + 'dsns' => System::getEnv('_APP_CONNECTIONS_QUEUE', $fallbackForRedis), + 'multiple' => false, + 'schemes' => ['redis'], + ], + 'pubsub' => [ + 'type' => 'pubsub', + 'dsns' => System::getEnv('_APP_CONNECTIONS_PUBSUB', $fallbackForRedis), + 'multiple' => false, + 'schemes' => ['redis'], + ], + 'cache' => [ + 'type' => 'cache', + 'dsns' => System::getEnv('_APP_CONNECTIONS_CACHE', $fallbackForRedis), + 'multiple' => true, + 'schemes' => ['redis'], + ], + ]; + + $maxConnections = System::getEnv('_APP_CONNECTIONS_MAX', 151); + $instanceConnections = $maxConnections / System::getEnv('_APP_POOL_CLIENTS', 14); + + $multiprocessing = System::getEnv('_APP_SERVER_MULTIPROCESS', 'disabled') === 'enabled'; + + if ($multiprocessing) { + $workerCount = swoole_cpu_num() * intval(System::getEnv('_APP_WORKER_PER_CORE', 6)); + } else { + $workerCount = 1; + } + + if ($workerCount > $instanceConnections) { + throw new \Exception('Pool size is too small. Increase the number of allowed database connections or decrease the number of workers.', 500); + } + + $poolSize = (int)($instanceConnections / $workerCount); + + foreach ($connections as $key => $connection) { + $type = $connection['type'] ?? ''; + $multiple = $connection['multiple'] ?? false; + $schemes = $connection['schemes'] ?? []; + $config = []; + $dsns = explode(',', $connection['dsns'] ?? ''); + foreach ($dsns as &$dsn) { + $dsn = explode('=', $dsn); + $name = ($multiple) ? $key . '_' . $dsn[0] : $key; + $dsn = $dsn[1] ?? ''; + $config[] = $name; + if (empty($dsn)) { + //throw new Exception(Exception::GENERAL_SERVER_ERROR, "Missing value for DSN connection in {$key}"); + continue; + } + + $dsn = new DSN($dsn); + $dsnHost = $dsn->getHost(); + $dsnPort = $dsn->getPort(); + $dsnUser = $dsn->getUser(); + $dsnPass = $dsn->getPassword(); + $dsnScheme = $dsn->getScheme(); + $dsnDatabase = $dsn->getPath(); + + if (!in_array($dsnScheme, $schemes)) { + throw new Exception(Exception::GENERAL_SERVER_ERROR, "Invalid console database scheme"); + } + + /** + * Get Resource + * + * Creation could be reused across connection types like database, cache, queue, etc. + * + * Resource assignment to an adapter will happen below. + */ + $resource = match ($dsnScheme) { + 'mysql', + 'mariadb' => function () use ($dsnHost, $dsnPort, $dsnUser, $dsnPass, $dsnDatabase) { + return new PDOProxy(function () use ($dsnHost, $dsnPort, $dsnUser, $dsnPass, $dsnDatabase) { + return new PDO("mysql:host={$dsnHost};port={$dsnPort};dbname={$dsnDatabase};charset=utf8mb4", $dsnUser, $dsnPass, array( + PDO::ATTR_TIMEOUT => 3, // Seconds + PDO::ATTR_PERSISTENT => true, + PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, + PDO::ATTR_EMULATE_PREPARES => true, + PDO::ATTR_STRINGIFY_FETCHES => true + )); + }); + }, + 'redis' => function () use ($dsnHost, $dsnPort, $dsnPass) { + $redis = new Redis(); + @$redis->pconnect($dsnHost, (int)$dsnPort); + if ($dsnPass) { + $redis->auth($dsnPass); + } + $redis->setOption(Redis::OPT_READ_TIMEOUT, -1); + + return $redis; + }, + default => throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Invalid scheme'), + }; + + $pool = new Pool($name, $poolSize, function () use ($type, $resource, $dsn) { + // Get Adapter + switch ($type) { + case 'database': + $adapter = match ($dsn->getScheme()) { + 'mariadb' => new MariaDB($resource()), + 'mysql' => new MySQL($resource()), + default => null + }; + + $adapter->setDatabase($dsn->getPath()); + break; + case 'pubsub': + $adapter = $resource(); + break; + case 'queue': + $adapter = match ($dsn->getScheme()) { + 'redis' => new Queue\Connection\Redis($dsn->getHost(), $dsn->getPort()), + default => null + }; + break; + case 'cache': + $adapter = match ($dsn->getScheme()) { + 'redis' => new RedisCache($resource()), + default => null + }; + break; + + default: + throw new Exception(Exception::GENERAL_SERVER_ERROR, "Server error: Missing adapter implementation."); + } + + return $adapter; + }); + + $group->add($pool); + } + + Config::setParam('pools-' . $key, $config); + } + + return $group; +}); + +$register->set('db', function () { + // This is usually for our workers or CLI commands scope + $dbHost = System::getEnv('_APP_DB_HOST', ''); + $dbPort = System::getEnv('_APP_DB_PORT', ''); + $dbUser = System::getEnv('_APP_DB_USER', ''); + $dbPass = System::getEnv('_APP_DB_PASS', ''); + $dbScheme = System::getEnv('_APP_DB_SCHEMA', ''); + + return new PDO( + "mysql:host={$dbHost};port={$dbPort};dbname={$dbScheme};charset=utf8mb4", + $dbUser, + $dbPass, + SQL::getPDOAttributes() + ); +}); + +$register->set('smtp', function () { + $mail = new PHPMailer(true); + + $mail->isSMTP(); + + $username = System::getEnv('_APP_SMTP_USERNAME'); + $password = System::getEnv('_APP_SMTP_PASSWORD'); + + $mail->XMailer = 'Appwrite Mailer'; + $mail->Host = System::getEnv('_APP_SMTP_HOST', 'smtp'); + $mail->Port = System::getEnv('_APP_SMTP_PORT', 25); + $mail->SMTPAuth = !empty($username) && !empty($password); + $mail->Username = $username; + $mail->Password = $password; + $mail->SMTPSecure = System::getEnv('_APP_SMTP_SECURE', ''); + $mail->SMTPAutoTLS = false; + $mail->CharSet = 'UTF-8'; + + $from = \urldecode(System::getEnv('_APP_SYSTEM_EMAIL_NAME', APP_NAME . ' Server')); + $email = System::getEnv('_APP_SYSTEM_EMAIL_ADDRESS', APP_EMAIL_TEAM); + + $mail->setFrom($email, $from); + $mail->addReplyTo($email, $from); + + $mail->isHTML(true); + + return $mail; +}); +$register->set('geodb', function () { + return new Reader(__DIR__ . '/assets/dbip/dbip-country-lite-2024-02.mmdb'); +}); +$register->set('passwordsDictionary', function () { + $content = \file_get_contents(__DIR__ . '/assets/security/10k-common-passwords'); + $content = explode("\n", $content); + $content = array_flip($content); + return $content; +}); +$register->set('promiseAdapter', function () { + return new Swoole(); +}); +$register->set('hooks', function () { + return new Hooks(); +}); +/* + * Localization + */ +Locale::$exceptions = false; + +$locales = Config::getParam('locale-codes', []); + +foreach ($locales as $locale) { + $code = $locale['code']; + + $path = __DIR__ . '/config/locale/translations/' . $code . '.json'; + + if (!\file_exists($path)) { + $path = __DIR__ . '/config/locale/translations/' . \substr($code, 0, 2) . '.json'; // if `ar-ae` doesn't exist, look for `ar` + if (!\file_exists($path)) { + $path = __DIR__ . '/config/locale/translations/en.json'; // if none translation exists, use default from `en.json` + } + } + + Locale::setLanguageFromJSON($code, $path); +} + +\stream_context_set_default([ // Set global user agent and http settings + 'http' => [ + 'method' => 'GET', + 'user_agent' => \sprintf( + APP_USERAGENT, + System::getEnv('_APP_VERSION', 'UNKNOWN'), + System::getEnv('_APP_EMAIL_SECURITY', System::getEnv('_APP_SYSTEM_SECURITY_EMAIL_ADDRESS', APP_EMAIL_SECURITY)) + ), + 'timeout' => 2, + ], +]); + +// Runtime Execution +App::setResource('log', fn () => new Log()); +App::setResource('logger', function ($register) { + return $register->get('logger'); +}, ['register']); + +App::setResource('hooks', function ($register) { + return $register->get('hooks'); +}, ['register']); + +App::setResource('register', fn () => $register); +App::setResource('locale', fn () => new Locale(System::getEnv('_APP_LOCALE', 'en'))); + +App::setResource('localeCodes', function () { + return array_map(fn ($locale) => $locale['code'], Config::getParam('locale-codes', [])); +}); + +// Queues +App::setResource('queue', function (Group $pools) { + return $pools->get('queue')->pop()->getResource(); +}, ['pools']); +App::setResource('queueForMessaging', function (Connection $queue) { + return new Messaging($queue); +}, ['queue']); +App::setResource('queueForMails', function (Connection $queue) { + return new Mail($queue); +}, ['queue']); +App::setResource('queueForBuilds', function (Connection $queue) { + return new Build($queue); +}, ['queue']); +App::setResource('queueForDatabase', function (Connection $queue) { + return new EventDatabase($queue); +}, ['queue']); +App::setResource('queueForDeletes', function (Connection $queue) { + return new Delete($queue); +}, ['queue']); +App::setResource('queueForEvents', function (Connection $queue) { + return new Event($queue); +}, ['queue']); +App::setResource('queueForAudits', function (Connection $queue) { + return new Audit($queue); +}, ['queue']); +App::setResource('queueForFunctions', function (Connection $queue) { + return new Func($queue); +}, ['queue']); +App::setResource('queueForUsage', function (Connection $queue) { + return new Usage($queue); +}, ['queue']); +App::setResource('queueForCertificates', function (Connection $queue) { + return new Certificate($queue); +}, ['queue']); +App::setResource('queueForMigrations', function (Connection $queue) { + return new Migration($queue); +}, ['queue']); +App::setResource('clients', function ($request, $console, $project) { + $console->setAttribute('platforms', [ // Always allow current host + '$collection' => ID::custom('platforms'), + 'name' => 'Current Host', + 'type' => Origin::CLIENT_TYPE_WEB, + 'hostname' => $request->getHostname(), + ], Document::SET_TYPE_APPEND); + + $hostnames = explode(',', System::getEnv('_APP_CONSOLE_HOSTNAMES', '')); + $validator = new Hostname(); + foreach ($hostnames as $hostname) { + $hostname = trim($hostname); + if (!$validator->isValid($hostname)) { + continue; + } + $console->setAttribute('platforms', [ + '$collection' => ID::custom('platforms'), + 'type' => Origin::CLIENT_TYPE_WEB, + 'name' => $hostname, + 'hostname' => $hostname, + ], Document::SET_TYPE_APPEND); + } + + /** + * Get All verified client URLs for both console and current projects + * + Filter for duplicated entries + */ + $clientsConsole = \array_map( + fn ($node) => $node['hostname'], + \array_filter( + $console->getAttribute('platforms', []), + fn ($node) => (isset($node['type']) && ($node['type'] === Origin::CLIENT_TYPE_WEB) && !empty($node['hostname'])) + ) + ); + + $clients = $clientsConsole; + $platforms = $project->getAttribute('platforms', []); + + foreach ($platforms as $node) { + if ( + isset($node['type']) && + ($node['type'] === Origin::CLIENT_TYPE_WEB || + $node['type'] === Origin::CLIENT_TYPE_FLUTTER_WEB) && + !empty($node['hostname']) + ) { + $clients[] = $node['hostname']; + } + } + + return \array_unique($clients); +}, ['request', 'console', 'project']); + +App::setResource('user', function ($mode, $project, $console, $request, $response, $dbForProject, $dbForConsole) { + /** @var Appwrite\Utopia\Request $request */ + /** @var Appwrite\Utopia\Response $response */ + /** @var Utopia\Database\Document $project */ + /** @var Utopia\Database\Database $dbForProject */ + /** @var Utopia\Database\Database $dbForConsole */ + /** @var string $mode */ + + Authorization::setDefaultStatus(true); + + Auth::setCookieName('a_session_' . $project->getId()); + + if (APP_MODE_ADMIN === $mode) { + Auth::setCookieName('a_session_' . $console->getId()); + } + + $session = Auth::decodeSession( + $request->getCookie( + Auth::$cookieName, // Get sessions + $request->getCookie(Auth::$cookieName . '_legacy', '') + ) + ); + + // Get session from header for SSR clients + if (empty($session['id']) && empty($session['secret'])) { + $sessionHeader = $request->getHeader('x-appwrite-session', ''); + + if (!empty($sessionHeader)) { + $session = Auth::decodeSession($sessionHeader); + } + } + + // Get fallback session from old clients (no SameSite support) or clients who block 3rd-party cookies + if ($response) { + $response->addHeader('X-Debug-Fallback', 'false'); + } + + if (empty($session['id']) && empty($session['secret'])) { + if ($response) { + $response->addHeader('X-Debug-Fallback', 'true'); + } + $fallback = $request->getHeader('x-fallback-cookies', ''); + $fallback = \json_decode($fallback, true); + $session = Auth::decodeSession(((isset($fallback[Auth::$cookieName])) ? $fallback[Auth::$cookieName] : '')); + } + + Auth::$unique = $session['id'] ?? ''; + Auth::$secret = $session['secret'] ?? ''; + + if (APP_MODE_ADMIN !== $mode) { + if ($project->isEmpty()) { + $user = new Document([]); + } else { + if ($project->getId() === 'console') { + $user = $dbForConsole->getDocument('users', Auth::$unique); + } else { + $user = $dbForProject->getDocument('users', Auth::$unique); + } + } + } else { + $user = $dbForConsole->getDocument('users', Auth::$unique); + } + + if ( + $user->isEmpty() // Check a document has been found in the DB + || !Auth::sessionVerify($user->getAttribute('sessions', []), Auth::$secret) + ) { // Validate user has valid login token + $user = new Document([]); + } + + if (APP_MODE_ADMIN === $mode) { + if ($user->find('teamInternalId', $project->getAttribute('teamInternalId'), 'memberships')) { + Authorization::setDefaultStatus(false); // Cancel security segmentation for admin users. + } else { + $user = new Document([]); + } + } + + $authJWT = $request->getHeader('x-appwrite-jwt', ''); + + if (!empty($authJWT) && !$project->isEmpty()) { // JWT authentication + $jwt = new JWT(System::getEnv('_APP_OPENSSL_KEY_V1'), 'HS256', 900, 10); // Instantiate with key, algo, maxAge and leeway. + + try { + $payload = $jwt->decode($authJWT); + } catch (JWTException $error) { + throw new Exception(Exception::USER_JWT_INVALID, 'Failed to verify JWT. ' . $error->getMessage()); + } + + $jwtUserId = $payload['userId'] ?? ''; + $jwtSessionId = $payload['sessionId'] ?? ''; + + if ($jwtUserId && $jwtSessionId) { + $user = $dbForProject->getDocument('users', $jwtUserId); + } + + if (empty($user->find('$id', $jwtSessionId, 'sessions'))) { // Match JWT to active token + $user = new Document([]); + } + } + + $dbForProject->setMetadata('user', $user->getId()); + $dbForConsole->setMetadata('user', $user->getId()); + + return $user; +}, ['mode', 'project', 'console', 'request', 'response', 'dbForProject', 'dbForConsole']); + +App::setResource('project', function ($dbForConsole, $request, $console) { + /** @var Appwrite\Utopia\Request $request */ + /** @var Utopia\Database\Database $dbForConsole */ + /** @var Utopia\Database\Document $console */ + + $projectId = $request->getParam('project', $request->getHeader('x-appwrite-project', '')); + + if (empty($projectId) || $projectId === 'console') { + return $console; + } + + $project = Authorization::skip(fn () => $dbForConsole->getDocument('projects', $projectId)); + + return $project; +}, ['dbForConsole', 'request', 'console']); + +App::setResource('session', function (Document $user) { + if ($user->isEmpty()) { + return; + } + + $sessions = $user->getAttribute('sessions', []); + $sessionId = Auth::sessionVerify($user->getAttribute('sessions'), Auth::$secret); + + if (!$sessionId) { + return; + } + + foreach ($sessions as $session) {/** @var Document $session */ + if ($sessionId === $session->getId()) { + return $session; + } + } + + return; +}, ['user']); + +App::setResource('console', function () { + return new Document([ + '$id' => ID::custom('console'), + '$internalId' => ID::custom('console'), + 'name' => 'Appwrite', + '$collection' => ID::custom('projects'), + 'description' => 'Appwrite core engine', + 'logo' => '', + 'teamId' => -1, + 'webhooks' => [], + 'keys' => [], + 'platforms' => [ + [ + '$collection' => ID::custom('platforms'), + 'name' => 'Localhost', + 'type' => Origin::CLIENT_TYPE_WEB, + 'hostname' => 'localhost', + ], // Current host is added on app init + ], + 'legalName' => '', + 'legalCountry' => '', + 'legalState' => '', + 'legalCity' => '', + 'legalAddress' => '', + 'legalTaxId' => '', + 'auths' => [ + 'invites' => System::getEnv('_APP_CONSOLE_INVITES', 'enabled') === 'enabled', + 'limit' => (System::getEnv('_APP_CONSOLE_WHITELIST_ROOT', 'enabled') === 'enabled') ? 1 : 0, // limit signup to 1 user + 'duration' => Auth::TOKEN_EXPIRATION_LOGIN_LONG, // 1 Year in seconds + ], + 'authWhitelistEmails' => (!empty(System::getEnv('_APP_CONSOLE_WHITELIST_EMAILS', null))) ? \explode(',', System::getEnv('_APP_CONSOLE_WHITELIST_EMAILS', null)) : [], + 'authWhitelistIPs' => (!empty(System::getEnv('_APP_CONSOLE_WHITELIST_IPS', null))) ? \explode(',', System::getEnv('_APP_CONSOLE_WHITELIST_IPS', null)) : [], + 'oAuthProviders' => [ + 'githubEnabled' => true, + 'githubSecret' => System::getEnv('_APP_CONSOLE_GITHUB_SECRET', ''), + 'githubAppid' => System::getEnv('_APP_CONSOLE_GITHUB_APP_ID', '') + ], + ]); +}, []); + +App::setResource('dbForProject', function (Group $pools, Database $dbForConsole, Cache $cache, Document $project) { + if ($project->isEmpty() || $project->getId() === 'console') { + return $dbForConsole; + } + + try { + $dsn = new DSN($project->getAttribute('database')); + } catch (\InvalidArgumentException) { + // TODO: Temporary until all projects are using shared tables + $dsn = new DSN('mysql://' . $project->getAttribute('database')); + } + + $dbAdapter = $pools + ->get($dsn->getHost()) + ->pop() + ->getResource(); + + $database = new Database($dbAdapter, $cache); + + $database + ->setMetadata('host', \gethostname()) + ->setMetadata('project', $project->getId()) + ->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS); + + try { + $dsn = new DSN($project->getAttribute('database')); + } catch (\InvalidArgumentException) { + // TODO: Temporary until all projects are using shared tables + $dsn = new DSN('mysql://' . $project->getAttribute('database')); + } + + if ($dsn->getHost() === System::getEnv('_APP_DATABASE_SHARED_TABLES', '')) { + $database + ->setSharedTables(true) + ->setTenant($project->getInternalId()) + ->setNamespace($dsn->getParam('namespace')); + } else { + $database + ->setSharedTables(false) + ->setTenant(null) + ->setNamespace('_' . $project->getInternalId()); + } + + return $database; +}, ['pools', 'dbForConsole', 'cache', 'project']); + +App::setResource('dbForConsole', function (Group $pools, Cache $cache) { + $dbAdapter = $pools + ->get('console') + ->pop() + ->getResource(); + + $database = new Database($dbAdapter, $cache); + + $database + ->setNamespace('_console') + ->setMetadata('host', \gethostname()) + ->setMetadata('project', 'console') + ->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS); + + return $database; +}, ['pools', 'cache']); + +App::setResource('getProjectDB', function (Group $pools, Database $dbForConsole, $cache) { + $databases = []; // TODO: @Meldiron This should probably be responsibility of utopia-php/pools + + return function (Document $project) use ($pools, $dbForConsole, $cache, &$databases) { + if ($project->isEmpty() || $project->getId() === 'console') { + return $dbForConsole; + } + + try { + $dsn = new DSN($project->getAttribute('database')); + } catch (\InvalidArgumentException) { + // TODO: Temporary until all projects are using shared tables + $dsn = new DSN('mysql://' . $project->getAttribute('database')); + } + + $configure = (function (Database $database) use ($project, $dsn) { + $database + ->setMetadata('host', \gethostname()) + ->setMetadata('project', $project->getId()) + ->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS); + + if ($dsn->getHost() === System::getEnv('_APP_DATABASE_SHARED_TABLES', '')) { + $database + ->setSharedTables(true) + ->setTenant($project->getInternalId()) + ->setNamespace($dsn->getParam('namespace')); + } else { + $database + ->setSharedTables(false) + ->setTenant(null) + ->setNamespace('_' . $project->getInternalId()); + } + }); + + if (isset($databases[$dsn->getHost()])) { + $database = $databases[$dsn->getHost()]; + $configure($database); + return $database; + } + + $dbAdapter = $pools + ->get($dsn->getHost()) + ->pop() + ->getResource(); + + $database = new Database($dbAdapter, $cache); + $databases[$dsn->getHost()] = $database; + $configure($database); + + return $database; + }; +}, ['pools', 'dbForConsole', 'cache']); + +App::setResource('cache', function (Group $pools) { + $list = Config::getParam('pools-cache', []); + $adapters = []; + + foreach ($list as $value) { + $adapters[] = $pools + ->get($value) + ->pop() + ->getResource() + ; + } + + return new Cache(new Sharding($adapters)); +}, ['pools']); + +App::setResource('deviceForLocal', function () { + return new Local(); +}); + +App::setResource('deviceForFiles', function ($project) { + return getDevice(APP_STORAGE_UPLOADS . '/app-' . $project->getId()); +}, ['project']); + +App::setResource('deviceForFunctions', function ($project) { + return getDevice(APP_STORAGE_FUNCTIONS . '/app-' . $project->getId()); +}, ['project']); + +App::setResource('deviceForBuilds', function ($project) { + return getDevice(APP_STORAGE_BUILDS . '/app-' . $project->getId()); +}, ['project']); + +function getDevice($root): Device +{ + $connection = System::getEnv('_APP_CONNECTIONS_STORAGE', ''); + + if (!empty($connection)) { + $acl = 'private'; + $device = Storage::DEVICE_LOCAL; + $accessKey = ''; + $accessSecret = ''; + $bucket = ''; + $region = ''; + + try { + $dsn = new DSN($connection); + $device = $dsn->getScheme(); + $accessKey = $dsn->getUser() ?? ''; + $accessSecret = $dsn->getPassword() ?? ''; + $bucket = $dsn->getPath() ?? ''; + $region = $dsn->getParam('region'); + } catch (\Throwable $e) { + Console::warning($e->getMessage() . 'Invalid DSN. Defaulting to Local device.'); + } + + switch ($device) { + case Storage::DEVICE_S3: + return new S3($root, $accessKey, $accessSecret, $bucket, $region, $acl); + case STORAGE::DEVICE_DO_SPACES: + $device = new DOSpaces($root, $accessKey, $accessSecret, $bucket, $region, $acl); + $device->setHttpVersion(S3::HTTP_VERSION_1_1); + return $device; + case Storage::DEVICE_BACKBLAZE: + return new Backblaze($root, $accessKey, $accessSecret, $bucket, $region, $acl); + case Storage::DEVICE_LINODE: + return new Linode($root, $accessKey, $accessSecret, $bucket, $region, $acl); + case Storage::DEVICE_WASABI: + return new Wasabi($root, $accessKey, $accessSecret, $bucket, $region, $acl); + case Storage::DEVICE_LOCAL: + default: + return new Local($root); + } + } else { + switch (strtolower(System::getEnv('_APP_STORAGE_DEVICE', Storage::DEVICE_LOCAL) ?? '')) { + case Storage::DEVICE_LOCAL: + default: + return new Local($root); + case Storage::DEVICE_S3: + $s3AccessKey = System::getEnv('_APP_STORAGE_S3_ACCESS_KEY', ''); + $s3SecretKey = System::getEnv('_APP_STORAGE_S3_SECRET', ''); + $s3Region = System::getEnv('_APP_STORAGE_S3_REGION', ''); + $s3Bucket = System::getEnv('_APP_STORAGE_S3_BUCKET', ''); + $s3Acl = 'private'; + return new S3($root, $s3AccessKey, $s3SecretKey, $s3Bucket, $s3Region, $s3Acl); + case Storage::DEVICE_DO_SPACES: + $doSpacesAccessKey = System::getEnv('_APP_STORAGE_DO_SPACES_ACCESS_KEY', ''); + $doSpacesSecretKey = System::getEnv('_APP_STORAGE_DO_SPACES_SECRET', ''); + $doSpacesRegion = System::getEnv('_APP_STORAGE_DO_SPACES_REGION', ''); + $doSpacesBucket = System::getEnv('_APP_STORAGE_DO_SPACES_BUCKET', ''); + $doSpacesAcl = 'private'; + $device = new DOSpaces($root, $doSpacesAccessKey, $doSpacesSecretKey, $doSpacesBucket, $doSpacesRegion, $doSpacesAcl); + $device->setHttpVersion(S3::HTTP_VERSION_1_1); + return $device; + case Storage::DEVICE_BACKBLAZE: + $backblazeAccessKey = System::getEnv('_APP_STORAGE_BACKBLAZE_ACCESS_KEY', ''); + $backblazeSecretKey = System::getEnv('_APP_STORAGE_BACKBLAZE_SECRET', ''); + $backblazeRegion = System::getEnv('_APP_STORAGE_BACKBLAZE_REGION', ''); + $backblazeBucket = System::getEnv('_APP_STORAGE_BACKBLAZE_BUCKET', ''); + $backblazeAcl = 'private'; + return new Backblaze($root, $backblazeAccessKey, $backblazeSecretKey, $backblazeBucket, $backblazeRegion, $backblazeAcl); + case Storage::DEVICE_LINODE: + $linodeAccessKey = System::getEnv('_APP_STORAGE_LINODE_ACCESS_KEY', ''); + $linodeSecretKey = System::getEnv('_APP_STORAGE_LINODE_SECRET', ''); + $linodeRegion = System::getEnv('_APP_STORAGE_LINODE_REGION', ''); + $linodeBucket = System::getEnv('_APP_STORAGE_LINODE_BUCKET', ''); + $linodeAcl = 'private'; + return new Linode($root, $linodeAccessKey, $linodeSecretKey, $linodeBucket, $linodeRegion, $linodeAcl); + case Storage::DEVICE_WASABI: + $wasabiAccessKey = System::getEnv('_APP_STORAGE_WASABI_ACCESS_KEY', ''); + $wasabiSecretKey = System::getEnv('_APP_STORAGE_WASABI_SECRET', ''); + $wasabiRegion = System::getEnv('_APP_STORAGE_WASABI_REGION', ''); + $wasabiBucket = System::getEnv('_APP_STORAGE_WASABI_BUCKET', ''); + $wasabiAcl = 'private'; + return new Wasabi($root, $wasabiAccessKey, $wasabiSecretKey, $wasabiBucket, $wasabiRegion, $wasabiAcl); + } + } +} + +App::setResource('mode', function ($request) { + /** @var Appwrite\Utopia\Request $request */ + + /** + * Defines the mode for the request: + * - 'default' => Requests for Client and Server Side + * - 'admin' => Request from the Console on non-console projects + */ + return $request->getParam('mode', $request->getHeader('x-appwrite-mode', APP_MODE_DEFAULT)); +}, ['request']); + +App::setResource('geodb', function ($register) { + /** @var Utopia\Registry\Registry $register */ + return $register->get('geodb'); +}, ['register']); + +App::setResource('passwordsDictionary', function ($register) { + /** @var Utopia\Registry\Registry $register */ + return $register->get('passwordsDictionary'); +}, ['register']); + + +App::setResource('servers', function () { + $platforms = Config::getParam('platforms'); + $server = $platforms[APP_PLATFORM_SERVER]; + + $languages = array_map(function ($language) { + return strtolower($language['name']); + }, $server['sdks']); + + return $languages; +}); + +App::setResource('promiseAdapter', function ($register) { + return $register->get('promiseAdapter'); +}, ['register']); + +App::setResource('schema', function ($utopia, $dbForProject) { + + $complexity = function (int $complexity, array $args) { + $queries = Query::parseQueries($args['queries'] ?? []); + $query = Query::getByType($queries, [Query::TYPE_LIMIT])[0] ?? null; + $limit = $query ? $query->getValue() : APP_LIMIT_LIST_DEFAULT; + + return $complexity * $limit; + }; + + $attributes = function (int $limit, int $offset) use ($dbForProject) { + $attrs = Authorization::skip(fn () => $dbForProject->find('attributes', [ + Query::limit($limit), + Query::offset($offset), + ])); + + return \array_map(function ($attr) { + return $attr->getArrayCopy(); + }, $attrs); + }; + + $urls = [ + 'list' => function (string $databaseId, string $collectionId, array $args) { + return "/v1/databases/$databaseId/collections/$collectionId/documents"; + }, + 'create' => function (string $databaseId, string $collectionId, array $args) { + return "/v1/databases/$databaseId/collections/$collectionId/documents"; + }, + 'read' => function (string $databaseId, string $collectionId, array $args) { + return "/v1/databases/$databaseId/collections/$collectionId/documents/{$args['documentId']}"; + }, + 'update' => function (string $databaseId, string $collectionId, array $args) { + return "/v1/databases/$databaseId/collections/$collectionId/documents/{$args['documentId']}"; + }, + 'delete' => function (string $databaseId, string $collectionId, array $args) { + return "/v1/databases/$databaseId/collections/$collectionId/documents/{$args['documentId']}"; + }, + ]; + + $params = [ + 'list' => function (string $databaseId, string $collectionId, array $args) { + return [ 'queries' => $args['queries']]; + }, + 'create' => function (string $databaseId, string $collectionId, array $args) { + $id = $args['id'] ?? 'unique()'; + $permissions = $args['permissions'] ?? null; + + unset($args['id']); + unset($args['permissions']); + + // Order must be the same as the route params + return [ + 'databaseId' => $databaseId, + 'documentId' => $id, + 'collectionId' => $collectionId, + 'data' => $args, + 'permissions' => $permissions, + ]; + }, + 'update' => function (string $databaseId, string $collectionId, array $args) { + $documentId = $args['id']; + $permissions = $args['permissions'] ?? null; + + unset($args['id']); + unset($args['permissions']); + + // Order must be the same as the route params + return [ + 'databaseId' => $databaseId, + 'collectionId' => $collectionId, + 'documentId' => $documentId, + 'data' => $args, + 'permissions' => $permissions, + ]; + }, + ]; + + return Schema::build( + $utopia, + $complexity, + $attributes, + $urls, + $params, + ); +}, ['utopia', 'dbForProject']); + +App::setResource('contributors', function () { + $path = 'app/config/contributors.json'; + $list = (file_exists($path)) ? json_decode(file_get_contents($path), true) : []; + return $list; +}); + +App::setResource('employees', function () { + $path = 'app/config/employees.json'; + $list = (file_exists($path)) ? json_decode(file_get_contents($path), true) : []; + return $list; +}); + +App::setResource('heroes', function () { + $path = 'app/config/heroes.json'; + $list = (file_exists($path)) ? json_decode(file_get_contents($path), true) : []; + return $list; +}); + +App::setResource('gitHub', function (Cache $cache) { + return new VcsGitHub($cache); +}, ['cache']); + +App::setResource('requestTimestamp', function ($request) { + //TODO: Move this to the Request class itself + $timestampHeader = $request->getHeader('x-appwrite-timestamp'); + $requestTimestamp = null; + if (!empty($timestampHeader)) { + try { + $requestTimestamp = new \DateTime($timestampHeader); + } catch (\Throwable $e) { + throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'Invalid X-Appwrite-Timestamp header value'); + } + } + return $requestTimestamp; +}, ['request']); +App::setResource('plan', function (array $plan = []) { + return []; +}); diff --git a/composer.json b/composer.json index 2ac000424f..6e4c411a78 100644 --- a/composer.json +++ b/composer.json @@ -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.*", diff --git a/composer.lock b/composer.lock index ffb651e0fd..7d72f0072a 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": "cea93b7a5d3b401c01b535df70df0b35", + "content-hash": "89f2552eaa152516be5bf89628c0f86e", "packages": [ { "name": "adhocore/jwt", @@ -210,16 +210,16 @@ }, { "name": "beberlei/assert", - "version": "v3.x-dev", + "version": "v3.3.2", "source": { "type": "git", "url": "https://github.com/beberlei/assert.git", - "reference": "d63a6943fc4fd1a2aedb65994e3548715105abcf" + "reference": "cb70015c04be1baee6f5f5c953703347c0ac1655" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/beberlei/assert/zipball/d63a6943fc4fd1a2aedb65994e3548715105abcf", - "reference": "d63a6943fc4fd1a2aedb65994e3548715105abcf", + "url": "https://api.github.com/repos/beberlei/assert/zipball/cb70015c04be1baee6f5f5c953703347c0ac1655", + "reference": "cb70015c04be1baee6f5f5c953703347c0ac1655", "shasum": "" }, "require": { @@ -227,12 +227,13 @@ "ext-json": "*", "ext-mbstring": "*", "ext-simplexml": "*", - "php": "^7" + "php": "^7.0 || ^8.0" }, "require-dev": { "friendsofphp/php-cs-fixer": "*", - "phpstan/phpstan-shim": "*", - "phpunit/phpunit": ">=6.0.0 <8" + "phpstan/phpstan": "*", + "phpunit/phpunit": ">=6.0.0", + "yoast/phpunit-polyfills": "^0.1.0" }, "suggest": { "ext-intl": "Needed to allow Assertion::count(), Assertion::isCountable(), Assertion::minCount(), and Assertion::maxCount() to operate on ResourceBundles" @@ -270,9 +271,9 @@ ], "support": { "issues": "https://github.com/beberlei/assert/issues", - "source": "https://github.com/beberlei/assert/tree/v3" + "source": "https://github.com/beberlei/assert/tree/v3.3.2" }, - "time": "2019-12-19T17:51:41+00:00" + "time": "2021-12-16T21:41:27+00:00" }, { "name": "chillerlan/php-qrcode", @@ -564,16 +565,16 @@ }, { "name": "jean85/pretty-package-versions", - "version": "2.x-dev", + "version": "2.0.6", "source": { "type": "git", "url": "https://github.com/Jean85/pretty-package-versions.git", - "reference": "4ea62fbb39a29d65ef6cda413158baa7f1d98550" + "reference": "f9fdd29ad8e6d024f52678b570e5593759b550b4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Jean85/pretty-package-versions/zipball/4ea62fbb39a29d65ef6cda413158baa7f1d98550", - "reference": "4ea62fbb39a29d65ef6cda413158baa7f1d98550", + "url": "https://api.github.com/repos/Jean85/pretty-package-versions/zipball/f9fdd29ad8e6d024f52678b570e5593759b550b4", + "reference": "f9fdd29ad8e6d024f52678b570e5593759b550b4", "shasum": "" }, "require": { @@ -585,9 +586,8 @@ "jean85/composer-provided-replaced-stub-package": "^1.0", "phpstan/phpstan": "^1.4", "phpunit/phpunit": "^7.5|^8.5|^9.4", - "vimeo/psalm": "^4.3 || ^5.0" + "vimeo/psalm": "^4.3" }, - "default-branch": true, "type": "library", "extra": { "branch-alias": { @@ -618,9 +618,9 @@ ], "support": { "issues": "https://github.com/Jean85/pretty-package-versions/issues", - "source": "https://github.com/Jean85/pretty-package-versions/tree/2.x" + "source": "https://github.com/Jean85/pretty-package-versions/tree/2.0.6" }, - "time": "2024-04-08T08:58:14+00:00" + "time": "2024-03-08T09:58:59+00:00" }, { "name": "league/csv", @@ -905,7 +905,7 @@ }, { "name": "paragonie/constant_time_encoding", - "version": "v2.x-dev", + "version": "v2.7.0", "source": { "type": "git", "url": "https://github.com/paragonie/constant_time_encoding.git", @@ -1053,7 +1053,7 @@ }, { "name": "spomky-labs/otphp", - "version": "v10.0.x-dev", + "version": "v10.0.3", "source": { "type": "git", "url": "https://github.com/Spomky-Labs/otphp.git", @@ -1082,7 +1082,6 @@ "phpunit/phpunit": "^8.0", "thecodingmachine/phpstan-safe-rule": "^1.0 || ^2.0" }, - "default-branch": true, "type": "library", "extra": { "branch-alias": { @@ -1129,16 +1128,16 @@ }, { "name": "symfony/polyfill-mbstring", - "version": "1.x-dev", + "version": "v1.30.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "8740a072b86292957feb42703edde77fcfca84fb" + "reference": "fd22ab50000ef01661e2a31d850ebaa297f8e03c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/8740a072b86292957feb42703edde77fcfca84fb", - "reference": "8740a072b86292957feb42703edde77fcfca84fb", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/fd22ab50000ef01661e2a31d850ebaa297f8e03c", + "reference": "fd22ab50000ef01661e2a31d850ebaa297f8e03c", "shasum": "" }, "require": { @@ -1150,7 +1149,6 @@ "suggest": { "ext-mbstring": "For best performance" }, - "default-branch": true, "type": "library", "extra": { "thanks": { @@ -1190,7 +1188,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/1.x" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.30.0" }, "funding": [ { @@ -1206,11 +1204,11 @@ "type": "tidelift" } ], - "time": "2024-06-20T08:18:00+00:00" + "time": "2024-06-19T12:30:46+00:00" }, { "name": "symfony/polyfill-php80", - "version": "1.x-dev", + "version": "v1.30.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php80.git", @@ -1225,7 +1223,6 @@ "require": { "php": ">=7.1" }, - "default-branch": true, "type": "library", "extra": { "thanks": { @@ -1430,23 +1427,23 @@ }, { "name": "utopia-php/abuse", - "version": "dev-feat-framework-v2", + "version": "0.38.0", "source": { "type": "git", "url": "https://github.com/utopia-php/abuse.git", - "reference": "a2292d71da901ea13129d56f89876626ba92adf0" + "reference": "b7be9086c9d9b4561d810cbd42fdda798742f56c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/abuse/zipball/a2292d71da901ea13129d56f89876626ba92adf0", - "reference": "a2292d71da901ea13129d56f89876626ba92adf0", + "url": "https://api.github.com/repos/utopia-php/abuse/zipball/b7be9086c9d9b4561d810cbd42fdda798742f56c", + "reference": "b7be9086c9d9b4561d810cbd42fdda798742f56c", "shasum": "" }, "require": { "ext-curl": "*", "ext-pdo": "*", "php": ">=8.0", - "utopia-php/database": "dev-feat-framework-v2 as 0.49.99" + "utopia-php/database": "0.50.*" }, "require-dev": { "laravel/pint": "1.5.*", @@ -1473,27 +1470,27 @@ ], "support": { "issues": "https://github.com/utopia-php/abuse/issues", - "source": "https://github.com/utopia-php/abuse/tree/feat-framework-v2" + "source": "https://github.com/utopia-php/abuse/tree/0.38.0" }, - "time": "2024-04-18T17:04:17+00:00" + "time": "2024-06-24T00:52:02+00:00" }, { "name": "utopia-php/analytics", - "version": "dev-feat-framework-v2", + "version": "0.10.2", "source": { "type": "git", "url": "https://github.com/utopia-php/analytics.git", - "reference": "5d59c2e50381a25adecbca979ed5a7c81307442f" + "reference": "14c805114736f44c26d6d24b176a2f8b93d86a1f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/analytics/zipball/5d59c2e50381a25adecbca979ed5a7c81307442f", - "reference": "5d59c2e50381a25adecbca979ed5a7c81307442f", + "url": "https://api.github.com/repos/utopia-php/analytics/zipball/14c805114736f44c26d6d24b176a2f8b93d86a1f", + "reference": "14c805114736f44c26d6d24b176a2f8b93d86a1f", "shasum": "" }, "require": { "php": ">=8.0", - "utopia-php/cli": "0.17.*" + "utopia-php/cli": "^0.15.0" }, "require-dev": { "laravel/pint": "dev-main", @@ -1519,27 +1516,27 @@ ], "support": { "issues": "https://github.com/utopia-php/analytics/issues", - "source": "https://github.com/utopia-php/analytics/tree/feat-framework-v2" + "source": "https://github.com/utopia-php/analytics/tree/0.10.2" }, - "time": "2024-03-07T15:54:19+00:00" + "time": "2023-03-22T12:01:09+00:00" }, { "name": "utopia-php/audit", - "version": "dev-feat-framework-v2", + "version": "0.40.0", "source": { "type": "git", "url": "https://github.com/utopia-php/audit.git", - "reference": "49c2a113277bfa0d7d1774c150de2d2fa07349e7" + "reference": "735ae211ce5fee5b52b736731571b4030b1d7cdc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/audit/zipball/49c2a113277bfa0d7d1774c150de2d2fa07349e7", - "reference": "49c2a113277bfa0d7d1774c150de2d2fa07349e7", + "url": "https://api.github.com/repos/utopia-php/audit/zipball/735ae211ce5fee5b52b736731571b4030b1d7cdc", + "reference": "735ae211ce5fee5b52b736731571b4030b1d7cdc", "shasum": "" }, "require": { "php": ">=8.0", - "utopia-php/database": "dev-feat-framework-v2 as 0.49.99" + "utopia-php/database": "0.50.*" }, "require-dev": { "laravel/pint": "1.5.*", @@ -1566,9 +1563,9 @@ ], "support": { "issues": "https://github.com/utopia-php/audit/issues", - "source": "https://github.com/utopia-php/audit/tree/feat-framework-v2" + "source": "https://github.com/utopia-php/audit/tree/0.40.0" }, - "time": "2024-04-18T17:02:14+00:00" + "time": "2024-06-24T00:52:17+00:00" }, { "name": "utopia-php/cache", @@ -1622,29 +1619,27 @@ }, { "name": "utopia-php/cli", - "version": "dev-dev-coroutines", + "version": "0.15.0", "source": { "type": "git", "url": "https://github.com/utopia-php/cli.git", - "reference": "d48b696891dee1e46df2491d192bb91cf4df8f94" + "reference": "ccb7c8125ffe0254fef8f25744bfa376eb7bd0ea" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/cli/zipball/d48b696891dee1e46df2491d192bb91cf4df8f94", - "reference": "d48b696891dee1e46df2491d192bb91cf4df8f94", + "url": "https://api.github.com/repos/utopia-php/cli/zipball/ccb7c8125ffe0254fef8f25744bfa376eb7bd0ea", + "reference": "ccb7c8125ffe0254fef8f25744bfa376eb7bd0ea", "shasum": "" }, "require": { "php": ">=7.4", - "utopia-php/di": "dev-feat-framework-v2", - "utopia-php/framework": "dev-feat-di-upgrade as 0.34.99" + "utopia-php/framework": "0.*.*" }, "require-dev": { "laravel/pint": "1.2.*", - "phpstan/phpstan": "^1.10", "phpunit/phpunit": "^9.3", "squizlabs/php_codesniffer": "^3.6", - "swoole/ide-helper": "4.8.8" + "vimeo/psalm": "4.0.1" }, "type": "library", "autoload": { @@ -1667,9 +1662,9 @@ ], "support": { "issues": "https://github.com/utopia-php/cli/issues", - "source": "https://github.com/utopia-php/cli/tree/dev-coroutines" + "source": "https://github.com/utopia-php/cli/tree/0.15.0" }, - "time": "2024-06-24T13:24:20+00:00" + "time": "2023-03-01T05:55:14+00:00" }, { "name": "utopia-php/config", @@ -1724,16 +1719,16 @@ }, { "name": "utopia-php/database", - "version": "dev-feat-framework-v2", + "version": "0.50.0", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "5b0d6ba40141dd9d4983d3aac018782f8ecdfc8a" + "reference": "ce3eaccb2f3bbd34b2b97419836fec633b26b8f7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/5b0d6ba40141dd9d4983d3aac018782f8ecdfc8a", - "reference": "5b0d6ba40141dd9d4983d3aac018782f8ecdfc8a", + "url": "https://api.github.com/repos/utopia-php/database/zipball/ce3eaccb2f3bbd34b2b97419836fec633b26b8f7", + "reference": "ce3eaccb2f3bbd34b2b97419836fec633b26b8f7", "shasum": "" }, "require": { @@ -1741,7 +1736,7 @@ "ext-pdo": "*", "php": ">=8.0", "utopia-php/cache": "0.10.*", - "utopia-php/framework": "0.34.*", + "utopia-php/framework": "0.33.*", "utopia-php/mongo": "0.3.*" }, "require-dev": { @@ -1752,7 +1747,7 @@ "phpunit/phpunit": "^9.4", "rregeer/phpunit-coverage-check": "^0.3.1", "swoole/ide-helper": "4.8.0", - "utopia-php/cli": "0.17.*" + "utopia-php/cli": "^0.14.0" }, "type": "library", "autoload": { @@ -1774,84 +1769,27 @@ ], "support": { "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/feat-framework-v2" + "source": "https://github.com/utopia-php/database/tree/0.50.0" }, - "time": "2024-06-06T00:07:47+00:00" - }, - { - "name": "utopia-php/di", - "version": "dev-feat-framework-v2", - "source": { - "type": "git", - "url": "git@github.com:utopia-php/di.git", - "reference": "1fb7da5ead16de5d795ef10c1e22e39726eb6943" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/utopia-php/di/zipball/1fb7da5ead16de5d795ef10c1e22e39726eb6943", - "reference": "1fb7da5ead16de5d795ef10c1e22e39726eb6943", - "shasum": "" - }, - "require": { - "php": ">=8.2" - }, - "require-dev": { - "laravel/pint": "^1.2", - "phpbench/phpbench": "^1.2", - "phpstan/phpstan": "^1.10", - "phpunit/phpunit": "^9.5.25", - "swoole/ide-helper": "4.8.3" - }, - "type": "library", - "autoload": { - "psr-4": { - "Utopia\\": "src/", - "Tests\\E2E\\": "tests/e2e" - } - }, - "scripts": { - "lint": [ - "vendor/bin/pint --test" - ], - "format": [ - "vendor/bin/pint" - ], - "check": [ - "vendor/bin/phpstan analyse -c phpstan.neon" - ], - "test": [ - "vendor/bin/phpunit --configuration phpunit.xml" - ] - }, - "license": [ - "MIT" - ], - "description": "A simple and lite library for managing dependency injections", - "keywords": [ - "framework", - "http", - "php", - "upf" - ], - "time": "2024-06-11T16:03:00+00:00" + "time": "2024-06-21T03:21:42+00:00" }, { "name": "utopia-php/domains", - "version": "dev-feat-framework-v2", + "version": "0.5.0", "source": { "type": "git", "url": "https://github.com/utopia-php/domains.git", - "reference": "4e7055f0aaba0c16ae60c972faefb9189fa0db1c" + "reference": "bf07f60326f8389f378ddf6fcde86217e5cfe18c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/domains/zipball/4e7055f0aaba0c16ae60c972faefb9189fa0db1c", - "reference": "4e7055f0aaba0c16ae60c972faefb9189fa0db1c", + "url": "https://api.github.com/repos/utopia-php/domains/zipball/bf07f60326f8389f378ddf6fcde86217e5cfe18c", + "reference": "bf07f60326f8389f378ddf6fcde86217e5cfe18c", "shasum": "" }, "require": { "php": ">=8.0", - "utopia-php/framework": "0.34.*" + "utopia-php/framework": "0.*.*" }, "require-dev": { "laravel/pint": "1.2.*", @@ -1891,9 +1829,9 @@ ], "support": { "issues": "https://github.com/utopia-php/domains/issues", - "source": "https://github.com/utopia-php/domains/tree/feat-framework-v2" + "source": "https://github.com/utopia-php/domains/tree/0.5.0" }, - "time": "2024-03-08T09:24:35+00:00" + "time": "2024-01-03T22:04:27+00:00" }, { "name": "utopia-php/dsn", @@ -1983,30 +1921,26 @@ }, { "name": "utopia-php/framework", - "version": "dev-feat-di-upgrade", + "version": "0.33.6", "source": { "type": "git", "url": "https://github.com/utopia-php/http.git", - "reference": "f4ce8a42a5613f40992e6fe84ff2e23584465adb" + "reference": "8fe57da0cecd57e3b17cd395b4a666a24f4c07a6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/http/zipball/f4ce8a42a5613f40992e6fe84ff2e23584465adb", - "reference": "f4ce8a42a5613f40992e6fe84ff2e23584465adb", + "url": "https://api.github.com/repos/utopia-php/http/zipball/8fe57da0cecd57e3b17cd395b4a666a24f4c07a6", + "reference": "8fe57da0cecd57e3b17cd395b4a666a24f4c07a6", "shasum": "" }, "require": { - "ext-swoole": "*", - "php": ">=8.0", - "utopia-php/servers": "dev-dev" + "php": ">=8.0" }, "require-dev": { - "ext-xdebug": "*", "laravel/pint": "^1.2", "phpbench/phpbench": "^1.2", "phpstan/phpstan": "^1.10", - "phpunit/phpunit": "^9.5.25", - "swoole/ide-helper": "4.8.3" + "phpunit/phpunit": "^9.5.25" }, "type": "library", "autoload": { @@ -2018,18 +1952,17 @@ "license": [ "MIT" ], - "description": "A simple, light and advanced PHP HTTP framework", + "description": "A simple, light and advanced PHP framework", "keywords": [ "framework", - "http", "php", "upf" ], "support": { "issues": "https://github.com/utopia-php/http/issues", - "source": "https://github.com/utopia-php/http/tree/feat-di-upgrade" + "source": "https://github.com/utopia-php/http/tree/0.33.6" }, - "time": "2024-07-03T20:21:21+00:00" + "time": "2024-03-21T18:10:57+00:00" }, { "name": "utopia-php/image", @@ -2186,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": { @@ -2231,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", @@ -2344,21 +2277,21 @@ }, { "name": "utopia-php/orchestration", - "version": "dev-feat-framework-v2", + "version": "0.9.1", "source": { "type": "git", "url": "https://github.com/utopia-php/orchestration.git", - "reference": "ede2b7a60bd1630ef8bd7fdbbc3cf73275fc9f28" + "reference": "55f43513b3f940a3f4f9c2cde7682d0c2581beb0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/orchestration/zipball/ede2b7a60bd1630ef8bd7fdbbc3cf73275fc9f28", - "reference": "ede2b7a60bd1630ef8bd7fdbbc3cf73275fc9f28", + "url": "https://api.github.com/repos/utopia-php/orchestration/zipball/55f43513b3f940a3f4f9c2cde7682d0c2581beb0", + "reference": "55f43513b3f940a3f4f9c2cde7682d0c2581beb0", "shasum": "" }, "require": { "php": ">=8.0", - "utopia-php/cli": "0.17.*" + "utopia-php/cli": "0.15.*" }, "require-dev": { "laravel/pint": "^1.2", @@ -2388,31 +2321,31 @@ ], "support": { "issues": "https://github.com/utopia-php/orchestration/issues", - "source": "https://github.com/utopia-php/orchestration/tree/feat-framework-v2" + "source": "https://github.com/utopia-php/orchestration/tree/0.9.1" }, - "time": "2024-03-07T15:56:18+00:00" + "time": "2023-03-17T15:05:06+00:00" }, { "name": "utopia-php/platform", - "version": "dev-feat-framework-v2", + "version": "0.7.0", "source": { "type": "git", "url": "https://github.com/utopia-php/platform.git", - "reference": "f40b23bf84f8fd95bc954ef9297663c01fed5c4c" + "reference": "beeea0f2c9bce14a6869fc5c87a1047cdecb5c52" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/platform/zipball/f40b23bf84f8fd95bc954ef9297663c01fed5c4c", - "reference": "f40b23bf84f8fd95bc954ef9297663c01fed5c4c", + "url": "https://api.github.com/repos/utopia-php/platform/zipball/beeea0f2c9bce14a6869fc5c87a1047cdecb5c52", + "reference": "beeea0f2c9bce14a6869fc5c87a1047cdecb5c52", "shasum": "" }, "require": { "ext-json": "*", "ext-redis": "*", "php": ">=8.0", - "utopia-php/cli": "dev-dev-coroutines as 0.17.99", - "utopia-php/framework": "dev-feat-di-upgrade as 0.34.99", - "utopia-php/queue": "dev-feat-coroutine-and-di as 0.7.99" + "utopia-php/cli": "0.15.*", + "utopia-php/framework": "0.33.*", + "utopia-php/queue": "0.7.*" }, "require-dev": { "laravel/pint": "1.2.*", @@ -2438,9 +2371,9 @@ ], "support": { "issues": "https://github.com/utopia-php/platform/issues", - "source": "https://github.com/utopia-php/platform/tree/feat-framework-v2" + "source": "https://github.com/utopia-php/platform/tree/0.7.0" }, - "time": "2024-06-21T19:25:15+00:00" + "time": "2024-05-08T17:00:55+00:00" }, { "name": "utopia-php/pools", @@ -2548,23 +2481,22 @@ }, { "name": "utopia-php/queue", - "version": "dev-feat-coroutine-and-di", + "version": "0.7.0", "source": { "type": "git", "url": "https://github.com/utopia-php/queue.git", - "reference": "cad5651b38f0f69e20e805424d0c29818c15c174" + "reference": "917565256eb94bcab7246f7a746b1a486813761b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/queue/zipball/cad5651b38f0f69e20e805424d0c29818c15c174", - "reference": "cad5651b38f0f69e20e805424d0c29818c15c174", + "url": "https://api.github.com/repos/utopia-php/queue/zipball/917565256eb94bcab7246f7a746b1a486813761b", + "reference": "917565256eb94bcab7246f7a746b1a486813761b", "shasum": "" }, "require": { "php": ">=8.0", - "utopia-php/cli": "0.17.*", - "utopia-php/di": "dev-feat-framework-v2", - "utopia-php/servers": "dev-dev" + "utopia-php/cli": "0.15.*", + "utopia-php/framework": "0.*.*" }, "require-dev": { "laravel/pint": "^0.2.3", @@ -2574,7 +2506,6 @@ "workerman/workerman": "^4.0" }, "suggest": { - "ext-redis": "Needed to support Redis connections", "ext-swoole": "Needed to support Swoole.", "workerman/workerman": "Needed to support Workerman." }, @@ -2605,9 +2536,9 @@ ], "support": { "issues": "https://github.com/utopia-php/queue/issues", - "source": "https://github.com/utopia-php/queue/tree/feat-coroutine-and-di" + "source": "https://github.com/utopia-php/queue/tree/0.7.0" }, - "time": "2024-06-07T18:50:32+00:00" + "time": "2024-01-17T19:00:43+00:00" }, { "name": "utopia-php/registry", @@ -2661,85 +2592,18 @@ }, "time": "2021-03-10T10:45:22+00:00" }, - { - "name": "utopia-php/servers", - "version": "dev-dev", - "source": { - "type": "git", - "url": "git@github.com:utopia-php/servers.git", - "reference": "4565c1c111f6da6b18bc0f00f350377a1e691e48" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/utopia-php/servers/zipball/4565c1c111f6da6b18bc0f00f350377a1e691e48", - "reference": "4565c1c111f6da6b18bc0f00f350377a1e691e48", - "shasum": "" - }, - "require": { - "php": ">=8.0", - "utopia-php/di": "dev-feat-framework-v2" - }, - "require-dev": { - "laravel/pint": "^0.2.3", - "phpstan/phpstan": "^1.8", - "phpunit/phpunit": "^9.5.5" - }, - "type": "library", - "autoload": { - "psr-4": { - "Utopia\\Servers\\": "src/Servers" - } - }, - "autoload-dev": { - "psr-4": { - "Tests\\E2E\\": "tests/Servers/Unit" - } - }, - "scripts": { - "test": [ - "phpunit" - ], - "analyse": [ - "vendor/bin/phpstan analyse" - ], - "format": [ - "vendor/bin/pint" - ], - "lint": [ - "vendor/bin/pint --test" - ] - }, - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Team Appwrite", - "email": "team@appwrite.io" - } - ], - "description": "A base library for building Utopia style servers.", - "keywords": [ - "framework", - "php", - "servers", - "upf", - "utopia" - ], - "time": "2024-06-07T18:49:59+00:00" - }, { "name": "utopia-php/storage", - "version": "dev-feat-framework-v2-v2", + "version": "0.18.4", "source": { "type": "git", "url": "https://github.com/utopia-php/storage.git", - "reference": "80eafa63cb86b33ce35d48c0189bae6ebdc02734" + "reference": "94ab8758fabcefee5c5fa723616e45719833f922" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/storage/zipball/80eafa63cb86b33ce35d48c0189bae6ebdc02734", - "reference": "80eafa63cb86b33ce35d48c0189bae6ebdc02734", + "url": "https://api.github.com/repos/utopia-php/storage/zipball/94ab8758fabcefee5c5fa723616e45719833f922", + "reference": "94ab8758fabcefee5c5fa723616e45719833f922", "shasum": "" }, "require": { @@ -2751,7 +2615,7 @@ "ext-zlib": "*", "ext-zstd": "*", "php": ">=8.0", - "utopia-php/framework": "0.34.*", + "utopia-php/framework": "0.*.*", "utopia-php/system": "0.*.*" }, "require-dev": { @@ -2779,9 +2643,60 @@ ], "support": { "issues": "https://github.com/utopia-php/storage/issues", - "source": "https://github.com/utopia-php/storage/tree/feat-framework-v2-v2" + "source": "https://github.com/utopia-php/storage/tree/0.18.4" }, - "time": "2024-03-08T09:39:46+00:00" + "time": "2024-04-02T08:24:09+00:00" + }, + { + "name": "utopia-php/swoole", + "version": "0.8.2", + "source": { + "type": "git", + "url": "https://github.com/utopia-php/swoole.git", + "reference": "5fa9d42c608ad46a4ce42a6d2b2eae00592fccd4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/utopia-php/swoole/zipball/5fa9d42c608ad46a4ce42a6d2b2eae00592fccd4", + "reference": "5fa9d42c608ad46a4ce42a6d2b2eae00592fccd4", + "shasum": "" + }, + "require": { + "ext-swoole": "*", + "php": ">=8.0", + "utopia-php/framework": "0.33.*" + }, + "require-dev": { + "laravel/pint": "1.2.*", + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^9.3", + "swoole/ide-helper": "5.0.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "Utopia\\Swoole\\": "src/Swoole" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "An extension for Utopia Framework to work with PHP Swoole as a PHP FPM alternative", + "keywords": [ + "framework", + "http", + "php", + "server", + "swoole", + "upf", + "utopia" + ], + "support": { + "issues": "https://github.com/utopia-php/swoole/issues", + "source": "https://github.com/utopia-php/swoole/tree/0.8.2" + }, + "time": "2024-02-01T14:54:12+00:00" }, { "name": "utopia-php/system", @@ -2888,49 +2803,6 @@ }, "time": "2024-06-05T17:38:29+00:00" }, - { - "name": "utopia-php/view", - "version": "0.2.0", - "source": { - "type": "git", - "url": "https://github.com/utopia-php/view.git", - "reference": "6ee55e83bc014c39ed6b69390f6d399116f65e88" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/utopia-php/view/zipball/6ee55e83bc014c39ed6b69390f6d399116f65e88", - "reference": "6ee55e83bc014c39ed6b69390f6d399116f65e88", - "shasum": "" - }, - "require": { - "php": ">=8.0" - }, - "require-dev": { - "laravel/pint": "^1.2", - "phpstan/phpstan": "^1.10", - "phpunit/phpunit": "^9.5.25" - }, - "type": "library", - "autoload": { - "psr-4": { - "Utopia\\View\\": "src/View" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "A simple, light and advanced PHP rendering engine", - "keywords": [ - "php", - "view" - ], - "support": { - "issues": "https://github.com/utopia-php/view/issues", - "source": "https://github.com/utopia-php/view/tree/0.2.0" - }, - "time": "2024-04-01T17:21:29+00:00" - }, { "name": "utopia-php/websocket", "version": "0.1.0", @@ -3116,16 +2988,16 @@ "packages-dev": [ { "name": "appwrite/sdk-generator", - "version": "0.38.8", + "version": "0.38.7", "source": { "type": "git", "url": "https://github.com/appwrite/sdk-generator.git", - "reference": "6367c57ddbcf7b88cacb900c4fe7ef3f28bf38ef" + "reference": "0a66c1149ef05ed9f45ce1c897c4a0ce9ee5e95a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/6367c57ddbcf7b88cacb900c4fe7ef3f28bf38ef", - "reference": "6367c57ddbcf7b88cacb900c4fe7ef3f28bf38ef", + "url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/0a66c1149ef05ed9f45ce1c897c4a0ce9ee5e95a", + "reference": "0a66c1149ef05ed9f45ce1c897c4a0ce9ee5e95a", "shasum": "" }, "require": { @@ -3161,13 +3033,13 @@ "description": "Appwrite PHP library for generating API SDKs for multiple programming languages and platforms", "support": { "issues": "https://github.com/appwrite/sdk-generator/issues", - "source": "https://github.com/appwrite/sdk-generator/tree/0.38.8" + "source": "https://github.com/appwrite/sdk-generator/tree/0.38.7" }, - "time": "2024-06-17T00:42:27+00:00" + "time": "2024-06-10T00:23:02+00:00" }, { "name": "doctrine/deprecations", - "version": "1.1.x-dev", + "version": "1.1.3", "source": { "type": "git", "url": "https://github.com/doctrine/deprecations.git", @@ -3194,7 +3066,6 @@ "suggest": { "psr/log": "Allows logging deprecations via PSR-3 logger implementation" }, - "default-branch": true, "type": "library", "autoload": { "psr-4": { @@ -3215,29 +3086,29 @@ }, { "name": "doctrine/instantiator", - "version": "1.5.x-dev", + "version": "1.5.0", "source": { "type": "git", "url": "https://github.com/doctrine/instantiator.git", - "reference": "12be2483e1f0e850b353e26869e4e6c038459501" + "reference": "0a0fa9780f5d4e507415a065172d26a98d02047b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/instantiator/zipball/12be2483e1f0e850b353e26869e4e6c038459501", - "reference": "12be2483e1f0e850b353e26869e4e6c038459501", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/0a0fa9780f5d4e507415a065172d26a98d02047b", + "reference": "0a0fa9780f5d4e507415a065172d26a98d02047b", "shasum": "" }, "require": { "php": "^7.1 || ^8.0" }, "require-dev": { - "doctrine/coding-standard": "^9 || ^12", + "doctrine/coding-standard": "^9 || ^11", "ext-pdo": "*", "ext-phar": "*", "phpbench/phpbench": "^0.16 || ^1", "phpstan/phpstan": "^1.4", "phpstan/phpstan-phpunit": "^1", - "phpunit/phpunit": "^7.5 || ^8.5 || ^9.6", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", "vimeo/psalm": "^4.30 || ^5.4" }, "type": "library", @@ -3265,7 +3136,7 @@ ], "support": { "issues": "https://github.com/doctrine/instantiator/issues", - "source": "https://github.com/doctrine/instantiator/tree/1.5.x" + "source": "https://github.com/doctrine/instantiator/tree/1.5.0" }, "funding": [ { @@ -3281,7 +3152,7 @@ "type": "tidelift" } ], - "time": "2023-12-09T14:16:53+00:00" + "time": "2022-12-30T00:15:36+00:00" }, { "name": "laravel/pint", @@ -3475,7 +3346,7 @@ }, { "name": "myclabs/deep-copy", - "version": "1.x-dev", + "version": "1.12.0", "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", @@ -3500,7 +3371,6 @@ "phpspec/prophecy": "^1.10", "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13" }, - "default-branch": true, "type": "library", "autoload": { "files": [ @@ -3536,16 +3406,16 @@ }, { "name": "nikic/php-parser", - "version": "v5.1.0", + "version": "v5.0.2", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "683130c2ff8c2739f4822ff7ac5c873ec529abd1" + "reference": "139676794dc1e9231bf7bcd123cfc0c99182cb13" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/683130c2ff8c2739f4822ff7ac5c873ec529abd1", - "reference": "683130c2ff8c2739f4822ff7ac5c873ec529abd1", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/139676794dc1e9231bf7bcd123cfc0c99182cb13", + "reference": "139676794dc1e9231bf7bcd123cfc0c99182cb13", "shasum": "" }, "require": { @@ -3556,7 +3426,7 @@ }, "require-dev": { "ircmaxell/php-yacc": "^0.0.7", - "phpunit/phpunit": "^9.0" + "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0" }, "bin": [ "bin/php-parse" @@ -3588,13 +3458,13 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v5.1.0" + "source": "https://github.com/nikic/PHP-Parser/tree/v5.0.2" }, - "time": "2024-07-01T20:03:41+00:00" + "time": "2024-03-05T20:51:40+00:00" }, { "name": "phar-io/manifest", - "version": "dev-master", + "version": "2.0.4", "source": { "type": "git", "url": "https://github.com/phar-io/manifest.git", @@ -3614,7 +3484,6 @@ "phar-io/version": "^3.0.1", "php": "^7.2 || ^8.0" }, - "default-branch": true, "type": "library", "extra": { "branch-alias": { @@ -3713,25 +3582,25 @@ }, { "name": "phpdocumentor/reflection-common", - "version": "dev-master", + "version": "2.2.0", "source": { "type": "git", "url": "https://github.com/phpDocumentor/ReflectionCommon.git", - "reference": "a0eeab580cbdf4414fef6978732510a36ed0a9d6" + "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/a0eeab580cbdf4414fef6978732510a36ed0a9d6", - "reference": "a0eeab580cbdf4414fef6978732510a36ed0a9d6", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/1d01c49d4ed62f25aa84a747ad35d5a16924662b", + "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b", "shasum": "" }, "require": { - "php": ">=7.1" + "php": "^7.2 || ^8.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.x-dev" + "dev-2.x": "2.x-dev" } }, "autoload": { @@ -3760,22 +3629,22 @@ ], "support": { "issues": "https://github.com/phpDocumentor/ReflectionCommon/issues", - "source": "https://github.com/phpDocumentor/ReflectionCommon/tree/master" + "source": "https://github.com/phpDocumentor/ReflectionCommon/tree/2.x" }, - "time": "2021-06-25T13:47:51+00:00" + "time": "2020-06-27T09:03:43+00:00" }, { "name": "phpdocumentor/reflection-docblock", - "version": "5.x-dev", + "version": "5.4.1", "source": { "type": "git", "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", - "reference": "aa53f8d4374d1f5bd3fc598548d6272cb5d9bf39" + "reference": "9d07b3f7fdcf5efec5d1609cba3c19c5ea2bdc9c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/aa53f8d4374d1f5bd3fc598548d6272cb5d9bf39", - "reference": "aa53f8d4374d1f5bd3fc598548d6272cb5d9bf39", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/9d07b3f7fdcf5efec5d1609cba3c19c5ea2bdc9c", + "reference": "9d07b3f7fdcf5efec5d1609cba3c19c5ea2bdc9c", "shasum": "" }, "require": { @@ -3796,7 +3665,6 @@ "phpunit/phpunit": "^9.5", "vimeo/psalm": "^5.13" }, - "default-branch": true, "type": "library", "extra": { "branch-alias": { @@ -3825,29 +3693,29 @@ "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", "support": { "issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues", - "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/5.x" + "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/5.4.1" }, - "time": "2024-05-21T06:14:15+00:00" + "time": "2024-05-21T05:55:05+00:00" }, { "name": "phpdocumentor/type-resolver", - "version": "1.x-dev", + "version": "1.8.2", "source": { "type": "git", "url": "https://github.com/phpDocumentor/TypeResolver.git", - "reference": "eee054a3d40f09920f5b27c9b09a6483f88d9d24" + "reference": "153ae662783729388a584b4361f2545e4d841e3c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/eee054a3d40f09920f5b27c9b09a6483f88d9d24", - "reference": "eee054a3d40f09920f5b27c9b09a6483f88d9d24", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/153ae662783729388a584b4361f2545e4d841e3c", + "reference": "153ae662783729388a584b4361f2545e4d841e3c", "shasum": "" }, "require": { "doctrine/deprecations": "^1.0", "php": "^7.3 || ^8.0", "phpdocumentor/reflection-common": "^2.0", - "phpstan/phpdoc-parser": "^1.18" + "phpstan/phpdoc-parser": "^1.13" }, "require-dev": { "ext-tokenizer": "*", @@ -3859,7 +3727,6 @@ "rector/rector": "^0.13.9", "vimeo/psalm": "^4.25" }, - "default-branch": true, "type": "library", "extra": { "branch-alias": { @@ -3884,22 +3751,22 @@ "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", "support": { "issues": "https://github.com/phpDocumentor/TypeResolver/issues", - "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.x" + "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.8.2" }, - "time": "2024-05-24T14:24:30+00:00" + "time": "2024-02-23T11:10:43+00:00" }, { "name": "phpspec/prophecy", - "version": "dev-master", + "version": "v1.19.0", "source": { "type": "git", "url": "https://github.com/phpspec/prophecy.git", - "reference": "f9e07be0992e7bf1cad210829055b99318df142f" + "reference": "67a759e7d8746d501c41536ba40cd9c0a07d6a87" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpspec/prophecy/zipball/f9e07be0992e7bf1cad210829055b99318df142f", - "reference": "f9e07be0992e7bf1cad210829055b99318df142f", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/67a759e7d8746d501c41536ba40cd9c0a07d6a87", + "reference": "67a759e7d8746d501c41536ba40cd9c0a07d6a87", "shasum": "" }, "require": { @@ -3914,7 +3781,6 @@ "phpstan/phpstan": "^1.9", "phpunit/phpunit": "^8.0 || ^9.0 || ^10.0" }, - "default-branch": true, "type": "library", "extra": { "branch-alias": { @@ -3954,9 +3820,9 @@ ], "support": { "issues": "https://github.com/phpspec/prophecy/issues", - "source": "https://github.com/phpspec/prophecy/tree/master" + "source": "https://github.com/phpspec/prophecy/tree/v1.19.0" }, - "time": "2024-03-29T09:25:04+00:00" + "time": "2024-02-29T11:52:51+00:00" }, { "name": "phpstan/phpdoc-parser", @@ -4005,77 +3871,18 @@ }, "time": "2024-05-31T08:52:43+00:00" }, - { - "name": "phpstan/phpstan", - "version": "1.8.x-dev", - "source": { - "type": "git", - "url": "https://github.com/phpstan/phpstan.git", - "reference": "b20042710baa0d9c07636cc66d4c400f03f1477a" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/b20042710baa0d9c07636cc66d4c400f03f1477a", - "reference": "b20042710baa0d9c07636cc66d4c400f03f1477a", - "shasum": "" - }, - "require": { - "php": "^7.2|^8.0" - }, - "conflict": { - "phpstan/phpstan-shim": "*" - }, - "bin": [ - "phpstan", - "phpstan.phar" - ], - "type": "library", - "autoload": { - "files": [ - "bootstrap.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "PHPStan - PHP Static Analysis Tool", - "keywords": [ - "dev", - "static analysis" - ], - "support": { - "issues": "https://github.com/phpstan/phpstan/issues", - "source": "https://github.com/phpstan/phpstan/tree/1.8.x" - }, - "funding": [ - { - "url": "https://github.com/ondrejmirtes", - "type": "github" - }, - { - "url": "https://github.com/phpstan", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan", - "type": "tidelift" - } - ], - "time": "2022-10-29T12:56:57+00:00" - }, { "name": "phpunit/php-code-coverage", - "version": "9.2.x-dev", + "version": "9.2.31", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "39d628812d8d83344a6c1b07799e3700d830d355" + "reference": "48c34b5d8d983006bd2adc2d0de92963b9155965" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/39d628812d8d83344a6c1b07799e3700d830d355", - "reference": "39d628812d8d83344a6c1b07799e3700d830d355", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/48c34b5d8d983006bd2adc2d0de92963b9155965", + "reference": "48c34b5d8d983006bd2adc2d0de92963b9155965", "shasum": "" }, "require": { @@ -4094,7 +3901,7 @@ "theseer/tokenizer": "^1.2.0" }, "require-dev": { - "phpunit/phpunit": "^9.6" + "phpunit/phpunit": "^9.3" }, "suggest": { "ext-pcov": "PHP extension that provides line coverage", @@ -4103,7 +3910,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "9.2-dev" + "dev-master": "9.2-dev" } }, "autoload": { @@ -4132,7 +3939,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2" + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.31" }, "funding": [ { @@ -4140,20 +3947,20 @@ "type": "github" } ], - "time": "2024-06-29T07:23:05+00:00" + "time": "2024-03-02T06:37:42+00:00" }, { "name": "phpunit/php-file-iterator", - "version": "3.0.x-dev", + "version": "3.0.6", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-file-iterator.git", - "reference": "38b24367e1b340aa78b96d7cab042942d917bb84" + "reference": "cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/38b24367e1b340aa78b96d7cab042942d917bb84", - "reference": "38b24367e1b340aa78b96d7cab042942d917bb84", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf", + "reference": "cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf", "shasum": "" }, "require": { @@ -4192,7 +3999,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", - "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/3.0" + "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/3.0.6" }, "funding": [ { @@ -4200,7 +4007,7 @@ "type": "github" } ], - "time": "2022-02-11T16:23:04+00:00" + "time": "2021-12-02T12:48:52+00:00" }, { "name": "phpunit/php-invoker", @@ -4488,7 +4295,7 @@ }, { "name": "psr/log", - "version": "dev-master", + "version": "3.0.0", "source": { "type": "git", "url": "https://github.com/php-fig/log.git", @@ -4503,7 +4310,6 @@ "require": { "php": ">=8.0.0" }, - "default-branch": true, "type": "library", "extra": { "branch-alias": { @@ -4539,7 +4345,7 @@ }, { "name": "sebastian/cli-parser", - "version": "1.0.x-dev", + "version": "1.0.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/cli-parser.git", @@ -4706,16 +4512,16 @@ }, { "name": "sebastian/comparator", - "version": "4.0.x-dev", + "version": "4.0.8", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "b247957a1c8dc81a671770f74b479c0a78a818f1" + "reference": "fa0f136dd2334583309d32b62544682ee972b51a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/b247957a1c8dc81a671770f74b479c0a78a818f1", - "reference": "b247957a1c8dc81a671770f74b479c0a78a818f1", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/fa0f136dd2334583309d32b62544682ee972b51a", + "reference": "fa0f136dd2334583309d32b62544682ee972b51a", "shasum": "" }, "require": { @@ -4768,7 +4574,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/comparator/issues", - "source": "https://github.com/sebastianbergmann/comparator/tree/4.0" + "source": "https://github.com/sebastianbergmann/comparator/tree/4.0.8" }, "funding": [ { @@ -4776,11 +4582,11 @@ "type": "github" } ], - "time": "2022-09-14T12:46:14+00:00" + "time": "2022-09-14T12:41:17+00:00" }, { "name": "sebastian/complexity", - "version": "2.0.x-dev", + "version": "2.0.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/complexity.git", @@ -4837,7 +4643,7 @@ }, { "name": "sebastian/diff", - "version": "4.0.x-dev", + "version": "4.0.6", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/diff.git", @@ -4903,7 +4709,7 @@ }, { "name": "sebastian/environment", - "version": "5.1.x-dev", + "version": "5.1.5", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/environment.git", @@ -4954,7 +4760,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/environment/issues", - "source": "https://github.com/sebastianbergmann/environment/tree/5.1" + "source": "https://github.com/sebastianbergmann/environment/tree/5.1.5" }, "funding": [ { @@ -4966,7 +4772,7 @@ }, { "name": "sebastian/exporter", - "version": "4.0.x-dev", + "version": "4.0.6", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/exporter.git", @@ -5043,7 +4849,7 @@ }, { "name": "sebastian/global-state", - "version": "5.0.x-dev", + "version": "5.0.7", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/global-state.git", @@ -5107,7 +4913,7 @@ }, { "name": "sebastian/lines-of-code", - "version": "1.0.x-dev", + "version": "1.0.4", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/lines-of-code.git", @@ -5276,7 +5082,7 @@ }, { "name": "sebastian/recursion-context", - "version": "4.0.x-dev", + "version": "4.0.5", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/recursion-context.git", @@ -5339,16 +5145,16 @@ }, { "name": "sebastian/resource-operations", - "version": "dev-main", + "version": "3.0.4", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/resource-operations.git", - "reference": "ff553e7482dcee39fa4acc2b175d6ddeb0f7bc25" + "reference": "05d5692a7993ecccd56a03e40cd7e5b09b1d404e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/ff553e7482dcee39fa4acc2b175d6ddeb0f7bc25", - "reference": "ff553e7482dcee39fa4acc2b175d6ddeb0f7bc25", + "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/05d5692a7993ecccd56a03e40cd7e5b09b1d404e", + "reference": "05d5692a7993ecccd56a03e40cd7e5b09b1d404e", "shasum": "" }, "require": { @@ -5357,7 +5163,6 @@ "require-dev": { "phpunit/phpunit": "^9.0" }, - "default-branch": true, "type": "library", "extra": { "branch-alias": { @@ -5382,7 +5187,7 @@ "description": "Provides a list of PHP built-in functions that operate on resources", "homepage": "https://www.github.com/sebastianbergmann/resource-operations", "support": { - "source": "https://github.com/sebastianbergmann/resource-operations/tree/main" + "source": "https://github.com/sebastianbergmann/resource-operations/tree/3.0.4" }, "funding": [ { @@ -5390,11 +5195,11 @@ "type": "github" } ], - "time": "2024-03-14T18:47:08+00:00" + "time": "2024-03-14T16:00:52+00:00" }, { "name": "sebastian/type", - "version": "3.2.x-dev", + "version": "3.2.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/type.git", @@ -5438,7 +5243,7 @@ "homepage": "https://github.com/sebastianbergmann/type", "support": { "issues": "https://github.com/sebastianbergmann/type/issues", - "source": "https://github.com/sebastianbergmann/type/tree/3.2" + "source": "https://github.com/sebastianbergmann/type/tree/3.2.1" }, "funding": [ { @@ -5450,7 +5255,7 @@ }, { "name": "sebastian/version", - "version": "3.0.x-dev", + "version": "3.0.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/version.git", @@ -5535,7 +5340,7 @@ }, { "name": "symfony/polyfill-ctype", - "version": "1.x-dev", + "version": "v1.30.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", @@ -5556,7 +5361,6 @@ "suggest": { "ext-ctype": "For best performance" }, - "default-branch": true, "type": "library", "extra": { "thanks": { @@ -5785,88 +5589,9 @@ "time": "2023-11-21T18:54:41+00:00" } ], - "aliases": [ - { - "package": "utopia-php/abuse", - "version": "dev-feat-framework-v2", - "alias": "0.37.99", - "alias_normalized": "0.37.99.0" - }, - { - "package": "utopia-php/analytics", - "version": "dev-feat-framework-v2", - "alias": "0.10.99", - "alias_normalized": "0.10.99.0" - }, - { - "package": "utopia-php/audit", - "version": "dev-feat-framework-v2", - "alias": "0.39.99", - "alias_normalized": "0.39.99.0" - }, - { - "package": "utopia-php/cli", - "version": "dev-dev-coroutines", - "alias": "0.17.99", - "alias_normalized": "0.17.99.0" - }, - { - "package": "utopia-php/database", - "version": "dev-feat-framework-v2", - "alias": "0.49.99", - "alias_normalized": "0.49.99.0" - }, - { - "package": "utopia-php/domains", - "version": "dev-feat-framework-v2", - "alias": "0.5.99", - "alias_normalized": "0.5.99.0" - }, - { - "package": "utopia-php/framework", - "version": "dev-feat-di-upgrade", - "alias": "0.34.99", - "alias_normalized": "0.34.99.0" - }, - { - "package": "utopia-php/orchestration", - "version": "dev-feat-framework-v2", - "alias": "0.9.99", - "alias_normalized": "0.9.99.0" - }, - { - "package": "utopia-php/platform", - "version": "dev-feat-framework-v2", - "alias": "0.5.99", - "alias_normalized": "0.5.99.0" - }, - { - "package": "utopia-php/queue", - "version": "dev-feat-coroutine-and-di", - "alias": "0.7.99", - "alias_normalized": "0.7.99.0" - }, - { - "package": "utopia-php/storage", - "version": "dev-feat-framework-v2-v2", - "alias": "0.18.99", - "alias_normalized": "0.18.99.0" - } - ], - "minimum-stability": "dev", - "stability-flags": { - "utopia-php/abuse": 20, - "utopia-php/analytics": 20, - "utopia-php/audit": 20, - "utopia-php/cli": 20, - "utopia-php/database": 20, - "utopia-php/domains": 20, - "utopia-php/framework": 20, - "utopia-php/orchestration": 20, - "utopia-php/platform": 20, - "utopia-php/queue": 20, - "utopia-php/storage": 20 - }, + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], "prefer-stable": false, "prefer-lowest": false, "platform": { diff --git a/docker-compose.yml b/docker-compose.yml index 3b2f7da5e2..08afd26344 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 @@ -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 diff --git a/docs/references/account/update-mfa-authenticator.md b/docs/references/account/update-mfa-authenticator.md index 253826a891..23a3251441 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/Migration/Migration.php b/src/Appwrite/Migration/Migration.php index 695c42f05f..d56124862f 100644 --- a/src/Appwrite/Migration/Migration.php +++ b/src/Appwrite/Migration/Migration.php @@ -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'], diff --git a/src/Appwrite/Migration/Version/V20.php b/src/Appwrite/Migration/Version/V20.php index 1a599d32f2..5a0807cedf 100644 --- a/src/Appwrite/Migration/Version/V20.php +++ b/src/Appwrite/Migration/Version/V20.php @@ -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; } diff --git a/src/Appwrite/Platform/Tasks/Migrate.php b/src/Appwrite/Platform/Tasks/Migrate.php index 5d000acdc5..81f5d9c879 100644 --- a/src/Appwrite/Platform/Tasks/Migrate.php +++ b/src/Appwrite/Platform/Tasks/Migrate.php @@ -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)) diff --git a/src/Appwrite/Platform/Workers/Deletes.php b/src/Appwrite/Platform/Workers/Deletes.php index 2cb1a276b4..d0a5d43f71 100644 --- a/src/Appwrite/Platform/Workers/Deletes.php +++ b/src/Appwrite/Platform/Workers/Deletes.php @@ -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); diff --git a/src/Appwrite/Platform/Workers/Messaging.php b/src/Appwrite/Platform/Workers/Messaging.php index 19d2c5a0fe..6f642fabb7 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 0bb5ca4650..92bc52561c 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 0b8ea71aad..6274f5ddae 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 { /** @@ -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', diff --git a/tests/e2e/Services/Functions/FunctionsCustomServerTest.php b/tests/e2e/Services/Functions/FunctionsCustomServerTest.php index 7d68f7562f..9031faf8d0 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/Health/HealthCustomServerTest.php b/tests/e2e/Services/Health/HealthCustomServerTest.php index 5fd7ab96da..8360af542e 100644 --- a/tests/e2e/Services/Health/HealthCustomServerTest.php +++ b/tests/e2e/Services/Health/HealthCustomServerTest.php @@ -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']); diff --git a/tests/e2e/Services/Messaging/MessagingBase.php b/tests/e2e/Services/Messaging/MessagingBase.php index 0540479eb5..dc5ddb9d70 100644 --- a/tests/e2e/Services/Messaging/MessagingBase.php +++ b/tests/e2e/Services/Messaging/MessagingBase.php @@ -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 */ diff --git a/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php b/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php index 8cdd325501..2af0c8f1cc 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']); - } } diff --git a/tests/e2e/Services/Storage/StorageBase.php b/tests/e2e/Services/Storage/StorageBase.php index a6334d8091..d2d75409b8 100644 --- a/tests/e2e/Services/Storage/StorageBase.php +++ b/tests/e2e/Services/Storage/StorageBase.php @@ -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()), diff --git a/tests/e2e/Services/Users/UsersBase.php b/tests/e2e/Services/Users/UsersBase.php index 1737e0483d..a24b6f8161 100644 --- a/tests/e2e/Services/Users/UsersBase.php +++ b/tests/e2e/Services/Users/UsersBase.php @@ -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',