Merge branch 'main' into feat-implement-webauthn
This commit is contained in:
commit
5251f6d780
46 changed files with 451 additions and 766 deletions
3
.env
3
.env
|
@ -9,7 +9,8 @@ _APP_CONSOLE_COUNTRIES_DENYLIST=AQ
|
|||
_APP_CONSOLE_HOSTNAMES=localhost,appwrite.io,*.appwrite.io
|
||||
_APP_SYSTEM_EMAIL_NAME=Appwrite
|
||||
_APP_SYSTEM_EMAIL_ADDRESS=team@appwrite.io
|
||||
_APP_SYSTEM_SECURITY_EMAIL_ADDRESS=security@appwrite.io
|
||||
_APP_EMAIL_SECURITY=security@appwrite.io
|
||||
_APP_EMAIL_CERTIFICATES=certificates@appwrite.io
|
||||
_APP_SYSTEM_RESPONSE_FORMAT=
|
||||
_APP_OPTIONS_ABUSE=disabled
|
||||
_APP_OPTIONS_ROUTER_PROTECTION=disabled
|
||||
|
|
3
.github/workflows/tests.yml
vendored
3
.github/workflows/tests.yml
vendored
|
@ -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
|
||||
|
|
2
.gitmodules
vendored
2
.gitmodules
vendored
|
@ -1,4 +1,4 @@
|
|||
[submodule "app/console"]
|
||||
path = app/console
|
||||
url = https://github.com/appwrite/console
|
||||
branch = 4.3.5
|
||||
branch = 4.3.14
|
||||
|
|
|
@ -29,7 +29,7 @@ ENV VITE_APPWRITE_GROWTH_ENDPOINT=$VITE_APPWRITE_GROWTH_ENDPOINT
|
|||
RUN npm ci
|
||||
RUN npm run build
|
||||
|
||||
FROM appwrite/base:0.9.0 as final
|
||||
FROM appwrite/base:0.9.1 as final
|
||||
|
||||
LABEL maintainer="team@appwrite.io"
|
||||
|
||||
|
|
|
@ -109,7 +109,7 @@ CLI::setResource('getProjectDB', function (Group $pools, Database $dbForConsole,
|
|||
if (isset($databases[$dsn->getHost()])) {
|
||||
$database = $databases[$dsn->getHost()];
|
||||
|
||||
if ($dsn->getHost() === DATABASE_SHARED_TABLES) {
|
||||
if ($dsn->getHost() === System::getEnv('_APP_DATABASE_SHARED_TABLES', '')) {
|
||||
$database
|
||||
->setSharedTables(true)
|
||||
->setTenant($project->getInternalId())
|
||||
|
@ -133,7 +133,7 @@ CLI::setResource('getProjectDB', function (Group $pools, Database $dbForConsole,
|
|||
|
||||
$databases[$dsn->getHost()] = $database;
|
||||
|
||||
if ($dsn->getHost() === DATABASE_SHARED_TABLES) {
|
||||
if ($dsn->getHost() === System::getEnv('_APP_DATABASE_SHARED_TABLES', '')) {
|
||||
$database
|
||||
->setSharedTables(true)
|
||||
->setTenant($project->getInternalId())
|
||||
|
@ -202,7 +202,7 @@ CLI::setResource('logError', function (Registry $register) {
|
|||
}, ['register']);
|
||||
|
||||
$platform = new Appwrite();
|
||||
$platform->init(Service::TYPE_CLI);
|
||||
$platform->init(Service::TYPE_TASK);
|
||||
|
||||
$cli = $platform->getCli();
|
||||
|
||||
|
|
|
@ -4,7 +4,9 @@
|
|||
<table border="0" cellspacing="0" cellpadding="0" style="padding-top: 10px; padding-bottom: 10px; display: inline-block;">
|
||||
<tr>
|
||||
<td align="center" style="border-radius: 8px; background-color: #ffffff;">
|
||||
<p style="text-align: start; font-size: 14px; font-family: Inter; color: #414146; text-decoration: none; border-radius: 8px; padding: 32px; border: 1px solid #EDEDF0; display: inline-block; word-break: break-word;">{{error}}</p>
|
||||
<p style="text-align: start; font-size: 14px; font-family: Inter; color: #414146; text-decoration: none; border-radius: 8px; padding: 32px; border: 1px solid #EDEDF0; display: inline-block; word-break: break-word;">
|
||||
{{error}}
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
|
|
@ -43,6 +43,12 @@
|
|||
"emails.invitation.footer": "If you are not interested, you can ignore this message.",
|
||||
"emails.invitation.thanks": "Thanks",
|
||||
"emails.invitation.signature": "{{project}} team",
|
||||
"emails.certificate.subject": "Certificate failure for %s",
|
||||
"emails.certificate.hello": "Hello",
|
||||
"emails.certificate.body": "Certificate for your domain '{{domain}}' could not be generated. This is attempt no. {{attempt}}, and the failure was caused by: {{error}}",
|
||||
"emails.certificate.footer": "Your previous certificate will be valid for 30 days since the first failure. We highly recommend investigating this case, otherwise your domain will end up without a valid SSL communication.",
|
||||
"emails.certificate.thanks": "Thanks",
|
||||
"emails.certificate.signature": "{{project}} team",
|
||||
"sms.verification.body": "{{secret}}",
|
||||
"locale.country.unknown": "Unknown",
|
||||
"countries.af": "Afghanistan",
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -162,13 +162,31 @@ return [
|
|||
],
|
||||
[
|
||||
'name' => '_APP_SYSTEM_SECURITY_EMAIL_ADDRESS',
|
||||
'description' => 'This is the email address used to issue SSL certificates for custom domains or the user agent in your webhooks payload.',
|
||||
'description' => 'Deprecated since 1.5.1 use _APP_EMAIL_SECURITY and _APP_EMAIL_CERTIFICATES instead',
|
||||
'introduction' => '0.7.0',
|
||||
'default' => 'certs@appwrite.io',
|
||||
'required' => false,
|
||||
'question' => '',
|
||||
'filter' => ''
|
||||
],
|
||||
[
|
||||
'name' => '_APP_EMAIL_SECURITY',
|
||||
'description' => 'This is the email address used as the user agent in your webhooks payload.',
|
||||
'introduction' => '1.5.1',
|
||||
'default' => '',
|
||||
'required' => false,
|
||||
'question' => '',
|
||||
'filter' => ''
|
||||
],
|
||||
[
|
||||
'name' => '_APP_EMAIL_CERTIFICATES',
|
||||
'description' => 'This is the email address used to issue SSL certificates for custom domains',
|
||||
'introduction' => '1.5.1',
|
||||
'default' => '',
|
||||
'required' => true,
|
||||
'question' => '',
|
||||
'filter' => ''
|
||||
],
|
||||
[
|
||||
'name' => '_APP_USAGE_STATS',
|
||||
'description' => 'This variable allows you to disable the collection and displaying of usage stats. This value is set to \'enabled\' by default, to disable the usage stats set the value to \'disabled\'. When disabled, it\'s recommended to turn off the Worker Usage container to reduce resource usage.',
|
||||
|
@ -450,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,
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit 5169fe16d63066f64ab5013c78953aea04e24b53
|
||||
Subproject commit 412bc3331891c15ba7baf4f445e82ac3a678c6be
|
|
@ -88,8 +88,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)
|
||||
});
|
||||
|
@ -292,7 +292,9 @@ App::post('/v1/account')
|
|||
$existingTarget = $dbForProject->findOne('targets', [
|
||||
Query::equal('identifier', [$email]),
|
||||
]);
|
||||
$user->setAttribute('targets', [...$user->getAttribute('targets', []), $existingTarget]);
|
||||
if($existingTarget) {
|
||||
$user->setAttribute('targets', $existingTarget, Document::SET_TYPE_APPEND);
|
||||
}
|
||||
}
|
||||
|
||||
$dbForProject->purgeCachedDocument('users', $user->getId());
|
||||
|
@ -1702,7 +1704,7 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect')
|
|||
'secret' => Auth::hash($secret), // One way hash encryption to protect DB leak
|
||||
'userAgent' => $request->getUserAgent('UNKNOWN'),
|
||||
'ip' => $request->getIP(),
|
||||
'factors' => ['email'],
|
||||
'factors' => [TYPE::EMAIL, 'oauth2'], // include a special oauth2 factor to bypass MFA checks
|
||||
'countryCode' => ($record) ? \strtolower($record['country']['iso_code']) : '--',
|
||||
'expire' => DateTime::addSeconds(new \DateTime(), $duration)
|
||||
], $detector->getOS(), $detector->getClient(), $detector->getDevice()));
|
||||
|
@ -2720,6 +2722,7 @@ App::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')
|
||||
|
|
|
@ -321,7 +321,7 @@ App::get('/v1/avatars/favicon')
|
|||
->setUserAgent(\sprintf(
|
||||
APP_USERAGENT,
|
||||
System::getEnv('_APP_VERSION', 'UNKNOWN'),
|
||||
System::getEnv('_APP_SYSTEM_SECURITY_EMAIL_ADDRESS', APP_EMAIL_SECURITY)
|
||||
System::getEnv('_APP_EMAIL_SECURITY', System::getEnv('_APP_SYSTEM_SECURITY_EMAIL_ADDRESS', APP_EMAIL_SECURITY))
|
||||
))
|
||||
->fetch($url);
|
||||
} catch (\Throwable) {
|
||||
|
|
|
@ -852,7 +852,7 @@ App::get('/v1/health/queue/failed/:name')
|
|||
Event::FUNCTIONS_QUEUE_NAME,
|
||||
Event::USAGE_QUEUE_NAME,
|
||||
Event::USAGE_DUMP_QUEUE_NAME,
|
||||
Event::WEBHOOK_CLASS_NAME,
|
||||
Event::WEBHOOK_QUEUE_NAME,
|
||||
Event::CERTIFICATES_QUEUE_NAME,
|
||||
Event::BUILDS_QUEUE_NAME,
|
||||
Event::MESSAGING_QUEUE_NAME,
|
||||
|
|
|
@ -111,35 +111,8 @@ App::post('/v1/projects')
|
|||
|
||||
$projectId = ($projectId == 'unique()') ? ID::unique() : $projectId;
|
||||
|
||||
$backups['database_db_fra1_v14x_02'] = ['from' => '03:00', 'to' => '05:00'];
|
||||
$backups['database_db_fra1_v14x_03'] = ['from' => '00:00', 'to' => '02:00'];
|
||||
$backups['database_db_fra1_v14x_04'] = ['from' => '00:00', 'to' => '02:00'];
|
||||
$backups['database_db_fra1_v14x_05'] = ['from' => '00:00', 'to' => '02:00'];
|
||||
$backups['database_db_fra1_v14x_06'] = ['from' => '00:00', 'to' => '02:00'];
|
||||
$backups['database_db_fra1_v14x_07'] = ['from' => '00:00', 'to' => '02:00'];
|
||||
|
||||
$databases = Config::getParam('pools-database', []);
|
||||
|
||||
/**
|
||||
* Remove databases from the list that are currently undergoing an backup
|
||||
*/
|
||||
if (count($databases) > 1) {
|
||||
$now = new \DateTime();
|
||||
|
||||
foreach ($databases as $index => $database) {
|
||||
if (empty($backups[$database])) {
|
||||
continue;
|
||||
}
|
||||
$backup = $backups[$database];
|
||||
$from = \DateTime::createFromFormat('H:i', $backup['from']);
|
||||
$to = \DateTime::createFromFormat('H:i', $backup['to']);
|
||||
if ($now >= $from && $now <= $to) {
|
||||
unset($databases[$index]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$databaseOverride = System::getEnv('_APP_DATABASE_OVERRIDE');
|
||||
$index = \array_search($databaseOverride, $databases);
|
||||
if ($index !== false) {
|
||||
|
@ -152,37 +125,12 @@ App::post('/v1/projects')
|
|||
throw new Exception(Exception::PROJECT_RESERVED_PROJECT, "'console' is a reserved project.");
|
||||
}
|
||||
|
||||
// TODO: 1 in 5 projects use shared tables. Temporary until all projects are using shared tables.
|
||||
if (
|
||||
(
|
||||
!\mt_rand(0, 4)
|
||||
&& System::getEnv('_APP_DATABASE_SHARED_TABLES', 'enabled') === 'enabled'
|
||||
&& System::getEnv('_APP_EDITION', 'self-hosted') !== 'self-hosted'
|
||||
) ||
|
||||
(
|
||||
$dsn === DATABASE_SHARED_TABLES
|
||||
)
|
||||
) {
|
||||
// TODO: Temporary until all projects are using shared tables.
|
||||
if ($dsn === System::getEnv('_APP_DATABASE_SHARED_TABLES', '')) {
|
||||
$schema = 'appwrite';
|
||||
$database = 'appwrite';
|
||||
$namespace = System::getEnv('_APP_DATABASE_SHARED_NAMESPACE', '');
|
||||
$dsn = $schema . '://' . DATABASE_SHARED_TABLES . '?database=' . $database;
|
||||
|
||||
if (!empty($namespace)) {
|
||||
$dsn .= '&namespace=' . $namespace;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Allow overriding in development mode. Temporary until all projects are using shared tables.
|
||||
if (
|
||||
App::isDevelopment()
|
||||
&& System::getEnv('_APP_EDITION', 'self-hosted') !== 'self-hosted'
|
||||
&& $request->getHeader('x-appwrited-share-tables', false)
|
||||
) {
|
||||
$schema = 'appwrite';
|
||||
$database = 'appwrite';
|
||||
$namespace = System::getEnv('_APP_DATABASE_SHARED_NAMESPACE', '');
|
||||
$dsn = $schema . '://' . DATABASE_SHARED_TABLES . '?database=' . $database;
|
||||
$dsn = $schema . '://' . System::getEnv('_APP_DATABASE_SHARED_TABLES', '') . '?database=' . $database;
|
||||
|
||||
if (!empty($namespace)) {
|
||||
$dsn .= '&namespace=' . $namespace;
|
||||
|
@ -236,7 +184,7 @@ App::post('/v1/projects')
|
|||
$adapter = $pools->get($dsn->getHost())->pop()->getResource();
|
||||
$dbForProject = new Database($adapter, $cache);
|
||||
|
||||
if ($dsn->getHost() === DATABASE_SHARED_TABLES) {
|
||||
if ($dsn->getHost() === System::getEnv('_APP_DATABASE_SHARED_TABLES', '')) {
|
||||
$dbForProject
|
||||
->setSharedTables(true)
|
||||
->setTenant($project->getInternalId())
|
||||
|
|
|
@ -63,7 +63,7 @@ App::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)
|
||||
|
@ -240,7 +240,7 @@ App::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)
|
||||
|
@ -351,7 +351,7 @@ App::post('/v1/storage/buckets/:bucketId/files')
|
|||
->label('sdk.response.model', Response::MODEL_FILE)
|
||||
->param('bucketId', '', new UID(), 'Storage bucket unique ID. You can create a new storage bucket using the Storage service [server integration](https://appwrite.io/docs/server/storage#createBucket).')
|
||||
->param('fileId', '', new CustomId(), 'File ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
|
||||
->param('file', [], new File(), 'Binary file. Appwrite SDKs provide helpers to handle file input. [Learn about file input](https://appwrite.io/docs/storage#file-input).', skipValidation: true)
|
||||
->param('file', [], new File(), 'Binary file. Appwrite SDKs provide helpers to handle file input. [Learn about file input](https://appwrite.io/docs/products/storage/upload-download#input-file).', skipValidation: true)
|
||||
->param('permissions', null, new Permissions(APP_LIMIT_ARRAY_PARAMS_SIZE, [Database::PERMISSION_READ, Database::PERMISSION_UPDATE, Database::PERMISSION_DELETE, Database::PERMISSION_WRITE]), 'An array of permission strings. By default, only the current user is granted all permissions. [Learn more about permissions](https://appwrite.io/docs/permissions).', true)
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
|
|
|
@ -387,7 +387,7 @@ App::post('/v1/teams/:teamId/memberships')
|
|||
->param('userId', '', new UID(), 'ID of the user to be added to a team.', true)
|
||||
->param('phone', '', new Phone(), 'Phone number. Format this number with a leading \'+\' and a country code, e.g., +16175551212.', true)
|
||||
->param('roles', [], new ArrayList(new Key(), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Array of strings. Use this param to set the user roles in the team. A role can be any string. Learn more about [roles and permissions](https://appwrite.io/docs/permissions). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' roles are allowed, each 32 characters long.')
|
||||
->param('url', '', fn ($clients) => new Host($clients), 'URL to redirect the user back to your app from the invitation email. Only URLs from hostnames in your project platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.', true, ['clients']) // TODO add our own built-in confirm page
|
||||
->param('url', '', fn ($clients) => new Host($clients), 'URL to redirect the user back to your app from the invitation email. This parameter is not required when an API key is supplied. Only URLs from hostnames in your project platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.', true, ['clients']) // TODO add our own built-in confirm page
|
||||
->param('name', '', new Text(128), 'Name of the new team member. Max length: 128 chars.', true)
|
||||
->inject('response')
|
||||
->inject('project')
|
||||
|
|
|
@ -138,7 +138,9 @@ function createUser(string $hash, mixed $hashOptions, string $userId, ?string $e
|
|||
$existingTarget = $dbForProject->findOne('targets', [
|
||||
Query::equal('identifier', [$email]),
|
||||
]);
|
||||
$user->setAttribute('targets', [...$user->getAttribute('targets', []), $existingTarget]);
|
||||
if($existingTarget) {
|
||||
$user->setAttribute('targets', $existingTarget, Document::SET_TYPE_APPEND);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -160,7 +162,9 @@ function createUser(string $hash, mixed $hashOptions, string $userId, ?string $e
|
|||
$existingTarget = $dbForProject->findOne('targets', [
|
||||
Query::equal('identifier', [$phone]),
|
||||
]);
|
||||
$user->setAttribute('targets', [...$user->getAttribute('targets', []), $existingTarget]);
|
||||
if($existingTarget) {
|
||||
$user->setAttribute('targets', $existingTarget, Document::SET_TYPE_APPEND);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1780,7 +1784,7 @@ App::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());
|
||||
|
||||
|
@ -1797,6 +1801,7 @@ App::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(),
|
||||
|
@ -1808,7 +1813,6 @@ App::post('/v1/users/:userId/sessions')
|
|||
$session = $dbForProject->createDocument('sessions', $session);
|
||||
$session
|
||||
->setAttribute('secret', $secret)
|
||||
->setAttribute('expire', $expire)
|
||||
->setAttribute('countryName', $countryName);
|
||||
|
||||
$queueForEvents
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
require_once __DIR__ . '/../init.php';
|
||||
|
||||
use Appwrite\Auth\Auth;
|
||||
use Appwrite\Event\Certificate;
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Event\Usage;
|
||||
|
@ -583,7 +584,7 @@ App::init()
|
|||
->addHeader('Server', 'Appwrite')
|
||||
->addHeader('X-Content-Type-Options', 'nosniff')
|
||||
->addHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, PATCH, DELETE')
|
||||
->addHeader('Access-Control-Allow-Headers', 'Origin, Cookie, Set-Cookie, X-Requested-With, Content-Type, Access-Control-Allow-Origin, Access-Control-Request-Headers, Accept, X-Appwrite-Project, X-Appwrite-Key, X-Appwrite-Locale, X-Appwrite-Mode, X-Appwrite-JWT, X-Appwrite-Response-Format, X-Appwrite-Timeout, X-Appwrite-Shared-Tables, X-SDK-Version, X-SDK-Name, X-SDK-Language, X-SDK-Platform, X-SDK-GraphQL, X-Appwrite-ID, X-Appwrite-Timestamp, Content-Range, Range, Cache-Control, Expires, Pragma, X-Forwarded-For, X-Forwarded-User-Agent')
|
||||
->addHeader('Access-Control-Allow-Headers', 'Origin, Cookie, Set-Cookie, X-Requested-With, Content-Type, Access-Control-Allow-Origin, Access-Control-Request-Headers, Accept, X-Appwrite-Project, X-Appwrite-Key, X-Appwrite-Locale, X-Appwrite-Mode, X-Appwrite-JWT, X-Appwrite-Response-Format, X-Appwrite-Timeout, X-SDK-Version, X-SDK-Name, X-SDK-Language, X-SDK-Platform, X-SDK-GraphQL, X-Appwrite-ID, X-Appwrite-Timestamp, Content-Range, Range, Cache-Control, Expires, Pragma, X-Forwarded-For, X-Forwarded-User-Agent')
|
||||
->addHeader('Access-Control-Expose-Headers', 'X-Appwrite-Session, X-Fallback-Cookies')
|
||||
->addHeader('Access-Control-Allow-Origin', $refDomain)
|
||||
->addHeader('Access-Control-Allow-Credentials', 'true');
|
||||
|
@ -634,7 +635,7 @@ App::options()
|
|||
$response
|
||||
->addHeader('Server', 'Appwrite')
|
||||
->addHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, PATCH, DELETE')
|
||||
->addHeader('Access-Control-Allow-Headers', 'Origin, Cookie, Set-Cookie, X-Requested-With, Content-Type, Access-Control-Allow-Origin, Access-Control-Request-Headers, Accept, X-Appwrite-Project, X-Appwrite-Key, X-Appwrite-Locale, X-Appwrite-Mode, X-Appwrite-JWT, X-Appwrite-Response-Format, X-Appwrite-Timeout, X-Appwrite-Shared-Tables, X-SDK-Version, X-SDK-Name, X-SDK-Language, X-SDK-Platform, X-SDK-GraphQL, X-Appwrite-ID, X-Appwrite-Timestamp, Content-Range, Range, Cache-Control, Expires, Pragma, X-Appwrite-Session, X-Fallback-Cookies, X-Forwarded-For, X-Forwarded-User-Agent')
|
||||
->addHeader('Access-Control-Allow-Headers', 'Origin, Cookie, Set-Cookie, X-Requested-With, Content-Type, Access-Control-Allow-Origin, Access-Control-Request-Headers, Accept, X-Appwrite-Project, X-Appwrite-Key, X-Appwrite-Locale, X-Appwrite-Mode, X-Appwrite-JWT, X-Appwrite-Response-Format, X-Appwrite-Timeout, X-SDK-Version, X-SDK-Name, X-SDK-Language, X-SDK-Platform, X-SDK-GraphQL, X-Appwrite-ID, X-Appwrite-Timestamp, Content-Range, Range, Cache-Control, Expires, Pragma, X-Appwrite-Session, X-Fallback-Cookies, X-Forwarded-For, X-Forwarded-User-Agent')
|
||||
->addHeader('Access-Control-Expose-Headers', 'X-Appwrite-Session, X-Fallback-Cookies')
|
||||
->addHeader('Access-Control-Allow-Origin', $origin)
|
||||
->addHeader('Access-Control-Allow-Credentials', 'true')
|
||||
|
@ -649,7 +650,8 @@ App::error()
|
|||
->inject('project')
|
||||
->inject('logger')
|
||||
->inject('log')
|
||||
->action(function (Throwable $error, App $utopia, Request $request, Response $response, Document $project, ?Logger $logger, Log $log) {
|
||||
->inject('queueForUsage')
|
||||
->action(function (Throwable $error, App $utopia, Request $request, Response $response, Document $project, ?Logger $logger, Log $log, Usage $queueForUsage) {
|
||||
$version = System::getEnv('_APP_VERSION', 'UNKNOWN');
|
||||
$route = $utopia->getRoute();
|
||||
$class = \get_class($error);
|
||||
|
@ -738,6 +740,26 @@ App::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) {
|
||||
try {
|
||||
/** @var Utopia\Database\Document $user */
|
||||
|
|
15
app/init.php
15
app/init.php
|
@ -112,7 +112,7 @@ const APP_LIMIT_LIST_DEFAULT = 25; // Default maximum number of items to return
|
|||
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 = 443;
|
||||
const APP_CACHE_BUSTER = 4314;
|
||||
const APP_VERSION_STABLE = '1.5.7';
|
||||
const APP_DATABASE_ATTRIBUTE_EMAIL = 'email';
|
||||
const APP_DATABASE_ATTRIBUTE_ENUM = 'enum';
|
||||
|
@ -143,9 +143,6 @@ const APP_SOCIAL_STACKSHARE = 'https://stackshare.io/appwrite';
|
|||
const APP_SOCIAL_YOUTUBE = 'https://www.youtube.com/c/appwrite?sub_confirmation=1';
|
||||
const APP_HOSTNAME_INTERNAL = 'appwrite';
|
||||
|
||||
// Databases
|
||||
const DATABASE_SHARED_TABLES = 'database_db_fra1_self_hosted_16_0';
|
||||
|
||||
// Database Reconnect
|
||||
const DATABASE_RECONNECT_SLEEP = 2;
|
||||
const DATABASE_RECONNECT_MAX_ATTEMPTS = 10;
|
||||
|
@ -610,9 +607,9 @@ Database::addFilter(
|
|||
])
|
||||
));
|
||||
if (\count($targetIds) > 0) {
|
||||
return $database->find('targets', [
|
||||
return $database->skipValidation(fn () => $database->find('targets', [
|
||||
Query::equal('$internalId', $targetIds)
|
||||
]);
|
||||
]));
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
@ -1011,7 +1008,7 @@ foreach ($locales as $locale) {
|
|||
'user_agent' => \sprintf(
|
||||
APP_USERAGENT,
|
||||
System::getEnv('_APP_VERSION', 'UNKNOWN'),
|
||||
System::getEnv('_APP_SYSTEM_SECURITY_EMAIL_ADDRESS', APP_EMAIL_SECURITY)
|
||||
System::getEnv('_APP_EMAIL_SECURITY', System::getEnv('_APP_SYSTEM_SECURITY_EMAIL_ADDRESS', APP_EMAIL_SECURITY))
|
||||
),
|
||||
'timeout' => 2,
|
||||
],
|
||||
|
@ -1338,7 +1335,7 @@ App::setResource('dbForProject', function (Group $pools, Database $dbForConsole,
|
|||
$dsn = new DSN('mysql://' . $project->getAttribute('database'));
|
||||
}
|
||||
|
||||
if ($dsn->getHost() === DATABASE_SHARED_TABLES) {
|
||||
if ($dsn->getHost() === System::getEnv('_APP_DATABASE_SHARED_TABLES', '')) {
|
||||
$database
|
||||
->setSharedTables(true)
|
||||
->setTenant($project->getInternalId())
|
||||
|
@ -1391,7 +1388,7 @@ App::setResource('getProjectDB', function (Group $pools, Database $dbForConsole,
|
|||
->setMetadata('project', $project->getId())
|
||||
->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS);
|
||||
|
||||
if ($dsn->getHost() === DATABASE_SHARED_TABLES) {
|
||||
if ($dsn->getHost() === System::getEnv('_APP_DATABASE_SHARED_TABLES', '')) {
|
||||
$database
|
||||
->setSharedTables(true)
|
||||
->setTenant($project->getInternalId())
|
||||
|
|
|
@ -92,7 +92,7 @@ if (!function_exists("getProjectDB")) {
|
|||
|
||||
$database = new Database($adapter, getCache());
|
||||
|
||||
if ($dsn->getHost() === DATABASE_SHARED_TABLES) {
|
||||
if ($dsn->getHost() === System::getEnv('_APP_DATABASE_SHARED_TABLES', '')) {
|
||||
$database
|
||||
->setSharedTables(true)
|
||||
->setTenant($project->getInternalId())
|
||||
|
|
|
@ -79,7 +79,7 @@ $image = $this->getParam('image', '');
|
|||
- _APP_CONSOLE_HOSTNAMES
|
||||
- _APP_SYSTEM_EMAIL_NAME
|
||||
- _APP_SYSTEM_EMAIL_ADDRESS
|
||||
- _APP_SYSTEM_SECURITY_EMAIL_ADDRESS
|
||||
- _APP_EMAIL_SECURITY
|
||||
- _APP_SYSTEM_RESPONSE_FORMAT
|
||||
- _APP_OPTIONS_ABUSE
|
||||
- _APP_OPTIONS_ROUTER_PROTECTION
|
||||
|
@ -249,6 +249,7 @@ $image = $this->getParam('image', '');
|
|||
- _APP_ENV
|
||||
- _APP_WORKER_PER_CORE
|
||||
- _APP_OPENSSL_KEY_V1
|
||||
- _APP_EMAIL_SECURITY
|
||||
- _APP_SYSTEM_SECURITY_EMAIL_ADDRESS
|
||||
- _APP_DB_HOST
|
||||
- _APP_DB_PORT
|
||||
|
@ -430,7 +431,7 @@ $image = $this->getParam('image', '');
|
|||
- _APP_DOMAIN
|
||||
- _APP_DOMAIN_TARGET
|
||||
- _APP_DOMAIN_FUNCTIONS
|
||||
- _APP_SYSTEM_SECURITY_EMAIL_ADDRESS
|
||||
- _APP_EMAIL_CERTIFICATES
|
||||
- _APP_REDIS_HOST
|
||||
- _APP_REDIS_PORT
|
||||
- _APP_REDIS_USER
|
||||
|
@ -580,7 +581,7 @@ $image = $this->getParam('image', '');
|
|||
- _APP_OPENSSL_KEY_V1
|
||||
- _APP_DOMAIN
|
||||
- _APP_DOMAIN_TARGET
|
||||
- _APP_SYSTEM_SECURITY_EMAIL_ADDRESS
|
||||
- _APP_EMAIL_SECURITY
|
||||
- _APP_REDIS_HOST
|
||||
- _APP_REDIS_PORT
|
||||
- _APP_REDIS_USER
|
||||
|
|
|
@ -93,7 +93,7 @@ Server::setResource('dbForProject', function (Cache $cache, Registry $register,
|
|||
$dsn = new DSN('mysql://' . $project->getAttribute('database'));
|
||||
}
|
||||
|
||||
if ($dsn->getHost() === DATABASE_SHARED_TABLES) {
|
||||
if ($dsn->getHost() === System::getEnv('_APP_DATABASE_SHARED_TABLES', '')) {
|
||||
$database
|
||||
->setSharedTables(true)
|
||||
->setTenant($project->getInternalId())
|
||||
|
@ -126,7 +126,7 @@ Server::setResource('getProjectDB', function (Group $pools, Database $dbForConso
|
|||
if (isset($databases[$dsn->getHost()])) {
|
||||
$database = $databases[$dsn->getHost()];
|
||||
|
||||
if ($dsn->getHost() === DATABASE_SHARED_TABLES) {
|
||||
if ($dsn->getHost() === System::getEnv('_APP_DATABASE_SHARED_TABLES', '')) {
|
||||
$database
|
||||
->setSharedTables(true)
|
||||
->setTenant($project->getInternalId())
|
||||
|
@ -150,7 +150,7 @@ Server::setResource('getProjectDB', function (Group $pools, Database $dbForConso
|
|||
|
||||
$databases[$dsn->getHost()] = $database;
|
||||
|
||||
if ($dsn->getHost() === DATABASE_SHARED_TABLES) {
|
||||
if ($dsn->getHost() === System::getEnv('_APP_DATABASE_SHARED_TABLES', '')) {
|
||||
$database
|
||||
->setSharedTables(true)
|
||||
->setTenant($project->getInternalId())
|
||||
|
@ -284,11 +284,6 @@ if (!isset($args[1])) {
|
|||
|
||||
\array_shift($args);
|
||||
$workerName = $args[0];
|
||||
$workerIndex = $args[1] ?? '';
|
||||
|
||||
if (!empty($workerIndex)) {
|
||||
$workerName .= '_' . $workerIndex;
|
||||
}
|
||||
|
||||
if (\str_starts_with($workerName, 'databases')) {
|
||||
$queueName = System::getEnv('_APP_QUEUE_NAME', 'database_db_main');
|
||||
|
|
|
@ -44,13 +44,13 @@
|
|||
"ext-sockets": "*",
|
||||
"appwrite/php-runtimes": "0.13.*",
|
||||
"appwrite/php-clamav": "2.0.*",
|
||||
"utopia-php/abuse": "0.37.*",
|
||||
"utopia-php/abuse": "0.38.*",
|
||||
"utopia-php/analytics": "0.10.*",
|
||||
"utopia-php/audit": "0.39.*",
|
||||
"utopia-php/cache": "0.9.*",
|
||||
"utopia-php/audit": "0.40.*",
|
||||
"utopia-php/cache": "0.10.*",
|
||||
"utopia-php/cli": "0.15.*",
|
||||
"utopia-php/config": "0.2.*",
|
||||
"utopia-php/database": "0.49.*",
|
||||
"utopia-php/database": "0.50.*",
|
||||
"utopia-php/domains": "0.5.*",
|
||||
"utopia-php/dsn": "0.2.1",
|
||||
"utopia-php/framework": "0.33.*",
|
||||
|
@ -58,10 +58,10 @@
|
|||
"utopia-php/image": "0.6.*",
|
||||
"utopia-php/locale": "0.4.*",
|
||||
"utopia-php/logger": "0.5.*",
|
||||
"utopia-php/messaging": "0.11.*",
|
||||
"utopia-php/messaging": "0.12.*",
|
||||
"utopia-php/migration": "0.4.*",
|
||||
"utopia-php/orchestration": "0.9.*",
|
||||
"utopia-php/platform": "0.5.*",
|
||||
"utopia-php/platform": "0.7.*",
|
||||
"utopia-php/pools": "0.5.*",
|
||||
"utopia-php/preloader": "0.2.*",
|
||||
"utopia-php/queue": "0.7.*",
|
||||
|
|
97
composer.lock
generated
97
composer.lock
generated
|
@ -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": "98887b7e78e0d2ce4282751818d13c1b",
|
||||
"content-hash": "b9d369872767c1af0db14ae6d8fea0f5",
|
||||
"packages": [
|
||||
{
|
||||
"name": "adhocore/jwt",
|
||||
|
@ -2273,23 +2273,23 @@
|
|||
},
|
||||
{
|
||||
"name": "utopia-php/abuse",
|
||||
"version": "0.37.1",
|
||||
"version": "0.38.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/utopia-php/abuse.git",
|
||||
"reference": "4dfcff4754c7804d1a70039792c0f2d59a5cc981"
|
||||
"reference": "b7be9086c9d9b4561d810cbd42fdda798742f56c"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/utopia-php/abuse/zipball/4dfcff4754c7804d1a70039792c0f2d59a5cc981",
|
||||
"reference": "4dfcff4754c7804d1a70039792c0f2d59a5cc981",
|
||||
"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": "0.49.*"
|
||||
"utopia-php/database": "0.50.*"
|
||||
},
|
||||
"require-dev": {
|
||||
"laravel/pint": "1.5.*",
|
||||
|
@ -2316,9 +2316,9 @@
|
|||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/utopia-php/abuse/issues",
|
||||
"source": "https://github.com/utopia-php/abuse/tree/0.37.1"
|
||||
"source": "https://github.com/utopia-php/abuse/tree/0.38.0"
|
||||
},
|
||||
"time": "2024-06-05T18:03:59+00:00"
|
||||
"time": "2024-06-24T00:52:02+00:00"
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/analytics",
|
||||
|
@ -2368,21 +2368,21 @@
|
|||
},
|
||||
{
|
||||
"name": "utopia-php/audit",
|
||||
"version": "0.39.1",
|
||||
"version": "0.40.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/utopia-php/audit.git",
|
||||
"reference": "7ea91e0ceea7b94293612fea94022b73315677c2"
|
||||
"reference": "735ae211ce5fee5b52b736731571b4030b1d7cdc"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/utopia-php/audit/zipball/7ea91e0ceea7b94293612fea94022b73315677c2",
|
||||
"reference": "7ea91e0ceea7b94293612fea94022b73315677c2",
|
||||
"url": "https://api.github.com/repos/utopia-php/audit/zipball/735ae211ce5fee5b52b736731571b4030b1d7cdc",
|
||||
"reference": "735ae211ce5fee5b52b736731571b4030b1d7cdc",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=8.0",
|
||||
"utopia-php/database": "0.49.*"
|
||||
"utopia-php/database": "0.50.*"
|
||||
},
|
||||
"require-dev": {
|
||||
"laravel/pint": "1.5.*",
|
||||
|
@ -2409,22 +2409,22 @@
|
|||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/utopia-php/audit/issues",
|
||||
"source": "https://github.com/utopia-php/audit/tree/0.39.1"
|
||||
"source": "https://github.com/utopia-php/audit/tree/0.40.0"
|
||||
},
|
||||
"time": "2024-06-05T19:28:22+00:00"
|
||||
"time": "2024-06-24T00:52:17+00:00"
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/cache",
|
||||
"version": "0.9.1",
|
||||
"version": "0.10.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/utopia-php/cache.git",
|
||||
"reference": "552b4c554bb14d0c529631ce304cdf4a2b9d06a6"
|
||||
"reference": "b22c6eb6d308de246b023efd0fc9758aee8b8247"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/utopia-php/cache/zipball/552b4c554bb14d0c529631ce304cdf4a2b9d06a6",
|
||||
"reference": "552b4c554bb14d0c529631ce304cdf4a2b9d06a6",
|
||||
"url": "https://api.github.com/repos/utopia-php/cache/zipball/b22c6eb6d308de246b023efd0fc9758aee8b8247",
|
||||
"reference": "b22c6eb6d308de246b023efd0fc9758aee8b8247",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -2459,9 +2459,9 @@
|
|||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/utopia-php/cache/issues",
|
||||
"source": "https://github.com/utopia-php/cache/tree/0.9.1"
|
||||
"source": "https://github.com/utopia-php/cache/tree/0.10.2"
|
||||
},
|
||||
"time": "2024-03-19T17:07:20+00:00"
|
||||
"time": "2024-06-25T20:36:35+00:00"
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/cli",
|
||||
|
@ -2565,23 +2565,23 @@
|
|||
},
|
||||
{
|
||||
"name": "utopia-php/database",
|
||||
"version": "0.49.11",
|
||||
"version": "0.50.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/utopia-php/database.git",
|
||||
"reference": "4f4b35d99ecdee971c3042279bb1ac8264825030"
|
||||
"reference": "ce3eaccb2f3bbd34b2b97419836fec633b26b8f7"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/utopia-php/database/zipball/4f4b35d99ecdee971c3042279bb1ac8264825030",
|
||||
"reference": "4f4b35d99ecdee971c3042279bb1ac8264825030",
|
||||
"url": "https://api.github.com/repos/utopia-php/database/zipball/ce3eaccb2f3bbd34b2b97419836fec633b26b8f7",
|
||||
"reference": "ce3eaccb2f3bbd34b2b97419836fec633b26b8f7",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-mbstring": "*",
|
||||
"ext-pdo": "*",
|
||||
"php": ">=8.0",
|
||||
"utopia-php/cache": "0.9.*",
|
||||
"utopia-php/cache": "0.10.*",
|
||||
"utopia-php/framework": "0.33.*",
|
||||
"utopia-php/mongo": "0.3.*"
|
||||
},
|
||||
|
@ -2615,9 +2615,9 @@
|
|||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/utopia-php/database/issues",
|
||||
"source": "https://github.com/utopia-php/database/tree/0.49.11"
|
||||
"source": "https://github.com/utopia-php/database/tree/0.50.0"
|
||||
},
|
||||
"time": "2024-05-30T12:40:27+00:00"
|
||||
"time": "2024-06-21T03:21:42+00:00"
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/domains",
|
||||
|
@ -2965,16 +2965,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": {
|
||||
|
@ -3010,9 +3010,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",
|
||||
|
@ -3173,16 +3173,16 @@
|
|||
},
|
||||
{
|
||||
"name": "utopia-php/platform",
|
||||
"version": "0.5.2",
|
||||
"version": "0.7.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/utopia-php/platform.git",
|
||||
"reference": "b9feabc79b92dc2b05683a986ad43bce5c1583e3"
|
||||
"reference": "beeea0f2c9bce14a6869fc5c87a1047cdecb5c52"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/utopia-php/platform/zipball/b9feabc79b92dc2b05683a986ad43bce5c1583e3",
|
||||
"reference": "b9feabc79b92dc2b05683a986ad43bce5c1583e3",
|
||||
"url": "https://api.github.com/repos/utopia-php/platform/zipball/beeea0f2c9bce14a6869fc5c87a1047cdecb5c52",
|
||||
"reference": "beeea0f2c9bce14a6869fc5c87a1047cdecb5c52",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -3190,7 +3190,8 @@
|
|||
"ext-redis": "*",
|
||||
"php": ">=8.0",
|
||||
"utopia-php/cli": "0.15.*",
|
||||
"utopia-php/framework": "0.33.*"
|
||||
"utopia-php/framework": "0.33.*",
|
||||
"utopia-php/queue": "0.7.*"
|
||||
},
|
||||
"require-dev": {
|
||||
"laravel/pint": "1.2.*",
|
||||
|
@ -3216,9 +3217,9 @@
|
|||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/utopia-php/platform/issues",
|
||||
"source": "https://github.com/utopia-php/platform/tree/0.5.2"
|
||||
"source": "https://github.com/utopia-php/platform/tree/0.7.0"
|
||||
},
|
||||
"time": "2024-05-22T12:50:35+00:00"
|
||||
"time": "2024-05-08T17:00:55+00:00"
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/pools",
|
||||
|
@ -3601,22 +3602,22 @@
|
|||
},
|
||||
{
|
||||
"name": "utopia-php/vcs",
|
||||
"version": "0.6.6",
|
||||
"version": "0.6.7",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/utopia-php/vcs.git",
|
||||
"reference": "e538264cfee5e3efdfe1771efba04750cf20b2c4"
|
||||
"reference": "8d8ff1ac68e991b95adb6f91fcde8f9bb8f24974"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/utopia-php/vcs/zipball/e538264cfee5e3efdfe1771efba04750cf20b2c4",
|
||||
"reference": "e538264cfee5e3efdfe1771efba04750cf20b2c4",
|
||||
"url": "https://api.github.com/repos/utopia-php/vcs/zipball/8d8ff1ac68e991b95adb6f91fcde8f9bb8f24974",
|
||||
"reference": "8d8ff1ac68e991b95adb6f91fcde8f9bb8f24974",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"adhocore/jwt": "^1.1",
|
||||
"php": ">=8.0",
|
||||
"utopia-php/cache": "^0.9.0",
|
||||
"utopia-php/cache": "^0.10.0",
|
||||
"utopia-php/framework": "0.*.*"
|
||||
},
|
||||
"require-dev": {
|
||||
|
@ -3644,9 +3645,9 @@
|
|||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/utopia-php/vcs/issues",
|
||||
"source": "https://github.com/utopia-php/vcs/tree/0.6.6"
|
||||
"source": "https://github.com/utopia-php/vcs/tree/0.6.7"
|
||||
},
|
||||
"time": "2024-05-17T09:36:30+00:00"
|
||||
"time": "2024-06-05T17:38:29+00:00"
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/websocket",
|
||||
|
|
|
@ -101,7 +101,7 @@ services:
|
|||
- _APP_CONSOLE_HOSTNAMES
|
||||
- _APP_SYSTEM_EMAIL_NAME
|
||||
- _APP_SYSTEM_EMAIL_ADDRESS
|
||||
- _APP_SYSTEM_SECURITY_EMAIL_ADDRESS
|
||||
- _APP_EMAIL_SECURITY
|
||||
- _APP_SYSTEM_RESPONSE_FORMAT
|
||||
- _APP_OPTIONS_ABUSE
|
||||
- _APP_OPTIONS_ROUTER_PROTECTION
|
||||
|
@ -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
|
||||
|
@ -286,7 +289,7 @@ services:
|
|||
- _APP_ENV
|
||||
- _APP_WORKER_PER_CORE
|
||||
- _APP_OPENSSL_KEY_V1
|
||||
- _APP_SYSTEM_SECURITY_EMAIL_ADDRESS
|
||||
- _APP_EMAIL_SECURITY
|
||||
- _APP_DB_HOST
|
||||
- _APP_DB_PORT
|
||||
- _APP_DB_SCHEMA
|
||||
|
@ -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
|
||||
|
@ -475,7 +482,7 @@ services:
|
|||
- _APP_DOMAIN
|
||||
- _APP_DOMAIN_TARGET
|
||||
- _APP_DOMAIN_FUNCTIONS
|
||||
- _APP_SYSTEM_SECURITY_EMAIL_ADDRESS
|
||||
- _APP_EMAIL_CERTIFICATES
|
||||
- _APP_REDIS_HOST
|
||||
- _APP_REDIS_PORT
|
||||
- _APP_REDIS_USER
|
||||
|
@ -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
|
||||
|
@ -635,7 +646,7 @@ services:
|
|||
- _APP_OPENSSL_KEY_V1
|
||||
- _APP_DOMAIN
|
||||
- _APP_DOMAIN_TARGET
|
||||
- _APP_SYSTEM_SECURITY_EMAIL_ADDRESS
|
||||
- _APP_EMAIL_SECURITY
|
||||
- _APP_REDIS_HOST
|
||||
- _APP_REDIS_PORT
|
||||
- _APP_REDIS_USER
|
||||
|
@ -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
|
||||
|
@ -686,6 +698,7 @@ services:
|
|||
- _APP_MAINTENANCE_RETENTION_USAGE_HOURLY
|
||||
- _APP_MAINTENANCE_RETENTION_SCHEDULES
|
||||
- _APP_MAINTENANCE_DELAY
|
||||
- _APP_DATABASE_SHARED_TABLES
|
||||
|
||||
appwrite-worker-usage:
|
||||
entrypoint: worker-usage
|
||||
|
@ -717,6 +730,7 @@ services:
|
|||
- _APP_LOGGING_PROVIDER
|
||||
- _APP_LOGGING_CONFIG
|
||||
- _APP_USAGE_AGGREGATION_INTERVAL
|
||||
- _APP_DATABASE_SHARED_TABLES
|
||||
|
||||
appwrite-worker-usage-dump:
|
||||
entrypoint: worker-usage-dump
|
||||
|
@ -748,6 +762,7 @@ services:
|
|||
- _APP_LOGGING_PROVIDER
|
||||
- _APP_LOGGING_CONFIG
|
||||
- _APP_USAGE_AGGREGATION_INTERVAL
|
||||
- _APP_DATABASE_SHARED_TABLES
|
||||
|
||||
appwrite-task-scheduler-functions:
|
||||
entrypoint: schedule-functions
|
||||
|
@ -775,6 +790,7 @@ services:
|
|||
- _APP_DB_SCHEMA
|
||||
- _APP_DB_USER
|
||||
- _APP_DB_PASS
|
||||
- _APP_DATABASE_SHARED_TABLES
|
||||
|
||||
appwrite-task-scheduler-messages:
|
||||
entrypoint: schedule-messages
|
||||
|
@ -802,6 +818,7 @@ services:
|
|||
- _APP_DB_SCHEMA
|
||||
- _APP_DB_USER
|
||||
- _APP_DB_PASS
|
||||
- _APP_DATABASE_SHARED_TABLES
|
||||
|
||||
appwrite-assistant:
|
||||
container_name: appwrite-assistant
|
||||
|
@ -900,20 +917,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
|
||||
|
@ -931,14 +935,6 @@ services:
|
|||
volumes:
|
||||
- appwrite-redis:/data:rw
|
||||
|
||||
# clamav:
|
||||
# image: appwrite/clamav:1.2.0
|
||||
# container_name: appwrite-clamav
|
||||
# networks:
|
||||
# - appwrite
|
||||
# volumes:
|
||||
# - appwrite-uploads:/storage/uploads
|
||||
|
||||
# Dev Tools Start ------------------------------------------------------------------------------------------
|
||||
#
|
||||
# The Appwrite Team uses the following tools to help debug, monitor and diagnose the Appwrite stack
|
||||
|
|
|
@ -1 +1 @@
|
|||
Verify an authenticator app after adding it using the [add authenticator](/docs/references/cloud/client-web/account#createMfaAuthenticator) method. add
|
||||
Verify an authenticator app after adding it using the [add authenticator](/docs/references/cloud/client-web/account#createMfaAuthenticator) method.
|
|
@ -239,7 +239,7 @@ abstract class Migration
|
|||
}
|
||||
|
||||
/**
|
||||
* Creates colletion from the config collection.
|
||||
* Creates collection from the config collection.
|
||||
*
|
||||
* @param string $id
|
||||
* @param string|null $name
|
||||
|
@ -266,6 +266,7 @@ abstract class Migration
|
|||
'type' => $attribute['type'],
|
||||
'size' => $attribute['size'],
|
||||
'required' => $attribute['required'],
|
||||
'default' => $attribute['default'] ?? null,
|
||||
'signed' => $attribute['signed'],
|
||||
'array' => $attribute['array'],
|
||||
'filters' => $attribute['filters'],
|
||||
|
|
|
@ -336,6 +336,32 @@ class V20 extends Migration
|
|||
Console::warning("Purge cache from {$id}: {$th->getMessage()}");
|
||||
}
|
||||
|
||||
break;
|
||||
case 'topics':
|
||||
try {
|
||||
$this->projectDB->updateAttributeDefault($id, 'emailTotal', 0);
|
||||
} catch (Throwable $th) {
|
||||
Console::warning("'topics' from {$id}: {$th->getMessage()}");
|
||||
}
|
||||
|
||||
try {
|
||||
$this->projectDB->updateAttributeDefault($id, 'pushTotal', 0);
|
||||
} catch (Throwable $th) {
|
||||
Console::warning("'topics' from {$id}: {$th->getMessage()}");
|
||||
}
|
||||
|
||||
try {
|
||||
$this->projectDB->updateAttributeDefault($id, 'smsTotal', 0);
|
||||
} catch (Throwable $th) {
|
||||
Console::warning("'topics' from {$id}: {$th->getMessage()}");
|
||||
}
|
||||
|
||||
try {
|
||||
$this->projectDB->purgeCachedCollection($id);
|
||||
} catch (Throwable $th) {
|
||||
Console::warning("Purge cache from {$id}: {$th->getMessage()}");
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
|
@ -2,15 +2,13 @@
|
|||
|
||||
namespace Appwrite\Platform;
|
||||
|
||||
use Appwrite\Platform\Services\Tasks;
|
||||
use Appwrite\Platform\Services\Workers;
|
||||
use Appwrite\Platform\Modules\Core;
|
||||
use Utopia\Platform\Platform;
|
||||
|
||||
class Appwrite extends Platform
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
$this->addService('tasks', new Tasks());
|
||||
$this->addService('workers', new Workers());
|
||||
parent::__construct(new Core());
|
||||
}
|
||||
}
|
||||
|
|
17
src/Appwrite/Platform/Modules/Core.php
Normal file
17
src/Appwrite/Platform/Modules/Core.php
Normal file
|
@ -0,0 +1,17 @@
|
|||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Modules;
|
||||
|
||||
use Appwrite\Platform\Services\Tasks;
|
||||
use Appwrite\Platform\Services\Workers;
|
||||
use Utopia\Platform\Module;
|
||||
|
||||
class Core extends Module
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
$this->addService('tasks', new Tasks());
|
||||
$this->addService('workers', new Workers());
|
||||
}
|
||||
|
||||
}
|
|
@ -22,7 +22,7 @@ class Tasks extends Service
|
|||
{
|
||||
public function __construct()
|
||||
{
|
||||
$this->type = self::TYPE_CLI;
|
||||
$this->type = Service::TYPE_TASK;
|
||||
$this
|
||||
->addAction(Doctor::getName(), new Doctor())
|
||||
->addAction(Install::getName(), new Install())
|
||||
|
|
|
@ -20,7 +20,7 @@ class Workers extends Service
|
|||
{
|
||||
public function __construct()
|
||||
{
|
||||
$this->type = self::TYPE_WORKER;
|
||||
$this->type = Service::TYPE_WORKER;
|
||||
$this
|
||||
->addAction(Audits::getName(), new Audits())
|
||||
->addAction(Builds::getName(), new Builds())
|
||||
|
|
|
@ -93,6 +93,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))
|
||||
|
|
|
@ -135,9 +135,9 @@ class Certificates extends Action
|
|||
|
||||
try {
|
||||
// Email for alerts is required by LetsEncrypt
|
||||
$email = System::getEnv('_APP_SYSTEM_SECURITY_EMAIL_ADDRESS');
|
||||
$email = System::getEnv('_APP_EMAIL_CERTIFICATES', System::getEnv('_APP_SYSTEM_SECURITY_EMAIL_ADDRESS'));
|
||||
if (empty($email)) {
|
||||
throw new Exception('You must set a valid security email address (_APP_SYSTEM_SECURITY_EMAIL_ADDRESS) to issue an SSL certificate.');
|
||||
throw new Exception('You must set a valid security email address (_APP_EMAIL_CERTIFICATES) to issue an SSL certificate.');
|
||||
}
|
||||
|
||||
// Validate domain and DNS records. Skip if job is forced
|
||||
|
@ -439,42 +439,26 @@ class Certificates extends Action
|
|||
|
||||
$locale = new Locale(System::getEnv('_APP_LOCALE', 'en'));
|
||||
|
||||
// Send mail to administratore mail
|
||||
// Send mail to administrator mail
|
||||
$template = Template::fromFile(__DIR__ . '/../../../../app/config/locale/templates/email-certificate-failed.tpl');
|
||||
$template->setParam('{{domain}}', $domain);
|
||||
$template->setParam('{{error}}', \nl2br($errorMessage));
|
||||
$template->setParam('{{attempts}}', $attempt);
|
||||
|
||||
// TODO: Use setbodyTemplate once #7307 is merged
|
||||
$subject = 'Certificate failed to generate';
|
||||
$body = Template::fromFile(__DIR__ . '/../../../../app/config/locale/templates/email-base-styled.tpl');
|
||||
|
||||
$subject = \sprintf($locale->getText("emails.certificate.subject"), $domain);
|
||||
|
||||
$message = Template::fromFile(__DIR__ . '/../../../../app/config/locale/templates/email-inner-base.tpl');
|
||||
$message
|
||||
->setParam('{{body}}', $locale->getText("emails.certificate.body"), escapeHtml: false)
|
||||
->setParam('{{hello}}', $locale->getText("emails.certificate.hello"))
|
||||
->setParam('{{footer}}', $locale->getText("emails.certificate.footer"))
|
||||
->setParam('{{thanks}}', $locale->getText("emails.certificate.thanks"))
|
||||
->setParam('{{signature}}', $locale->getText("emails.certificate.signature"));
|
||||
$body = $message->render();
|
||||
$body = $template->render();
|
||||
|
||||
$emailVariables = [
|
||||
'direction' => $locale->getText('settings.direction'),
|
||||
'domain' => $domain,
|
||||
'error' => '<br><pre>' . $errorMessage . '</pre>',
|
||||
'attempt' => $attempt,
|
||||
'project' => 'Console',
|
||||
'redirect' => 'https://' . $domain,
|
||||
];
|
||||
|
||||
$subject = \sprintf($locale->getText("emails.certificate.subject"), $domain);
|
||||
|
||||
$queueForMails
|
||||
->setSubject($subject)
|
||||
->setBody($body)
|
||||
->setName('Appwrite Administrator')
|
||||
->setbodyTemplate(__DIR__ . '/../../../../app/config/locale/templates/email-base-styled.tpl')
|
||||
->setVariables($emailVariables)
|
||||
->setRecipient(System::getEnv('_APP_SYSTEM_SECURITY_EMAIL_ADDRESS'))
|
||||
->setRecipient(System::getEnv('_APP_EMAIL_CERTIFICATES', System::getEnv('_APP_SYSTEM_SECURITY_EMAIL_ADDRESS')))
|
||||
->trigger();
|
||||
}
|
||||
|
||||
|
|
|
@ -498,14 +498,14 @@ class Deletes extends Action
|
|||
$collections = $dbForProject->listCollections($limit);
|
||||
|
||||
foreach ($collections as $collection) {
|
||||
if ($dsn->getHost() !== DATABASE_SHARED_TABLES || !\in_array($collection->getId(), $projectCollectionIds)) {
|
||||
if ($dsn->getHost() !== System::getEnv('_APP_DATABASE_SHARED_TABLES', '') || !\in_array($collection->getId(), $projectCollectionIds)) {
|
||||
$dbForProject->deleteCollection($collection->getId());
|
||||
} else {
|
||||
$this->deleteByGroup($collection->getId(), [], database: $dbForProject);
|
||||
}
|
||||
}
|
||||
|
||||
if ($dsn->getHost() === DATABASE_SHARED_TABLES) {
|
||||
if ($dsn->getHost() === System::getEnv('_APP_DATABASE_SHARED_TABLES', '')) {
|
||||
$collectionsIds = \array_map(fn ($collection) => $collection->getId(), $collections);
|
||||
|
||||
if (empty(\array_diff($collectionsIds, $projectCollectionIds))) {
|
||||
|
@ -554,7 +554,7 @@ class Deletes extends Action
|
|||
], $dbForConsole);
|
||||
|
||||
// Delete metadata table
|
||||
if ($dsn->getHost() !== DATABASE_SHARED_TABLES) {
|
||||
if ($dsn->getHost() !== System::getEnv('_APP_DATABASE_SHARED_TABLES', '')) {
|
||||
$dbForProject->deleteCollection('_metadata');
|
||||
} else {
|
||||
$this->deleteByGroup('_metadata', [], $dbForProject);
|
||||
|
|
|
@ -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']),
|
||||
|
|
|
@ -104,7 +104,7 @@ class Webhooks extends Action
|
|||
\curl_setopt($ch, CURLOPT_USERAGENT, \sprintf(
|
||||
APP_USERAGENT,
|
||||
System::getEnv('_APP_VERSION', 'UNKNOWN'),
|
||||
System::getEnv('_APP_SYSTEM_SECURITY_EMAIL_ADDRESS', APP_EMAIL_SECURITY)
|
||||
System::getEnv('_APP_EMAIL_SECURITY', System::getEnv('_APP_SYSTEM_SECURITY_EMAIL_ADDRESS', APP_EMAIL_SECURITY))
|
||||
));
|
||||
\curl_setopt(
|
||||
$ch,
|
||||
|
|
|
@ -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']);
|
||||
|
|
|
@ -150,7 +150,7 @@ class AccountCustomClientTest extends Scope
|
|||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertNotEmpty($response['body']);
|
||||
$this->assertNotEmpty($response['body']['$id']);
|
||||
$this->assertEquals(true, (new DatetimeValidator())->isValid($response['body']['registration']));
|
||||
$this->assertTrue((new DatetimeValidator())->isValid($response['body']['registration']));
|
||||
$this->assertEquals($response['body']['email'], $email);
|
||||
$this->assertEquals($response['body']['name'], $name);
|
||||
$this->assertArrayHasKey('accessedAt', $response['body']);
|
||||
|
@ -294,7 +294,7 @@ class AccountCustomClientTest extends Scope
|
|||
$this->assertIsNumeric($response['body']['total']);
|
||||
$this->assertEquals("user.create", $response['body']['logs'][2]['event']);
|
||||
$this->assertEquals(filter_var($response['body']['logs'][2]['ip'], FILTER_VALIDATE_IP), $response['body']['logs'][2]['ip']);
|
||||
$this->assertEquals(true, (new DatetimeValidator())->isValid($response['body']['logs'][2]['time']));
|
||||
$this->assertTrue((new DatetimeValidator())->isValid($response['body']['logs'][2]['time']));
|
||||
|
||||
$this->assertEquals('Windows', $response['body']['logs'][1]['osName']);
|
||||
$this->assertEquals('WIN', $response['body']['logs'][1]['osCode']);
|
||||
|
@ -327,7 +327,6 @@ class AccountCustomClientTest extends Scope
|
|||
$this->assertEquals('desktop', $response['body']['logs'][2]['deviceName']);
|
||||
$this->assertEquals('', $response['body']['logs'][2]['deviceBrand']);
|
||||
$this->assertEquals('', $response['body']['logs'][2]['deviceModel']);
|
||||
$this->assertEquals($response['body']['logs'][2]['ip'], filter_var($response['body']['logs'][2]['ip'], FILTER_VALIDATE_IP));
|
||||
|
||||
$this->assertEquals('--', $response['body']['logs'][2]['countryCode']);
|
||||
$this->assertEquals('Unknown', $response['body']['logs'][2]['countryName']);
|
||||
|
@ -343,7 +342,7 @@ class AccountCustomClientTest extends Scope
|
|||
]
|
||||
]);
|
||||
|
||||
$this->assertEquals($responseLimit['headers']['status-code'], 200);
|
||||
$this->assertEquals(200, $responseLimit['headers']['status-code']);
|
||||
$this->assertIsArray($responseLimit['body']['logs']);
|
||||
$this->assertNotEmpty($responseLimit['body']['logs']);
|
||||
$this->assertCount(1, $responseLimit['body']['logs']);
|
||||
|
@ -382,7 +381,7 @@ class AccountCustomClientTest extends Scope
|
|||
]
|
||||
]);
|
||||
|
||||
$this->assertEquals($responseLimitOffset['headers']['status-code'], 200);
|
||||
$this->assertEquals(200, $responseLimitOffset['headers']['status-code']);
|
||||
$this->assertIsArray($responseLimitOffset['body']['logs']);
|
||||
$this->assertNotEmpty($responseLimitOffset['body']['logs']);
|
||||
$this->assertCount(1, $responseLimitOffset['body']['logs']);
|
||||
|
@ -430,7 +429,7 @@ class AccountCustomClientTest extends Scope
|
|||
$this->assertIsArray($response['body']);
|
||||
$this->assertNotEmpty($response['body']);
|
||||
$this->assertNotEmpty($response['body']['$id']);
|
||||
$this->assertEquals(true, (new DatetimeValidator())->isValid($response['body']['registration']));
|
||||
$this->assertTrue((new DatetimeValidator())->isValid($response['body']['registration']));
|
||||
$this->assertEquals($response['body']['email'], $email);
|
||||
$this->assertEquals($response['body']['name'], $newName);
|
||||
|
||||
|
@ -497,7 +496,7 @@ class AccountCustomClientTest extends Scope
|
|||
$this->assertIsArray($response['body']);
|
||||
$this->assertNotEmpty($response['body']);
|
||||
$this->assertNotEmpty($response['body']['$id']);
|
||||
$this->assertEquals(true, (new DatetimeValidator())->isValid($response['body']['registration']));
|
||||
$this->assertTrue((new DatetimeValidator())->isValid($response['body']['registration']));
|
||||
$this->assertEquals($response['body']['email'], $email);
|
||||
|
||||
$response = $this->client->call(Client::METHOD_POST, '/account/sessions/email', array_merge([
|
||||
|
@ -587,7 +586,7 @@ class AccountCustomClientTest extends Scope
|
|||
$this->assertIsArray($response['body']);
|
||||
$this->assertNotEmpty($response['body']);
|
||||
$this->assertNotEmpty($response['body']['$id']);
|
||||
$this->assertEquals(true, (new DatetimeValidator())->isValid($response['body']['registration']));
|
||||
$this->assertTrue((new DatetimeValidator())->isValid($response['body']['registration']));
|
||||
$this->assertEquals($response['body']['email'], $newEmail);
|
||||
|
||||
/**
|
||||
|
@ -629,7 +628,7 @@ class AccountCustomClientTest extends Scope
|
|||
$this->assertEquals(201, $response['headers']['status-code']);
|
||||
$this->assertNotEmpty($response['body']);
|
||||
$this->assertNotEmpty($response['body']['$id']);
|
||||
$this->assertEquals(true, (new DatetimeValidator())->isValid($response['body']['registration']));
|
||||
$this->assertTrue((new DatetimeValidator())->isValid($response['body']['registration']));
|
||||
$this->assertEquals($response['body']['email'], $data['email']);
|
||||
$this->assertEquals($response['body']['name'], $data['name']);
|
||||
|
||||
|
@ -771,7 +770,7 @@ class AccountCustomClientTest extends Scope
|
|||
$this->assertEquals(201, $response['headers']['status-code']);
|
||||
$this->assertNotEmpty($response['body']['$id']);
|
||||
$this->assertEmpty($response['body']['secret']);
|
||||
$this->assertEquals(true, (new DatetimeValidator())->isValid($response['body']['expire']));
|
||||
$this->assertTrue((new DatetimeValidator())->isValid($response['body']['expire']));
|
||||
|
||||
$lastEmail = $this->getLastEmail();
|
||||
|
||||
|
@ -1073,7 +1072,7 @@ class AccountCustomClientTest extends Scope
|
|||
$this->assertEquals(201, $response['headers']['status-code']);
|
||||
$this->assertNotEmpty($response['body']['$id']);
|
||||
$this->assertEmpty($response['body']['secret']);
|
||||
$this->assertEquals(true, (new DatetimeValidator())->isValid($response['body']['expire']));
|
||||
$this->assertTrue((new DatetimeValidator())->isValid($response['body']['expire']));
|
||||
|
||||
$lastEmail = $this->getLastEmail();
|
||||
|
||||
|
@ -1668,7 +1667,7 @@ class AccountCustomClientTest extends Scope
|
|||
$this->assertIsArray($response['body']);
|
||||
$this->assertNotEmpty($response['body']);
|
||||
$this->assertNotEmpty($response['body']['$id']);
|
||||
$this->assertEquals(true, (new DatetimeValidator())->isValid($response['body']['registration']));
|
||||
$this->assertTrue((new DatetimeValidator())->isValid($response['body']['registration']));
|
||||
$this->assertEquals($response['body']['email'], $email);
|
||||
|
||||
$response = $this->client->call(Client::METHOD_POST, '/account/sessions/email', array_merge([
|
||||
|
@ -1756,13 +1755,11 @@ class AccountCustomClientTest extends Scope
|
|||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertEquals($response['body']['$id'], $userId);
|
||||
$this->assertEquals($response['body']['name'], 'User Name');
|
||||
$this->assertEquals($response['body']['email'], 'useroauth@localhost.test');
|
||||
$this->assertEquals('User Name', $response['body']['name']);
|
||||
$this->assertEquals('useroauth@localhost.test', $response['body']['email']);
|
||||
|
||||
// Since we only support one oauth user, let's also check updateSession here
|
||||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
|
||||
$response = $this->client->call(Client::METHOD_GET, '/account/sessions/current', array_merge([
|
||||
'origin' => 'http://localhost',
|
||||
'content-type' => 'application/json',
|
||||
|
@ -1808,7 +1805,7 @@ class AccountCustomClientTest extends Scope
|
|||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertEmpty($response['body']['secret']);
|
||||
$this->assertEquals($response['body']['provider'], 'anonymous');
|
||||
$this->assertEquals('anonymous', $response['body']['provider']);
|
||||
|
||||
$sessionID = $response['body']['$id'];
|
||||
|
||||
|
@ -1821,7 +1818,7 @@ class AccountCustomClientTest extends Scope
|
|||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertEmpty($response['body']['secret']);
|
||||
$this->assertEquals($response['body']['provider'], 'anonymous');
|
||||
$this->assertEquals('anonymous', $response['body']['provider']);
|
||||
|
||||
$response = $this->client->call(Client::METHOD_GET, '/account/sessions/97823askjdkasd80921371980', array_merge([
|
||||
'origin' => 'http://localhost',
|
||||
|
@ -1934,7 +1931,7 @@ class AccountCustomClientTest extends Scope
|
|||
$this->assertEquals(201, $response['headers']['status-code']);
|
||||
$this->assertNotEmpty($response['body']['$id']);
|
||||
$this->assertEmpty($response['body']['secret']);
|
||||
$this->assertEquals(true, (new DatetimeValidator())->isValid($response['body']['expire']));
|
||||
$this->assertTrue((new DatetimeValidator())->isValid($response['body']['expire']));
|
||||
|
||||
$userId = $response['body']['userId'];
|
||||
|
||||
|
@ -2085,7 +2082,7 @@ class AccountCustomClientTest extends Scope
|
|||
$this->assertIsArray($response['body']);
|
||||
$this->assertNotEmpty($response['body']);
|
||||
$this->assertNotEmpty($response['body']['$id']);
|
||||
$this->assertEquals(true, (new DatetimeValidator())->isValid($response['body']['registration']));
|
||||
$this->assertTrue((new DatetimeValidator())->isValid($response['body']['registration']));
|
||||
$this->assertEquals($response['body']['email'], $email);
|
||||
|
||||
$response = $this->client->call(Client::METHOD_POST, '/account/sessions/email', array_merge([
|
||||
|
@ -2127,7 +2124,7 @@ class AccountCustomClientTest extends Scope
|
|||
$this->assertIsArray($response['body']);
|
||||
$this->assertNotEmpty($response['body']);
|
||||
$this->assertNotEmpty($response['body']['$id']);
|
||||
$this->assertEquals(true, (new DatetimeValidator())->isValid($response['body']['registration']));
|
||||
$this->assertTrue((new DatetimeValidator())->isValid($response['body']['registration']));
|
||||
$this->assertEquals($response['body']['phone'], $newPhone);
|
||||
|
||||
/**
|
||||
|
@ -2240,7 +2237,7 @@ class AccountCustomClientTest extends Scope
|
|||
$this->assertEquals(201, $response['headers']['status-code']);
|
||||
$this->assertNotEmpty($response['body']['$id']);
|
||||
$this->assertEmpty($response['body']['secret']);
|
||||
$this->assertEquals(true, (new DatetimeValidator())->isValid($response['body']['expire']));
|
||||
$this->assertTrue((new DatetimeValidator())->isValid($response['body']['expire']));
|
||||
|
||||
$smsRequest = $this->getLastRequest();
|
||||
|
||||
|
@ -2327,7 +2324,7 @@ class AccountCustomClientTest extends Scope
|
|||
$this->assertNotEmpty($response['body']['$id']);
|
||||
$this->assertEmpty($response['body']['secret']);
|
||||
$this->assertEmpty($response['body']['phrase']);
|
||||
$this->assertEquals(true, (new DatetimeValidator())->isValid($response['body']['expire']));
|
||||
$this->assertTrue((new DatetimeValidator())->isValid($response['body']['expire']));
|
||||
|
||||
$userId = $response['body']['userId'];
|
||||
|
||||
|
@ -2452,7 +2449,7 @@ class AccountCustomClientTest extends Scope
|
|||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertNotEmpty($response['body']);
|
||||
$this->assertNotEmpty($response['body']['$id']);
|
||||
$this->assertEquals(true, (new DatetimeValidator())->isValid($response['body']['registration']));
|
||||
$this->assertTrue((new DatetimeValidator())->isValid($response['body']['registration']));
|
||||
$this->assertEquals($response['body']['email'], $email);
|
||||
$this->assertTrue($response['body']['emailVerification']);
|
||||
|
||||
|
@ -2512,7 +2509,7 @@ class AccountCustomClientTest extends Scope
|
|||
$this->assertIsArray($response['body']);
|
||||
$this->assertNotEmpty($response['body']);
|
||||
$this->assertNotEmpty($response['body']['$id']);
|
||||
$this->assertEquals(true, (new DatetimeValidator())->isValid($response['body']['registration']));
|
||||
$this->assertTrue((new DatetimeValidator())->isValid($response['body']['registration']));
|
||||
$this->assertEquals($response['body']['email'], $email);
|
||||
|
||||
$response = $this->client->call(Client::METHOD_POST, '/account/sessions/email', array_merge([
|
||||
|
|
|
@ -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',
|
||||
|
@ -716,4 +718,94 @@ class FunctionsCustomClientTest extends Scope
|
|||
|
||||
return [];
|
||||
}
|
||||
|
||||
public function testNonOverrideOfHeaders(): array
|
||||
{
|
||||
/**
|
||||
* Test for SUCCESS
|
||||
*/
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$apikey = $this->getProject()['apiKey'];
|
||||
|
||||
$function = $this->client->call(Client::METHOD_POST, '/functions', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
'x-appwrite-key' => $apikey,
|
||||
], [
|
||||
'functionId' => ID::unique(),
|
||||
'name' => 'Test',
|
||||
'execute' => [Role::any()->toString()],
|
||||
'runtime' => 'node-18.0',
|
||||
'entrypoint' => 'index.js'
|
||||
]);
|
||||
|
||||
$functionId = $function['body']['$id'] ?? '';
|
||||
|
||||
$this->assertEquals(201, $function['headers']['status-code']);
|
||||
|
||||
$folder = 'node';
|
||||
$code = realpath(__DIR__ . '/../../../resources/functions') . "/$folder/code.tar.gz";
|
||||
$this->packageCode($folder);
|
||||
|
||||
$deployment = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/deployments', [
|
||||
'content-type' => 'multipart/form-data',
|
||||
'x-appwrite-project' => $projectId,
|
||||
'x-appwrite-key' => $apikey,
|
||||
], [
|
||||
'entrypoint' => 'index.js',
|
||||
'code' => new CURLFile($code, 'application/x-gzip', \basename($code)), //different tarball names intentional
|
||||
'activate' => true
|
||||
]);
|
||||
|
||||
$deploymentId = $deployment['body']['$id'] ?? '';
|
||||
|
||||
$this->assertEquals(202, $deployment['headers']['status-code']);
|
||||
|
||||
// Poll until deployment is built
|
||||
while (true) {
|
||||
$deployment = $this->client->call(Client::METHOD_GET, '/functions/' . $function['body']['$id'] . '/deployments/' . $deploymentId, [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey'],
|
||||
]);
|
||||
|
||||
if (
|
||||
$deployment['headers']['status-code'] >= 400
|
||||
|| \in_array($deployment['body']['status'], ['ready', 'failed'])
|
||||
) {
|
||||
break;
|
||||
}
|
||||
|
||||
\sleep(1);
|
||||
}
|
||||
|
||||
$this->assertEquals('ready', $deployment['body']['status']);
|
||||
|
||||
$execution = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/executions', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'x-appwrite-event' => "OVERRIDDEN",
|
||||
'x-appwrite-trigger' => "OVERRIDDEN",
|
||||
'x-appwrite-user-id' => "OVERRIDDEN",
|
||||
'x-appwrite-user-jwt' => "OVERRIDDEN",
|
||||
]);
|
||||
|
||||
$output = json_decode($execution['body']['responseBody'], true);
|
||||
$this->assertNotEquals('OVERRIDDEN', $output['APPWRITE_FUNCTION_JWT']);
|
||||
$this->assertNotEquals('OVERRIDDEN', $output['APPWRITE_FUNCTION_EVENT']);
|
||||
$this->assertNotEquals('OVERRIDDEN', $output['APPWRITE_FUNCTION_TRIGGER']);
|
||||
$this->assertNotEquals('OVERRIDDEN', $output['APPWRITE_FUNCTION_USER_ID']);
|
||||
|
||||
// Cleanup : Delete function
|
||||
$response = $this->client->call(Client::METHOD_DELETE, '/functions/' . $functionId, [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey'],
|
||||
], []);
|
||||
|
||||
$this->assertEquals(204, $response['headers']['status-code']);
|
||||
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -988,7 +988,7 @@ class FunctionsCustomServerTest extends Scope
|
|||
$this->assertEquals($executions['body']['executions'][0]['logs'], '');
|
||||
$this->assertStringContainsString('timed out', $executions['body']['executions'][0]['errors']);
|
||||
|
||||
sleep(70); //wait for scheduled execution to be created and time out
|
||||
sleep(75); // Wait for scheduled execution to be created and time out
|
||||
|
||||
$executions = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/executions', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
|
@ -999,12 +999,6 @@ class FunctionsCustomServerTest extends Scope
|
|||
$this->assertCount(2, $executions['body']['executions']);
|
||||
$this->assertIsArray($executions['body']['executions']);
|
||||
$this->assertEquals($executions['body']['executions'][1]['trigger'], 'schedule');
|
||||
$this->assertEquals($executions['body']['executions'][1]['status'], 'failed');
|
||||
$this->assertEquals($executions['body']['executions'][1]['responseStatusCode'], 500);
|
||||
$this->assertLessThan(20, $executions['body']['executions'][1]['duration']);
|
||||
$this->assertEquals($executions['body']['executions'][1]['responseBody'], '');
|
||||
$this->assertEquals($executions['body']['executions'][1]['logs'], '');
|
||||
$this->assertStringContainsString('timed out', $executions['body']['executions'][1]['errors']);
|
||||
|
||||
// Cleanup : Delete function
|
||||
$response = $this->client->call(Client::METHOD_DELETE, '/functions/' . $functionId, [
|
||||
|
|
|
@ -455,7 +455,7 @@ class HealthCustomServerTest extends Scope
|
|||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertEquals('/CN=www.google.com', $response['body']['name']);
|
||||
$this->assertEquals('www.google.com', $response['body']['subjectSN']);
|
||||
$this->assertEquals('Google Trust Services LLC', $response['body']['issuerOrganisation']);
|
||||
$this->assertStringContainsString('Google Trust Services', $response['body']['issuerOrganisation']);
|
||||
$this->assertIsInt($response['body']['validFrom']);
|
||||
$this->assertIsInt($response['body']['validTo']);
|
||||
|
||||
|
|
|
@ -511,6 +511,55 @@ trait MessagingBase
|
|||
];
|
||||
}
|
||||
|
||||
public function testSubscriberTargetSubQuery()
|
||||
{
|
||||
$response = $this->client->call(Client::METHOD_POST, '/messaging/topics', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey'],
|
||||
], [
|
||||
'topicId' => 'sub-query-test',
|
||||
'name' => 'sub-query-test',
|
||||
]);
|
||||
|
||||
$this->assertEquals(201, $response['headers']['status-code']);
|
||||
|
||||
$topic = $response['body'];
|
||||
|
||||
$prefix = uniqid();
|
||||
|
||||
for ($i = 1; $i <= 101; $i++) {
|
||||
$response = $this->client->call(Client::METHOD_POST, '/users', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey'],
|
||||
], [
|
||||
'userId' => "$prefix-$i",
|
||||
'email' => "$prefix-$i@example.com",
|
||||
'password' => 'password',
|
||||
'name' => "User $prefix $i",
|
||||
]);
|
||||
|
||||
$this->assertEquals(201, $response['headers']['status-code']);
|
||||
$user = $response['body'];
|
||||
$targets = $user['targets'] ?? [];
|
||||
|
||||
$this->assertGreaterThan(0, count($targets));
|
||||
|
||||
$target = $targets[0];
|
||||
|
||||
$response = $this->client->call(Client::METHOD_POST, '/messaging/topics/' . $topic['$id'] . '/subscribers', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'subscriberId' => $user['$id'],
|
||||
'targetId' => $target['$id'],
|
||||
]);
|
||||
|
||||
$this->assertEquals(201, $response['headers']['status-code']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testCreateSubscriber
|
||||
*/
|
||||
|
|
|
@ -9,7 +9,6 @@ use Tests\E2E\General\UsageTest;
|
|||
use Tests\E2E\Scopes\ProjectConsole;
|
||||
use Tests\E2E\Scopes\Scope;
|
||||
use Tests\E2E\Scopes\SideClient;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\DateTime;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Helpers\ID;
|
||||
|
@ -3494,504 +3493,4 @@ class ProjectsConsoleClientTest extends Scope
|
|||
|
||||
return $data;
|
||||
}
|
||||
|
||||
public function testTenantIsolation(): void
|
||||
{
|
||||
// Create a team and a project
|
||||
$team = $this->client->call(Client::METHOD_POST, '/teams', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'teamId' => ID::unique(),
|
||||
'name' => 'Amazing Team',
|
||||
]);
|
||||
|
||||
$teamId = $team['body']['$id'];
|
||||
|
||||
// Project-level isolation
|
||||
$project1 = $this->client->call(Client::METHOD_POST, '/projects', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-shared-tables' => false
|
||||
], $this->getHeaders()), [
|
||||
'projectId' => ID::unique(),
|
||||
'name' => 'Amazing Project',
|
||||
'teamId' => $teamId,
|
||||
'region' => 'default'
|
||||
]);
|
||||
|
||||
// Application level isolation (shared tables)
|
||||
$project2 = $this->client->call(Client::METHOD_POST, '/projects', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-shared-tables' => true
|
||||
], $this->getHeaders()), [
|
||||
'projectId' => ID::unique(),
|
||||
'name' => 'Amazing Project',
|
||||
'teamId' => $teamId,
|
||||
'region' => 'default'
|
||||
]);
|
||||
|
||||
// Project-level isolation
|
||||
$project3 = $this->client->call(Client::METHOD_POST, '/projects', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-shared-tables' => false
|
||||
], $this->getHeaders()), [
|
||||
'projectId' => ID::unique(),
|
||||
'name' => 'Amazing Project',
|
||||
'teamId' => $teamId,
|
||||
'region' => 'default'
|
||||
]);
|
||||
|
||||
// Application level isolation (shared tables)
|
||||
$project4 = $this->client->call(Client::METHOD_POST, '/projects', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-shared-tables' => true
|
||||
], $this->getHeaders()), [
|
||||
'projectId' => ID::unique(),
|
||||
'name' => 'Amazing Project',
|
||||
'teamId' => $teamId,
|
||||
'region' => 'default'
|
||||
]);
|
||||
|
||||
// Create and API key in each project
|
||||
$key1 = $this->client->call(Client::METHOD_POST, '/projects/' . $project1['body']['$id'] . '/keys', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'name' => 'Key Test',
|
||||
'scopes' => ['databases.read', 'databases.write', 'collections.read', 'collections.write', 'attributes.read', 'attributes.write', 'indexes.read', 'indexes.write', 'documents.read', 'documents.write', 'users.read', 'users.write'],
|
||||
]);
|
||||
|
||||
$key2 = $this->client->call(Client::METHOD_POST, '/projects/' . $project2['body']['$id'] . '/keys', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'name' => 'Key Test',
|
||||
'scopes' => ['databases.read', 'databases.write', 'collections.read', 'collections.write', 'attributes.read', 'attributes.write', 'indexes.read', 'indexes.write', 'documents.read', 'documents.write', 'users.read', 'users.write'],
|
||||
]);
|
||||
|
||||
$key3 = $this->client->call(Client::METHOD_POST, '/projects/' . $project3['body']['$id'] . '/keys', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'name' => 'Key Test',
|
||||
'scopes' => ['databases.read', 'databases.write', 'collections.read', 'collections.write', 'attributes.read', 'attributes.write', 'indexes.read', 'indexes.write', 'documents.read', 'documents.write', 'users.read', 'users.write'],
|
||||
]);
|
||||
|
||||
$key4 = $this->client->call(Client::METHOD_POST, '/projects/' . $project4['body']['$id'] . '/keys', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'name' => 'Key Test',
|
||||
'scopes' => ['databases.read', 'databases.write', 'collections.read', 'collections.write', 'attributes.read', 'attributes.write', 'indexes.read', 'indexes.write', 'documents.read', 'documents.write', 'users.read', 'users.write'],
|
||||
]);
|
||||
|
||||
// Create a database in each project
|
||||
$database1 = $this->client->call(Client::METHOD_POST, '/databases', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $project1['body']['$id'],
|
||||
'x-appwrite-key' => $key1['body']['secret']
|
||||
], [
|
||||
'databaseId' => ID::unique(),
|
||||
'name' => 'Amazing Database',
|
||||
]);
|
||||
|
||||
$database2 = $this->client->call(Client::METHOD_POST, '/databases', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $project2['body']['$id'],
|
||||
'x-appwrite-key' => $key2['body']['secret']
|
||||
], [
|
||||
'databaseId' => ID::unique(),
|
||||
'name' => 'Amazing Database',
|
||||
]);
|
||||
|
||||
$database3 = $this->client->call(Client::METHOD_POST, '/databases', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $project3['body']['$id'],
|
||||
'x-appwrite-key' => $key3['body']['secret']
|
||||
], [
|
||||
'databaseId' => ID::unique(),
|
||||
'name' => 'Amazing Database',
|
||||
]);
|
||||
|
||||
$database4 = $this->client->call(Client::METHOD_POST, '/databases', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $project4['body']['$id'],
|
||||
'x-appwrite-key' => $key4['body']['secret']
|
||||
], [
|
||||
'databaseId' => ID::unique(),
|
||||
'name' => 'Amazing Database',
|
||||
]);
|
||||
|
||||
// Create a collection in each project
|
||||
$collection1 = $this->client->call(Client::METHOD_POST, '/databases/' . $database1['body']['$id'] . '/collections', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $project1['body']['$id'],
|
||||
'x-appwrite-key' => $key1['body']['secret']
|
||||
], [
|
||||
'databaseId' => $database1['body']['$id'],
|
||||
'collectionId' => ID::unique(),
|
||||
'name' => 'Amazing Collection',
|
||||
]);
|
||||
|
||||
$collection2 = $this->client->call(Client::METHOD_POST, '/databases/' . $database2['body']['$id'] . '/collections', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $project2['body']['$id'],
|
||||
'x-appwrite-key' => $key2['body']['secret']
|
||||
], [
|
||||
'databaseId' => $database2['body']['$id'],
|
||||
'collectionId' => ID::unique(),
|
||||
'name' => 'Amazing Collection',
|
||||
]);
|
||||
|
||||
$collection3 = $this->client->call(Client::METHOD_POST, '/databases/' . $database3['body']['$id'] . '/collections', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $project3['body']['$id'],
|
||||
'x-appwrite-key' => $key3['body']['secret']
|
||||
], [
|
||||
'databaseId' => $database3['body']['$id'],
|
||||
'collectionId' => ID::unique(),
|
||||
'name' => 'Amazing Collection',
|
||||
]);
|
||||
|
||||
$collection4 = $this->client->call(Client::METHOD_POST, '/databases/' . $database4['body']['$id'] . '/collections', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $project4['body']['$id'],
|
||||
'x-appwrite-key' => $key4['body']['secret']
|
||||
], [
|
||||
'databaseId' => $database4['body']['$id'],
|
||||
'collectionId' => ID::unique(),
|
||||
'name' => 'Amazing Collection',
|
||||
]);
|
||||
|
||||
// Create an attribute in each project
|
||||
$attribute1 = $this->client->call(Client::METHOD_POST, '/databases/' . $database1['body']['$id'] . '/collections/' . $collection1['body']['$id'] . '/attributes/string', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $project1['body']['$id'],
|
||||
'x-appwrite-key' => $key1['body']['secret']
|
||||
], [
|
||||
'databaseId' => $database1['body']['$id'],
|
||||
'collectionId' => $collection1['body']['$id'],
|
||||
'key' => ID::unique(),
|
||||
'size' => 255,
|
||||
'required' => true
|
||||
]);
|
||||
|
||||
$attribute2 = $this->client->call(Client::METHOD_POST, '/databases/' . $database2['body']['$id'] . '/collections/' . $collection2['body']['$id'] . '/attributes/string', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $project2['body']['$id'],
|
||||
'x-appwrite-key' => $key2['body']['secret']
|
||||
], [
|
||||
'databaseId' => $database2['body']['$id'],
|
||||
'collectionId' => $collection2['body']['$id'],
|
||||
'key' => ID::unique(),
|
||||
'size' => 255,
|
||||
'required' => true
|
||||
]);
|
||||
|
||||
$attribute3 = $this->client->call(Client::METHOD_POST, '/databases/' . $database3['body']['$id'] . '/collections/' . $collection3['body']['$id'] . '/attributes/string', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $project3['body']['$id'],
|
||||
'x-appwrite-key' => $key3['body']['secret']
|
||||
], [
|
||||
'databaseId' => $database3['body']['$id'],
|
||||
'collectionId' => $collection3['body']['$id'],
|
||||
'key' => ID::unique(),
|
||||
'size' => 255,
|
||||
'required' => true
|
||||
]);
|
||||
|
||||
$attribute4 = $this->client->call(Client::METHOD_POST, '/databases/' . $database4['body']['$id'] . '/collections/' . $collection4['body']['$id'] . '/attributes/string', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $project4['body']['$id'],
|
||||
'x-appwrite-key' => $key4['body']['secret']
|
||||
], [
|
||||
'databaseId' => $database4['body']['$id'],
|
||||
'collectionId' => $collection4['body']['$id'],
|
||||
'key' => ID::unique(),
|
||||
'size' => 255,
|
||||
'required' => true
|
||||
]);
|
||||
|
||||
// Wait for attributes
|
||||
\sleep(2);
|
||||
|
||||
// Create an index in each project
|
||||
$index1 = $this->client->call(Client::METHOD_POST, '/databases/' . $database1['body']['$id'] . '/collections/' . $collection1['body']['$id'] . '/indexes', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $project1['body']['$id'],
|
||||
'x-appwrite-key' => $key1['body']['secret']
|
||||
], [
|
||||
'databaseId' => $database1['body']['$id'],
|
||||
'collectionId' => $collection1['body']['$id'],
|
||||
'key' => ID::unique(),
|
||||
'type' => Database::INDEX_KEY,
|
||||
'attributes' => [$attribute1['body']['key']],
|
||||
]);
|
||||
|
||||
$index2 = $this->client->call(Client::METHOD_POST, '/databases/' . $database2['body']['$id'] . '/collections/' . $collection2['body']['$id'] . '/indexes', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $project2['body']['$id'],
|
||||
'x-appwrite-key' => $key2['body']['secret']
|
||||
], [
|
||||
'databaseId' => $database2['body']['$id'],
|
||||
'collectionId' => $collection2['body']['$id'],
|
||||
'key' => ID::unique(),
|
||||
'type' => Database::INDEX_KEY,
|
||||
'attributes' => [$attribute2['body']['key']],
|
||||
]);
|
||||
|
||||
$index3 = $this->client->call(Client::METHOD_POST, '/databases/' . $database3['body']['$id'] . '/collections/' . $collection3['body']['$id'] . '/indexes', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $project3['body']['$id'],
|
||||
'x-appwrite-key' => $key3['body']['secret']
|
||||
], [
|
||||
'databaseId' => $database3['body']['$id'],
|
||||
'collectionId' => $collection3['body']['$id'],
|
||||
'key' => ID::unique(),
|
||||
'type' => Database::INDEX_KEY,
|
||||
'attributes' => [$attribute3['body']['key']],
|
||||
]);
|
||||
|
||||
$index4 = $this->client->call(Client::METHOD_POST, '/databases/' . $database4['body']['$id'] . '/collections/' . $collection4['body']['$id'] . '/indexes', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $project4['body']['$id'],
|
||||
'x-appwrite-key' => $key4['body']['secret']
|
||||
], [
|
||||
'databaseId' => $database4['body']['$id'],
|
||||
'collectionId' => $collection4['body']['$id'],
|
||||
'key' => ID::unique(),
|
||||
'type' => Database::INDEX_KEY,
|
||||
'attributes' => [$attribute4['body']['key']],
|
||||
]);
|
||||
|
||||
// Wait for indexes
|
||||
\sleep(2);
|
||||
|
||||
// Assert that each project has only 1 database, 1 collection, 1 attribute and 1 index
|
||||
$databasesProject1 = $this->client->call(Client::METHOD_GET, '/databases', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $project1['body']['$id'],
|
||||
'x-appwrite-key' => $key1['body']['secret']
|
||||
]);
|
||||
|
||||
$this->assertEquals(1, $databasesProject1['body']['total']);
|
||||
$this->assertEquals(1, \count($databasesProject1['body']['databases']));
|
||||
|
||||
$databasesProject2 = $this->client->call(Client::METHOD_GET, '/databases', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $project2['body']['$id'],
|
||||
'x-appwrite-key' => $key2['body']['secret']
|
||||
]);
|
||||
|
||||
$this->assertEquals(1, $databasesProject2['body']['total']);
|
||||
$this->assertEquals(1, \count($databasesProject2['body']['databases']));
|
||||
|
||||
$databasesProject3 = $this->client->call(Client::METHOD_GET, '/databases', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $project3['body']['$id'],
|
||||
'x-appwrite-key' => $key3['body']['secret']
|
||||
]);
|
||||
|
||||
$this->assertEquals(1, $databasesProject3['body']['total']);
|
||||
$this->assertEquals(1, \count($databasesProject3['body']['databases']));
|
||||
|
||||
$databasesProject4 = $this->client->call(Client::METHOD_GET, '/databases', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $project4['body']['$id'],
|
||||
'x-appwrite-key' => $key4['body']['secret']
|
||||
]);
|
||||
|
||||
$this->assertEquals(1, $databasesProject4['body']['total']);
|
||||
$this->assertEquals(1, \count($databasesProject4['body']['databases']));
|
||||
|
||||
$collectionsProject1 = $this->client->call(Client::METHOD_GET, '/databases/' . $database1['body']['$id'] . '/collections', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $project1['body']['$id'],
|
||||
'x-appwrite-key' => $key1['body']['secret']
|
||||
]);
|
||||
|
||||
$this->assertEquals(1, $collectionsProject1['body']['total']);
|
||||
$this->assertEquals(1, \count($collectionsProject1['body']['collections']));
|
||||
|
||||
$collectionsProject2 = $this->client->call(Client::METHOD_GET, '/databases/' . $database2['body']['$id'] . '/collections', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $project2['body']['$id'],
|
||||
'x-appwrite-key' => $key2['body']['secret']
|
||||
]);
|
||||
|
||||
$this->assertEquals(1, $collectionsProject2['body']['total']);
|
||||
$this->assertEquals(1, \count($collectionsProject2['body']['collections']));
|
||||
|
||||
$collectionsProject3 = $this->client->call(Client::METHOD_GET, '/databases/' . $database3['body']['$id'] . '/collections', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $project3['body']['$id'],
|
||||
'x-appwrite-key' => $key3['body']['secret']
|
||||
]);
|
||||
|
||||
$this->assertEquals(1, $collectionsProject3['body']['total']);
|
||||
$this->assertEquals(1, \count($collectionsProject3['body']['collections']));
|
||||
|
||||
$collectionsProject4 = $this->client->call(Client::METHOD_GET, '/databases/' . $database4['body']['$id'] . '/collections', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $project4['body']['$id'],
|
||||
'x-appwrite-key' => $key4['body']['secret']
|
||||
]);
|
||||
|
||||
$this->assertEquals(1, $collectionsProject4['body']['total']);
|
||||
$this->assertEquals(1, \count($collectionsProject4['body']['collections']));
|
||||
|
||||
$attributesProject1 = $this->client->call(Client::METHOD_GET, '/databases/' . $database1['body']['$id'] . '/collections/' . $collection1['body']['$id'] . '/attributes', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $project1['body']['$id'],
|
||||
'x-appwrite-key' => $key1['body']['secret']
|
||||
]);
|
||||
|
||||
$this->assertEquals(1, $attributesProject1['body']['total']);
|
||||
$this->assertEquals(1, \count($attributesProject1['body']['attributes']));
|
||||
$this->assertEquals('available', $attributesProject1['body']['attributes'][0]['status']);
|
||||
|
||||
$attributesProject2 = $this->client->call(Client::METHOD_GET, '/databases/' . $database2['body']['$id'] . '/collections/' . $collection2['body']['$id'] . '/attributes', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $project2['body']['$id'],
|
||||
'x-appwrite-key' => $key2['body']['secret']
|
||||
]);
|
||||
|
||||
$this->assertEquals(1, $attributesProject2['body']['total']);
|
||||
$this->assertEquals(1, \count($attributesProject2['body']['attributes']));
|
||||
$this->assertEquals('available', $attributesProject2['body']['attributes'][0]['status']);
|
||||
|
||||
$attributesProject3 = $this->client->call(Client::METHOD_GET, '/databases/' . $database3['body']['$id'] . '/collections/' . $collection3['body']['$id'] . '/attributes', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $project3['body']['$id'],
|
||||
'x-appwrite-key' => $key3['body']['secret']
|
||||
]);
|
||||
|
||||
$this->assertEquals(1, $attributesProject3['body']['total']);
|
||||
$this->assertEquals(1, \count($attributesProject3['body']['attributes']));
|
||||
$this->assertEquals('available', $attributesProject3['body']['attributes'][0]['status']);
|
||||
|
||||
$attributesProject4 = $this->client->call(Client::METHOD_GET, '/databases/' . $database4['body']['$id'] . '/collections/' . $collection4['body']['$id'] . '/attributes', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $project4['body']['$id'],
|
||||
'x-appwrite-key' => $key4['body']['secret']
|
||||
]);
|
||||
|
||||
$this->assertEquals(1, $attributesProject4['body']['total']);
|
||||
$this->assertEquals(1, \count($attributesProject4['body']['attributes']));
|
||||
$this->assertEquals('available', $attributesProject4['body']['attributes'][0]['status']);
|
||||
|
||||
$indexesProject1 = $this->client->call(Client::METHOD_GET, '/databases/' . $database1['body']['$id'] . '/collections/' . $collection1['body']['$id'] . '/indexes', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $project1['body']['$id'],
|
||||
'x-appwrite-key' => $key1['body']['secret']
|
||||
]);
|
||||
|
||||
$this->assertEquals(1, $indexesProject1['body']['total']);
|
||||
$this->assertEquals(1, \count($indexesProject1['body']['indexes']));
|
||||
|
||||
$indexesProject2 = $this->client->call(Client::METHOD_GET, '/databases/' . $database2['body']['$id'] . '/collections/' . $collection2['body']['$id'] . '/indexes', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $project2['body']['$id'],
|
||||
'x-appwrite-key' => $key2['body']['secret']
|
||||
]);
|
||||
|
||||
$this->assertEquals(1, $indexesProject2['body']['total']);
|
||||
$this->assertEquals(1, \count($indexesProject2['body']['indexes']));
|
||||
|
||||
$indexesProject3 = $this->client->call(Client::METHOD_GET, '/databases/' . $database3['body']['$id'] . '/collections/' . $collection3['body']['$id'] . '/indexes', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $project3['body']['$id'],
|
||||
'x-appwrite-key' => $key3['body']['secret']
|
||||
]);
|
||||
|
||||
$this->assertEquals(1, $indexesProject3['body']['total']);
|
||||
$this->assertEquals(1, \count($indexesProject3['body']['indexes']));
|
||||
|
||||
$indexesProject4 = $this->client->call(Client::METHOD_GET, '/databases/' . $database4['body']['$id'] . '/collections/' . $collection4['body']['$id'] . '/indexes', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $project4['body']['$id'],
|
||||
'x-appwrite-key' => $key4['body']['secret']
|
||||
]);
|
||||
|
||||
$this->assertEquals(1, $indexesProject4['body']['total']);
|
||||
$this->assertEquals(1, \count($indexesProject4['body']['indexes']));
|
||||
|
||||
// Attempt to read cross-type resources
|
||||
$collectionProject2WithProject1Key = $this->client->call(Client::METHOD_GET, '/databases/' . $database2['body']['$id'] . '/collections/' . $collection2['body']['$id'], [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $project1['body']['$id'],
|
||||
'x-appwrite-key' => $key1['body']['secret']
|
||||
]);
|
||||
|
||||
$this->assertEquals(404, $collectionProject2WithProject1Key['headers']['status-code']);
|
||||
|
||||
$collectionProject1WithProject2Key = $this->client->call(Client::METHOD_GET, '/databases/' . $database1['body']['$id'] . '/collections/' . $collection1['body']['$id'], [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $project2['body']['$id'],
|
||||
'x-appwrite-key' => $key2['body']['secret']
|
||||
]);
|
||||
|
||||
$this->assertEquals(404, $collectionProject1WithProject2Key['headers']['status-code']);
|
||||
|
||||
// Attempt to read cross-tenant resources
|
||||
$collectionProject3WithProject1Key = $this->client->call(Client::METHOD_GET, '/databases/' . $database3['body']['$id'] . '/collections/' . $collection3['body']['$id'], [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $project1['body']['$id'],
|
||||
'x-appwrite-key' => $key1['body']['secret']
|
||||
]);
|
||||
|
||||
$this->assertEquals(404, $collectionProject3WithProject1Key['headers']['status-code']);
|
||||
|
||||
$collectionProject1WithProject3Key = $this->client->call(Client::METHOD_GET, '/databases/' . $database1['body']['$id'] . '/collections/' . $collection1['body']['$id'], [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $project3['body']['$id'],
|
||||
'x-appwrite-key' => $key3['body']['secret']
|
||||
]);
|
||||
|
||||
$this->assertEquals(404, $collectionProject1WithProject3Key['headers']['status-code']);
|
||||
|
||||
// Assert that shared project resources can have the same ID as they're unique on tenant + ID not just ID
|
||||
$collection5 = $this->client->call(Client::METHOD_POST, '/databases/' . $database2['body']['$id'] . '/collections', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $project2['body']['$id'],
|
||||
'x-appwrite-key' => $key2['body']['secret']
|
||||
], [
|
||||
'databaseId' => $database2['body']['$id'],
|
||||
'collectionId' => $collection4['body']['$id'],
|
||||
'name' => 'Amazing Collection',
|
||||
]);
|
||||
|
||||
$this->assertEquals(201, $collection5['headers']['status-code']);
|
||||
|
||||
// Assert that users across projects on shared tables can have the same email as they're unique on tenant + email not just email
|
||||
$user1 = $this->client->call(Client::METHOD_POST, '/users', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $project2['body']['$id'],
|
||||
'x-appwrite-key' => $key2['body']['secret']
|
||||
], [
|
||||
'userId' => 'user',
|
||||
'email' => 'test@appwrite.io',
|
||||
'password' => 'password',
|
||||
'name' => 'Test User',
|
||||
]);
|
||||
|
||||
$this->assertEquals(201, $user1['headers']['status-code']);
|
||||
|
||||
$user2 = $this->client->call(Client::METHOD_POST, '/users', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $project4['body']['$id'],
|
||||
'x-appwrite-key' => $key4['body']['secret']
|
||||
], [
|
||||
'userId' => 'user',
|
||||
'email' => 'test@appwrite.io',
|
||||
'password' => 'password',
|
||||
'name' => 'Test User',
|
||||
]);
|
||||
|
||||
$this->assertEquals(201, $user2['headers']['status-code']);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -225,7 +225,7 @@ trait StorageBase
|
|||
'bucketId' => ID::unique(),
|
||||
'name' => 'Test Bucket 2',
|
||||
'fileSecurity' => true,
|
||||
'maximumFileSize' => 200000000, //200MB
|
||||
'maximumFileSize' => 6000000000, //6GB
|
||||
'allowedFileExtensions' => ["jpg", "png"],
|
||||
'permissions' => [
|
||||
Permission::read(Role::any()),
|
||||
|
|
|
@ -290,6 +290,28 @@ trait UsersBase
|
|||
$this->assertArrayNotHasKey('secret', $token['body']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testCreateUser
|
||||
*/
|
||||
public function testCreateSession(array $data): void
|
||||
{
|
||||
/**
|
||||
* Test for SUCCESS
|
||||
*/
|
||||
$response = $this->client->call(Client::METHOD_POST, '/users/' . $data['userId'] . '/sessions', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()));
|
||||
|
||||
$this->assertEquals(201, $response['headers']['status-code']);
|
||||
|
||||
$session = $response['body'];
|
||||
$this->assertEquals($data['userId'], $session['userId']);
|
||||
$this->assertNotEmpty($session['secret']);
|
||||
$this->assertNotEmpty($session['expire']);
|
||||
$this->assertEquals('server', $session['provider']);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Tests all optional parameters of createUser (email, phone, anonymous..)
|
||||
|
@ -986,7 +1008,7 @@ trait UsersBase
|
|||
'password' => 'password'
|
||||
]);
|
||||
|
||||
$this->assertEquals($session['headers']['status-code'], 401);
|
||||
$this->assertEquals(401, $session['headers']['status-code']);
|
||||
|
||||
$user = $this->client->call(Client::METHOD_PATCH, '/users/' . $data['userId'] . '/password', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
|
|
Loading…
Reference in a new issue