mirror of
https://github.com/appwrite/appwrite
synced 2026-05-23 17:08:45 +00:00
Merge branch '1.3.x' into doc-auth-1.3
This commit is contained in:
commit
5354e5ee44
81 changed files with 14388 additions and 513 deletions
|
|
@ -1,6 +1,13 @@
|
|||
# Version TBD
|
||||
- Fix expire to formatTz in create account session [#4985](https://github.com/appwrite/appwrite/pull/4985)
|
||||
|
||||
## Features
|
||||
- Password dictionary setting allows to compare user's password against command password database [4906](https://github.com/appwrite/appwrite/pull/4906)
|
||||
- Password history setting allows to save user's last used password so that it may not be used again. Maximum number of history saved is 20, which can be configured. Minimum is 0 which means disabled. [#4866](https://github.com/appwrite/appwrite/pull/4866)
|
||||
- Update APIs to check X-Appwrite-Timestamp header [#5024](https://github.com/appwrite/appwrite/pull/5024)
|
||||
|
||||
## Bugs
|
||||
- Fix not storing function's response on response codes 5xx [#4610](https://github.com/appwrite/appwrite/pull/4610)
|
||||
- Fix expire to formatTz in create account session [#4985](https://github.com/appwrite/appwrite/pull/4985)
|
||||
|
||||
# Version 1.2.1
|
||||
## Changes
|
||||
|
|
|
|||
|
|
@ -436,6 +436,14 @@ composer lint
|
|||
composer lint <your file path>
|
||||
```
|
||||
|
||||
## Clearing the Cache
|
||||
|
||||
If you need to clear the cache, you can do so by running the following command:
|
||||
|
||||
```bash
|
||||
docker compose exec redis redis-cli FLUSHALL
|
||||
```
|
||||
|
||||
## Tutorials
|
||||
|
||||
From time to time, our team will add tutorials that will help contributors find their way in the Appwrite source code. Below is a list of currently available tutorials:
|
||||
|
|
|
|||
10000
app/assets/security/10k-common-passwords
Normal file
10000
app/assets/security/10k-common-passwords
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -1234,6 +1234,17 @@ $collections = [
|
|||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('passwordHistory'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 16384,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => true,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('password'),
|
||||
'type' => Database::VAR_STRING,
|
||||
|
|
@ -1854,6 +1865,17 @@ $collections = [
|
|||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('prefs'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 65535,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => new \stdClass(),
|
||||
'array' => false,
|
||||
'filters' => ['json'],
|
||||
],
|
||||
],
|
||||
'indexes' => [
|
||||
[
|
||||
|
|
|
|||
|
|
@ -408,6 +408,11 @@ return [
|
|||
'description' => 'Document with the requested ID already exists.',
|
||||
'code' => 409,
|
||||
],
|
||||
Exception::DOCUMENT_UPDATE_CONFLICT => [
|
||||
'name' => Exception::DOCUMENT_UPDATE_CONFLICT,
|
||||
'description' => 'Remote document is newer than local.',
|
||||
'code' => 409,
|
||||
],
|
||||
|
||||
/** Attributes */
|
||||
Exception::ATTRIBUTE_NOT_FOUND => [
|
||||
|
|
|
|||
|
|
@ -183,13 +183,16 @@ return [
|
|||
],
|
||||
],
|
||||
'create' => [
|
||||
'$description' => 'This event triggers when a bucket is created.'
|
||||
'$description' => 'This event triggers when a team is created.'
|
||||
],
|
||||
'delete' => [
|
||||
'$description' => 'This event triggers when a bucket is deleted.',
|
||||
'$description' => 'This event triggers when a team is deleted.',
|
||||
],
|
||||
'update' => [
|
||||
'$description' => 'This event triggers when a bucket is updated.',
|
||||
'$description' => 'This event triggers when a team is updated.',
|
||||
'prefs' => [
|
||||
'$description' => 'This event triggers when a team\'s preferences are updated.',
|
||||
],
|
||||
]
|
||||
],
|
||||
'functions' => [
|
||||
|
|
|
|||
|
|
@ -185,7 +185,7 @@ return [
|
|||
[
|
||||
'key' => 'web',
|
||||
'name' => 'Console',
|
||||
'version' => '0.0.1',
|
||||
'version' => '0.0.2-preview-0.0',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-console',
|
||||
'package' => '',
|
||||
'enabled' => true,
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -40,6 +40,8 @@ use Utopia\Validator\ArrayList;
|
|||
use Utopia\Validator\Assoc;
|
||||
use Utopia\Validator\Text;
|
||||
use Utopia\Validator\WhiteList;
|
||||
use Appwrite\Auth\Validator\PasswordHistory;
|
||||
use Appwrite\Auth\Validator\PasswordDictionary;
|
||||
|
||||
$oauthDefaultSuccess = '/auth/oauth2/success';
|
||||
$oauthDefaultFailure = '/auth/oauth2/failure';
|
||||
|
|
@ -60,11 +62,11 @@ App::post('/v1/account')
|
|||
->label('sdk.description', '/docs/references/account/create.md')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_CREATED)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_ACCOUNT)
|
||||
->label('sdk.response.model', Response::MODEL_USER)
|
||||
->label('abuse-limit', 10)
|
||||
->param('userId', '', new CustomId(), 'Unique 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('email', '', new Email(), 'User email.')
|
||||
->param('password', '', new Password(), 'User password. Must be at least 8 chars.')
|
||||
->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('name', '', new Text(128), 'User name. Max length: 128 chars.', true)
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
|
|
@ -72,7 +74,6 @@ App::post('/v1/account')
|
|||
->inject('dbForProject')
|
||||
->inject('events')
|
||||
->action(function (string $userId, string $email, string $password, string $name, Request $request, Response $response, Document $project, Database $dbForProject, Event $events) {
|
||||
|
||||
$email = \strtolower($email);
|
||||
if ('console' === $project->getId()) {
|
||||
$whitelistEmails = $project->getAttribute('authWhitelistEmails');
|
||||
|
|
@ -97,6 +98,8 @@ App::post('/v1/account')
|
|||
}
|
||||
}
|
||||
|
||||
$passwordHistory = $project->getAttribute('auths', [])['passwordHistory'] ?? 0;
|
||||
$password = Auth::passwordHash($password, Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS);
|
||||
try {
|
||||
$userId = $userId == 'unique()' ? ID::unique() : $userId;
|
||||
$user = Authorization::skip(fn() => $dbForProject->createDocument('users', new Document([
|
||||
|
|
@ -109,10 +112,11 @@ App::post('/v1/account')
|
|||
'email' => $email,
|
||||
'emailVerification' => false,
|
||||
'status' => true,
|
||||
'password' => Auth::passwordHash($password, Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS),
|
||||
'password' => $password,
|
||||
'passwordHistory' => $passwordHistory > 0 ? [$password] : [],
|
||||
'passwordUpdate' => DateTime::now(),
|
||||
'hash' => Auth::DEFAULT_ALGO,
|
||||
'hashOptions' => Auth::DEFAULT_ALGO_OPTIONS,
|
||||
'passwordUpdate' => DateTime::now(),
|
||||
'registration' => DateTime::now(),
|
||||
'reset' => false,
|
||||
'name' => $name,
|
||||
|
|
@ -501,8 +505,11 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect')
|
|||
}
|
||||
}
|
||||
|
||||
$passwordHistory = $project->getAttribute('auths', [])['passwordHistory'] ?? 0;
|
||||
|
||||
try {
|
||||
$userId = ID::unique();
|
||||
$password = Auth::passwordHash(Auth::passwordGenerator(), Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS);
|
||||
$user = Authorization::skip(fn() => $dbForProject->createDocument('users', new Document([
|
||||
'$id' => $userId,
|
||||
'$permissions' => [
|
||||
|
|
@ -513,7 +520,8 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect')
|
|||
'email' => $email,
|
||||
'emailVerification' => true,
|
||||
'status' => true, // Email should already be authenticated by OAuth2 provider
|
||||
'password' => Auth::passwordHash(Auth::passwordGenerator(), Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS),
|
||||
'passwordHistory' => $passwordHistory > 0 ? [$password] : null,
|
||||
'password' => $password,
|
||||
'hash' => Auth::DEFAULT_ALGO,
|
||||
'hashOptions' => Auth::DEFAULT_ALGO_OPTIONS,
|
||||
'passwordUpdate' => null,
|
||||
|
|
@ -1320,7 +1328,7 @@ App::get('/v1/account')
|
|||
->label('sdk.description', '/docs/references/account/get.md')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_ACCOUNT)
|
||||
->label('sdk.response.model', Response::MODEL_USER)
|
||||
->label('sdk.offline.model', '/account')
|
||||
->label('sdk.offline.key', 'current')
|
||||
->inject('response')
|
||||
|
|
@ -1512,19 +1520,22 @@ App::patch('/v1/account/name')
|
|||
->label('sdk.description', '/docs/references/account/update-name.md')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_ACCOUNT)
|
||||
->label('sdk.response.model', Response::MODEL_USER)
|
||||
->label('sdk.offline.model', '/account')
|
||||
->label('sdk.offline.key', 'current')
|
||||
->param('name', '', new Text(128), 'User name. Max length: 128 chars.')
|
||||
->inject('requestTimestamp')
|
||||
->inject('response')
|
||||
->inject('user')
|
||||
->inject('dbForProject')
|
||||
->inject('events')
|
||||
->action(function (string $name, Response $response, Document $user, Database $dbForProject, Event $events) {
|
||||
->action(function (string $name, ?\DateTime $requestTimestamp, Response $response, Document $user, Database $dbForProject, Event $events) {
|
||||
|
||||
$user = $dbForProject->updateDocument('users', $user->getId(), $user
|
||||
$user
|
||||
->setAttribute('name', $name)
|
||||
->setAttribute('search', implode(' ', [$user->getId(), $name, $user->getAttribute('email', ''), $user->getAttribute('phone', '')])));
|
||||
->setAttribute('search', implode(' ', [$user->getId(), $name, $user->getAttribute('email', ''), $user->getAttribute('phone', '')]));
|
||||
|
||||
$user = $dbForProject->withRequestTimestamp($requestTimestamp, fn () => $dbForProject->updateDocument('users', $user->getId(), $user));
|
||||
|
||||
$events->setParam('userId', $user->getId());
|
||||
|
||||
|
|
@ -1549,24 +1560,43 @@ App::patch('/v1/account/password')
|
|||
->label('sdk.response.model', Response::MODEL_ACCOUNT)
|
||||
->label('sdk.offline.model', '/account')
|
||||
->label('sdk.offline.key', 'current')
|
||||
->param('password', '', new Password(), 'New user password. Must be at least 8 chars.')
|
||||
->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')
|
||||
->inject('response')
|
||||
->inject('user')
|
||||
->inject('project')
|
||||
->inject('dbForProject')
|
||||
->inject('events')
|
||||
->action(function (string $password, string $oldPassword, Response $response, Document $user, Database $dbForProject, Event $events) {
|
||||
->action(function (string $password, string $oldPassword, ?\DateTime $requestTimestamp, Response $response, Document $user, Document $project, Database $dbForProject, Event $events) {
|
||||
|
||||
// Check old password only if its an existing user.
|
||||
if (!empty($user->getAttribute('passwordUpdate')) && !Auth::passwordVerify($oldPassword, $user->getAttribute('password'), $user->getAttribute('hash'), $user->getAttribute('hashOptions'))) { // Double check user password
|
||||
throw new Exception(Exception::USER_INVALID_CREDENTIALS);
|
||||
}
|
||||
|
||||
$user = $dbForProject->updateDocument('users', $user->getId(), $user
|
||||
->setAttribute('password', Auth::passwordHash($password, Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS))
|
||||
->setAttribute('hash', Auth::DEFAULT_ALGO)
|
||||
->setAttribute('hashOptions', Auth::DEFAULT_ALGO_OPTIONS)
|
||||
->setAttribute('passwordUpdate', DateTime::now()));
|
||||
$newPassword = Auth::passwordHash($password, Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS);
|
||||
$historyLimit = $project->getAttribute('auths', [])['passwordHistory'] ?? 0;
|
||||
$history = [];
|
||||
if ($historyLimit > 0) {
|
||||
$history = $user->getAttribute('passwordHistory', []);
|
||||
$validator = new PasswordHistory($history, $user->getAttribute('hash'), $user->getAttribute('hashOptions'));
|
||||
if (!$validator->isValid($password)) {
|
||||
throw new Exception(Exception::USER_PASSWORD_RECENTLY_USED, 'The password was recently used', 409);
|
||||
}
|
||||
|
||||
$history[] = $newPassword;
|
||||
array_slice($history, (count($history) - $historyLimit), $historyLimit);
|
||||
}
|
||||
|
||||
$user
|
||||
->setAttribute('password', $newPassword)
|
||||
->setAttribute('passwordHistory', $history)
|
||||
->setAttribute('passwordUpdate', DateTime::now())
|
||||
->setAttribute('hash', Auth::DEFAULT_ALGO)
|
||||
->setAttribute('hashOptions', Auth::DEFAULT_ALGO_OPTIONS);
|
||||
|
||||
$user = $dbForProject->withRequestTimestamp($requestTimestamp, fn () => $dbForProject->updateDocument('users', $user->getId(), $user));
|
||||
|
||||
$events->setParam('userId', $user->getId());
|
||||
|
||||
|
|
@ -1588,16 +1618,17 @@ App::patch('/v1/account/email')
|
|||
->label('sdk.description', '/docs/references/account/update-email.md')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_ACCOUNT)
|
||||
->label('sdk.response.model', Response::MODEL_USER)
|
||||
->label('sdk.offline.model', '/account')
|
||||
->label('sdk.offline.key', 'current')
|
||||
->param('email', '', new Email(), 'User email.')
|
||||
->param('password', '', new Password(), 'User password. Must be at least 8 chars.')
|
||||
->inject('requestTimestamp')
|
||||
->inject('response')
|
||||
->inject('user')
|
||||
->inject('dbForProject')
|
||||
->inject('events')
|
||||
->action(function (string $email, string $password, Response $response, Document $user, Database $dbForProject, Event $events) {
|
||||
->action(function (string $email, string $password, ?\DateTime $requestTimestamp, Response $response, Document $user, Database $dbForProject, Event $events) {
|
||||
$isAnonymousUser = Auth::isAnonymousUser($user); // Check if request is from an anonymous account for converting
|
||||
|
||||
if (
|
||||
|
|
@ -1618,7 +1649,7 @@ App::patch('/v1/account/email')
|
|||
->setAttribute('search', implode(' ', [$user->getId(), $user->getAttribute('name', ''), $email, $user->getAttribute('phone', '')]));
|
||||
|
||||
try {
|
||||
$user = $dbForProject->updateDocument('users', $user->getId(), $user);
|
||||
$user = $dbForProject->withRequestTimestamp($requestTimestamp, fn () => $dbForProject->updateDocument('users', $user->getId(), $user));
|
||||
} catch (Duplicate $th) {
|
||||
throw new Exception(Exception::USER_EMAIL_ALREADY_EXISTS);
|
||||
}
|
||||
|
|
@ -1643,16 +1674,17 @@ App::patch('/v1/account/phone')
|
|||
->label('sdk.description', '/docs/references/account/update-phone.md')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_ACCOUNT)
|
||||
->label('sdk.response.model', Response::MODEL_USER)
|
||||
->label('sdk.offline.model', '/account')
|
||||
->label('sdk.offline.key', 'current')
|
||||
->param('phone', '', new Phone(), 'Phone number. Format this number with a leading \'+\' and a country code, e.g., +16175551212.')
|
||||
->param('password', '', new Password(), 'User password. Must be at least 8 chars.')
|
||||
->inject('requestTimestamp')
|
||||
->inject('response')
|
||||
->inject('user')
|
||||
->inject('dbForProject')
|
||||
->inject('events')
|
||||
->action(function (string $phone, string $password, Response $response, Document $user, Database $dbForProject, Event $events) {
|
||||
->action(function (string $phone, string $password, ?\DateTime $requestTimestamp, Response $response, Document $user, Database $dbForProject, Event $events) {
|
||||
|
||||
$isAnonymousUser = Auth::isAnonymousUser($user); // Check if request is from an anonymous account for converting
|
||||
|
||||
|
|
@ -1669,7 +1701,7 @@ App::patch('/v1/account/phone')
|
|||
->setAttribute('search', implode(' ', [$user->getId(), $user->getAttribute('name', ''), $user->getAttribute('email', ''), $phone]));
|
||||
|
||||
try {
|
||||
$user = $dbForProject->updateDocument('users', $user->getId(), $user);
|
||||
$user = $dbForProject->withRequestTimestamp($requestTimestamp, fn () => $dbForProject->updateDocument('users', $user->getId(), $user));
|
||||
} catch (Duplicate $th) {
|
||||
throw new Exception(Exception::USER_PHONE_ALREADY_EXISTS);
|
||||
}
|
||||
|
|
@ -1694,17 +1726,20 @@ App::patch('/v1/account/prefs')
|
|||
->label('sdk.description', '/docs/references/account/update-prefs.md')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_ACCOUNT)
|
||||
->label('sdk.response.model', Response::MODEL_USER)
|
||||
->label('sdk.offline.model', '/account/prefs')
|
||||
->label('sdk.offline.key', 'current')
|
||||
->param('prefs', [], new Assoc(), 'Prefs key-value JSON object.')
|
||||
->inject('requestTimestamp')
|
||||
->inject('response')
|
||||
->inject('user')
|
||||
->inject('dbForProject')
|
||||
->inject('events')
|
||||
->action(function (array $prefs, Response $response, Document $user, Database $dbForProject, Event $events) {
|
||||
->action(function (array $prefs, ?\DateTime $requestTimestamp, Response $response, Document $user, Database $dbForProject, Event $events) {
|
||||
|
||||
$user = $dbForProject->updateDocument('users', $user->getId(), $user->setAttribute('prefs', $prefs));
|
||||
$user->setAttribute('prefs', $prefs);
|
||||
|
||||
$user = $dbForProject->withRequestTimestamp($requestTimestamp, fn () => $dbForProject->updateDocument('users', $user->getId(), $user));
|
||||
|
||||
$events->setParam('userId', $user->getId());
|
||||
|
||||
|
|
@ -1727,14 +1762,16 @@ App::patch('/v1/account/status')
|
|||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_ACCOUNT)
|
||||
->inject('request')
|
||||
->inject('requestTimestamp')
|
||||
->inject('response')
|
||||
->inject('user')
|
||||
->inject('dbForProject')
|
||||
->inject('events')
|
||||
->action(function (Request $request, Response $response, Document $user, Database $dbForProject, Event $events) {
|
||||
->action(function (?\DateTime $requestTimestamp, Response $response, Document $user, Database $dbForProject, Event $events) {
|
||||
|
||||
$user = $dbForProject->updateDocument('users', $user->getId(), $user->setAttribute('status', false));
|
||||
$user->setAttribute('status', false);
|
||||
|
||||
$user = $dbForProject->withRequestTimestamp($requestTimestamp, fn () => $dbForProject->updateDocument('users', $user->getId(), $user));
|
||||
|
||||
$events
|
||||
->setParam('userId', $user->getId())
|
||||
|
|
@ -1764,6 +1801,7 @@ App::delete('/v1/account/sessions/:sessionId')
|
|||
->label('sdk.response.model', Response::MODEL_NONE)
|
||||
->label('abuse-limit', 100)
|
||||
->param('sessionId', '', new UID(), 'Session ID. Use the string \'current\' to delete the current device session.')
|
||||
->inject('requestTimestamp')
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
->inject('user')
|
||||
|
|
@ -1771,7 +1809,7 @@ App::delete('/v1/account/sessions/:sessionId')
|
|||
->inject('locale')
|
||||
->inject('events')
|
||||
->inject('project')
|
||||
->action(function (?string $sessionId, Request $request, Response $response, Document $user, Database $dbForProject, Locale $locale, Event $events, Document $project) {
|
||||
->action(function (?string $sessionId, ?\DateTime $requestTimestamp, Request $request, Response $response, Document $user, Database $dbForProject, Locale $locale, Event $events, Document $project) {
|
||||
|
||||
$protocol = $request->getProtocol();
|
||||
$authDuration = $project->getAttribute('auths', [])['duration'] ?? Auth::TOKEN_EXPIRATION_LOGIN_LONG;
|
||||
|
|
@ -1783,9 +1821,11 @@ App::delete('/v1/account/sessions/:sessionId')
|
|||
|
||||
foreach ($sessions as $key => $session) {/** @var Document $session */
|
||||
if ($sessionId == $session->getId()) {
|
||||
unset($sessions[$key]);
|
||||
$dbForProject->withRequestTimestamp($requestTimestamp, function () use ($dbForProject, $session) {
|
||||
return $dbForProject->deleteDocument('sessions', $session->getId());
|
||||
});
|
||||
|
||||
$dbForProject->deleteDocument('sessions', $session->getId());
|
||||
unset($sessions[$key]);
|
||||
|
||||
$session->setAttribute('current', false);
|
||||
|
||||
|
|
@ -2146,9 +2186,9 @@ App::put('/v1/account/recovery')
|
|||
|
||||
$profile = $dbForProject->updateDocument('users', $profile->getId(), $profile
|
||||
->setAttribute('password', Auth::passwordHash($password, Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS))
|
||||
->setAttribute('passwordUpdate', DateTime::now())
|
||||
->setAttribute('hash', Auth::DEFAULT_ALGO)
|
||||
->setAttribute('hashOptions', Auth::DEFAULT_ALGO_OPTIONS)
|
||||
->setAttribute('passwordUpdate', DateTime::now())
|
||||
->setAttribute('emailVerification', true));
|
||||
|
||||
$recoveryDocument = $dbForProject->getDocument('tokens', $recovery);
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ use Appwrite\Extend\Exception;
|
|||
use Utopia\Audit\Audit;
|
||||
use Utopia\Database\Helpers\Permission;
|
||||
use Utopia\Database\Helpers\Role;
|
||||
use Utopia\Database\Validator\DatetimeValidator;
|
||||
use Utopia\Database\Validator\Datetime as DatetimeValidator;
|
||||
use Utopia\Database\Helpers\ID;
|
||||
use Utopia\Validator\Boolean;
|
||||
use Utopia\Validator\FloatValidator;
|
||||
|
|
@ -48,6 +48,7 @@ use Appwrite\Utopia\Database\Validator\Queries\Databases;
|
|||
use Appwrite\Utopia\Database\Validator\Queries\Documents;
|
||||
use Utopia\Config\Config;
|
||||
use MaxMind\Db\Reader;
|
||||
use Utopia\Validator\Nullable;
|
||||
|
||||
/**
|
||||
* Create attribute of varying type
|
||||
|
|
@ -147,6 +148,143 @@ function createAttribute(string $databaseId, string $collectionId, Document $att
|
|||
return $attribute;
|
||||
}
|
||||
|
||||
function updateAttribute(
|
||||
string $databaseId,
|
||||
string $collectionId,
|
||||
string $key,
|
||||
Database $dbForProject,
|
||||
Event $events,
|
||||
string $type,
|
||||
string $filter = null,
|
||||
string|bool|int|float $default = null,
|
||||
bool $required = null,
|
||||
int|float $min = null,
|
||||
int|float $max = null,
|
||||
array $elements = null
|
||||
): Document {
|
||||
$db = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
|
||||
|
||||
if ($db->isEmpty()) {
|
||||
throw new Exception(Exception::DATABASE_NOT_FOUND);
|
||||
}
|
||||
|
||||
$collection = $dbForProject->getDocument('database_' . $db->getInternalId(), $collectionId);
|
||||
|
||||
if ($collection->isEmpty()) {
|
||||
throw new Exception(Exception::COLLECTION_NOT_FOUND);
|
||||
}
|
||||
|
||||
$attribute = $dbForProject->getDocument('attributes', ID::custom($db->getInternalId() . '_' . $collection->getInternalId() . '_' . $key));
|
||||
|
||||
if ($attribute->isEmpty()) {
|
||||
throw new Exception(Exception::ATTRIBUTE_NOT_FOUND);
|
||||
}
|
||||
|
||||
if ($attribute->getAttribute('status') !== 'available') {
|
||||
throw new Exception(Exception::ATTRIBUTE_NOT_AVAILABLE);
|
||||
}
|
||||
|
||||
if ($attribute->getAttribute(('type') !== $type)) {
|
||||
throw new Exception(Exception::ATTRIBUTE_TYPE_INVALID);
|
||||
}
|
||||
|
||||
if ($attribute->getAttribute('type') === Database::VAR_STRING && $attribute->getAttribute(('filter') !== $filter)) {
|
||||
throw new Exception(Exception::ATTRIBUTE_TYPE_INVALID);
|
||||
}
|
||||
|
||||
if ($required && isset($default)) {
|
||||
throw new Exception(Exception::ATTRIBUTE_DEFAULT_UNSUPPORTED, 'Cannot set default value for required attribute');
|
||||
}
|
||||
|
||||
if ($attribute->getAttribute('array', false) && isset($default)) {
|
||||
throw new Exception(Exception::ATTRIBUTE_DEFAULT_UNSUPPORTED, 'Cannot set default value for array attributes');
|
||||
}
|
||||
|
||||
$collectionId = 'database_' . $db->getInternalId() . '_collection_' . $collection->getInternalId();
|
||||
|
||||
$attribute
|
||||
->setAttribute('default', $default)
|
||||
->setAttribute('required', $required);
|
||||
|
||||
$formatOptions = $attribute->getAttribute('formatOptions');
|
||||
|
||||
switch ($attribute->getAttribute('format')) {
|
||||
case APP_DATABASE_ATTRIBUTE_INT_RANGE:
|
||||
case APP_DATABASE_ATTRIBUTE_FLOAT_RANGE:
|
||||
if ($min === $formatOptions['min'] && $max === $formatOptions['max']) {
|
||||
break;
|
||||
}
|
||||
|
||||
if ($min > $max) {
|
||||
throw new Exception(Exception::ATTRIBUTE_VALUE_INVALID, 'Minimum value must be lesser than maximum value');
|
||||
}
|
||||
|
||||
if ($attribute->getAttribute('format') === APP_DATABASE_ATTRIBUTE_INT_RANGE) {
|
||||
$validator = new Range($min, $max, Database::VAR_INTEGER);
|
||||
} else {
|
||||
$validator = new Range($min, $max, Database::VAR_FLOAT);
|
||||
|
||||
if (!is_null($default)) {
|
||||
$default = \floatval($default);
|
||||
}
|
||||
}
|
||||
|
||||
if (!is_null($default) && !$validator->isValid($default)) {
|
||||
throw new Exception(Exception::ATTRIBUTE_VALUE_INVALID, $validator->getDescription());
|
||||
}
|
||||
|
||||
$options = [
|
||||
'min' => $min,
|
||||
'max' => $max
|
||||
];
|
||||
$attribute->setAttribute('formatOptions', $options);
|
||||
|
||||
break;
|
||||
case APP_DATABASE_ATTRIBUTE_ENUM:
|
||||
if (empty($elements)) {
|
||||
throw new Exception(Exception::ATTRIBUTE_VALUE_INVALID, 'Enum elements must not be empty');
|
||||
}
|
||||
|
||||
foreach ($elements as $element) {
|
||||
if (\strlen($element) === 0) {
|
||||
throw new Exception(Exception::ATTRIBUTE_VALUE_INVALID, 'Each enum element must not be empty');
|
||||
}
|
||||
}
|
||||
|
||||
if (!is_null($default) && !in_array($default, $elements)) {
|
||||
throw new Exception(Exception::ATTRIBUTE_VALUE_INVALID, 'Default value not found in elements');
|
||||
}
|
||||
|
||||
$options = [
|
||||
'elements' => $elements
|
||||
];
|
||||
|
||||
$attribute->setAttribute('formatOptions', $options);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
$dbForProject->updateAttribute(
|
||||
collection: $collectionId,
|
||||
id: $key,
|
||||
required: $required,
|
||||
default: $default,
|
||||
formatOptions: $options ?? null
|
||||
);
|
||||
|
||||
$dbForProject->updateDocument('attributes', $db->getInternalId() . '_' . $collection->getInternalId() . '_' . $key, $attribute);
|
||||
$dbForProject->deleteCachedDocument('database_' . $db->getInternalId(), $collectionId);
|
||||
|
||||
$events
|
||||
->setContext('collection', $collection)
|
||||
->setContext('database', $db)
|
||||
->setParam('databaseId', $databaseId)
|
||||
->setParam('collectionId', $collection->getId())
|
||||
->setParam('attributeId', $attribute->getId());
|
||||
|
||||
return $attribute;
|
||||
}
|
||||
|
||||
App::post('/v1/databases')
|
||||
->desc('Create Database')
|
||||
->groups(['api', 'database'])
|
||||
|
|
@ -964,7 +1102,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/enum')
|
|||
->param('databaseId', '', new UID(), 'Database ID.')
|
||||
->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).')
|
||||
->param('key', '', new Key(), 'Attribute Key.')
|
||||
->param('elements', [], new ArrayList(new Text(APP_LIMIT_ARRAY_ELEMENT_SIZE), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Array of elements in enumerated type. Uses length of longest element to determine size. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' elements are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long.')
|
||||
->param('elements', [], new ArrayList(new Text(APP_LIMIT_ARRAY_ELEMENT_SIZE, min: 0), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Array of elements in enumerated type. Uses length of longest element to determine size. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' elements are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long.')
|
||||
->param('required', null, new Boolean(), 'Is attribute required?')
|
||||
->param('default', null, new Text(0), 'Default value for attribute when not provided. Cannot be set when attribute is required.', true)
|
||||
->param('array', false, new Boolean(), 'Is attribute an array?', true)
|
||||
|
|
@ -1392,7 +1530,8 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/attributes/:key')
|
|||
Response::MODEL_ATTRIBUTE_URL,
|
||||
Response::MODEL_ATTRIBUTE_IP,
|
||||
Response::MODEL_ATTRIBUTE_DATETIME,
|
||||
Response::MODEL_ATTRIBUTE_STRING])// needs to be last, since its condition would dominate any other string attribute
|
||||
Response::MODEL_ATTRIBUTE_STRING // needs to be last, since its condition would dominate any other string attribute
|
||||
])
|
||||
->param('databaseId', '', new UID(), 'Database ID.')
|
||||
->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).')
|
||||
->param('key', '', new Key(), 'Attribute Key.')
|
||||
|
|
@ -1440,6 +1579,394 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/attributes/:key')
|
|||
$response->dynamic($attribute, $model);
|
||||
});
|
||||
|
||||
App::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/string/:key')
|
||||
->desc('Update String Attribute')
|
||||
->groups(['api', 'database', 'schema'])
|
||||
->label('scope', 'collections.write')
|
||||
->label('event', 'databases.[databaseId].collections.[collectionId].attributes.[attributeId].update')
|
||||
->label('audits.event', 'attribute.update')
|
||||
->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}')
|
||||
->label('usage.metric', 'collections.{scope}.requests.update')
|
||||
->label('usage.params', ['databaseId:{request.databaseId}'])
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
|
||||
->label('sdk.namespace', 'databases')
|
||||
->label('sdk.method', 'updateStringAttribute')
|
||||
->label('sdk.description', '/docs/references/databases/update-string-attribute.md')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.model', Response::MODEL_ATTRIBUTE_STRING)
|
||||
->param('databaseId', '', new UID(), 'Database ID.')
|
||||
->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).')
|
||||
->param('key', '', new Key(), 'Attribute Key.')
|
||||
->param('required', null, new Boolean(), 'Is attribute required?')
|
||||
->param('default', null, new Nullable(new Text(0)), 'Default value for attribute when not provided. Cannot be set when attribute is required.')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('events')
|
||||
->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, ?string $default, Response $response, Database $dbForProject, Event $events) {
|
||||
$attribute = updateAttribute(
|
||||
databaseId: $databaseId,
|
||||
collectionId: $collectionId,
|
||||
key: $key,
|
||||
dbForProject: $dbForProject,
|
||||
events: $events,
|
||||
type: Database::VAR_STRING,
|
||||
default: $default,
|
||||
required: $required
|
||||
);
|
||||
|
||||
$response
|
||||
->setStatusCode(Response::STATUS_CODE_OK)
|
||||
->dynamic($attribute, Response::MODEL_ATTRIBUTE_STRING);
|
||||
});
|
||||
|
||||
App::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/email/:key')
|
||||
->desc('Update Email Attribute')
|
||||
->groups(['api', 'database', 'schema'])
|
||||
->label('scope', 'collections.write')
|
||||
->label('event', 'databases.[databaseId].collections.[collectionId].attributes.[attributeId].update')
|
||||
->label('audits.event', 'attribute.update')
|
||||
->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}')
|
||||
->label('usage.metric', 'collections.{scope}.requests.update')
|
||||
->label('usage.params', ['databaseId:{request.databaseId}'])
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
|
||||
->label('sdk.namespace', 'databases')
|
||||
->label('sdk.method', 'updateEmailAttribute')
|
||||
->label('sdk.description', '/docs/references/databases/update-email-attribute.md')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.model', Response::MODEL_ATTRIBUTE_EMAIL)
|
||||
->param('databaseId', '', new UID(), 'Database ID.')
|
||||
->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).')
|
||||
->param('key', '', new Key(), 'Attribute Key.')
|
||||
->param('required', null, new Boolean(), 'Is attribute required?')
|
||||
->param('default', null, new Nullable(new Email()), 'Default value for attribute when not provided. Cannot be set when attribute is required.')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('events')
|
||||
->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, ?string $default, Response $response, Database $dbForProject, Event $events) {
|
||||
$attribute = updateAttribute(
|
||||
databaseId: $databaseId,
|
||||
collectionId: $collectionId,
|
||||
key: $key,
|
||||
dbForProject: $dbForProject,
|
||||
events: $events,
|
||||
type: Database::VAR_STRING,
|
||||
filter: APP_DATABASE_ATTRIBUTE_EMAIL,
|
||||
default: $default,
|
||||
required: $required
|
||||
);
|
||||
|
||||
$response
|
||||
->setStatusCode(Response::STATUS_CODE_OK)
|
||||
->dynamic($attribute, Response::MODEL_ATTRIBUTE_EMAIL);
|
||||
});
|
||||
|
||||
App::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/enum/:key')
|
||||
->desc('Update Enum Attribute')
|
||||
->groups(['api', 'database', 'schema'])
|
||||
->label('scope', 'collections.write')
|
||||
->label('event', 'databases.[databaseId].collections.[collectionId].attributes.[attributeId].update')
|
||||
->label('audits.event', 'attribute.update')
|
||||
->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}')
|
||||
->label('usage.metric', 'collections.{scope}.requests.update')
|
||||
->label('usage.params', ['databaseId:{request.databaseId}'])
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
|
||||
->label('sdk.namespace', 'databases')
|
||||
->label('sdk.method', 'updateEnumAttribute')
|
||||
->label('sdk.description', '/docs/references/databases/update-enum-attribute.md')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.model', Response::MODEL_ATTRIBUTE_ENUM)
|
||||
->param('databaseId', '', new UID(), 'Database ID.')
|
||||
->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).')
|
||||
->param('key', '', new Key(), 'Attribute Key.')
|
||||
->param('elements', null, new ArrayList(new Text(APP_LIMIT_ARRAY_ELEMENT_SIZE), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Array of elements in enumerated type. Uses length of longest element to determine size. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' elements are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long.')
|
||||
->param('required', null, new Boolean(), 'Is attribute required?')
|
||||
->param('default', null, new Nullable(new Text(0)), 'Default value for attribute when not provided. Cannot be set when attribute is required.')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('events')
|
||||
->action(function (string $databaseId, string $collectionId, string $key, ?array $elements, ?bool $required, ?string $default, Response $response, Database $dbForProject, Event $events) {
|
||||
$attribute = updateAttribute(
|
||||
databaseId: $databaseId,
|
||||
collectionId: $collectionId,
|
||||
key: $key,
|
||||
dbForProject: $dbForProject,
|
||||
events: $events,
|
||||
type: Database::VAR_STRING,
|
||||
filter: APP_DATABASE_ATTRIBUTE_ENUM,
|
||||
default: $default,
|
||||
required: $required,
|
||||
elements: $elements
|
||||
);
|
||||
|
||||
$response
|
||||
->setStatusCode(Response::STATUS_CODE_OK)
|
||||
->dynamic($attribute, Response::MODEL_ATTRIBUTE_ENUM);
|
||||
});
|
||||
|
||||
App::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/ip/:key')
|
||||
->desc('Update IP Address Attribute')
|
||||
->groups(['api', 'database', 'schema'])
|
||||
->label('scope', 'collections.write')
|
||||
->label('event', 'databases.[databaseId].collections.[collectionId].attributes.[attributeId].update')
|
||||
->label('audits.event', 'attribute.update')
|
||||
->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}')
|
||||
->label('usage.metric', 'collections.{scope}.requests.update')
|
||||
->label('usage.params', ['databaseId:{request.databaseId}'])
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
|
||||
->label('sdk.namespace', 'databases')
|
||||
->label('sdk.method', 'updateIpAttribute')
|
||||
->label('sdk.description', '/docs/references/databases/update-ip-attribute.md')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.model', Response::MODEL_ATTRIBUTE_IP)
|
||||
->param('databaseId', '', new UID(), 'Database ID.')
|
||||
->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).')
|
||||
->param('key', '', new Key(), 'Attribute Key.')
|
||||
->param('required', null, new Boolean(), 'Is attribute required?')
|
||||
->param('default', null, new Nullable(new IP()), 'Default value for attribute when not provided. Cannot be set when attribute is required.')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('events')
|
||||
->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, ?string $default, Response $response, Database $dbForProject, Event $events) {
|
||||
$attribute = updateAttribute(
|
||||
databaseId: $databaseId,
|
||||
collectionId: $collectionId,
|
||||
key: $key,
|
||||
dbForProject: $dbForProject,
|
||||
events: $events,
|
||||
type: Database::VAR_STRING,
|
||||
filter: APP_DATABASE_ATTRIBUTE_IP,
|
||||
default: $default,
|
||||
required: $required
|
||||
);
|
||||
|
||||
$response
|
||||
->setStatusCode(Response::STATUS_CODE_OK)
|
||||
->dynamic($attribute, Response::MODEL_ATTRIBUTE_IP);
|
||||
});
|
||||
|
||||
App::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/url/:key')
|
||||
->desc('Update URL Attribute')
|
||||
->groups(['api', 'database', 'schema'])
|
||||
->label('scope', 'collections.write')
|
||||
->label('event', 'databases.[databaseId].collections.[collectionId].attributes.[attributeId].update')
|
||||
->label('audits.event', 'attribute.update')
|
||||
->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}')
|
||||
->label('usage.metric', 'collections.{scope}.requests.update')
|
||||
->label('usage.params', ['databaseId:{request.databaseId}'])
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
|
||||
->label('sdk.namespace', 'databases')
|
||||
->label('sdk.method', 'updateUrlAttribute')
|
||||
->label('sdk.description', '/docs/references/databases/update-url-attribute.md')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.model', Response::MODEL_ATTRIBUTE_URL)
|
||||
->param('databaseId', '', new UID(), 'Database ID.')
|
||||
->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).')
|
||||
->param('key', '', new Key(), 'Attribute Key.')
|
||||
->param('required', null, new Boolean(), 'Is attribute required?')
|
||||
->param('default', null, new Nullable(new URL()), 'Default value for attribute when not provided. Cannot be set when attribute is required.')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('events')
|
||||
->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, ?string $default, Response $response, Database $dbForProject, Event $events) {
|
||||
$attribute = updateAttribute(
|
||||
databaseId: $databaseId,
|
||||
collectionId: $collectionId,
|
||||
key: $key,
|
||||
dbForProject: $dbForProject,
|
||||
events: $events,
|
||||
type: Database::VAR_STRING,
|
||||
filter: APP_DATABASE_ATTRIBUTE_URL,
|
||||
default: $default,
|
||||
required: $required
|
||||
);
|
||||
|
||||
$response
|
||||
->setStatusCode(Response::STATUS_CODE_OK)
|
||||
->dynamic($attribute, Response::MODEL_ATTRIBUTE_URL);
|
||||
});
|
||||
|
||||
App::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/integer/:key')
|
||||
->desc('Update Integer Attribute')
|
||||
->groups(['api', 'database', 'schema'])
|
||||
->label('scope', 'collections.write')
|
||||
->label('event', 'databases.[databaseId].collections.[collectionId].attributes.[attributeId].update')
|
||||
->label('audits.event', 'attribute.update')
|
||||
->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}')
|
||||
->label('usage.metric', 'collections.{scope}.requests.update')
|
||||
->label('usage.params', ['databaseId:{request.databaseId}'])
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
|
||||
->label('sdk.namespace', 'databases')
|
||||
->label('sdk.method', 'updateIntegerAttribute')
|
||||
->label('sdk.description', '/docs/references/databases/update-integer-attribute.md')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.model', Response::MODEL_ATTRIBUTE_INTEGER)
|
||||
->param('databaseId', '', new UID(), 'Database ID.')
|
||||
->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).')
|
||||
->param('key', '', new Key(), 'Attribute Key.')
|
||||
->param('required', null, new Boolean(), 'Is attribute required?')
|
||||
->param('min', null, new Integer(), 'Minimum value to enforce on new documents')
|
||||
->param('max', null, new Integer(), 'Maximum value to enforce on new documents')
|
||||
->param('default', null, new Nullable(new Integer()), 'Default value for attribute when not provided. Cannot be set when attribute is required.')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('events')
|
||||
->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, ?int $min, ?int $max, ?int $default, Response $response, Database $dbForProject, Event $events) {
|
||||
$attribute = updateAttribute(
|
||||
databaseId: $databaseId,
|
||||
collectionId: $collectionId,
|
||||
key: $key,
|
||||
dbForProject: $dbForProject,
|
||||
events: $events,
|
||||
type: Database::VAR_INTEGER,
|
||||
default: $default,
|
||||
required: $required,
|
||||
min: $min,
|
||||
max: $max
|
||||
);
|
||||
|
||||
$formatOptions = $attribute->getAttribute('formatOptions', []);
|
||||
|
||||
if (!empty($formatOptions)) {
|
||||
$attribute->setAttribute('min', \intval($formatOptions['min']));
|
||||
$attribute->setAttribute('max', \intval($formatOptions['max']));
|
||||
}
|
||||
|
||||
$response
|
||||
->setStatusCode(Response::STATUS_CODE_OK)
|
||||
->dynamic($attribute, Response::MODEL_ATTRIBUTE_INTEGER);
|
||||
});
|
||||
|
||||
App::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/float/:key')
|
||||
->desc('Update Float Attribute')
|
||||
->groups(['api', 'database', 'schema'])
|
||||
->label('scope', 'collections.write')
|
||||
->label('event', 'databases.[databaseId].collections.[collectionId].attributes.[attributeId].update')
|
||||
->label('audits.event', 'attribute.update')
|
||||
->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}')
|
||||
->label('usage.metric', 'collections.{scope}.requests.update')
|
||||
->label('usage.params', ['databaseId:{request.databaseId}'])
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
|
||||
->label('sdk.namespace', 'databases')
|
||||
->label('sdk.method', 'updateFloatAttribute')
|
||||
->label('sdk.description', '/docs/references/databases/update-float-attribute.md')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.model', Response::MODEL_ATTRIBUTE_FLOAT)
|
||||
->param('databaseId', '', new UID(), 'Database ID.')
|
||||
->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).')
|
||||
->param('key', '', new Key(), 'Attribute Key.')
|
||||
->param('required', null, new Boolean(), 'Is attribute required?')
|
||||
->param('min', null, new FloatValidator(), 'Minimum value to enforce on new documents')
|
||||
->param('max', null, new FloatValidator(), 'Maximum value to enforce on new documents')
|
||||
->param('default', null, new Nullable(new FloatValidator()), 'Default value for attribute when not provided. Cannot be set when attribute is required.')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('events')
|
||||
->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, ?float $min, ?float $max, ?float $default, Response $response, Database $dbForProject, Event $events) {
|
||||
$attribute = updateAttribute(
|
||||
databaseId: $databaseId,
|
||||
collectionId: $collectionId,
|
||||
key: $key,
|
||||
dbForProject: $dbForProject,
|
||||
events: $events,
|
||||
type: Database::VAR_FLOAT,
|
||||
default: $default,
|
||||
required: $required,
|
||||
min: $min,
|
||||
max: $max
|
||||
);
|
||||
|
||||
$formatOptions = $attribute->getAttribute('formatOptions', []);
|
||||
|
||||
if (!empty($formatOptions)) {
|
||||
$attribute->setAttribute('min', \floatval($formatOptions['min']));
|
||||
$attribute->setAttribute('max', \floatval($formatOptions['max']));
|
||||
}
|
||||
|
||||
$response
|
||||
->setStatusCode(Response::STATUS_CODE_OK)
|
||||
->dynamic($attribute, Response::MODEL_ATTRIBUTE_FLOAT);
|
||||
});
|
||||
|
||||
App::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/boolean/:key')
|
||||
->desc('Update Boolean Attribute')
|
||||
->groups(['api', 'database', 'schema'])
|
||||
->label('scope', 'collections.write')
|
||||
->label('event', 'databases.[databaseId].collections.[collectionId].attributes.[attributeId].update')
|
||||
->label('audits.event', 'attribute.update')
|
||||
->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}')
|
||||
->label('usage.metric', 'collections.{scope}.requests.update')
|
||||
->label('usage.params', ['databaseId:{request.databaseId}'])
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
|
||||
->label('sdk.namespace', 'databases')
|
||||
->label('sdk.method', 'updateBooleanAttribute')
|
||||
->label('sdk.description', '/docs/references/databases/update-boolean-attribute.md')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.model', Response::MODEL_ATTRIBUTE_BOOLEAN)
|
||||
->param('databaseId', '', new UID(), 'Database ID.')
|
||||
->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).')
|
||||
->param('key', '', new Key(), 'Attribute Key.')
|
||||
->param('required', null, new Boolean(), 'Is attribute required?')
|
||||
->param('default', null, new Nullable(new Boolean()), 'Default value for attribute when not provided. Cannot be set when attribute is required.')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('events')
|
||||
->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, ?bool $default, Response $response, Database $dbForProject, Event $events) {
|
||||
$attribute = updateAttribute(
|
||||
databaseId: $databaseId,
|
||||
collectionId: $collectionId,
|
||||
key: $key,
|
||||
dbForProject: $dbForProject,
|
||||
events: $events,
|
||||
type: Database::VAR_BOOLEAN,
|
||||
default: $default,
|
||||
required: $required
|
||||
);
|
||||
|
||||
$response
|
||||
->setStatusCode(Response::STATUS_CODE_OK)
|
||||
->dynamic($attribute, Response::MODEL_ATTRIBUTE_BOOLEAN);
|
||||
});
|
||||
|
||||
App::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/datetime/:key')
|
||||
->desc('Update DateTime Attribute')
|
||||
->groups(['api', 'database', 'schema'])
|
||||
->label('scope', 'collections.write')
|
||||
->label('event', 'databases.[databaseId].collections.[collectionId].attributes.[attributeId].update')
|
||||
->label('audits.event', 'attribute.update')
|
||||
->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}')
|
||||
->label('usage.metric', 'collections.{scope}.requests.update')
|
||||
->label('usage.params', ['databaseId:{request.databaseId}'])
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
|
||||
->label('sdk.namespace', 'databases')
|
||||
->label('sdk.method', 'updateDatetimeAttribute')
|
||||
->label('sdk.description', '/docs/references/databases/update-datetime-attribute.md')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.model', Response::MODEL_ATTRIBUTE_DATETIME)
|
||||
->param('databaseId', '', new UID(), 'Database ID.')
|
||||
->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).')
|
||||
->param('key', '', new Key(), 'Attribute Key.')
|
||||
->param('required', null, new Boolean(), 'Is attribute required?')
|
||||
->param('default', null, new Nullable(new DatetimeValidator()), 'Default value for attribute when not provided. Cannot be set when attribute is required.')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('events')
|
||||
->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, ?string $default, Response $response, Database $dbForProject, Event $events) {
|
||||
$attribute = updateAttribute(
|
||||
databaseId: $databaseId,
|
||||
collectionId: $collectionId,
|
||||
key: $key,
|
||||
dbForProject: $dbForProject,
|
||||
events: $events,
|
||||
type: Database::VAR_DATETIME,
|
||||
default: $default,
|
||||
required: $required
|
||||
);
|
||||
|
||||
$response
|
||||
->setStatusCode(Response::STATUS_CODE_OK)
|
||||
->dynamic($attribute, Response::MODEL_ATTRIBUTE_DATETIME);
|
||||
});
|
||||
|
||||
App::delete('/v1/databases/:databaseId/collections/:collectionId/attributes/:key')
|
||||
->alias('/v1/database/collections/:collectionId/attributes/:key', ['databaseId' => 'default'])
|
||||
->desc('Delete Attribute')
|
||||
|
|
@ -1483,7 +2010,7 @@ App::delete('/v1/databases/:databaseId/collections/:collectionId/attributes/:key
|
|||
}
|
||||
|
||||
// Only update status if removing available attribute
|
||||
if ($attribute->getAttribute('status' === 'available')) {
|
||||
if ($attribute->getAttribute('status') === 'available') {
|
||||
$attribute = $dbForProject->updateDocument('attributes', $attribute->getId(), $attribute->setAttribute('status', 'deleting'));
|
||||
}
|
||||
|
||||
|
|
@ -2007,7 +2534,7 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents')
|
|||
}
|
||||
|
||||
// Validate queries
|
||||
$queriesValidator = new Documents($collection->getAttribute('attributes'), $collection->getAttribute('indexes'));
|
||||
$queriesValidator = new Documents($collection->getAttribute('attributes'));
|
||||
$validQueries = $queriesValidator->isValid($queries);
|
||||
if (!$validQueries) {
|
||||
throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, $queriesValidator->getDescription());
|
||||
|
|
@ -2252,11 +2779,12 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/documents/:docum
|
|||
->param('documentId', '', new UID(), 'Document ID.')
|
||||
->param('data', [], new JSON(), 'Document data as JSON object. Include only attribute and value pairs to be updated.', 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 permissions strings. By default, the current permissions are inherited. [Learn more about permissions](/docs/permissions).', true)
|
||||
->inject('requestTimestamp')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('events')
|
||||
->inject('mode')
|
||||
->action(function (string $databaseId, string $collectionId, string $documentId, string|array $data, ?array $permissions, Response $response, Database $dbForProject, Event $events, string $mode) {
|
||||
->action(function (string $databaseId, string $collectionId, string $documentId, string|array $data, ?array $permissions, ?\DateTime $requestTimestamp, Response $response, Database $dbForProject, Event $events, string $mode) {
|
||||
|
||||
$data = (\is_string($data)) ? \json_decode($data, true) : $data; // Cast to JSON array
|
||||
|
||||
|
|
@ -2286,6 +2814,7 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/documents/:docum
|
|||
}
|
||||
|
||||
// Read permission should not be required for update
|
||||
/** @var Document */
|
||||
$document = Authorization::skip(fn() => $dbForProject->getDocument('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $documentId));
|
||||
|
||||
if ($document->isEmpty()) {
|
||||
|
|
@ -2331,10 +2860,31 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/documents/:docum
|
|||
$data['$permissions'] = $permissions;
|
||||
|
||||
try {
|
||||
$privateCollectionId = 'database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId();
|
||||
if ($documentSecurity && !$valid) {
|
||||
$document = $dbForProject->updateDocument('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $document->getId(), new Document($data));
|
||||
$document = $dbForProject->withRequestTimestamp(
|
||||
$requestTimestamp,
|
||||
function () use ($dbForProject, $privateCollectionId, $document, $data) {
|
||||
return $dbForProject->updateDocument(
|
||||
$privateCollectionId,
|
||||
$document->getId(),
|
||||
new Document($data)
|
||||
);
|
||||
}
|
||||
);
|
||||
} else {
|
||||
$document = Authorization::skip(fn() => $dbForProject->updateDocument('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $document->getId(), new Document($data)));
|
||||
$document = Authorization::skip(function () use ($dbForProject, $requestTimestamp, $privateCollectionId, $document, $data) {
|
||||
return $dbForProject->withRequestTimestamp(
|
||||
$requestTimestamp,
|
||||
function () use ($dbForProject, $privateCollectionId, $document, $data) {
|
||||
return $dbForProject->updateDocument(
|
||||
$privateCollectionId,
|
||||
$document->getId(),
|
||||
new Document($data)
|
||||
);
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -2385,12 +2935,13 @@ App::delete('/v1/databases/:databaseId/collections/:collectionId/documents/:docu
|
|||
->param('databaseId', '', new UID(), 'Database ID.')
|
||||
->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).')
|
||||
->param('documentId', '', new UID(), 'Document ID.')
|
||||
->inject('requestTimestamp')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('events')
|
||||
->inject('deletes')
|
||||
->inject('mode')
|
||||
->action(function (string $databaseId, string $collectionId, string $documentId, Response $response, Database $dbForProject, Event $events, Delete $deletes, string $mode) {
|
||||
->action(function (string $databaseId, string $collectionId, string $documentId, ?\DateTime $requestTimestamp, Response $response, Database $dbForProject, Event $events, Delete $deletes, string $mode) {
|
||||
|
||||
$database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
|
||||
|
||||
|
|
@ -2420,17 +2971,25 @@ App::delete('/v1/databases/:databaseId/collections/:collectionId/documents/:docu
|
|||
throw new Exception(Exception::DOCUMENT_NOT_FOUND);
|
||||
}
|
||||
|
||||
$privateCollectionId = 'database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId();
|
||||
|
||||
if ($documentSecurity && !$valid) {
|
||||
try {
|
||||
$dbForProject->deleteDocument('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $documentId);
|
||||
$dbForProject->withRequestTimestamp($requestTimestamp, function () use ($dbForProject, $privateCollectionId, $documentId) {
|
||||
return $dbForProject->deleteDocument($privateCollectionId, $documentId);
|
||||
});
|
||||
} catch (AuthorizationException) {
|
||||
throw new Exception(Exception::USER_UNAUTHORIZED);
|
||||
}
|
||||
} else {
|
||||
Authorization::skip(fn() => $dbForProject->deleteDocument('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $documentId));
|
||||
Authorization::skip(function () use ($dbForProject, $requestTimestamp, $privateCollectionId, $documentId) {
|
||||
return $dbForProject->withRequestTimestamp($requestTimestamp, function () use ($dbForProject, $privateCollectionId, $documentId) {
|
||||
return $dbForProject->deleteDocument($privateCollectionId, $documentId);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
$dbForProject->deleteCachedDocument('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $documentId);
|
||||
$dbForProject->deleteCachedDocument($privateCollectionId, $documentId);
|
||||
|
||||
/**
|
||||
* Reset $collection attribute to remove prefix.
|
||||
|
|
|
|||
|
|
@ -25,7 +25,6 @@ use Appwrite\Task\Validator\Cron;
|
|||
use Appwrite\Utopia\Database\Validator\Queries\Deployments;
|
||||
use Appwrite\Utopia\Database\Validator\Queries\Executions;
|
||||
use Appwrite\Utopia\Database\Validator\Queries\Functions;
|
||||
use Appwrite\Utopia\Database\Validator\Queries\Variables;
|
||||
use Utopia\App;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
|
|
@ -33,7 +32,6 @@ use Utopia\Database\DateTime;
|
|||
use Utopia\Database\Query;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Validator\ArrayList;
|
||||
use Utopia\Validator\Assoc;
|
||||
use Utopia\Validator\Text;
|
||||
use Utopia\Validator\Range;
|
||||
use Utopia\Validator\WhiteList;
|
||||
|
|
@ -63,7 +61,7 @@ App::post('/v1/functions')
|
|||
->label('sdk.response.model', Response::MODEL_FUNCTION)
|
||||
->param('functionId', '', new CustomId(), 'Function 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('name', '', new Text(128), 'Function name. Max length: 128 chars.')
|
||||
->param('execute', [], new Roles(APP_LIMIT_ARRAY_PARAMS_SIZE), 'An array of strings with execution roles. By default no user is granted with any execute permissions. [learn more about permissions](https://appwrite.io/docs/permissions). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' roles are allowed, each 64 characters long.')
|
||||
->param('execute', [], new Roles(APP_LIMIT_ARRAY_PARAMS_SIZE), 'An array of strings with execution roles. By default no user is granted with any execute permissions. [learn more about permissions](https://appwrite.io/docs/permissions). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' roles are allowed, each 64 characters long.', true)
|
||||
->param('runtime', '', new WhiteList(array_keys(Config::getParam('runtimes')), true), 'Execution runtime.')
|
||||
->param('events', [], new ArrayList(new ValidatorEvent(), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Events list. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' events are allowed.', true)
|
||||
->param('schedule', '', new Cron(), 'Schedule CRON syntax.', true)
|
||||
|
|
@ -440,7 +438,7 @@ App::put('/v1/functions/:functionId')
|
|||
->label('sdk.response.model', Response::MODEL_FUNCTION)
|
||||
->param('functionId', '', new UID(), 'Function ID.')
|
||||
->param('name', '', new Text(128), 'Function name. Max length: 128 chars.')
|
||||
->param('execute', [], new Roles(APP_LIMIT_ARRAY_PARAMS_SIZE), 'An array of strings with execution roles. By default no user is granted with any execute permissions. [learn more about permissions](https://appwrite.io/docs/permissions). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' roles are allowed, each 64 characters long.')
|
||||
->param('execute', [], new Roles(APP_LIMIT_ARRAY_PARAMS_SIZE), 'An array of strings with execution roles. By default no user is granted with any execute permissions. [learn more about permissions](https://appwrite.io/docs/permissions). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' roles are allowed, each 64 characters long.', true)
|
||||
->param('events', [], new ArrayList(new ValidatorEvent(), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Events list. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' events are allowed.', true)
|
||||
->param('schedule', '', new Cron(), 'Schedule CRON syntax.', true)
|
||||
->param('timeout', 15, new Range(1, (int) App::getEnv('_APP_FUNCTIONS_TIMEOUT', 900)), 'Maximum execution time in seconds.', true)
|
||||
|
|
@ -601,8 +599,8 @@ App::post('/v1/functions/:functionId/deployments')
|
|||
->label('sdk.response.model', Response::MODEL_DEPLOYMENT)
|
||||
->param('functionId', '', new UID(), 'Function ID.')
|
||||
->param('entrypoint', '', new Text('1028'), 'Entrypoint File.')
|
||||
->param('code', [], new File(), 'Gzip file with your code package. When used with the Appwrite CLI, pass the path to your code directory, and the CLI will automatically package your code. Use a path that is within the current directory.', false)
|
||||
->param('activate', false, new Boolean(true), 'Automatically activate the deployment when it is finished building.', false)
|
||||
->param('code', [], new File(), 'Gzip file with your code package. When used with the Appwrite CLI, pass the path to your code directory, and the CLI will automatically package your code. Use a path that is within the current directory.', skipValidation: true)
|
||||
->param('activate', false, new Boolean(true), 'Automatically activate the deployment when it is finished building.')
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
|
|
@ -1348,7 +1346,7 @@ App::post('/v1/functions/:functionId/variables')
|
|||
->label('sdk.response.model', Response::MODEL_VARIABLE)
|
||||
->param('functionId', '', new UID(), 'Function unique ID.', false)
|
||||
->param('key', null, new Text(Database::LENGTH_KEY), 'Variable key. Max length: ' . Database::LENGTH_KEY . ' chars.', false)
|
||||
->param('value', null, new Text(8192), 'Variable value. Max length: 8192 chars.', false)
|
||||
->param('value', null, new Text(8192, 0), 'Variable value. Max length: 8192 chars.', false)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->action(function (string $functionId, string $key, string $value, Response $response, Database $dbForProject) {
|
||||
|
|
@ -1464,7 +1462,7 @@ App::put('/v1/functions/:functionId/variables/:variableId')
|
|||
->param('functionId', '', new UID(), 'Function unique ID.', false)
|
||||
->param('variableId', '', new UID(), 'Variable unique ID.', false)
|
||||
->param('key', null, new Text(255), 'Variable key. Max length: 255 chars.', false)
|
||||
->param('value', null, new Text(8192), 'Variable value. Max length: 8192 chars.', true)
|
||||
->param('value', null, new Text(8192, 0), 'Variable value. Max length: 8192 chars.', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->action(function (string $functionId, string $variableId, string $key, ?string $value, Response $response, Database $dbForProject) {
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ App::get('/v1/graphql')
|
|||
->label('sdk.response.model', Response::MODEL_ANY)
|
||||
->label('abuse-limit', 60)
|
||||
->label('abuse-time', 60)
|
||||
->param('query', '', new Text(0), 'The query to execute.')
|
||||
->param('query', '', new Text(0, 0), 'The query to execute.')
|
||||
->param('operationName', '', new Text(256), 'The name of the operation to execute.', true)
|
||||
->param('variables', '', new Text(0), 'The JSON encoded variables to use in the query.', true)
|
||||
->inject('request')
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ use Utopia\Database\Helpers\Permission;
|
|||
use Utopia\Database\Query;
|
||||
use Utopia\Database\Helpers\Role;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Database\Validator\DatetimeValidator;
|
||||
use Utopia\Database\Validator\Datetime as DatetimeValidator;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Domains\Domain;
|
||||
use Utopia\Registry\Registry;
|
||||
|
|
@ -80,7 +80,7 @@ App::post('/v1/projects')
|
|||
}
|
||||
|
||||
$auth = Config::getParam('auth', []);
|
||||
$auths = ['limit' => 0, 'maxSessions' => APP_LIMIT_USER_SESSIONS_DEFAULT, 'duration' => Auth::TOKEN_EXPIRATION_LOGIN_LONG];
|
||||
$auths = ['limit' => 0, 'maxSessions' => APP_LIMIT_USER_SESSIONS_DEFAULT, 'passwordHistory' => 0, 'passwordDictionary' => false, 'duration' => Auth::TOKEN_EXPIRATION_LOGIN_LONG];
|
||||
foreach ($auth as $index => $method) {
|
||||
$auths[$method['key'] ?? ''] = true;
|
||||
}
|
||||
|
|
@ -575,6 +575,68 @@ App::patch('/v1/projects/:projectId/auth/:method')
|
|||
$response->dynamic($project, Response::MODEL_PROJECT);
|
||||
});
|
||||
|
||||
App::patch('/v1/projects/:projectId/auth/password-history')
|
||||
->desc('Update authentication password history. Use this endpoint to set the number of password history to save and 0 to disable password history.')
|
||||
->groups(['api', 'projects'])
|
||||
->label('scope', 'projects.write')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
|
||||
->label('sdk.namespace', 'projects')
|
||||
->label('sdk.method', 'updateAuthPasswordHistory')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_PROJECT)
|
||||
->param('projectId', '', new UID(), 'Project unique ID.')
|
||||
->param('limit', 0, new Range(0, APP_LIMIT_USER_PASSWORD_HISTORY), 'Set the max number of passwords to store in user history. User can\'t choose a new password that is already stored in the password history list. Max number of passwords allowed in history is' . APP_LIMIT_USER_PASSWORD_HISTORY . '. Default value is 0')
|
||||
->inject('response')
|
||||
->inject('dbForConsole')
|
||||
->action(function (string $projectId, int $limit, Response $response, Database $dbForConsole) {
|
||||
|
||||
$project = $dbForConsole->getDocument('projects', $projectId);
|
||||
|
||||
if ($project->isEmpty()) {
|
||||
throw new Exception(Exception::PROJECT_NOT_FOUND);
|
||||
}
|
||||
|
||||
$auths = $project->getAttribute('auths', []);
|
||||
$auths['passwordHistory'] = $limit;
|
||||
|
||||
$dbForConsole->updateDocument('projects', $project->getId(), $project
|
||||
->setAttribute('auths', $auths));
|
||||
|
||||
$response->dynamic($project, Response::MODEL_PROJECT);
|
||||
});
|
||||
|
||||
App::patch('/v1/projects/:projectId/auth/password-dictionary')
|
||||
->desc('Update authentication password disctionary status. Use this endpoint to enable or disable the dicitonary check for user password')
|
||||
->groups(['api', 'projects'])
|
||||
->label('scope', 'projects.write')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
|
||||
->label('sdk.namespace', 'projects')
|
||||
->label('sdk.method', 'updateAuthPasswordDictionary')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_PROJECT)
|
||||
->param('projectId', '', new UID(), 'Project unique ID.')
|
||||
->param('enabled', false, new Boolean(false), 'Set whether or not to enable checking user\'s password against most commonly used passwords. Default is false.')
|
||||
->inject('response')
|
||||
->inject('dbForConsole')
|
||||
->action(function (string $projectId, bool $enabled, Response $response, Database $dbForConsole) {
|
||||
|
||||
$project = $dbForConsole->getDocument('projects', $projectId);
|
||||
|
||||
if ($project->isEmpty()) {
|
||||
throw new Exception(Exception::PROJECT_NOT_FOUND);
|
||||
}
|
||||
|
||||
$auths = $project->getAttribute('auths', []);
|
||||
$auths['passwordDictionary'] = $enabled;
|
||||
|
||||
$dbForConsole->updateDocument('projects', $project->getId(), $project
|
||||
->setAttribute('auths', $auths));
|
||||
|
||||
$response->dynamic($project, Response::MODEL_PROJECT);
|
||||
});
|
||||
|
||||
App::patch('/v1/projects/:projectId/auth/max-sessions')
|
||||
->desc('Update Project user sessions limit')
|
||||
->groups(['api', 'projects'])
|
||||
|
|
|
|||
|
|
@ -348,7 +348,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](/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.', false)
|
||||
->param('file', [], new File(), 'Binary 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](/docs/permissions).', true)
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
|
|
|
|||
|
|
@ -1,10 +1,12 @@
|
|||
<?php
|
||||
|
||||
use Appwrite\Auth\Auth;
|
||||
use Appwrite\Auth\Validator\Phone;
|
||||
use Appwrite\Detector\Detector;
|
||||
use Appwrite\Event\Delete;
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Event\Mail;
|
||||
use Appwrite\Event\Phone as EventPhone;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Network\Validator\Email;
|
||||
use Utopia\Validator\Host;
|
||||
|
|
@ -20,7 +22,6 @@ use Appwrite\Utopia\Response;
|
|||
use MaxMind\Db\Reader;
|
||||
use Utopia\App;
|
||||
use Utopia\Audit\Audit;
|
||||
use Utopia\CLI\Console;
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
|
|
@ -35,10 +36,9 @@ use Utopia\Database\Validator\Authorization;
|
|||
use Utopia\Database\Validator\Key;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Locale\Locale;
|
||||
use Utopia\Validator\Text;
|
||||
use Utopia\Validator\Range;
|
||||
use Utopia\Validator\ArrayList;
|
||||
use Utopia\Validator\WhiteList;
|
||||
use Utopia\Validator\Assoc;
|
||||
use Utopia\Validator\Text;
|
||||
|
||||
App::post('/v1/teams')
|
||||
->desc('Create Team')
|
||||
|
|
@ -76,6 +76,7 @@ App::post('/v1/teams')
|
|||
],
|
||||
'name' => $name,
|
||||
'total' => ($isPrivilegedUser || $isAppUser) ? 0 : 1,
|
||||
'prefs' => new \stdClass(),
|
||||
'search' => implode(' ', [$teamId, $name]),
|
||||
])));
|
||||
|
||||
|
|
@ -199,28 +200,22 @@ App::get('/v1/teams/:teamId')
|
|||
$response->dynamic($team, Response::MODEL_TEAM);
|
||||
});
|
||||
|
||||
App::put('/v1/teams/:teamId')
|
||||
->desc('Update Team')
|
||||
App::get('/v1/teams/:teamId/prefs')
|
||||
->desc('Get Team Preferences')
|
||||
->groups(['api', 'teams'])
|
||||
->label('event', 'teams.[teamId].update')
|
||||
->label('scope', 'teams.write')
|
||||
->label('audits.event', 'team.update')
|
||||
->label('audits.resource', 'team/{response.$id}')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
|
||||
->label('scope', 'teams.read')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_JWT])
|
||||
->label('sdk.namespace', 'teams')
|
||||
->label('sdk.method', 'update')
|
||||
->label('sdk.description', '/docs/references/teams/update-team.md')
|
||||
->label('sdk.method', 'getPrefs')
|
||||
->label('sdk.description', '/docs/references/teams/get-team-prefs.md')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_TEAM)
|
||||
->label('sdk.offline.model', '/teams')
|
||||
->label('sdk.offline.key', '{teamId}')
|
||||
->label('sdk.response.model', Response::MODEL_PREFERENCES)
|
||||
->label('sdk.offline.model', '/teams/{teamId}/prefs')
|
||||
->param('teamId', '', new UID(), 'Team ID.')
|
||||
->param('name', null, new Text(128), 'New team name. Max length: 128 chars.')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('events')
|
||||
->action(function (string $teamId, string $name, Response $response, Database $dbForProject, Event $events) {
|
||||
->action(function (string $teamId, Response $response, Database $dbForProject) {
|
||||
|
||||
$team = $dbForProject->getDocument('teams', $teamId);
|
||||
|
||||
|
|
@ -228,15 +223,90 @@ App::put('/v1/teams/:teamId')
|
|||
throw new Exception(Exception::TEAM_NOT_FOUND);
|
||||
}
|
||||
|
||||
$team = $dbForProject->updateDocument('teams', $team->getId(), $team
|
||||
$prefs = $team->getAttribute('prefs', new \stdClass());
|
||||
|
||||
$response->dynamic(new Document($prefs), Response::MODEL_PREFERENCES);
|
||||
});
|
||||
|
||||
App::put('/v1/teams/:teamId')
|
||||
->desc('Update Name')
|
||||
->groups(['api', 'teams'])
|
||||
->label('event', 'teams.[teamId].update')
|
||||
->label('scope', 'teams.write')
|
||||
->label('audits.event', 'team.update')
|
||||
->label('audits.resource', 'team/{response.$id}')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
|
||||
->label('sdk.namespace', 'teams')
|
||||
->label('sdk.method', 'updateName')
|
||||
->label('sdk.description', '/docs/references/teams/update-team-name.md')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_TEAM)
|
||||
->label('sdk.offline.model', '/teams')
|
||||
->label('sdk.offline.key', '{teamId}')
|
||||
->param('teamId', '', new UID(), 'Team ID.')
|
||||
->param('name', null, new Text(128), 'New team name. Max length: 128 chars.')
|
||||
->inject('requestTimestamp')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('events')
|
||||
->action(function (string $teamId, string $name, ?\DateTime $requestTimestamp, Response $response, Database $dbForProject, Event $events) {
|
||||
|
||||
$team = $dbForProject->getDocument('teams', $teamId);
|
||||
|
||||
if ($team->isEmpty()) {
|
||||
throw new Exception(Exception::TEAM_NOT_FOUND);
|
||||
}
|
||||
|
||||
$team
|
||||
->setAttribute('name', $name)
|
||||
->setAttribute('search', implode(' ', [$teamId, $name])));
|
||||
->setAttribute('search', implode(' ', [$teamId, $name]));
|
||||
|
||||
$team = $dbForProject->withRequestTimestamp($requestTimestamp, function () use ($dbForProject, $team) {
|
||||
return $dbForProject->updateDocument('teams', $team->getId(), $team);
|
||||
});
|
||||
|
||||
$events->setParam('teamId', $team->getId());
|
||||
|
||||
$response->dynamic($team, Response::MODEL_TEAM);
|
||||
});
|
||||
|
||||
App::put('/v1/teams/:teamId/prefs')
|
||||
->desc('Update Preferences')
|
||||
->groups(['api', 'teams'])
|
||||
->label('event', 'teams.[teamId].update.prefs')
|
||||
->label('scope', 'teams.write')
|
||||
->label('audits.event', 'team.update')
|
||||
->label('audits.resource', 'team/{response.$id}')
|
||||
->label('audits.userId', '{response.$id}')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_JWT])
|
||||
->label('sdk.namespace', 'teams')
|
||||
->label('sdk.method', 'updatePrefs')
|
||||
->label('sdk.description', '/docs/references/teams/update-team-prefs.md')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_PREFERENCES)
|
||||
->label('sdk.offline.model', '/teams/{teamId}/prefs')
|
||||
->param('teamId', '', new UID(), 'Team ID.')
|
||||
->param('prefs', '', new Assoc(), 'Prefs key-value JSON object.')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('events')
|
||||
->action(function (string $teamId, array $prefs, Response $response, Database $dbForProject, Event $events) {
|
||||
|
||||
$team = $dbForProject->getDocument('teams', $teamId);
|
||||
|
||||
if ($team->isEmpty()) {
|
||||
throw new Exception(Exception::TEAM_NOT_FOUND);
|
||||
}
|
||||
|
||||
$team = $dbForProject->updateDocument('teams', $team->getId(), $team->setAttribute('prefs', $prefs));
|
||||
|
||||
$events->setParam('teamId', $team->getId());
|
||||
|
||||
$response->dynamic(new Document($prefs), Response::MODEL_PREFERENCES);
|
||||
});
|
||||
|
||||
App::delete('/v1/teams/:teamId')
|
||||
->desc('Delete Team')
|
||||
->groups(['api', 'teams'])
|
||||
|
|
@ -309,7 +379,9 @@ App::post('/v1/teams/:teamId/memberships')
|
|||
->label('sdk.response.model', Response::MODEL_MEMBERSHIP)
|
||||
->label('abuse-limit', 10)
|
||||
->param('teamId', '', new UID(), 'Team ID.')
|
||||
->param('email', '', new Email(), 'Email of the new team member.')
|
||||
->param('email', '', new Email(), 'Email of the new team member.', true)
|
||||
->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](/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.', false, ['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)
|
||||
|
|
@ -319,9 +391,13 @@ App::post('/v1/teams/:teamId/memberships')
|
|||
->inject('dbForProject')
|
||||
->inject('locale')
|
||||
->inject('mails')
|
||||
->inject('messaging')
|
||||
->inject('events')
|
||||
->action(function (string $teamId, string $email, array $roles, string $url, string $name, Response $response, Document $project, Document $user, Database $dbForProject, Locale $locale, Mail $mails, Event $events) {
|
||||
->action(function (string $teamId, string $email, string $userId, string $phone, array $roles, string $url, string $name, Response $response, Document $project, Document $user, Database $dbForProject, Locale $locale, Mail $mails, EventPhone $messaging, Event $events) {
|
||||
|
||||
if (empty($userId) && empty($email) && empty($phone)) {
|
||||
throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'At least one of userId, email, or phone is required');
|
||||
}
|
||||
$isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles());
|
||||
$isAppUser = Auth::isAppUser(Authorization::getRoles());
|
||||
|
||||
|
|
@ -336,8 +412,31 @@ App::post('/v1/teams/:teamId/memberships')
|
|||
if ($team->isEmpty()) {
|
||||
throw new Exception(Exception::TEAM_NOT_FOUND);
|
||||
}
|
||||
|
||||
$invitee = $dbForProject->findOne('users', [Query::equal('email', [$email])]); // Get user by email address
|
||||
if (!empty($userId)) {
|
||||
$invitee = $dbForProject->getDocument('users', $userId);
|
||||
if ($invitee->isEmpty()) {
|
||||
throw new Exception(Exception::USER_NOT_FOUND, 'User with given userId doesn\'t exist.', 404);
|
||||
}
|
||||
if (!empty($email) && $invitee->getAttribute('email', '') != $email) {
|
||||
throw new Exception(Exception::USER_ALREADY_EXISTS, 'Given userId and email doesn\'t match', 409);
|
||||
}
|
||||
if (!empty($phone) && $invitee->getAttribute('phone', '') != $phone) {
|
||||
throw new Exception(Exception::USER_ALREADY_EXISTS, 'Given userId and phone doesn\'t match', 409);
|
||||
}
|
||||
$email = $invitee->getAttribute('email', '');
|
||||
$phone = $invitee->getAttribute('phone', '');
|
||||
$name = empty($name) ? $invitee->getAttribute('name', '') : $name;
|
||||
} elseif (!empty($email)) {
|
||||
$invitee = $dbForProject->findOne('users', [Query::equal('email', [$email])]); // Get user by email address
|
||||
if (!empty($invitee) && !empty($phone) && $invitee->getAttribute('phone', '') != $phone) {
|
||||
throw new Exception(Exception::USER_ALREADY_EXISTS, 'Given email and phone doesn\'t match', 409);
|
||||
}
|
||||
} elseif (!empty($phone)) {
|
||||
$invitee = $dbForProject->findOne('users', [Query::equal('phone', [$phone])]);
|
||||
if (!empty($invitee) && !empty($email) && $invitee->getAttribute('email', '') != $email) {
|
||||
throw new Exception(Exception::USER_ALREADY_EXISTS, 'Given phone and email doesn\'t match', 409);
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($invitee)) { // Create new user if no user with same email found
|
||||
$limit = $project->getAttribute('auths', [])['limit'] ?? 0;
|
||||
|
|
@ -360,7 +459,8 @@ App::post('/v1/teams/:teamId/memberships')
|
|||
Permission::update(Role::user($userId)),
|
||||
Permission::delete(Role::user($userId)),
|
||||
],
|
||||
'email' => $email,
|
||||
'email' => empty($email) ? null : $email,
|
||||
'phone' => empty($phone) ? null : $phone,
|
||||
'emailVerification' => false,
|
||||
'status' => true,
|
||||
'password' => Auth::passwordHash(Auth::passwordGenerator(), Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS),
|
||||
|
|
@ -432,46 +532,50 @@ App::post('/v1/teams/:teamId/memberships')
|
|||
} catch (Duplicate $th) {
|
||||
throw new Exception(Exception::TEAM_INVITE_ALREADY_EXISTS);
|
||||
}
|
||||
}
|
||||
|
||||
$url = Template::parseURL($url);
|
||||
$url['query'] = Template::mergeQuery(((isset($url['query'])) ? $url['query'] : ''), ['membershipId' => $membership->getId(), 'userId' => $invitee->getId(), 'secret' => $secret, 'teamId' => $teamId]);
|
||||
$url = Template::unParseURL($url);
|
||||
$url = Template::parseURL($url);
|
||||
$url['query'] = Template::mergeQuery(((isset($url['query'])) ? $url['query'] : ''), ['membershipId' => $membership->getId(), 'userId' => $invitee->getId(), 'secret' => $secret, 'teamId' => $teamId]);
|
||||
$url = Template::unParseURL($url);
|
||||
if (!empty($email)) {
|
||||
$projectName = $project->isEmpty() ? 'Console' : $project->getAttribute('name', '[APP-NAME]');
|
||||
|
||||
if (!$isPrivilegedUser && !$isAppUser) { // No need of confirmation when in admin or app mode
|
||||
$projectName = $project->isEmpty() ? 'Console' : $project->getAttribute('name', '[APP-NAME]');
|
||||
$from = $project->isEmpty() || $project->getId() === 'console' ? '' : \sprintf($locale->getText('emails.sender'), $projectName);
|
||||
$body = Template::fromFile(__DIR__ . '/../../config/locale/templates/email-base.tpl');
|
||||
$subject = \sprintf($locale->getText("emails.invitation.subject"), $team->getAttribute('name'), $projectName);
|
||||
$body->setParam('{{owner}}', $user->getAttribute('name'));
|
||||
$body->setParam('{{team}}', $team->getAttribute('name'));
|
||||
|
||||
$from = $project->isEmpty() || $project->getId() === 'console' ? '' : \sprintf($locale->getText('emails.sender'), $projectName);
|
||||
$body = Template::fromFile(__DIR__ . '/../../config/locale/templates/email-base.tpl');
|
||||
$subject = \sprintf($locale->getText("emails.invitation.subject"), $team->getAttribute('name'), $projectName);
|
||||
$body->setParam('{{owner}}', $user->getAttribute('name'));
|
||||
$body->setParam('{{team}}', $team->getAttribute('name'));
|
||||
$body
|
||||
->setParam('{{subject}}', $subject)
|
||||
->setParam('{{hello}}', $locale->getText("emails.invitation.hello"))
|
||||
->setParam('{{name}}', $user->getAttribute('name'))
|
||||
->setParam('{{body}}', $locale->getText("emails.invitation.body"))
|
||||
->setParam('{{redirect}}', $url)
|
||||
->setParam('{{footer}}', $locale->getText("emails.invitation.footer"))
|
||||
->setParam('{{thanks}}', $locale->getText("emails.invitation.thanks"))
|
||||
->setParam('{{signature}}', $locale->getText("emails.invitation.signature"))
|
||||
->setParam('{{project}}', $projectName)
|
||||
->setParam('{{direction}}', $locale->getText('settings.direction'))
|
||||
->setParam('{{bg-body}}', '#f7f7f7')
|
||||
->setParam('{{bg-content}}', '#ffffff')
|
||||
->setParam('{{text-content}}', '#000000');
|
||||
|
||||
$body
|
||||
->setParam('{{subject}}', $subject)
|
||||
->setParam('{{hello}}', $locale->getText("emails.invitation.hello"))
|
||||
->setParam('{{name}}', $user->getAttribute('name'))
|
||||
->setParam('{{body}}', $locale->getText("emails.invitation.body"))
|
||||
->setParam('{{redirect}}', $url)
|
||||
->setParam('{{footer}}', $locale->getText("emails.invitation.footer"))
|
||||
->setParam('{{thanks}}', $locale->getText("emails.invitation.thanks"))
|
||||
->setParam('{{signature}}', $locale->getText("emails.invitation.signature"))
|
||||
->setParam('{{project}}', $projectName)
|
||||
->setParam('{{direction}}', $locale->getText('settings.direction'))
|
||||
->setParam('{{bg-body}}', '#f7f7f7')
|
||||
->setParam('{{bg-content}}', '#ffffff')
|
||||
->setParam('{{text-content}}', '#000000');
|
||||
$body = $body->render();
|
||||
|
||||
$body = $body->render();
|
||||
|
||||
$mails
|
||||
->setSubject($subject)
|
||||
->setBody($body)
|
||||
->setFrom($from)
|
||||
->setRecipient($invitee->getAttribute('email'))
|
||||
->setName($invitee->getAttribute('name'))
|
||||
->trigger()
|
||||
;
|
||||
$mails
|
||||
->setSubject($subject)
|
||||
->setBody($body)
|
||||
->setFrom($from)
|
||||
->setRecipient($invitee->getAttribute('email'))
|
||||
->setName($invitee->getAttribute('name'))
|
||||
->trigger()
|
||||
;
|
||||
} elseif (!empty($phone)) {
|
||||
$messaging
|
||||
->setRecipient($phone)
|
||||
->setMessage($url)
|
||||
->trigger();
|
||||
}
|
||||
}
|
||||
|
||||
$events
|
||||
|
|
|
|||
|
|
@ -34,11 +34,14 @@ use Utopia\Validator\Text;
|
|||
use Utopia\Validator\Boolean;
|
||||
use MaxMind\Db\Reader;
|
||||
use Utopia\Validator\Integer;
|
||||
use Appwrite\Auth\Validator\PasswordHistory;
|
||||
use Appwrite\Auth\Validator\PasswordDictionary;
|
||||
|
||||
/** TODO: Remove function when we move to using utopia/platform */
|
||||
function createUser(string $hash, mixed $hashOptions, string $userId, ?string $email, ?string $password, ?string $phone, string $name, Database $dbForProject, Event $events): Document
|
||||
function createUser(string $hash, mixed $hashOptions, string $userId, ?string $email, ?string $password, ?string $phone, string $name, Document $project, Database $dbForProject, Event $events): Document
|
||||
{
|
||||
$hashOptionsObject = (\is_string($hashOptions)) ? \json_decode($hashOptions, true) : $hashOptions; // Cast to JSON array
|
||||
$passwordHistory = $project->getAttribute('auths', [])['passwordHistory'] ?? 0;
|
||||
|
||||
if (!empty($email)) {
|
||||
$email = \strtolower($email);
|
||||
|
|
@ -49,6 +52,7 @@ function createUser(string $hash, mixed $hashOptions, string $userId, ?string $e
|
|||
? ID::unique()
|
||||
: ID::custom($userId);
|
||||
|
||||
$password = (!empty($password)) ? ($hash === 'plaintext' ? Auth::passwordHash($password, $hash, $hashOptionsObject) : $password) : null;
|
||||
$user = $dbForProject->createDocument('users', new Document([
|
||||
'$id' => $userId,
|
||||
'$permissions' => [
|
||||
|
|
@ -61,10 +65,11 @@ function createUser(string $hash, mixed $hashOptions, string $userId, ?string $e
|
|||
'phone' => $phone,
|
||||
'phoneVerification' => false,
|
||||
'status' => true,
|
||||
'password' => (!empty($password)) ? ($hash === 'plaintext' ? Auth::passwordHash($password, $hash, $hashOptionsObject) : $password) : null,
|
||||
'password' => $password,
|
||||
'passwordHistory' => is_null($password) && $passwordHistory === 0 ? [] : [$password],
|
||||
'passwordUpdate' => (!empty($password)) ? DateTime::now() : null,
|
||||
'hash' => $hash === 'plaintext' ? Auth::DEFAULT_ALGO : $hash,
|
||||
'hashOptions' => $hash === 'plaintext' ? Auth::DEFAULT_ALGO_OPTIONS : $hashOptionsObject + ['type' => $hash],
|
||||
'passwordUpdate' => (!empty($password)) ? DateTime::now() : null,
|
||||
'registration' => DateTime::now(),
|
||||
'reset' => false,
|
||||
'name' => $name,
|
||||
|
|
@ -101,13 +106,15 @@ App::post('/v1/users')
|
|||
->param('userId', '', new CustomId(), 'User 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('email', null, new Email(), 'User email.', true)
|
||||
->param('phone', null, new Phone(), 'Phone number. Format this number with a leading \'+\' and a country code, e.g., +16175551212.', true)
|
||||
->param('password', null, new Password(), 'Plain text user password. Must be at least 8 chars.', true)
|
||||
->param('password', '', fn ($project, $passwordsDictionary) => new PasswordDictionary($passwordsDictionary, $project->getAttribute('auths', [])['passwordDictionary'] ?? false), 'Plain text user password. Must be at least 8 chars.', true, ['project', 'passwordsDictionary'])
|
||||
->param('name', '', new Text(128), 'User name. Max length: 128 chars.', true)
|
||||
->inject('response')
|
||||
->inject('project')
|
||||
->inject('dbForProject')
|
||||
->inject('events')
|
||||
->action(function (string $userId, ?string $email, ?string $phone, ?string $password, string $name, Response $response, Database $dbForProject, Event $events) {
|
||||
$user = createUser('plaintext', '{}', $userId, $email, $password, $phone, $name, $dbForProject, $events);
|
||||
->action(function (string $userId, ?string $email, ?string $phone, ?string $password, string $name, Response $response, Document $project, Database $dbForProject, Event $events) {
|
||||
|
||||
$user = createUser('plaintext', '{}', $userId, $email, $password, $phone, $name, $project, $dbForProject, $events);
|
||||
|
||||
$response
|
||||
->setStatusCode(Response::STATUS_CODE_CREATED)
|
||||
|
|
@ -134,10 +141,11 @@ App::post('/v1/users/bcrypt')
|
|||
->param('password', '', new Password(), 'User password hashed using Bcrypt.')
|
||||
->param('name', '', new Text(128), 'User name. Max length: 128 chars.', true)
|
||||
->inject('response')
|
||||
->inject('project')
|
||||
->inject('dbForProject')
|
||||
->inject('events')
|
||||
->action(function (string $userId, string $email, string $password, string $name, Response $response, Database $dbForProject, Event $events) {
|
||||
$user = createUser('bcrypt', '{}', $userId, $email, $password, null, $name, $dbForProject, $events);
|
||||
->action(function (string $userId, string $email, string $password, string $name, Response $response, Document $project, Database $dbForProject, Event $events) {
|
||||
$user = createUser('bcrypt', '{}', $userId, $email, $password, null, $name, $project, $dbForProject, $events);
|
||||
|
||||
$response
|
||||
->setStatusCode(Response::STATUS_CODE_CREATED)
|
||||
|
|
@ -164,10 +172,11 @@ App::post('/v1/users/md5')
|
|||
->param('password', '', new Password(), 'User password hashed using MD5.')
|
||||
->param('name', '', new Text(128), 'User name. Max length: 128 chars.', true)
|
||||
->inject('response')
|
||||
->inject('project')
|
||||
->inject('dbForProject')
|
||||
->inject('events')
|
||||
->action(function (string $userId, string $email, string $password, string $name, Response $response, Database $dbForProject, Event $events) {
|
||||
$user = createUser('md5', '{}', $userId, $email, $password, null, $name, $dbForProject, $events);
|
||||
->action(function (string $userId, string $email, string $password, string $name, Response $response, Document $project, Database $dbForProject, Event $events) {
|
||||
$user = createUser('md5', '{}', $userId, $email, $password, null, $name, $project, $dbForProject, $events);
|
||||
|
||||
$response
|
||||
->setStatusCode(Response::STATUS_CODE_CREATED)
|
||||
|
|
@ -194,10 +203,11 @@ App::post('/v1/users/argon2')
|
|||
->param('password', '', new Password(), 'User password hashed using Argon2.')
|
||||
->param('name', '', new Text(128), 'User name. Max length: 128 chars.', true)
|
||||
->inject('response')
|
||||
->inject('project')
|
||||
->inject('dbForProject')
|
||||
->inject('events')
|
||||
->action(function (string $userId, string $email, string $password, string $name, Response $response, Database $dbForProject, Event $events) {
|
||||
$user = createUser('argon2', '{}', $userId, $email, $password, null, $name, $dbForProject, $events);
|
||||
->action(function (string $userId, string $email, string $password, string $name, Response $response, Document $project, Database $dbForProject, Event $events) {
|
||||
$user = createUser('argon2', '{}', $userId, $email, $password, null, $name, $project, $dbForProject, $events);
|
||||
|
||||
$response
|
||||
->setStatusCode(Response::STATUS_CODE_CREATED)
|
||||
|
|
@ -225,16 +235,17 @@ App::post('/v1/users/sha')
|
|||
->param('passwordVersion', '', new WhiteList(['sha1', 'sha224', 'sha256', 'sha384', 'sha512/224', 'sha512/256', 'sha512', 'sha3-224', 'sha3-256', 'sha3-384', 'sha3-512']), "Optional SHA version used to hash password. Allowed values are: 'sha1', 'sha224', 'sha256', 'sha384', 'sha512/224', 'sha512/256', 'sha512', 'sha3-224', 'sha3-256', 'sha3-384', 'sha3-512'", true)
|
||||
->param('name', '', new Text(128), 'User name. Max length: 128 chars.', true)
|
||||
->inject('response')
|
||||
->inject('project')
|
||||
->inject('dbForProject')
|
||||
->inject('events')
|
||||
->action(function (string $userId, string $email, string $password, string $passwordVersion, string $name, Response $response, Database $dbForProject, Event $events) {
|
||||
->action(function (string $userId, string $email, string $password, string $passwordVersion, string $name, Response $response, Document $project, Database $dbForProject, Event $events) {
|
||||
$options = '{}';
|
||||
|
||||
if (!empty($passwordVersion)) {
|
||||
$options = '{"version":"' . $passwordVersion . '"}';
|
||||
}
|
||||
|
||||
$user = createUser('sha', $options, $userId, $email, $password, null, $name, $dbForProject, $events);
|
||||
$user = createUser('sha', $options, $userId, $email, $password, null, $name, $project, $dbForProject, $events);
|
||||
|
||||
$response
|
||||
->setStatusCode(Response::STATUS_CODE_CREATED)
|
||||
|
|
@ -261,10 +272,11 @@ App::post('/v1/users/phpass')
|
|||
->param('password', '', new Password(), 'User password hashed using PHPass.')
|
||||
->param('name', '', new Text(128), 'User name. Max length: 128 chars.', true)
|
||||
->inject('response')
|
||||
->inject('project')
|
||||
->inject('dbForProject')
|
||||
->inject('events')
|
||||
->action(function (string $userId, string $email, string $password, string $name, Response $response, Database $dbForProject, Event $events) {
|
||||
$user = createUser('phpass', '{}', $userId, $email, $password, null, $name, $dbForProject, $events);
|
||||
->action(function (string $userId, string $email, string $password, string $name, Response $response, Document $project, Database $dbForProject, Event $events) {
|
||||
$user = createUser('phpass', '{}', $userId, $email, $password, null, $name, $project, $dbForProject, $events);
|
||||
|
||||
$response
|
||||
->setStatusCode(Response::STATUS_CODE_CREATED)
|
||||
|
|
@ -296,9 +308,10 @@ App::post('/v1/users/scrypt')
|
|||
->param('passwordLength', 64, new Integer(), 'Optional hash length used to hash password.')
|
||||
->param('name', '', new Text(128), 'User name. Max length: 128 chars.', true)
|
||||
->inject('response')
|
||||
->inject('project')
|
||||
->inject('dbForProject')
|
||||
->inject('events')
|
||||
->action(function (string $userId, string $email, string $password, string $passwordSalt, int $passwordCpu, int $passwordMemory, int $passwordParallel, int $passwordLength, string $name, Response $response, Database $dbForProject, Event $events) {
|
||||
->action(function (string $userId, string $email, string $password, string $passwordSalt, int $passwordCpu, int $passwordMemory, int $passwordParallel, int $passwordLength, string $name, Response $response, Document $project, Database $dbForProject, Event $events) {
|
||||
$options = [
|
||||
'salt' => $passwordSalt,
|
||||
'costCpu' => $passwordCpu,
|
||||
|
|
@ -307,7 +320,7 @@ App::post('/v1/users/scrypt')
|
|||
'length' => $passwordLength
|
||||
];
|
||||
|
||||
$user = createUser('scrypt', \json_encode($options), $userId, $email, $password, null, $name, $dbForProject, $events);
|
||||
$user = createUser('scrypt', \json_encode($options), $userId, $email, $password, null, $name, $project, $dbForProject, $events);
|
||||
|
||||
$response
|
||||
->setStatusCode(Response::STATUS_CODE_CREATED)
|
||||
|
|
@ -337,10 +350,11 @@ App::post('/v1/users/scrypt-modified')
|
|||
->param('passwordSignerKey', '', new Text(128), 'Signer key used to hash password.')
|
||||
->param('name', '', new Text(128), 'User name. Max length: 128 chars.', true)
|
||||
->inject('response')
|
||||
->inject('project')
|
||||
->inject('dbForProject')
|
||||
->inject('events')
|
||||
->action(function (string $userId, string $email, string $password, string $passwordSalt, string $passwordSaltSeparator, string $passwordSignerKey, string $name, Response $response, Database $dbForProject, Event $events) {
|
||||
$user = createUser('scryptMod', '{"signerKey":"' . $passwordSignerKey . '","saltSeparator":"' . $passwordSaltSeparator . '","salt":"' . $passwordSalt . '"}', $userId, $email, $password, null, $name, $dbForProject, $events);
|
||||
->action(function (string $userId, string $email, string $password, string $passwordSalt, string $passwordSaltSeparator, string $passwordSignerKey, string $name, Response $response, Document $project, Database $dbForProject, Event $events) {
|
||||
$user = createUser('scryptMod', '{"signerKey":"' . $passwordSignerKey . '","saltSeparator":"' . $passwordSaltSeparator . '","salt":"' . $passwordSalt . '"}', $userId, $email, $password, null, $name, $project, $dbForProject, $events);
|
||||
|
||||
$response
|
||||
->setStatusCode(Response::STATUS_CODE_CREATED)
|
||||
|
|
@ -779,11 +793,12 @@ App::patch('/v1/users/:userId/password')
|
|||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_USER)
|
||||
->param('userId', '', new UID(), 'User ID.')
|
||||
->param('password', '', new Password(), 'New user password. Must be at least 8 chars.')
|
||||
->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'])
|
||||
->inject('response')
|
||||
->inject('project')
|
||||
->inject('dbForProject')
|
||||
->inject('events')
|
||||
->action(function (string $userId, string $password, Response $response, Database $dbForProject, Event $events) {
|
||||
->action(function (string $userId, string $password, Response $response, Document $project, Database $dbForProject, Event $events) {
|
||||
|
||||
$user = $dbForProject->getDocument('users', $userId);
|
||||
|
||||
|
|
@ -791,11 +806,27 @@ App::patch('/v1/users/:userId/password')
|
|||
throw new Exception(Exception::USER_NOT_FOUND);
|
||||
}
|
||||
|
||||
$newPassword = Auth::passwordHash($password, Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS);
|
||||
|
||||
$historyLimit = $project->getAttribute('auths', [])['passwordHistory'] ?? 0;
|
||||
$history = [];
|
||||
if ($historyLimit > 0) {
|
||||
$history = $user->getAttribute('passwordHistory', []);
|
||||
$validator = new PasswordHistory($history, $user->getAttribute('hash'), $user->getAttribute('hashOptions'));
|
||||
if (!$validator->isValid($password)) {
|
||||
throw new Exception(Exception::USER_PASSWORD_RECENTLY_USED, 'The password was recently used', 409);
|
||||
}
|
||||
|
||||
$history[] = $newPassword;
|
||||
array_slice($history, (count($history) - $historyLimit), $historyLimit);
|
||||
}
|
||||
|
||||
$user
|
||||
->setAttribute('password', Auth::passwordHash($password, Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS))
|
||||
->setAttribute('password', $newPassword)
|
||||
->setAttribute('passwordHistory', $history)
|
||||
->setAttribute('passwordUpdate', DateTime::now())
|
||||
->setAttribute('hash', Auth::DEFAULT_ALGO)
|
||||
->setAttribute('hashOptions', Auth::DEFAULT_ALGO_OPTIONS)
|
||||
->setAttribute('passwordUpdate', DateTime::now());
|
||||
->setAttribute('hashOptions', Auth::DEFAULT_ALGO_OPTIONS);
|
||||
|
||||
$user = $dbForProject->updateDocument('users', $user->getId(), $user);
|
||||
|
||||
|
|
|
|||
|
|
@ -233,7 +233,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-SDK-Version, X-SDK-Name, X-SDK-Language, X-SDK-Platform, X-SDK-GraphQL, X-Appwrite-ID, Content-Range, Range, Cache-Control, Expires, Pragma')
|
||||
->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-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')
|
||||
->addHeader('Access-Control-Expose-Headers', 'X-Fallback-Cookies')
|
||||
->addHeader('Access-Control-Allow-Origin', $refDomain)
|
||||
->addHeader('Access-Control-Allow-Credentials', 'true')
|
||||
|
|
@ -384,7 +384,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-SDK-Version, X-SDK-Name, X-SDK-Language, X-SDK-Platform, X-SDK-GraphQL, X-Appwrite-ID, Content-Range, Range, Cache-Control, Expires, Pragma, X-Fallback-Cookies')
|
||||
->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-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-Fallback-Cookies')
|
||||
->addHeader('Access-Control-Expose-Headers', 'X-Fallback-Cookies')
|
||||
->addHeader('Access-Control-Allow-Origin', $origin)
|
||||
->addHeader('Access-Control-Allow-Credentials', 'true')
|
||||
|
|
@ -485,6 +485,10 @@ App::error()
|
|||
$error->setType(AppwriteException::GENERAL_ROUTE_NOT_FOUND);
|
||||
break;
|
||||
}
|
||||
} elseif ($error instanceof Utopia\Database\Exception\Conflict) {
|
||||
$error = new AppwriteException(AppwriteException::DOCUMENT_UPDATE_CONFLICT, null, null, $error);
|
||||
$code = $error->getCode();
|
||||
$message = $error->getMessage();
|
||||
}
|
||||
|
||||
/** Wrap all exceptions inside Appwrite\Extend\Exception */
|
||||
|
|
@ -590,7 +594,7 @@ App::get('/.well-known/acme-challenge')
|
|||
$uriChunks = \explode('/', $request->getURI());
|
||||
$token = $uriChunks[\count($uriChunks) - 1];
|
||||
|
||||
$validator = new Text(100, [
|
||||
$validator = new Text(100, allowList: [
|
||||
...Text::NUMBERS,
|
||||
...Text::ALPHABET_LOWER,
|
||||
...Text::ALPHABET_UPPER,
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ use Utopia\Validator\Text;
|
|||
use Utopia\Storage\Validator\File;
|
||||
use Utopia\Validator\WhiteList;
|
||||
use Utopia\Database\Helpers\ID;
|
||||
use Utopia\Validator\Nullable;
|
||||
|
||||
App::get('/v1/mock/tests/foo')
|
||||
->desc('Get Foo')
|
||||
|
|
@ -428,6 +429,21 @@ App::get('/v1/mock/tests/general/empty')
|
|||
$response->noContent();
|
||||
});
|
||||
|
||||
App::post('/v1/mock/tests/general/nullable')
|
||||
->desc('Nullable Test')
|
||||
->groups(['mock'])
|
||||
->label('scope', 'public')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
|
||||
->label('sdk.namespace', 'general')
|
||||
->label('sdk.method', 'nullable')
|
||||
->label('sdk.description', 'Mock a nullable parameter.')
|
||||
->label('sdk.mock', true)
|
||||
->param('required', '', new Text(100), 'Sample string param')
|
||||
->param('nullable', '', new Nullable(new Text(100)), 'Sample string param')
|
||||
->param('optional', '', new Text(100), 'Sample string param', true)
|
||||
->action(function (string $required, string $nullable, ?string $optional) {
|
||||
});
|
||||
|
||||
/** Endpoint to test if required headers are sent from the SDK */
|
||||
App::get('/v1/mock/tests/general/headers')
|
||||
->desc('Get headers')
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ use Appwrite\Event\Audit;
|
|||
use Appwrite\Event\Database as EventDatabase;
|
||||
use Appwrite\Event\Delete;
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Event\Mail;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Messaging\Adapter\Realtime;
|
||||
use Appwrite\Usage\Stats;
|
||||
|
|
@ -16,7 +15,6 @@ use Utopia\Abuse\Abuse;
|
|||
use Utopia\Abuse\Adapters\TimeLimit;
|
||||
use Utopia\Cache\Adapter\Filesystem;
|
||||
use Utopia\Cache\Cache;
|
||||
use Utopia\CLI\Console;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\DateTime;
|
||||
use Utopia\Database\Document;
|
||||
|
|
|
|||
|
|
@ -165,14 +165,14 @@ App::post('/v1/runtimes')
|
|||
->desc("Create a new runtime server")
|
||||
->param('runtimeId', '', new Text(64), 'Unique runtime ID.')
|
||||
->param('source', '', new Text(0), 'Path to source files.')
|
||||
->param('destination', '', new Text(0), 'Destination folder to store build files into.', true)
|
||||
->param('destination', '', new Text(0, 0), 'Destination folder to store build files into.', true)
|
||||
->param('vars', [], new Assoc(), 'Environment Variables required for the build.')
|
||||
->param('commands', [], new ArrayList(new Text(1024), 100), 'Commands required to build the container. Maximum of 100 commands are allowed, each 1024 characters long.')
|
||||
->param('runtime', '', new Text(128), 'Runtime for the cloud function.')
|
||||
->param('baseImage', '', new Text(128), 'Base image name of the runtime.')
|
||||
->param('entrypoint', '', new Text(256), 'Entrypoint of the code file.', true)
|
||||
->param('remove', false, new Boolean(), 'Remove a runtime after execution.')
|
||||
->param('workdir', '', new Text(256), 'Working directory.', true)
|
||||
->param('workdir', '', new Text(256, 0), 'Working directory.', true)
|
||||
->inject('orchestrationPool')
|
||||
->inject('activeRuntimes')
|
||||
->inject('response')
|
||||
|
|
@ -459,7 +459,7 @@ App::post('/v1/execution')
|
|||
->desc('Create an execution')
|
||||
->param('runtimeId', '', new Text(64), 'The runtimeID to execute.')
|
||||
->param('vars', [], new Assoc(), 'Environment variables required for the build.')
|
||||
->param('data', '', new Text(8192), 'Data to be forwarded to the function, this is user specified.', true)
|
||||
->param('data', '', new Text(8192, 0), 'Data to be forwarded to the function, this is user specified.', true)
|
||||
->param('timeout', 15, new Range(1, (int) App::getEnv('_APP_FUNCTIONS_TIMEOUT', 900)), 'Function maximum execution time in seconds.')
|
||||
->inject('activeRuntimes')
|
||||
->inject('response')
|
||||
|
|
|
|||
|
|
@ -92,7 +92,6 @@ $http->on('start', function (Server $http) use ($payloadSize, $register) {
|
|||
$collections = Config::getParam('collections', []);
|
||||
|
||||
try {
|
||||
$redis->flushAll();
|
||||
Console::success('[Setup] - Creating database: appwrite...');
|
||||
$dbForConsole->create();
|
||||
} catch (\Exception $e) {
|
||||
|
|
|
|||
28
app/init.php
28
app/init.php
|
|
@ -53,7 +53,7 @@ use Utopia\Database\Database;
|
|||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Database\Validator\DatetimeValidator;
|
||||
use Utopia\Database\Validator\Datetime as DatetimeValidator;
|
||||
use Utopia\Database\Validator\Structure;
|
||||
use Utopia\Locale\Locale;
|
||||
use Utopia\Messaging\Adapters\SMS\Mock;
|
||||
|
|
@ -86,6 +86,7 @@ const APP_MODE_ADMIN = 'admin';
|
|||
const APP_PAGING_LIMIT = 12;
|
||||
const APP_LIMIT_COUNT = 5000;
|
||||
const APP_LIMIT_USERS = 10000;
|
||||
const APP_LIMIT_USER_PASSWORD_HISTORY = 20;
|
||||
const APP_LIMIT_USER_SESSIONS_MAX = 100;
|
||||
const APP_LIMIT_USER_SESSIONS_DEFAULT = 10;
|
||||
const APP_LIMIT_ANTIVIRUS = 20000000; //20MB
|
||||
|
|
@ -607,6 +608,12 @@ $register->set('smtp', function () {
|
|||
$register->set('geodb', function () {
|
||||
return new Reader(__DIR__ . '/assets/dbip/dbip-country-lite-2023-01.mmdb');
|
||||
});
|
||||
$register->set('passwordsDictionary', function () {
|
||||
$content = \file_get_contents(__DIR__ . '/assets/security/10k-common-passwords');
|
||||
$content = explode("\n", $content);
|
||||
$content = array_flip($content);
|
||||
return $content;
|
||||
});
|
||||
$register->set('db', function () {
|
||||
// This is usually for our workers or CLI commands scope
|
||||
$dbHost = App::getEnv('_APP_DB_HOST', '');
|
||||
|
|
@ -1027,6 +1034,11 @@ App::setResource('geodb', function ($register) {
|
|||
return $register->get('geodb');
|
||||
}, ['register']);
|
||||
|
||||
App::setResource('passwordsDictionary', function ($register) {
|
||||
/** @var Utopia\Registry\Registry $register */
|
||||
return $register->get('passwordsDictionary');
|
||||
}, ['register']);
|
||||
|
||||
App::setResource('sms', function () {
|
||||
$dsn = new DSN(App::getEnv('_APP_SMS_PROVIDER'));
|
||||
$user = $dsn->getUser();
|
||||
|
|
@ -1143,3 +1155,17 @@ App::setResource('schema', function ($utopia, $dbForProject) {
|
|||
$params,
|
||||
);
|
||||
}, ['utopia', 'dbForProject']);
|
||||
|
||||
App::setResource('requestTimestamp', function ($request) {
|
||||
// Validate x-appwrite-timestamp header
|
||||
$timestampHeader = $request->getHeader('x-appwrite-timestamp');
|
||||
$requestTimestamp = null;
|
||||
if (!empty($timestampHeader)) {
|
||||
try {
|
||||
$requestTimestamp = new \DateTime($timestampHeader);
|
||||
} catch (\Throwable $e) {
|
||||
throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'Invalid X-Appwrite-Timestamp header value');
|
||||
}
|
||||
}
|
||||
return $requestTimestamp;
|
||||
}, ['request']);
|
||||
|
|
|
|||
|
|
@ -43,21 +43,21 @@
|
|||
"ext-sockets": "*",
|
||||
"appwrite/php-clamav": "1.1.*",
|
||||
"appwrite/php-runtimes": "0.11.*",
|
||||
"utopia-php/abuse": "0.18.*",
|
||||
"utopia-php/abuse": "0.23.*",
|
||||
"utopia-php/analytics": "0.2.*",
|
||||
"utopia-php/audit": "0.20.*",
|
||||
"utopia-php/audit": "0.24.*",
|
||||
"utopia-php/cache": "0.8.*",
|
||||
"utopia-php/cli": "0.13.*",
|
||||
"utopia-php/config": "0.2.*",
|
||||
"utopia-php/database": "0.30.*",
|
||||
"utopia-php/preloader": "0.2.*",
|
||||
"utopia-php/database": "0.34.*",
|
||||
"utopia-php/domains": "1.1.*",
|
||||
"utopia-php/framework": "0.26.*",
|
||||
"utopia-php/framework": "0.28.*",
|
||||
"utopia-php/image": "0.5.*",
|
||||
"utopia-php/locale": "0.4.*",
|
||||
"utopia-php/logger": "0.3.*",
|
||||
"utopia-php/messaging": "0.1.*",
|
||||
"utopia-php/orchestration": "0.6.*",
|
||||
"utopia-php/preloader": "0.2.*",
|
||||
"utopia-php/registry": "0.5.*",
|
||||
"utopia-php/storage": "0.14.*",
|
||||
"utopia-php/swoole": "0.5.*",
|
||||
|
|
|
|||
85
composer.lock
generated
85
composer.lock
generated
|
|
@ -1808,29 +1808,28 @@
|
|||
},
|
||||
{
|
||||
"name": "utopia-php/abuse",
|
||||
"version": "0.18.0",
|
||||
"version": "0.23.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/utopia-php/abuse.git",
|
||||
"reference": "8496401234f73a49f8c4259d3e89ab4a7c1f9ecf"
|
||||
"reference": "4319461f4dde080b9dffaceea048f9e4f8949a52"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/utopia-php/abuse/zipball/8496401234f73a49f8c4259d3e89ab4a7c1f9ecf",
|
||||
"reference": "8496401234f73a49f8c4259d3e89ab4a7c1f9ecf",
|
||||
"url": "https://api.github.com/repos/utopia-php/abuse/zipball/4319461f4dde080b9dffaceea048f9e4f8949a52",
|
||||
"reference": "4319461f4dde080b9dffaceea048f9e4f8949a52",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-curl": "*",
|
||||
"ext-pdo": "*",
|
||||
"php": ">=8.0",
|
||||
"utopia-php/database": "0.30.*"
|
||||
"utopia-php/database": "0.34.*"
|
||||
},
|
||||
"require-dev": {
|
||||
"laravel/pint": "1.2.*",
|
||||
"phpstan/phpstan": "1.9.x-dev",
|
||||
"phpunit/phpunit": "^9.4",
|
||||
"vimeo/psalm": "4.0.1"
|
||||
"phpstan/phpstan": "^1.9",
|
||||
"phpunit/phpunit": "^9.4"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
|
|
@ -1852,9 +1851,9 @@
|
|||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/utopia-php/abuse/issues",
|
||||
"source": "https://github.com/utopia-php/abuse/tree/0.18.0"
|
||||
"source": "https://github.com/utopia-php/abuse/tree/0.23.0"
|
||||
},
|
||||
"time": "2023-02-14T09:56:04+00:00"
|
||||
"time": "2023-03-31T07:54:08+00:00"
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/analytics",
|
||||
|
|
@ -1913,28 +1912,26 @@
|
|||
},
|
||||
{
|
||||
"name": "utopia-php/audit",
|
||||
"version": "0.20.0",
|
||||
"version": "0.24.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/utopia-php/audit.git",
|
||||
"reference": "3fce3f4ad3ea9dfcb39b79668abd76331412a5ed"
|
||||
"reference": "237538b618506bbc0efc0e7884d49cdf52c20004"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/utopia-php/audit/zipball/3fce3f4ad3ea9dfcb39b79668abd76331412a5ed",
|
||||
"reference": "3fce3f4ad3ea9dfcb39b79668abd76331412a5ed",
|
||||
"url": "https://api.github.com/repos/utopia-php/audit/zipball/237538b618506bbc0efc0e7884d49cdf52c20004",
|
||||
"reference": "237538b618506bbc0efc0e7884d49cdf52c20004",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-pdo": "*",
|
||||
"php": ">=8.0",
|
||||
"utopia-php/database": "0.30.*"
|
||||
"utopia-php/database": "0.34.*"
|
||||
},
|
||||
"require-dev": {
|
||||
"laravel/pint": "1.2.*",
|
||||
"phpstan/phpstan": "^1.8",
|
||||
"phpunit/phpunit": "^9.3",
|
||||
"vimeo/psalm": "4.0.1"
|
||||
"phpunit/phpunit": "^9.3"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
|
|
@ -1956,9 +1953,9 @@
|
|||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/utopia-php/audit/issues",
|
||||
"source": "https://github.com/utopia-php/audit/tree/0.20.0"
|
||||
"source": "https://github.com/utopia-php/audit/tree/0.24.1"
|
||||
},
|
||||
"time": "2023-02-14T09:46:54+00:00"
|
||||
"time": "2023-03-23T07:29:11+00:00"
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/cache",
|
||||
|
|
@ -2115,33 +2112,37 @@
|
|||
},
|
||||
{
|
||||
"name": "utopia-php/database",
|
||||
"version": "0.30.1",
|
||||
"version": "0.34.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/utopia-php/database.git",
|
||||
"reference": "1cea72c1217357bf0747ae4f28ebef57e9dc0e65"
|
||||
"reference": "b592ab1bab9d7412cf5b756fdcdf850c03b1470d"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/utopia-php/database/zipball/1cea72c1217357bf0747ae4f28ebef57e9dc0e65",
|
||||
"reference": "1cea72c1217357bf0747ae4f28ebef57e9dc0e65",
|
||||
"url": "https://api.github.com/repos/utopia-php/database/zipball/b592ab1bab9d7412cf5b756fdcdf850c03b1470d",
|
||||
"reference": "b592ab1bab9d7412cf5b756fdcdf850c03b1470d",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-pdo": "*",
|
||||
"php": ">=8.0",
|
||||
"utopia-php/cache": "0.8.*",
|
||||
"utopia-php/framework": "0.*.*",
|
||||
"utopia-php/mongo": "0.0.2"
|
||||
"utopia-php/mongo": "0.1.*"
|
||||
},
|
||||
"require-dev": {
|
||||
"ext-mongodb": "*",
|
||||
"ext-redis": "*",
|
||||
"fakerphp/faker": "^1.14",
|
||||
"laravel/pint": "1.4.*",
|
||||
"mongodb/mongodb": "1.8.0",
|
||||
"pcov/clobber": "^2.0",
|
||||
"phpstan/phpstan": "1.9.*",
|
||||
"phpunit/phpunit": "^9.4",
|
||||
"rregeer/phpunit-coverage-check": "^0.3.1",
|
||||
"swoole/ide-helper": "4.8.0",
|
||||
"utopia-php/cli": "^0.14.0",
|
||||
"vimeo/psalm": "4.0.1"
|
||||
"utopia-php/cli": "^0.14.0"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
|
|
@ -2153,7 +2154,7 @@
|
|||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"description": "A simple library to manage application persistency using multiple database adapters",
|
||||
"description": "A simple library to manage application persistence using multiple database adapters",
|
||||
"keywords": [
|
||||
"database",
|
||||
"framework",
|
||||
|
|
@ -2163,9 +2164,9 @@
|
|||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/utopia-php/database/issues",
|
||||
"source": "https://github.com/utopia-php/database/tree/0.30.1"
|
||||
"source": "https://github.com/utopia-php/database/tree/0.34.1"
|
||||
},
|
||||
"time": "2023-02-14T06:25:03+00:00"
|
||||
"time": "2023-03-23T07:23:41+00:00"
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/domains",
|
||||
|
|
@ -2223,16 +2224,16 @@
|
|||
},
|
||||
{
|
||||
"name": "utopia-php/framework",
|
||||
"version": "0.26.0",
|
||||
"version": "0.28.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/utopia-php/framework.git",
|
||||
"reference": "e8da5576370366d3bf9c574ec855f8c96fe4f34e"
|
||||
"reference": "7f22c556fc5991e54e5811a68fb39809b21bda55"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/utopia-php/framework/zipball/e8da5576370366d3bf9c574ec855f8c96fe4f34e",
|
||||
"reference": "e8da5576370366d3bf9c574ec855f8c96fe4f34e",
|
||||
"url": "https://api.github.com/repos/utopia-php/framework/zipball/7f22c556fc5991e54e5811a68fb39809b21bda55",
|
||||
"reference": "7f22c556fc5991e54e5811a68fb39809b21bda55",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
|
@ -2261,9 +2262,9 @@
|
|||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/utopia-php/framework/issues",
|
||||
"source": "https://github.com/utopia-php/framework/tree/0.26.0"
|
||||
"source": "https://github.com/utopia-php/framework/tree/0.28.1"
|
||||
},
|
||||
"time": "2023-01-13T08:14:43+00:00"
|
||||
"time": "2023-03-02T08:16:01+00:00"
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/image",
|
||||
|
|
@ -2471,16 +2472,16 @@
|
|||
},
|
||||
{
|
||||
"name": "utopia-php/mongo",
|
||||
"version": "0.0.2",
|
||||
"version": "0.1.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/utopia-php/mongo.git",
|
||||
"reference": "62f9a9c0201af91b6d0dd4f0aa8a335ec9b56a1e"
|
||||
"reference": "f4b6ec74c5323ca16c500dd19109518d2eeb1f8f"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/utopia-php/mongo/zipball/62f9a9c0201af91b6d0dd4f0aa8a335ec9b56a1e",
|
||||
"reference": "62f9a9c0201af91b6d0dd4f0aa8a335ec9b56a1e",
|
||||
"url": "https://api.github.com/repos/utopia-php/mongo/zipball/f4b6ec74c5323ca16c500dd19109518d2eeb1f8f",
|
||||
"reference": "f4b6ec74c5323ca16c500dd19109518d2eeb1f8f",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
|
@ -2525,9 +2526,9 @@
|
|||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/utopia-php/mongo/issues",
|
||||
"source": "https://github.com/utopia-php/mongo/tree/0.0.2"
|
||||
"source": "https://github.com/utopia-php/mongo/tree/0.1.0"
|
||||
},
|
||||
"time": "2022-11-08T11:58:46+00:00"
|
||||
"time": "2023-01-12T14:02:08+00:00"
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/orchestration",
|
||||
|
|
|
|||
1
docs/references/databases/update-email-attribute.md
Normal file
1
docs/references/databases/update-email-attribute.md
Normal file
|
|
@ -0,0 +1 @@
|
|||
Update an email attribute. Changing the `default` value will not update already existing documents.
|
||||
1
docs/references/databases/update-enum-attribute.md
Normal file
1
docs/references/databases/update-enum-attribute.md
Normal file
|
|
@ -0,0 +1 @@
|
|||
Update an enum attribute. Changing the `default` value will not update already existing documents.
|
||||
1
docs/references/databases/update-float-attribute.md
Normal file
1
docs/references/databases/update-float-attribute.md
Normal file
|
|
@ -0,0 +1 @@
|
|||
Update a float attribute. Changing the `default` value will not update already existing documents.
|
||||
1
docs/references/databases/update-integer-attribute.md
Normal file
1
docs/references/databases/update-integer-attribute.md
Normal file
|
|
@ -0,0 +1 @@
|
|||
Update an integer attribute. Changing the `default` value will not update already existing documents.
|
||||
1
docs/references/databases/update-ip-attribute.md
Normal file
1
docs/references/databases/update-ip-attribute.md
Normal file
|
|
@ -0,0 +1 @@
|
|||
Update an ip attribute. Changing the `default` value will not update already existing documents.
|
||||
1
docs/references/databases/update-string-attribute.md
Normal file
1
docs/references/databases/update-string-attribute.md
Normal file
|
|
@ -0,0 +1 @@
|
|||
Update a string attribute. Changing the `default` value will not update already existing documents.
|
||||
1
docs/references/databases/update-url-attribute.md
Normal file
1
docs/references/databases/update-url-attribute.md
Normal file
|
|
@ -0,0 +1 @@
|
|||
Update an url attribute. Changing the `default` value will not update already existing documents.
|
||||
|
|
@ -1,5 +1,7 @@
|
|||
Invite a new member to join your team. If initiated from the client SDK, an email with a link to join the team will be sent to the member's email address and an account will be created for them should they not be signed up already. If initiated from server-side SDKs, the new member will automatically be added to the team.
|
||||
Invite a new member to join your team. Provide an ID for existing users, or invite unregistered users using an email or phone number. If initiated from a Client SDK, Appwrite will send an email or sms with a link to join the team to the invited user, and an account will be created for them if one doesn't exist. If initiated from a Server SDK, the new member will be added automatically to the team.
|
||||
|
||||
Use the 'url' parameter to redirect the user from the invitation email back to your app. When the user is redirected, use the [Update Team Membership Status](/docs/client/teams#teamsUpdateMembershipStatus) endpoint to allow the user to accept the invitation to the team.
|
||||
You only need to provide one of a user ID, email, or phone number. Appwrite will prioritize accepting the user ID > email > phone number if you provide more than one of these parameters.
|
||||
|
||||
Please note that to avoid a [Redirect Attack](https://github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md) the only valid redirect URL's are the once from domains you have set when adding your platforms in the console interface.
|
||||
Use the `url` parameter to redirect the user from the invitation email to your app. After the user is redirected, use the [Update Team Membership Status](/docs/client/teams#teamsUpdateMembershipStatus) endpoint to allow the user to accept the invitation to the team.
|
||||
|
||||
Please note that to avoid a [Redirect Attack](https://github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md) Appwrite will accept the only redirect URLs under the domains you have added as a platform on the Appwrite Console.
|
||||
|
|
|
|||
1
docs/references/teams/get-team-prefs.md
Normal file
1
docs/references/teams/get-team-prefs.md
Normal file
|
|
@ -0,0 +1 @@
|
|||
Get the team's shared preferences by its unique ID. If a preference doesn't need to be shared by all team members, prefer storing them in [user preferences](/docs/client/account#accountGetPrefs).
|
||||
1
docs/references/teams/update-team-name.md
Normal file
1
docs/references/teams/update-team-name.md
Normal file
|
|
@ -0,0 +1 @@
|
|||
Update the team's name by its unique ID.
|
||||
1
docs/references/teams/update-team-prefs.md
Normal file
1
docs/references/teams/update-team-prefs.md
Normal file
|
|
@ -0,0 +1 @@
|
|||
Update the team's preferences by its unique ID. The object you pass is stored as is and replaces any previous value. The maximum allowed prefs size is 64kB and throws an error if exceeded.
|
||||
|
|
@ -1 +0,0 @@
|
|||
Update a team using its ID. Only members with the owner role can update the team.
|
||||
77
src/Appwrite/Auth/Validator/PasswordDictionary.php
Normal file
77
src/Appwrite/Auth/Validator/PasswordDictionary.php
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
<?php
|
||||
|
||||
namespace Appwrite\Auth\Validator;
|
||||
|
||||
use Utopia\Database\Document;
|
||||
|
||||
/**
|
||||
* Password.
|
||||
*
|
||||
* Validates user password string
|
||||
*/
|
||||
class PasswordDictionary extends Password
|
||||
{
|
||||
protected array $dictionary;
|
||||
protected bool $enabled;
|
||||
|
||||
public function __construct(array $dictionary, bool $enabled = false)
|
||||
{
|
||||
$this->dictionary = $dictionary;
|
||||
$this->enabled = $enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Description.
|
||||
*
|
||||
* Returns validator description
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'Password must be at least 8 characters and should not be one of the commonly used password.';
|
||||
}
|
||||
|
||||
/**
|
||||
* Is valid.
|
||||
*
|
||||
* @param mixed $value
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isValid($value): bool
|
||||
{
|
||||
if (!parent::isValid($value)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($this->enabled && array_key_exists($value, $this->dictionary)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is array
|
||||
*
|
||||
* Function will return true if object is array.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isArray(): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Type
|
||||
*
|
||||
* Returns validator type.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getType(): string
|
||||
{
|
||||
return self::TYPE_STRING;
|
||||
}
|
||||
}
|
||||
75
src/Appwrite/Auth/Validator/PasswordHistory.php
Normal file
75
src/Appwrite/Auth/Validator/PasswordHistory.php
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
<?php
|
||||
|
||||
namespace Appwrite\Auth\Validator;
|
||||
|
||||
use Appwrite\Auth\Auth;
|
||||
|
||||
/**
|
||||
* Password.
|
||||
*
|
||||
* Validates user password string
|
||||
*/
|
||||
class PasswordHistory extends Password
|
||||
{
|
||||
protected array $history;
|
||||
|
||||
public function __construct(array $history, string $algo, array $algoOptions = [])
|
||||
{
|
||||
$this->history = $history;
|
||||
$this->algo = $algo;
|
||||
$this->algoOptions = $algoOptions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Description.
|
||||
*
|
||||
* Returns validator description
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'Password shouldn\'t be in the history.';
|
||||
}
|
||||
|
||||
/**
|
||||
* Is valid.
|
||||
*
|
||||
* @param mixed $value
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isValid($value): bool
|
||||
{
|
||||
foreach ($this->history as $hash) {
|
||||
if (Auth::passwordVerify($value, $hash, $this->algo, $this->algoOptions)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is array
|
||||
*
|
||||
* Function will return true if object is array.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isArray(): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Type
|
||||
*
|
||||
* Returns validator type.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getType(): string
|
||||
{
|
||||
return self::TYPE_STRING;
|
||||
}
|
||||
}
|
||||
|
|
@ -65,6 +65,7 @@ class Exception extends \Exception
|
|||
public const USER_ANONYMOUS_CONSOLE_PROHIBITED = 'user_anonymous_console_prohibited';
|
||||
public const USER_SESSION_ALREADY_EXISTS = 'user_session_already_exists';
|
||||
public const USER_NOT_FOUND = 'user_not_found';
|
||||
public const USER_PASSWORD_RECENTLY_USED = 'password_recently_used';
|
||||
public const USER_EMAIL_ALREADY_EXISTS = 'user_email_already_exists';
|
||||
public const USER_PASSWORD_MISMATCH = 'user_password_mismatch';
|
||||
public const USER_SESSION_NOT_FOUND = 'user_session_not_found';
|
||||
|
|
@ -134,6 +135,7 @@ class Exception extends \Exception
|
|||
public const DOCUMENT_INVALID_STRUCTURE = 'document_invalid_structure';
|
||||
public const DOCUMENT_MISSING_PAYLOAD = 'document_missing_payload';
|
||||
public const DOCUMENT_ALREADY_EXISTS = 'document_already_exists';
|
||||
public const DOCUMENT_UPDATE_CONFLICT = 'document_update_conflict';
|
||||
|
||||
/** Attribute */
|
||||
public const ATTRIBUTE_NOT_FOUND = 'attribute_not_found';
|
||||
|
|
@ -144,6 +146,7 @@ class Exception extends \Exception
|
|||
public const ATTRIBUTE_ALREADY_EXISTS = 'attribute_already_exists';
|
||||
public const ATTRIBUTE_LIMIT_EXCEEDED = 'attribute_limit_exceeded';
|
||||
public const ATTRIBUTE_VALUE_INVALID = 'attribute_value_invalid';
|
||||
public const ATTRIBUTE_TYPE_INVALID = 'attribute_type_invalid';
|
||||
|
||||
/** Indexes */
|
||||
public const INDEX_NOT_FOUND = 'index_not_found';
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ use GraphQL\Type\Definition\UnionType;
|
|||
use Utopia\App;
|
||||
use Utopia\Route;
|
||||
use Utopia\Validator;
|
||||
use Utopia\Validator\Nullable;
|
||||
|
||||
class Mapper
|
||||
{
|
||||
|
|
@ -109,9 +110,6 @@ class Mapper
|
|||
'type' => $parameterType,
|
||||
'description' => $parameter['description'],
|
||||
];
|
||||
if ($parameter['optional']) {
|
||||
$params[$name]['defaultValue'] = $parameter['default'];
|
||||
}
|
||||
}
|
||||
|
||||
$field = [
|
||||
|
|
@ -224,6 +222,12 @@ class Mapper
|
|||
? \call_user_func_array($validator, $utopia->getResources($injections))
|
||||
: $validator;
|
||||
|
||||
$isNullable = $validator instanceof Nullable;
|
||||
|
||||
if ($isNullable) {
|
||||
$validator = $validator->getValidator();
|
||||
}
|
||||
|
||||
switch ((!empty($validator)) ? $validator::class : '') {
|
||||
case 'Appwrite\Network\Validator\CNAME':
|
||||
case 'Appwrite\Task\Validator\Cron':
|
||||
|
|
@ -294,7 +298,7 @@ class Mapper
|
|||
break;
|
||||
}
|
||||
|
||||
if ($required) {
|
||||
if ($required && !$isNullable) {
|
||||
$type = Type::nonNull($type);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -50,6 +50,7 @@ abstract class Migration
|
|||
'1.1.2' => 'V16',
|
||||
'1.2.0' => 'V17',
|
||||
'1.2.1' => 'V17',
|
||||
'1.3.0' => 'V18',
|
||||
];
|
||||
|
||||
/**
|
||||
|
|
|
|||
103
src/Appwrite/Migration/Version/V18.php
Normal file
103
src/Appwrite/Migration/Version/V18.php
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
<?php
|
||||
|
||||
namespace Appwrite\Migration\Version;
|
||||
|
||||
use Appwrite\Auth\Auth;
|
||||
use Appwrite\Migration\Migration;
|
||||
use Utopia\CLI\Console;
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
|
||||
class V18 extends Migration
|
||||
{
|
||||
public function execute(): void
|
||||
{
|
||||
/**
|
||||
* Disable SubQueries for Performance.
|
||||
*/
|
||||
foreach (['subQueryIndexes', 'subQueryPlatforms', 'subQueryDomains', 'subQueryKeys', 'subQueryWebhooks', 'subQuerySessions', 'subQueryTokens', 'subQueryMemberships', 'subqueryVariables'] as $name) {
|
||||
Database::addFilter(
|
||||
$name,
|
||||
fn () => null,
|
||||
fn () => []
|
||||
);
|
||||
}
|
||||
|
||||
Console::log('Migrating Project: ' . $this->project->getAttribute('name') . ' (' . $this->project->getId() . ')');
|
||||
|
||||
Console::info('Migrating Collections');
|
||||
$this->migrateCollections();
|
||||
|
||||
Console::info('Migrating Documents');
|
||||
$this->forEachDocument([$this, 'fixDocument']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Migrate all Collections.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function migrateCollections(): void
|
||||
{
|
||||
foreach ($this->collections as $collection) {
|
||||
$id = $collection['$id'];
|
||||
|
||||
Console::log("Migrating Collection \"{$id}\"");
|
||||
|
||||
$this->projectDB->setNamespace("_{$this->project->getInternalId()}");
|
||||
|
||||
switch ($id) {
|
||||
case 'users':
|
||||
try {
|
||||
/**
|
||||
* Create 'passwordHistory' attribute
|
||||
*/
|
||||
$this->createAttributeFromCollection($this->projectDB, $id, 'passwordHistory');
|
||||
$this->projectDB->deleteCachedCollection($id);
|
||||
} catch (\Throwable $th) {
|
||||
Console::warning("'passwordHistory' from {$id}: {$th->getMessage()}");
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
usleep(50000);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fix run on each document
|
||||
*
|
||||
* @param \Utopia\Database\Document $document
|
||||
* @return \Utopia\Database\Document
|
||||
*/
|
||||
protected function fixDocument(Document $document)
|
||||
{
|
||||
switch ($document->getCollection()) {
|
||||
case 'projects':
|
||||
/**
|
||||
* Bump version number.
|
||||
*/
|
||||
$document->setAttribute('version', '1.3.0');
|
||||
|
||||
/**
|
||||
* Set default passwordHistory
|
||||
*/
|
||||
$document->setAttribute('auths', array_merge($document->getAttribute('auths', []), [
|
||||
'passwordHistory' => 0,
|
||||
'passwordDictionary' => false,
|
||||
]));
|
||||
break;
|
||||
case 'users':
|
||||
/**
|
||||
* Default Password history
|
||||
*/
|
||||
$document->setAttribute('passwordHistory', []);
|
||||
break;
|
||||
}
|
||||
|
||||
return $document;
|
||||
}
|
||||
}
|
||||
|
|
@ -8,6 +8,7 @@ use Appwrite\Utopia\Response\Model;
|
|||
use Utopia\Database\Helpers\Permission;
|
||||
use Utopia\Database\Helpers\Role;
|
||||
use Utopia\Validator;
|
||||
use Utopia\Validator\Nullable;
|
||||
|
||||
class OpenAPI3 extends Format
|
||||
{
|
||||
|
|
@ -284,6 +285,13 @@ class OpenAPI3 extends Format
|
|||
}
|
||||
}
|
||||
|
||||
$isNullable = $validator instanceof Nullable;
|
||||
|
||||
if ($isNullable) {
|
||||
/** @var Nullable $validator */
|
||||
$validator = $validator->getValidator();
|
||||
}
|
||||
|
||||
switch ((!empty($validator)) ? \get_class($validator) : '') {
|
||||
case 'Utopia\Validator\Text':
|
||||
$node['schema']['type'] = $validator->getType();
|
||||
|
|
@ -449,6 +457,10 @@ class OpenAPI3 extends Format
|
|||
if ($node['x-global'] ?? false) {
|
||||
$body['content'][$consumes[0]]['schema']['properties'][$name]['x-global'] = true;
|
||||
}
|
||||
|
||||
if ($isNullable) {
|
||||
$body['content'][$consumes[0]]['schema']['properties'][$name]['x-nullable'] = true;
|
||||
}
|
||||
}
|
||||
|
||||
$url = \str_replace(':' . $name, '{' . $name . '}', $url);
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ use Appwrite\Utopia\Response\Model;
|
|||
use Utopia\Database\Helpers\Permission;
|
||||
use Utopia\Database\Helpers\Role;
|
||||
use Utopia\Validator;
|
||||
use Utopia\Validator\Nullable;
|
||||
|
||||
class Swagger2 extends Format
|
||||
{
|
||||
|
|
@ -285,6 +286,13 @@ class Swagger2 extends Format
|
|||
}
|
||||
}
|
||||
|
||||
$isNullable = $validator instanceof Nullable;
|
||||
|
||||
if ($isNullable) {
|
||||
/** @var Nullable $validator */
|
||||
$validator = $validator->getValidator();
|
||||
}
|
||||
|
||||
switch ((!empty($validator)) ? \get_class($validator) : '') {
|
||||
case 'Utopia\Validator\Text':
|
||||
$node['type'] = $validator->getType();
|
||||
|
|
@ -448,6 +456,10 @@ class Swagger2 extends Format
|
|||
$body['schema']['properties'][$name]['x-global'] = true;
|
||||
}
|
||||
|
||||
if ($isNullable) {
|
||||
$body['schema']['properties'][$name]['x-nullable'] = true;
|
||||
}
|
||||
|
||||
if (\array_key_exists('items', $node)) {
|
||||
$body['schema']['properties'][$name]['items'] = $node['items'];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ class IndexedQueries extends Queries
|
|||
* @param Document[] $attributes
|
||||
* @param Document[] $indexes
|
||||
* @param Base ...$validators
|
||||
* @param bool $strict
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function __construct($attributes = [], $indexes = [], Base ...$validators)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -2,24 +2,24 @@
|
|||
|
||||
namespace Appwrite\Utopia\Database\Validator\Queries;
|
||||
|
||||
use Appwrite\Utopia\Database\Validator\IndexedQueries;
|
||||
use Appwrite\Utopia\Database\Validator\Query\Limit;
|
||||
use Appwrite\Utopia\Database\Validator\Query\Offset;
|
||||
use Appwrite\Utopia\Database\Validator\Queries;
|
||||
use Appwrite\Utopia\Database\Validator\Query\Cursor;
|
||||
use Appwrite\Utopia\Database\Validator\Query\Filter;
|
||||
use Appwrite\Utopia\Database\Validator\Query\Limit;
|
||||
use Appwrite\Utopia\Database\Validator\Query\Offset;
|
||||
use Appwrite\Utopia\Database\Validator\Query\Order;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
|
||||
class Documents extends IndexedQueries
|
||||
class Documents extends Queries
|
||||
{
|
||||
/**
|
||||
* Expression constructor
|
||||
*
|
||||
* @param Document[] $attributes
|
||||
* @param Document[] $indexes
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function __construct(array $attributes, array $indexes)
|
||||
public function __construct(array $attributes)
|
||||
{
|
||||
$attributes[] = new Document([
|
||||
'key' => '$id',
|
||||
|
|
@ -45,6 +45,6 @@ class Documents extends IndexedQueries
|
|||
new Order($attributes),
|
||||
];
|
||||
|
||||
parent::__construct($attributes, $indexes, ...$validators);
|
||||
parent::__construct(...$validators);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ class Limit extends Base
|
|||
*
|
||||
* @param int $maxLimit
|
||||
*/
|
||||
public function __construct(int $maxLimit = 100)
|
||||
public function __construct(int $maxLimit = PHP_INT_MAX)
|
||||
{
|
||||
$this->maxLimit = $maxLimit;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
namespace Appwrite\Utopia\Database\Validator\Query;
|
||||
|
||||
use Appwrite\Utopia\Database\Validator\Query\Base;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\Validator\Range;
|
||||
|
||||
|
|
@ -15,7 +14,7 @@ class Offset extends Base
|
|||
*
|
||||
* @param int $maxOffset
|
||||
*/
|
||||
public function __construct(int $maxOffset = 5000)
|
||||
public function __construct(int $maxOffset = PHP_INT_MAX)
|
||||
{
|
||||
$this->maxOffset = $maxOffset;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -120,6 +120,18 @@ class Project extends Model
|
|||
'default' => 10,
|
||||
'example' => 10,
|
||||
])
|
||||
->addRule('authPasswordHistory', [
|
||||
'type' => self::TYPE_INTEGER,
|
||||
'description' => 'Max allowed passwords in the history list per user. Max passwords limit allowed in history is 20. Use 0 for disabling password history.',
|
||||
'default' => 0,
|
||||
'example' => 5,
|
||||
])
|
||||
->addRule('authPasswordDictionary', [
|
||||
'type' => self::TYPE_BOOLEAN,
|
||||
'description' => 'Whether or not to check user\'s password against most commonly used passwords.',
|
||||
'default' => false,
|
||||
'example' => true,
|
||||
])
|
||||
->addRule('providers', [
|
||||
'type' => Response::MODEL_PROVIDER,
|
||||
'description' => 'List of Providers.',
|
||||
|
|
@ -240,6 +252,8 @@ class Project extends Model
|
|||
$document->setAttribute('authLimit', $authValues['limit'] ?? 0);
|
||||
$document->setAttribute('authDuration', $authValues['duration'] ?? Auth::TOKEN_EXPIRATION_LOGIN_LONG);
|
||||
$document->setAttribute('authSessionsLimit', $authValues['maxSessions'] ?? APP_LIMIT_USER_SESSIONS_DEFAULT);
|
||||
$document->setAttribute('authPasswordHistory', $authValues['passwordHistory'] ?? 0);
|
||||
$document->setAttribute('authPasswordDictionary', $authValues['passwordDictionary'] ?? false);
|
||||
|
||||
foreach ($auth as $index => $method) {
|
||||
$key = $method['key'];
|
||||
|
|
|
|||
|
|
@ -40,6 +40,12 @@ class Team extends Model
|
|||
'default' => 0,
|
||||
'example' => 7,
|
||||
])
|
||||
->addRule('prefs', [
|
||||
'type' => Response::MODEL_PREFERENCES,
|
||||
'description' => 'Team preferences as a key-value object',
|
||||
'default' => new \stdClass(),
|
||||
'example' => ['theme' => 'pink', 'timezone' => 'UTC'],
|
||||
])
|
||||
;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -25,7 +25,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-SDK-Version, X-SDK-Name, X-SDK-Language, X-SDK-Platform, X-SDK-GraphQL, X-Appwrite-ID, Content-Range, Range, Cache-Control, Expires, Pragma, X-Fallback-Cookies', $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-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-Fallback-Cookies', $response['headers']['access-control-allow-headers']);
|
||||
$this->assertEquals('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']);
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ use CURLFile;
|
|||
use Tests\E2E\Services\Functions\FunctionsBase;
|
||||
use Utopia\Database\Helpers\Permission;
|
||||
use Utopia\Database\Helpers\Role;
|
||||
use Utopia\Database\Validator\DatetimeValidator;
|
||||
use Utopia\Database\Validator\Datetime as DatetimeValidator;
|
||||
|
||||
class UsageTest extends Scope
|
||||
{
|
||||
|
|
|
|||
|
|
@ -46,14 +46,7 @@ trait ProjectCustom
|
|||
'name' => 'Demo Project',
|
||||
'teamId' => $team['body']['$id'],
|
||||
'description' => 'Demo Project Description',
|
||||
'logo' => '',
|
||||
'url' => 'https://appwrite.io',
|
||||
'legalName' => '',
|
||||
'legalCountry' => '',
|
||||
'legalState' => '',
|
||||
'legalCity' => '',
|
||||
'legalAddress' => '',
|
||||
'legalTaxId' => '',
|
||||
]);
|
||||
|
||||
$this->assertEquals(201, $project['headers']['status-code']);
|
||||
|
|
@ -111,8 +104,6 @@ trait ProjectCustom
|
|||
],
|
||||
'url' => 'http://request-catcher:5000/webhook',
|
||||
'security' => false,
|
||||
'httpUser' => '',
|
||||
'httpPass' => '',
|
||||
]);
|
||||
|
||||
$this->assertEquals(201, $webhook['headers']['status-code']);
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ use Appwrite\Tests\Retry;
|
|||
use Tests\E2E\Client;
|
||||
use Utopia\Database\Helpers\ID;
|
||||
use Utopia\Database\DateTime;
|
||||
use Utopia\Database\Validator\DatetimeValidator;
|
||||
use Utopia\Database\Validator\Datetime as DatetimeValidator;
|
||||
|
||||
trait AccountBase
|
||||
{
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ use Tests\E2E\Scopes\ProjectCustom;
|
|||
use Tests\E2E\Scopes\SideClient;
|
||||
use Utopia\Database\DateTime;
|
||||
use Utopia\Database\Helpers\ID;
|
||||
use Utopia\Database\Validator\DatetimeValidator;
|
||||
use Utopia\Database\Validator\Datetime as DatetimeValidator;
|
||||
|
||||
use function sleep;
|
||||
|
||||
|
|
|
|||
|
|
@ -2,11 +2,13 @@
|
|||
|
||||
namespace Tests\E2E\Services\Databases;
|
||||
|
||||
use Appwrite\Extend\Exception;
|
||||
use Tests\E2E\Client;
|
||||
use Utopia\Database\DateTime;
|
||||
use Utopia\Database\Helpers\ID;
|
||||
use Utopia\Database\Helpers\Permission;
|
||||
use Utopia\Database\Helpers\Role;
|
||||
use Utopia\Database\Validator\DatetimeValidator;
|
||||
use Utopia\Database\Validator\Datetime as DatetimeValidator;
|
||||
|
||||
trait DatabasesBase
|
||||
{
|
||||
|
|
@ -1440,18 +1442,17 @@ trait DatabasesBase
|
|||
|
||||
$this->assertCount(0, $documents['body']['documents']);
|
||||
|
||||
/**
|
||||
* Test for Failure
|
||||
*/
|
||||
$documents = $this->client->call(Client::METHOD_GET, '/databases/' . $databaseId . '/collections/' . $data['moviesId'] . '/documents', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'queries' => ['equal("actors", "Tom Holland")'],
|
||||
]);
|
||||
$this->assertEquals(400, $documents['headers']['status-code']);
|
||||
$this->assertEquals('Index not found: actors', $documents['body']['message']);
|
||||
$this->assertEquals(200, $documents['headers']['status-code']);
|
||||
|
||||
/**
|
||||
* Test for Failure
|
||||
*/
|
||||
$conditions = [];
|
||||
|
||||
for ($i = 0; $i < 101; $i++) {
|
||||
|
|
@ -1577,6 +1578,50 @@ trait DatabasesBase
|
|||
$this->assertEquals($document['body']['title'], 'Thor: Ragnarok');
|
||||
$this->assertEquals($document['body']['releaseYear'], 2017);
|
||||
|
||||
$response = $this->client->call(Client::METHOD_PATCH, '/databases/' . $databaseId . '/collections/' . $data['moviesId'] . '/documents/' . $id, array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-timestamp' => DateTime::formatTz(DateTime::now()),
|
||||
], $this->getHeaders()), [
|
||||
'data' => [
|
||||
'title' => 'Thor: Ragnarok',
|
||||
],
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
|
||||
/**
|
||||
* Test for failure
|
||||
*/
|
||||
|
||||
$response = $this->client->call(Client::METHOD_PATCH, '/databases/' . $databaseId . '/collections/' . $data['moviesId'] . '/documents/' . $id, array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-timestamp' => 'invalid',
|
||||
], $this->getHeaders()), [
|
||||
'data' => [
|
||||
'title' => 'Thor: Ragnarok',
|
||||
],
|
||||
]);
|
||||
|
||||
$this->assertEquals(400, $response['headers']['status-code']);
|
||||
$this->assertEquals('Invalid X-Appwrite-Timestamp header value', $response['body']['message']);
|
||||
$this->assertEquals(Exception::GENERAL_ARGUMENT_INVALID, $response['body']['type']);
|
||||
|
||||
$response = $this->client->call(Client::METHOD_PATCH, '/databases/' . $databaseId . '/collections/' . $data['moviesId'] . '/documents/' . $id, array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-timestamp' => DateTime::formatTz(DateTime::addSeconds(new \DateTime(), -1000)),
|
||||
], $this->getHeaders()), [
|
||||
'data' => [
|
||||
'title' => 'Thor: Ragnarok',
|
||||
],
|
||||
]);
|
||||
|
||||
$this->assertEquals(409, $response['headers']['status-code']);
|
||||
$this->assertEquals('Remote document is newer than local.', $response['body']['message']);
|
||||
$this->assertEquals(Exception::DOCUMENT_UPDATE_CONFLICT, $response['body']['type']);
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -109,6 +109,7 @@ class FunctionsCustomClientTest extends Scope
|
|||
], [
|
||||
'entrypoint' => 'index.php',
|
||||
'code' => new CURLFile($code, 'application/x-gzip', \basename($code)),
|
||||
'activate' => true
|
||||
]);
|
||||
|
||||
$deploymentId = $deployment['body']['$id'] ?? '';
|
||||
|
|
@ -223,6 +224,7 @@ class FunctionsCustomClientTest extends Scope
|
|||
], [
|
||||
'entrypoint' => 'index.php',
|
||||
'code' => new CURLFile($code, 'application/x-gzip', \basename($code)), //different tarball names intentional
|
||||
'activate' => true
|
||||
]);
|
||||
|
||||
$deploymentId = $deployment['body']['$id'] ?? '';
|
||||
|
|
@ -321,6 +323,7 @@ class FunctionsCustomClientTest extends Scope
|
|||
], [
|
||||
'entrypoint' => 'index.php',
|
||||
'code' => new CURLFile($code, 'application/x-gzip', \basename($code)), //different tarball names intentional
|
||||
'activate' => true
|
||||
]);
|
||||
|
||||
$deploymentId = $deployment['body']['$id'] ?? '';
|
||||
|
|
@ -549,6 +552,7 @@ class FunctionsCustomClientTest extends Scope
|
|||
], [
|
||||
'entrypoint' => 'index.php',
|
||||
'code' => new CURLFile($code, 'application/x-gzip', \basename($code)), //different tarball names intentional
|
||||
'activate' => true
|
||||
]);
|
||||
|
||||
$deploymentId = $deployment['body']['$id'] ?? '';
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ use Tests\E2E\Scopes\ProjectCustom;
|
|||
use Tests\E2E\Scopes\Scope;
|
||||
use Tests\E2E\Scopes\SideServer;
|
||||
use Utopia\Database\Helpers\ID;
|
||||
use Utopia\Database\Validator\DatetimeValidator;
|
||||
use Utopia\Database\Validator\Datetime as DatetimeValidator;
|
||||
|
||||
class FunctionsCustomServerTest extends Scope
|
||||
{
|
||||
|
|
@ -363,6 +363,7 @@ class FunctionsCustomServerTest extends Scope
|
|||
], $this->getHeaders()), [
|
||||
'entrypoint' => 'index.php',
|
||||
'code' => new CURLFile($code, 'application/x-gzip', \basename($code)),
|
||||
'activate' => true
|
||||
]);
|
||||
|
||||
$deploymentId = $deployment['body']['$id'] ?? '';
|
||||
|
|
@ -411,6 +412,7 @@ class FunctionsCustomServerTest extends Scope
|
|||
$largeTag = $this->client->call(Client::METHOD_POST, '/functions/' . $data['functionId'] . '/deployments', array_merge($headers, $this->getHeaders()), [
|
||||
'entrypoint' => 'index.php',
|
||||
'code' => $curlFile,
|
||||
'activate' => true
|
||||
]);
|
||||
$counter++;
|
||||
$id = $largeTag['body']['$id'];
|
||||
|
|
@ -741,7 +743,6 @@ class FunctionsCustomServerTest extends Scope
|
|||
/**
|
||||
* Test for SUCCESS
|
||||
*/
|
||||
|
||||
$execution = $this->client->call(Client::METHOD_POST, '/functions/' . $data['functionId'] . '/executions', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
|
|
@ -752,7 +753,6 @@ class FunctionsCustomServerTest extends Scope
|
|||
$this->assertEquals(201, $execution['headers']['status-code']);
|
||||
|
||||
$this->assertEquals('completed', $execution['body']['status']);
|
||||
$this->assertStringContainsString($data['deploymentId'], $execution['body']['response']);
|
||||
$this->assertStringContainsString('Test1', $execution['body']['response']);
|
||||
$this->assertStringContainsString('http', $execution['body']['response']);
|
||||
$this->assertStringContainsString('PHP', $execution['body']['response']);
|
||||
|
|
@ -869,7 +869,6 @@ class FunctionsCustomServerTest extends Scope
|
|||
'name' => 'Test ' . $name,
|
||||
'runtime' => $name,
|
||||
'events' => [],
|
||||
'schedule' => '',
|
||||
'timeout' => $timeout,
|
||||
]);
|
||||
|
||||
|
|
@ -953,7 +952,6 @@ class FunctionsCustomServerTest extends Scope
|
|||
'name' => 'Test ' . $name,
|
||||
'runtime' => $name,
|
||||
'events' => [],
|
||||
'schedule' => '',
|
||||
'timeout' => $timeout,
|
||||
]);
|
||||
|
||||
|
|
@ -967,6 +965,7 @@ class FunctionsCustomServerTest extends Scope
|
|||
], $this->getHeaders()), [
|
||||
'entrypoint' => $entrypoint,
|
||||
'code' => new CURLFile($code, 'application/x-gzip', basename($code)),
|
||||
'activate' => true
|
||||
]);
|
||||
|
||||
$deploymentId = $deployment['body']['$id'] ?? '';
|
||||
|
|
@ -1063,7 +1062,6 @@ class FunctionsCustomServerTest extends Scope
|
|||
'name' => 'Test ' . $name,
|
||||
'runtime' => $name,
|
||||
'events' => [],
|
||||
'schedule' => '',
|
||||
'timeout' => $timeout,
|
||||
]);
|
||||
|
||||
|
|
@ -1176,7 +1174,6 @@ class FunctionsCustomServerTest extends Scope
|
|||
'name' => 'Test ' . $name,
|
||||
'runtime' => $name,
|
||||
'events' => [],
|
||||
'schedule' => '',
|
||||
'timeout' => $timeout,
|
||||
]);
|
||||
|
||||
|
|
@ -1290,7 +1287,6 @@ class FunctionsCustomServerTest extends Scope
|
|||
'name' => 'Test ' . $name,
|
||||
'runtime' => $name,
|
||||
'events' => [],
|
||||
'schedule' => '',
|
||||
'timeout' => $timeout,
|
||||
]);
|
||||
|
||||
|
|
@ -1404,7 +1400,6 @@ class FunctionsCustomServerTest extends Scope
|
|||
'name' => 'Test ' . $name,
|
||||
'runtime' => $name,
|
||||
'events' => [],
|
||||
'schedule' => '',
|
||||
'timeout' => $timeout,
|
||||
]);
|
||||
|
||||
|
|
|
|||
|
|
@ -26,6 +26,15 @@ trait Base
|
|||
public static string $CREATE_IP_ATTRIBUTE = 'create_ip_attribute';
|
||||
public static string $CREATE_ENUM_ATTRIBUTE = 'create_enum_attribute';
|
||||
public static string $CREATE_DATETIME_ATTRIBUTE = 'create_datetime_attribute';
|
||||
public static string $UPDATE_STRING_ATTRIBUTE = 'update_string_attribute';
|
||||
public static string $UPDATE_INTEGER_ATTRIBUTE = 'update_integer_attribute';
|
||||
public static string $UPDATE_FLOAT_ATTRIBUTE = 'update_float_attribute';
|
||||
public static string $UPDATE_BOOLEAN_ATTRIBUTE = 'update_boolean_attribute';
|
||||
public static string $UPDATE_URL_ATTRIBUTE = 'update_url_attribute';
|
||||
public static string $UPDATE_EMAIL_ATTRIBUTE = 'update_email_attribute';
|
||||
public static string $UPDATE_IP_ATTRIBUTE = 'update_ip_attribute';
|
||||
public static string $UPDATE_ENUM_ATTRIBUTE = 'update_enum_attribute';
|
||||
public static string $UPDATE_DATETIME_ATTRIBUTE = 'update_datetime_attribute';
|
||||
public static string $GET_ATTRIBUTES = 'get_attributes';
|
||||
public static string $GET_ATTRIBUTE = 'get_attribute';
|
||||
public static string $DELETE_ATTRIBUTE = 'delete_attribute';
|
||||
|
|
@ -97,9 +106,12 @@ trait Base
|
|||
|
||||
// Teams
|
||||
public static string $GET_TEAM = 'get_team';
|
||||
public static string $GET_TEAM_PREFERENCES = 'get_team_preferences';
|
||||
public static string $GET_TEAMS = 'list_teams';
|
||||
public static string $CREATE_TEAM = 'create_team';
|
||||
public static string $UPDATE_TEAM = 'update_team';
|
||||
public static string $UPDATE_TEAM_NAME = 'update_team_name';
|
||||
public static string $UPDATE_TEAM_PREFERENCES = 'update_team_preferences';
|
||||
|
||||
public static string $DELETE_TEAM = 'delete_team';
|
||||
public static string $GET_TEAM_MEMBERSHIP = 'get_team_membership';
|
||||
public static string $GET_TEAM_MEMBERSHIPS = 'list_team_memberships';
|
||||
|
|
@ -451,6 +463,74 @@ trait Base
|
|||
array
|
||||
}
|
||||
}';
|
||||
case self::$UPDATE_STRING_ATTRIBUTE:
|
||||
return 'mutation updateStringAttribute($databaseId: String!, $collectionId: String!, $key: String!, $required: Boolean!, $default: String){
|
||||
databasesUpdateStringAttribute(databaseId: $databaseId, collectionId: $collectionId, key: $key, required: $required, default: $default) {
|
||||
required
|
||||
default
|
||||
}
|
||||
}';
|
||||
case self::$UPDATE_INTEGER_ATTRIBUTE:
|
||||
return 'mutation updateIntegerAttribute($databaseId: String!, $collectionId: String!, $key: String!, $required: Boolean!, $min: Int!, $max: Int!, $default: Int){
|
||||
databasesUpdateIntegerAttribute(databaseId: $databaseId, collectionId: $collectionId, key: $key, required: $required, min: $min, max: $max, default: $default) {
|
||||
required
|
||||
min
|
||||
max
|
||||
default
|
||||
}
|
||||
}';
|
||||
case self::$UPDATE_FLOAT_ATTRIBUTE:
|
||||
return 'mutation updateFloatAttribute($databaseId: String!, $collectionId: String!, $key: String!, $required: Boolean!, $min: Float!, $max: Float!, $default: Float){
|
||||
databasesUpdateFloatAttribute(databaseId: $databaseId, collectionId: $collectionId, key: $key, min: $min, max: $max, required: $required, default: $default) {
|
||||
required
|
||||
min
|
||||
max
|
||||
default
|
||||
}
|
||||
}';
|
||||
case self::$UPDATE_BOOLEAN_ATTRIBUTE:
|
||||
return 'mutation updateBooleanAttribute($databaseId: String!, $collectionId: String!, $key: String!, $required: Boolean!, $default: Boolean){
|
||||
databasesUpdateBooleanAttribute(databaseId: $databaseId, collectionId: $collectionId, key: $key, required: $required, default: $default) {
|
||||
required
|
||||
default
|
||||
}
|
||||
}';
|
||||
case self::$UPDATE_URL_ATTRIBUTE:
|
||||
return 'mutation updateUrlAttribute($databaseId: String!, $collectionId: String!, $key: String!, $required: Boolean!, $default: String){
|
||||
databasesUpdateUrlAttribute(databaseId: $databaseId, collectionId: $collectionId, key: $key, required: $required, default: $default) {
|
||||
required
|
||||
default
|
||||
}
|
||||
}';
|
||||
case self::$UPDATE_EMAIL_ATTRIBUTE:
|
||||
return 'mutation updateEmailAttribute($databaseId: String!, $collectionId: String!, $key: String!, $required: Boolean!, $default: String){
|
||||
databasesUpdateEmailAttribute(databaseId: $databaseId, collectionId: $collectionId, key: $key, required: $required, default: $default) {
|
||||
required
|
||||
default
|
||||
}
|
||||
}';
|
||||
case self::$UPDATE_IP_ATTRIBUTE:
|
||||
return 'mutation updateIpAttribute($databaseId: String!, $collectionId: String!, $key: String!, $required: Boolean!, $default: String){
|
||||
databasesUpdateIpAttribute(databaseId: $databaseId, collectionId: $collectionId, key: $key, required: $required, default: $default) {
|
||||
required
|
||||
default
|
||||
}
|
||||
}';
|
||||
case self::$UPDATE_ENUM_ATTRIBUTE:
|
||||
return 'mutation updateEnumAttribute($databaseId: String!, $collectionId: String!, $key: String!, $elements: [String!]!, $required: Boolean!, $default: String){
|
||||
databasesUpdateEnumAttribute(databaseId: $databaseId, collectionId: $collectionId, key: $key, elements: $elements, required: $required, default: $default) {
|
||||
elements
|
||||
required
|
||||
default
|
||||
}
|
||||
}';
|
||||
case self::$UPDATE_DATETIME_ATTRIBUTE:
|
||||
return 'mutation updateDatetimeAttribute($databaseId: String!, $collectionId: String!, $key: String!, $required: Boolean!, $default: String){
|
||||
databasesUpdateDatetimeAttribute(databaseId: $databaseId, collectionId: $collectionId, key: $key, required: $required, default: $default) {
|
||||
required
|
||||
default
|
||||
}
|
||||
}';
|
||||
case self::$CREATE_INDEX:
|
||||
return 'mutation createIndex($databaseId: String!, $collectionId: String!, $key: String!, $type: String!, $attributes: [String!]!, $orders: [String!]){
|
||||
databasesCreateIndex(databaseId: $databaseId, collectionId: $collectionId, key: $key, type: $type, attributes: $attributes, orders: $orders) {
|
||||
|
|
@ -1103,10 +1183,16 @@ trait Base
|
|||
}';
|
||||
case self::$GET_TEAM:
|
||||
return 'query getTeam($teamId: String!){
|
||||
teamsGet(teamId: $teamId) {
|
||||
_id
|
||||
name
|
||||
total
|
||||
teamsGet(teamId: $teamId) {
|
||||
_id
|
||||
name
|
||||
total
|
||||
}
|
||||
}';
|
||||
case self::$GET_TEAM_PREFERENCES:
|
||||
return 'query getTeamPreferences($teamId: String!) {
|
||||
teamsGetPrefs(teamId: $teamId) {
|
||||
data
|
||||
}
|
||||
}';
|
||||
case self::$GET_TEAMS:
|
||||
|
|
@ -1127,12 +1213,18 @@ trait Base
|
|||
total
|
||||
}
|
||||
}';
|
||||
case self::$UPDATE_TEAM:
|
||||
return 'mutation updateTeam($teamId: String!, $name: String!){
|
||||
teamsUpdate(teamId: $teamId, name : $name) {
|
||||
_id
|
||||
name
|
||||
total
|
||||
case self::$UPDATE_TEAM_NAME:
|
||||
return 'mutation updateTeamName($teamId: String!, $name: String!){
|
||||
teamsUpdateName(teamId: $teamId, name : $name) {
|
||||
_id
|
||||
name
|
||||
total
|
||||
}
|
||||
}';
|
||||
case self::$UPDATE_TEAM_PREFERENCES:
|
||||
return 'mutation updateTeamPrefs($teamId: String!, $prefs: Assoc!){
|
||||
teamsUpdatePrefs(teamId: $teamId, prefs: $prefs) {
|
||||
data
|
||||
}
|
||||
}';
|
||||
case self::$DELETE_TEAM:
|
||||
|
|
|
|||
|
|
@ -112,6 +112,42 @@ class DatabaseServerTest extends Scope
|
|||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testCreateStringAttribute
|
||||
* @throws Exception
|
||||
*/
|
||||
public function testUpdateStringAttribute($data): array
|
||||
{
|
||||
// Wait for attributes to be available
|
||||
sleep(3);
|
||||
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query = $this->getQuery(self::$UPDATE_STRING_ATTRIBUTE);
|
||||
$gqlPayload = [
|
||||
'query' => $query,
|
||||
'variables' => [
|
||||
'databaseId' => $data['database']['_id'],
|
||||
'collectionId' => $data['collection']['_id'],
|
||||
'key' => 'name',
|
||||
'required' => false,
|
||||
'default' => 'Default Value',
|
||||
]
|
||||
];
|
||||
|
||||
$attribute = $this->client->call(Client::METHOD_POST, '/graphql', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $gqlPayload);
|
||||
|
||||
$this->assertIsArray($attribute['body']['data']);
|
||||
$this->assertIsArray($attribute['body']['data']['databasesUpdateStringAttribute']);
|
||||
$this->assertFalse($attribute['body']['data']['databasesUpdateStringAttribute']['required']);
|
||||
$this->assertEquals('Default Value', $attribute['body']['data']['databasesUpdateStringAttribute']['default']);
|
||||
$this->assertEquals(200, $attribute['headers']['status-code']);
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testCreateCollection
|
||||
* @throws Exception
|
||||
|
|
@ -144,6 +180,46 @@ class DatabaseServerTest extends Scope
|
|||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testCreateIntegerAttribute
|
||||
* @throws Exception
|
||||
*/
|
||||
public function testUpdateIntegerAttribute($data): array
|
||||
{
|
||||
// Wait for attributes to be available
|
||||
sleep(3);
|
||||
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query = $this->getQuery(self::$UPDATE_INTEGER_ATTRIBUTE);
|
||||
$gqlPayload = [
|
||||
'query' => $query,
|
||||
'variables' => [
|
||||
'databaseId' => $data['database']['_id'],
|
||||
'collectionId' => $data['collection']['_id'],
|
||||
'key' => 'age',
|
||||
'required' => false,
|
||||
'min' => 12,
|
||||
'max' => 160,
|
||||
'default' => 50
|
||||
]
|
||||
];
|
||||
|
||||
$attribute = $this->client->call(Client::METHOD_POST, '/graphql', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $gqlPayload);
|
||||
|
||||
$this->assertIsArray($attribute['body']['data']);
|
||||
$this->assertIsArray($attribute['body']['data']['databasesUpdateIntegerAttribute']);
|
||||
$this->assertFalse($attribute['body']['data']['databasesUpdateIntegerAttribute']['required']);
|
||||
$this->assertEquals(12, $attribute['body']['data']['databasesUpdateIntegerAttribute']['min']);
|
||||
$this->assertEquals(160, $attribute['body']['data']['databasesUpdateIntegerAttribute']['max']);
|
||||
$this->assertEquals(50, $attribute['body']['data']['databasesUpdateIntegerAttribute']['default']);
|
||||
$this->assertEquals(200, $attribute['headers']['status-code']);
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testCreateCollection
|
||||
* @throws Exception
|
||||
|
|
@ -174,6 +250,42 @@ class DatabaseServerTest extends Scope
|
|||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testCreateBooleanAttribute
|
||||
* @throws Exception
|
||||
*/
|
||||
public function testUpdateBooleanAttribute($data): array
|
||||
{
|
||||
// Wait for attributes to be available
|
||||
sleep(3);
|
||||
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query = $this->getQuery(self::$UPDATE_BOOLEAN_ATTRIBUTE);
|
||||
$gqlPayload = [
|
||||
'query' => $query,
|
||||
'variables' => [
|
||||
'databaseId' => $data['database']['_id'],
|
||||
'collectionId' => $data['collection']['_id'],
|
||||
'key' => 'alive',
|
||||
'required' => false,
|
||||
'default' => true
|
||||
]
|
||||
];
|
||||
|
||||
$attribute = $this->client->call(Client::METHOD_POST, '/graphql', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $gqlPayload);
|
||||
|
||||
$this->assertIsArray($attribute['body']['data']);
|
||||
$this->assertIsArray($attribute['body']['data']['databasesUpdateBooleanAttribute']);
|
||||
$this->assertFalse($attribute['body']['data']['databasesUpdateBooleanAttribute']['required']);
|
||||
$this->assertTrue($attribute['body']['data']['databasesUpdateBooleanAttribute']['default']);
|
||||
$this->assertEquals(200, $attribute['headers']['status-code']);
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testCreateCollection
|
||||
* @throws Exception
|
||||
|
|
@ -207,6 +319,46 @@ class DatabaseServerTest extends Scope
|
|||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testCreateFloatAttribute
|
||||
* @throws Exception
|
||||
*/
|
||||
public function testUpdateFloatAttribute($data): array
|
||||
{
|
||||
// Wait for attributes to be available
|
||||
sleep(3);
|
||||
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query = $this->getQuery(self::$UPDATE_FLOAT_ATTRIBUTE);
|
||||
$gqlPayload = [
|
||||
'query' => $query,
|
||||
'variables' => [
|
||||
'databaseId' => $data['database']['_id'],
|
||||
'collectionId' => $data['collection']['_id'],
|
||||
'key' => 'salary',
|
||||
'required' => false,
|
||||
'min' => 100.0,
|
||||
'max' => 1000000.0,
|
||||
'default' => 2500.0
|
||||
]
|
||||
];
|
||||
|
||||
$attribute = $this->client->call(Client::METHOD_POST, '/graphql', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $gqlPayload);
|
||||
|
||||
$this->assertIsArray($attribute['body']['data']);
|
||||
$this->assertIsArray($attribute['body']['data']['databasesUpdateFloatAttribute']);
|
||||
$this->assertFalse($attribute['body']['data']['databasesUpdateFloatAttribute']['required']);
|
||||
$this->assertEquals(100.0, $attribute['body']['data']['databasesUpdateFloatAttribute']['min']);
|
||||
$this->assertEquals(1000000.0, $attribute['body']['data']['databasesUpdateFloatAttribute']['max']);
|
||||
$this->assertEquals(2500.0, $attribute['body']['data']['databasesUpdateFloatAttribute']['default']);
|
||||
$this->assertEquals(200, $attribute['headers']['status-code']);
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testCreateCollection
|
||||
* @throws Exception
|
||||
|
|
@ -237,6 +389,42 @@ class DatabaseServerTest extends Scope
|
|||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testCreateEmailAttribute
|
||||
* @throws Exception
|
||||
*/
|
||||
public function testUpdateEmailAttribute($data): array
|
||||
{
|
||||
// Wait for attributes to be available
|
||||
sleep(3);
|
||||
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query = $this->getQuery(self::$UPDATE_EMAIL_ATTRIBUTE);
|
||||
$gqlPayload = [
|
||||
'query' => $query,
|
||||
'variables' => [
|
||||
'databaseId' => $data['database']['_id'],
|
||||
'collectionId' => $data['collection']['_id'],
|
||||
'key' => 'email',
|
||||
'required' => false,
|
||||
'default' => 'torsten@appwrite.io',
|
||||
]
|
||||
];
|
||||
|
||||
$attribute = $this->client->call(Client::METHOD_POST, '/graphql', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $gqlPayload);
|
||||
|
||||
$this->assertIsArray($attribute['body']['data']);
|
||||
$this->assertIsArray($attribute['body']['data']['databasesUpdateEmailAttribute']);
|
||||
$this->assertFalse($attribute['body']['data']['databasesUpdateEmailAttribute']['required']);
|
||||
$this->assertEquals('torsten@appwrite.io', $attribute['body']['data']['databasesUpdateEmailAttribute']['default']);
|
||||
$this->assertEquals(200, $attribute['headers']['status-code']);
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testCreateCollection
|
||||
* @throws Exception
|
||||
|
|
@ -272,6 +460,50 @@ class DatabaseServerTest extends Scope
|
|||
return $data;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @depends testCreateEnumAttribute
|
||||
* @throws Exception
|
||||
*/
|
||||
public function testUpdateEnumAttribute($data): array
|
||||
{
|
||||
// Wait for attributes to be available
|
||||
sleep(3);
|
||||
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query = $this->getQuery(self::$UPDATE_ENUM_ATTRIBUTE);
|
||||
$gqlPayload = [
|
||||
'query' => $query,
|
||||
'variables' => [
|
||||
'databaseId' => $data['database']['_id'],
|
||||
'collectionId' => $data['collection']['_id'],
|
||||
'key' => 'role',
|
||||
'required' => false,
|
||||
'elements' => [
|
||||
'crew',
|
||||
'tech',
|
||||
'actor'
|
||||
],
|
||||
'default' => 'tech'
|
||||
]
|
||||
];
|
||||
|
||||
$attribute = $this->client->call(Client::METHOD_POST, '/graphql', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $gqlPayload);
|
||||
|
||||
$this->assertIsArray($attribute['body']['data']);
|
||||
$this->assertIsArray($attribute['body']['data']['databasesUpdateEnumAttribute']);
|
||||
$this->assertFalse($attribute['body']['data']['databasesUpdateEnumAttribute']['required']);
|
||||
$this->assertEquals('tech', $attribute['body']['data']['databasesUpdateEnumAttribute']['default']);
|
||||
$this->assertContains('tech', $attribute['body']['data']['databasesUpdateEnumAttribute']['elements']);
|
||||
$this->assertNotContains('guest', $attribute['body']['data']['databasesUpdateEnumAttribute']['elements']);
|
||||
$this->assertEquals(200, $attribute['headers']['status-code']);
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testCreateCollection
|
||||
* @throws Exception
|
||||
|
|
@ -302,6 +534,42 @@ class DatabaseServerTest extends Scope
|
|||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testCreateDatetimeAttribute
|
||||
* @throws Exception
|
||||
*/
|
||||
public function testUpdateDatetimeAttribute($data): array
|
||||
{
|
||||
// Wait for attributes to be available
|
||||
sleep(3);
|
||||
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query = $this->getQuery(self::$UPDATE_DATETIME_ATTRIBUTE);
|
||||
$gqlPayload = [
|
||||
'query' => $query,
|
||||
'variables' => [
|
||||
'databaseId' => $data['database']['_id'],
|
||||
'collectionId' => $data['collection']['_id'],
|
||||
'key' => 'dob',
|
||||
'required' => false,
|
||||
'default' => '2000-01-01T00:00:00Z'
|
||||
]
|
||||
];
|
||||
|
||||
$attribute = $this->client->call(Client::METHOD_POST, '/graphql', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $gqlPayload);
|
||||
|
||||
$this->assertIsArray($attribute['body']['data']);
|
||||
$this->assertIsArray($attribute['body']['data']['databasesUpdateDatetimeAttribute']);
|
||||
$this->assertFalse($attribute['body']['data']['databasesUpdateDatetimeAttribute']['required']);
|
||||
$this->assertEquals('2000-01-01T00:00:00Z', $attribute['body']['data']['databasesUpdateDatetimeAttribute']['default']);
|
||||
$this->assertEquals(200, $attribute['headers']['status-code']);
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testCreateCollection
|
||||
* @throws Exception
|
||||
|
|
@ -333,6 +601,42 @@ class DatabaseServerTest extends Scope
|
|||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testCreateIPAttribute
|
||||
* @throws Exception
|
||||
*/
|
||||
public function testUpdateIPAttribute($data): array
|
||||
{
|
||||
// Wait for attributes to be available
|
||||
sleep(3);
|
||||
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query = $this->getQuery(self::$UPDATE_IP_ATTRIBUTE);
|
||||
$gqlPayload = [
|
||||
'query' => $query,
|
||||
'variables' => [
|
||||
'databaseId' => $data['database']['_id'],
|
||||
'collectionId' => $data['collection']['_id'],
|
||||
'key' => 'ip',
|
||||
'required' => false,
|
||||
'default' => '127.0.0.1'
|
||||
]
|
||||
];
|
||||
|
||||
$attribute = $this->client->call(Client::METHOD_POST, '/graphql', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $gqlPayload);
|
||||
|
||||
$this->assertIsArray($attribute['body']['data']);
|
||||
$this->assertIsArray($attribute['body']['data']['databasesUpdateIpAttribute']);
|
||||
$this->assertFalse($attribute['body']['data']['databasesUpdateIpAttribute']['required']);
|
||||
$this->assertEquals('127.0.0.1', $attribute['body']['data']['databasesUpdateIpAttribute']['default']);
|
||||
$this->assertEquals(200, $attribute['headers']['status-code']);
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testCreateCollection
|
||||
* @throws Exception
|
||||
|
|
@ -365,15 +669,46 @@ class DatabaseServerTest extends Scope
|
|||
}
|
||||
|
||||
/**
|
||||
* @depends testCreateStringAttribute
|
||||
* @depends testCreateIntegerAttribute
|
||||
* @depends testCreateURLAttribute
|
||||
* @throws Exception
|
||||
*/
|
||||
public function testCreateIndex($data): array
|
||||
public function testUpdateURLAttribute($data): void
|
||||
{
|
||||
// Wait for attributes to be available
|
||||
sleep(3);
|
||||
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query = $this->getQuery(self::$UPDATE_URL_ATTRIBUTE);
|
||||
$gqlPayload = [
|
||||
'query' => $query,
|
||||
'variables' => [
|
||||
'databaseId' => $data['database']['_id'],
|
||||
'collectionId' => $data['collection']['_id'],
|
||||
'key' => 'url',
|
||||
'required' => false,
|
||||
'default' => 'https://cloud.appwrite.io'
|
||||
]
|
||||
];
|
||||
|
||||
$attribute = $this->client->call(Client::METHOD_POST, '/graphql', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $gqlPayload);
|
||||
|
||||
$this->assertIsArray($attribute['body']['data']);
|
||||
$this->assertIsArray($attribute['body']['data']['databasesUpdateUrlAttribute']);
|
||||
$this->assertFalse($attribute['body']['data']['databasesUpdateUrlAttribute']['required']);
|
||||
$this->assertEquals('https://cloud.appwrite.io', $attribute['body']['data']['databasesUpdateUrlAttribute']['default']);
|
||||
$this->assertEquals(200, $attribute['headers']['status-code']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testUpdateStringAttribute
|
||||
* @depends testUpdateIntegerAttribute
|
||||
* @throws Exception
|
||||
*/
|
||||
public function testCreateIndex($data): array
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query = $this->getQuery(self::$CREATE_INDEX);
|
||||
$gqlPayload = [
|
||||
|
|
@ -407,10 +742,10 @@ class DatabaseServerTest extends Scope
|
|||
}
|
||||
|
||||
/**
|
||||
* @depends testCreateStringAttribute
|
||||
* @depends testCreateIntegerAttribute
|
||||
* @depends testCreateBooleanAttribute
|
||||
* @depends testCreateEnumAttribute
|
||||
* @depends testUpdateStringAttribute
|
||||
* @depends testUpdateIntegerAttribute
|
||||
* @depends testUpdateBooleanAttribute
|
||||
* @depends testUpdateEnumAttribute
|
||||
* @throws Exception
|
||||
*/
|
||||
public function testCreateDocument($data): array
|
||||
|
|
@ -458,45 +793,45 @@ class DatabaseServerTest extends Scope
|
|||
];
|
||||
}
|
||||
|
||||
// /**
|
||||
// * @depends testCreateStringAttribute
|
||||
// * @depends testCreateIntegerAttribute
|
||||
// * @depends testCreateBooleanAttribute
|
||||
// * @depends testCreateFloatAttribute
|
||||
// * @depends testCreateEmailAttribute
|
||||
// * @depends testCreateEnumAttribute
|
||||
// * @depends testCreateDatetimeAttribute
|
||||
// * @throws Exception
|
||||
// */
|
||||
// public function testCreateCustomEntity(): array
|
||||
// {
|
||||
// $projectId = $this->getProject()['$id'];
|
||||
// $query = $this->getQuery(self::$CREATE_CUSTOM_ENTITY);
|
||||
// $gqlPayload = [
|
||||
// 'query' => $query,
|
||||
// 'variables' => [
|
||||
// 'name' => 'John Doe',
|
||||
// 'age' => 35,
|
||||
// 'alive' => true,
|
||||
// 'salary' => 9999.9,
|
||||
// 'email' => 'johndoe@appwrite.io',
|
||||
// 'role' => 'crew',
|
||||
// 'dob' => '2000-01-01T00:00:00Z',
|
||||
// ]
|
||||
// ];
|
||||
//
|
||||
// $actor = $this->client->call(Client::METHOD_POST, '/graphql', array_merge([
|
||||
// 'content-type' => 'application/json',
|
||||
// 'x-appwrite-project' => $projectId,
|
||||
// ], $this->getHeaders()), $gqlPayload);
|
||||
//
|
||||
// $this->assertArrayNotHasKey('errors', $actor['body']);
|
||||
// $this->assertIsArray($actor['body']['data']);
|
||||
// $actor = $actor['body']['data']['actorsCreate'];
|
||||
// $this->assertIsArray($actor);
|
||||
//
|
||||
// return $actor;
|
||||
// }
|
||||
// /**
|
||||
// * @depends testCreateStringAttribute
|
||||
// * @depends testCreateIntegerAttribute
|
||||
// * @depends testCreateBooleanAttribute
|
||||
// * @depends testCreateFloatAttribute
|
||||
// * @depends testCreateEmailAttribute
|
||||
// * @depends testCreateEnumAttribute
|
||||
// * @depends testCreateDatetimeAttribute
|
||||
// * @throws Exception
|
||||
// */
|
||||
// public function testCreateCustomEntity(): array
|
||||
// {
|
||||
// $projectId = $this->getProject()['$id'];
|
||||
// $query = $this->getQuery(self::$CREATE_CUSTOM_ENTITY);
|
||||
// $gqlPayload = [
|
||||
// 'query' => $query,
|
||||
// 'variables' => [
|
||||
// 'name' => 'John Doe',
|
||||
// 'age' => 35,
|
||||
// 'alive' => true,
|
||||
// 'salary' => 9999.9,
|
||||
// 'email' => 'johndoe@appwrite.io',
|
||||
// 'role' => 'crew',
|
||||
// 'dob' => '2000-01-01T00:00:00Z',
|
||||
// ]
|
||||
// ];
|
||||
//
|
||||
// $actor = $this->client->call(Client::METHOD_POST, '/graphql', array_merge([
|
||||
// 'content-type' => 'application/json',
|
||||
// 'x-appwrite-project' => $projectId,
|
||||
// ], $this->getHeaders()), $gqlPayload);
|
||||
//
|
||||
// $this->assertArrayNotHasKey('errors', $actor['body']);
|
||||
// $this->assertIsArray($actor['body']['data']);
|
||||
// $actor = $actor['body']['data']['actorsCreate'];
|
||||
// $this->assertIsArray($actor);
|
||||
//
|
||||
// return $actor;
|
||||
// }
|
||||
|
||||
public function testGetDatabases(): void
|
||||
{
|
||||
|
|
@ -593,8 +928,8 @@ class DatabaseServerTest extends Scope
|
|||
}
|
||||
|
||||
/**
|
||||
* @depends testCreateStringAttribute
|
||||
* @depends testCreateIntegerAttribute
|
||||
* @depends testUpdateStringAttribute
|
||||
* @depends testUpdateIntegerAttribute
|
||||
* @throws Exception
|
||||
*/
|
||||
public function testGetAttributes($data): void
|
||||
|
|
@ -752,52 +1087,52 @@ class DatabaseServerTest extends Scope
|
|||
$this->assertIsArray($document['body']['data']['databasesGetDocument']);
|
||||
}
|
||||
|
||||
// /**
|
||||
// * @depends testCreateCustomEntity
|
||||
// * @throws Exception
|
||||
// */
|
||||
// public function testGetCustomEntities($data)
|
||||
// {
|
||||
// $projectId = $this->getProject()['$id'];
|
||||
// $query = $this->getQuery(self::$GET_CUSTOM_ENTITIES);
|
||||
// $gqlPayload = [
|
||||
// 'query' => $query,
|
||||
// ];
|
||||
//
|
||||
// $customEntities = $this->client->call(Client::METHOD_POST, '/graphql', array_merge([
|
||||
// 'content-type' => 'application/json',
|
||||
// 'x-appwrite-project' => $projectId,
|
||||
// ], $this->getHeaders()), $gqlPayload);
|
||||
//
|
||||
// $this->assertArrayNotHasKey('errors', $customEntities['body']);
|
||||
// $this->assertIsArray($customEntities['body']['data']);
|
||||
// $this->assertIsArray($customEntities['body']['data']['actorsList']);
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * @depends testCreateCustomEntity
|
||||
// * @throws Exception
|
||||
// */
|
||||
// public function testGetCustomEntity($data)
|
||||
// {
|
||||
// $projectId = $this->getProject()['$id'];
|
||||
// $query = $this->getQuery(self::$GET_CUSTOM_ENTITY);
|
||||
// $gqlPayload = [
|
||||
// 'query' => $query,
|
||||
// 'variables' => [
|
||||
// 'id' => $data['id'],
|
||||
// ]
|
||||
// ];
|
||||
//
|
||||
// $entity = $this->client->call(Client::METHOD_POST, '/graphql', array_merge([
|
||||
// 'content-type' => 'application/json',
|
||||
// 'x-appwrite-project' => $projectId,
|
||||
// ], $this->getHeaders()), $gqlPayload);
|
||||
//
|
||||
// $this->assertArrayNotHasKey('errors', $entity['body']);
|
||||
// $this->assertIsArray($entity['body']['data']);
|
||||
// $this->assertIsArray($entity['body']['data']['actorsGet']);
|
||||
// }
|
||||
// /**
|
||||
// * @depends testCreateCustomEntity
|
||||
// * @throws Exception
|
||||
// */
|
||||
// public function testGetCustomEntities($data)
|
||||
// {
|
||||
// $projectId = $this->getProject()['$id'];
|
||||
// $query = $this->getQuery(self::$GET_CUSTOM_ENTITIES);
|
||||
// $gqlPayload = [
|
||||
// 'query' => $query,
|
||||
// ];
|
||||
//
|
||||
// $customEntities = $this->client->call(Client::METHOD_POST, '/graphql', array_merge([
|
||||
// 'content-type' => 'application/json',
|
||||
// 'x-appwrite-project' => $projectId,
|
||||
// ], $this->getHeaders()), $gqlPayload);
|
||||
//
|
||||
// $this->assertArrayNotHasKey('errors', $customEntities['body']);
|
||||
// $this->assertIsArray($customEntities['body']['data']);
|
||||
// $this->assertIsArray($customEntities['body']['data']['actorsList']);
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * @depends testCreateCustomEntity
|
||||
// * @throws Exception
|
||||
// */
|
||||
// public function testGetCustomEntity($data)
|
||||
// {
|
||||
// $projectId = $this->getProject()['$id'];
|
||||
// $query = $this->getQuery(self::$GET_CUSTOM_ENTITY);
|
||||
// $gqlPayload = [
|
||||
// 'query' => $query,
|
||||
// 'variables' => [
|
||||
// 'id' => $data['id'],
|
||||
// ]
|
||||
// ];
|
||||
//
|
||||
// $entity = $this->client->call(Client::METHOD_POST, '/graphql', array_merge([
|
||||
// 'content-type' => 'application/json',
|
||||
// 'x-appwrite-project' => $projectId,
|
||||
// ], $this->getHeaders()), $gqlPayload);
|
||||
//
|
||||
// $this->assertArrayNotHasKey('errors', $entity['body']);
|
||||
// $this->assertIsArray($entity['body']['data']);
|
||||
// $this->assertIsArray($entity['body']['data']['actorsGet']);
|
||||
// }
|
||||
|
||||
/**
|
||||
* @depends testCreateDatabase
|
||||
|
|
@ -885,33 +1220,33 @@ class DatabaseServerTest extends Scope
|
|||
$this->assertStringContainsString('New Document Name', $document['data']);
|
||||
}
|
||||
|
||||
// /**
|
||||
// * @depends testCreateCustomEntity
|
||||
// * @throws Exception
|
||||
// */
|
||||
// public function testUpdateCustomEntity(array $data)
|
||||
// {
|
||||
// $projectId = $this->getProject()['$id'];
|
||||
// $query = $this->getQuery(self::$UPDATE_CUSTOM_ENTITY);
|
||||
// $gqlPayload = [
|
||||
// 'query' => $query,
|
||||
// 'variables' => [
|
||||
// 'id' => $data['id'],
|
||||
// 'name' => 'New Custom Entity Name',
|
||||
// ]
|
||||
// ];
|
||||
//
|
||||
// $entity = $this->client->call(Client::METHOD_POST, '/graphql', array_merge([
|
||||
// 'content-type' => 'application/json',
|
||||
// 'x-appwrite-project' => $projectId,
|
||||
// ], $this->getHeaders()), $gqlPayload);
|
||||
//
|
||||
// $this->assertArrayNotHasKey('errors', $entity['body']);
|
||||
// $this->assertIsArray($entity['body']['data']);
|
||||
// $entity = $entity['body']['data']['actorsUpdate'];
|
||||
// $this->assertIsArray($entity);
|
||||
// $this->assertStringContainsString('New Custom Entity Name', $entity['name']);
|
||||
// }
|
||||
// /**
|
||||
// * @depends testCreateCustomEntity
|
||||
// * @throws Exception
|
||||
// */
|
||||
// public function testUpdateCustomEntity(array $data)
|
||||
// {
|
||||
// $projectId = $this->getProject()['$id'];
|
||||
// $query = $this->getQuery(self::$UPDATE_CUSTOM_ENTITY);
|
||||
// $gqlPayload = [
|
||||
// 'query' => $query,
|
||||
// 'variables' => [
|
||||
// 'id' => $data['id'],
|
||||
// 'name' => 'New Custom Entity Name',
|
||||
// ]
|
||||
// ];
|
||||
//
|
||||
// $entity = $this->client->call(Client::METHOD_POST, '/graphql', array_merge([
|
||||
// 'content-type' => 'application/json',
|
||||
// 'x-appwrite-project' => $projectId,
|
||||
// ], $this->getHeaders()), $gqlPayload);
|
||||
//
|
||||
// $this->assertArrayNotHasKey('errors', $entity['body']);
|
||||
// $this->assertIsArray($entity['body']['data']);
|
||||
// $entity = $entity['body']['data']['actorsUpdate'];
|
||||
// $this->assertIsArray($entity);
|
||||
// $this->assertStringContainsString('New Custom Entity Name', $entity['name']);
|
||||
// }
|
||||
|
||||
/**
|
||||
* @depends testCreateDocument
|
||||
|
|
@ -939,32 +1274,32 @@ class DatabaseServerTest extends Scope
|
|||
$this->assertEquals(204, $document['headers']['status-code']);
|
||||
}
|
||||
|
||||
// /**
|
||||
// * @depends testCreateCustomEntity
|
||||
// * @throws Exception
|
||||
// */
|
||||
// public function testDeleteCustomEntity(array $data)
|
||||
// {
|
||||
// $projectId = $this->getProject()['$id'];
|
||||
// $query = $this->getQuery(self::$DELETE_CUSTOM_ENTITY);
|
||||
// $gqlPayload = [
|
||||
// 'query' => $query,
|
||||
// 'variables' => [
|
||||
// 'id' => $data['id'],
|
||||
// ]
|
||||
// ];
|
||||
//
|
||||
// $entity = $this->client->call(Client::METHOD_POST, '/graphql', array_merge([
|
||||
// 'content-type' => 'application/json',
|
||||
// 'x-appwrite-project' => $projectId,
|
||||
// ], $this->getHeaders()), $gqlPayload);
|
||||
//
|
||||
// $this->assertIsNotArray($entity['body']);
|
||||
// $this->assertEquals(204, $entity['headers']['status-code']);
|
||||
// }
|
||||
// /**
|
||||
// * @depends testCreateCustomEntity
|
||||
// * @throws Exception
|
||||
// */
|
||||
// public function testDeleteCustomEntity(array $data)
|
||||
// {
|
||||
// $projectId = $this->getProject()['$id'];
|
||||
// $query = $this->getQuery(self::$DELETE_CUSTOM_ENTITY);
|
||||
// $gqlPayload = [
|
||||
// 'query' => $query,
|
||||
// 'variables' => [
|
||||
// 'id' => $data['id'],
|
||||
// ]
|
||||
// ];
|
||||
//
|
||||
// $entity = $this->client->call(Client::METHOD_POST, '/graphql', array_merge([
|
||||
// 'content-type' => 'application/json',
|
||||
// 'x-appwrite-project' => $projectId,
|
||||
// ], $this->getHeaders()), $gqlPayload);
|
||||
//
|
||||
// $this->assertIsNotArray($entity['body']);
|
||||
// $this->assertEquals(204, $entity['headers']['status-code']);
|
||||
// }
|
||||
|
||||
/**
|
||||
* @depends testCreateStringAttribute
|
||||
* @depends testUpdateStringAttribute
|
||||
* @throws Exception
|
||||
*/
|
||||
public function testDeleteAttribute($data): void
|
||||
|
|
|
|||
|
|
@ -111,6 +111,62 @@ class TeamsServerTest extends Scope
|
|||
$this->assertArrayNotHasKey('errors', $team['body']);
|
||||
$team = $team['body']['data']['teamsGet'];
|
||||
$this->assertIsArray($team);
|
||||
|
||||
return $team;
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testGetTeam
|
||||
*/
|
||||
public function testUpdateTeamPrefs($team)
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query = $this->getQuery(self::$UPDATE_TEAM_PREFERENCES);
|
||||
$graphQLPayload = [
|
||||
'query' => $query,
|
||||
'variables' => [
|
||||
'teamId' => $team['_id'],
|
||||
'prefs' => [
|
||||
'key' => 'value'
|
||||
]
|
||||
],
|
||||
];
|
||||
|
||||
$prefs = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $graphQLPayload);
|
||||
|
||||
$this->assertIsArray($prefs['body']['data']);
|
||||
$this->assertArrayNotHasKey('errors', $prefs['body']);
|
||||
$this->assertIsArray($prefs['body']['data']['teamsUpdatePrefs']);
|
||||
$this->assertEquals('{"key":"value"}', $prefs['body']['data']['teamsUpdatePrefs']['data']);
|
||||
|
||||
return $team;
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testUpdateTeamPrefs
|
||||
*/
|
||||
public function testGetTeamPreferences($team)
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query = $this->getQuery(self::$GET_TEAM_PREFERENCES);
|
||||
$graphQLPayload = [
|
||||
'query' => $query,
|
||||
'variables' => [
|
||||
'teamId' => $team['_id'],
|
||||
]
|
||||
];
|
||||
|
||||
$prefs = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $graphQLPayload);
|
||||
|
||||
$this->assertIsArray($prefs['body']['data']);
|
||||
$this->assertArrayNotHasKey('errors', $prefs['body']);
|
||||
$this->assertIsArray($prefs['body']['data']['teamsGetPrefs']);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -168,7 +224,7 @@ class TeamsServerTest extends Scope
|
|||
public function testUpdateTeam($team)
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query = $this->getQuery(self::$UPDATE_TEAM);
|
||||
$query = $this->getQuery(self::$UPDATE_TEAM_NAME);
|
||||
$graphQLPayload = [
|
||||
'query' => $query,
|
||||
'variables' => [
|
||||
|
|
@ -184,7 +240,7 @@ class TeamsServerTest extends Scope
|
|||
|
||||
$this->assertIsArray($team['body']['data']);
|
||||
$this->assertArrayNotHasKey('errors', $team['body']);
|
||||
$team = $team['body']['data']['teamsUpdate'];
|
||||
$team = $team['body']['data']['teamsUpdateName'];
|
||||
$this->assertEquals('New Name', $team['name']);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -992,6 +992,247 @@ class ProjectsConsoleClientTest extends Scope
|
|||
return $data;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @depends testUpdateProjectAuthLimit
|
||||
*/
|
||||
public function testUpdateProjectAuthPasswordHistory($data): array
|
||||
{
|
||||
$id = $data['projectId'] ?? '';
|
||||
|
||||
/**
|
||||
* Test for Failure
|
||||
*/
|
||||
$response = $this->client->call(Client::METHOD_PATCH, '/projects/' . $id . '/auth/password-history', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'limit' => 25,
|
||||
]);
|
||||
|
||||
$this->assertEquals(400, $response['headers']['status-code']);
|
||||
|
||||
|
||||
/**
|
||||
* Test for Success
|
||||
*/
|
||||
$response = $this->client->call(Client::METHOD_PATCH, '/projects/' . $id . '/auth/password-history', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'limit' => 1,
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertEquals(1, $response['body']['authPasswordHistory']);
|
||||
|
||||
|
||||
$email = uniqid() . 'user@localhost.test';
|
||||
$password = 'password';
|
||||
$name = 'User Name';
|
||||
|
||||
/**
|
||||
* Create new user
|
||||
*/
|
||||
$response = $this->client->call(Client::METHOD_POST, '/account', array_merge([
|
||||
'origin' => 'http://localhost',
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $id,
|
||||
]), [
|
||||
'userId' => ID::unique(),
|
||||
'email' => $email,
|
||||
'password' => $password,
|
||||
'name' => $name,
|
||||
]);
|
||||
|
||||
$this->assertEquals(201, $response['headers']['status-code']);
|
||||
|
||||
$userId = $response['body']['$id'];
|
||||
|
||||
// create session
|
||||
$session = $this->client->call(Client::METHOD_POST, '/account/sessions/email', [
|
||||
'origin' => 'http://localhost',
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $id,
|
||||
], [
|
||||
'email' => $email,
|
||||
'password' => $password,
|
||||
]);
|
||||
$this->assertEquals(201, $session['headers']['status-code']);
|
||||
$session = $this->client->parseCookie((string)$session['headers']['set-cookie'])['a_session_' . $id];
|
||||
|
||||
$response = $this->client->call(Client::METHOD_PATCH, '/account/password', array_merge([
|
||||
'origin' => 'http://localhost',
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $id,
|
||||
'cookie' => 'a_session_' . $id . '=' . $session,
|
||||
]), [
|
||||
'oldPassword' => $password,
|
||||
'password' => $password,
|
||||
]);
|
||||
|
||||
$this->assertEquals(409, $response['headers']['status-code']);
|
||||
|
||||
$headers = array_merge($this->getHeaders(), [
|
||||
'x-appwrite-mode' => 'admin',
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $id,
|
||||
]);
|
||||
|
||||
$response = $this->client->call(Client::METHOD_PATCH, '/users/' . $userId . '/password', $headers, [
|
||||
'password' => $password,
|
||||
]);
|
||||
|
||||
$this->assertEquals(409, $response['headers']['status-code']);
|
||||
|
||||
|
||||
/**
|
||||
* Reset
|
||||
*/
|
||||
$response = $this->client->call(Client::METHOD_PATCH, '/projects/' . $id . '/auth/password-history', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'limit' => 0,
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertEquals(0, $response['body']['authPasswordHistory']);
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testUpdateProjectAuthLimit
|
||||
*/
|
||||
public function testUpdateProjectAuthPasswordDictionary($data): array
|
||||
{
|
||||
$id = $data['projectId'] ?? '';
|
||||
|
||||
$password = 'password';
|
||||
$name = 'User Name';
|
||||
|
||||
/**
|
||||
* Test for Success
|
||||
*/
|
||||
|
||||
/**
|
||||
* create account
|
||||
*/
|
||||
$response = $this->client->call(Client::METHOD_POST, '/account', array_merge([
|
||||
'origin' => 'http://localhost',
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $id,
|
||||
]), [
|
||||
'userId' => ID::unique(),
|
||||
'email' => uniqid() . 'user@localhost.test',
|
||||
'password' => $password,
|
||||
'name' => $name,
|
||||
]);
|
||||
$this->assertEquals(201, $response['headers']['status-code']);
|
||||
$userId = $response['body']['$id'];
|
||||
|
||||
/**
|
||||
* Create user
|
||||
*/
|
||||
$user = $this->client->call(Client::METHOD_POST, '/users', array_merge($this->getHeaders(), [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $id,
|
||||
'x-appwrite-mode' => 'admin',
|
||||
]), [
|
||||
'userId' => ID::unique(),
|
||||
'email' => uniqid() . 'user@localhost.test',
|
||||
'password' => 'password',
|
||||
'name' => 'Cristiano Ronaldo',
|
||||
]);
|
||||
$this->assertEquals(201, $response['headers']['status-code']);
|
||||
|
||||
/**
|
||||
* Enable Disctionary
|
||||
*/
|
||||
$response = $this->client->call(Client::METHOD_PATCH, '/projects/' . $id . '/auth/password-dictionary', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'enabled' => true,
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertEquals(true, $response['body']['authPasswordDictionary']);
|
||||
|
||||
/**
|
||||
* Test for failure
|
||||
*/
|
||||
$response = $this->client->call(Client::METHOD_POST, '/account', array_merge([
|
||||
'origin' => 'http://localhost',
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $id,
|
||||
]), [
|
||||
'userId' => ID::unique(),
|
||||
'email' => uniqid() . 'user@localhost.test',
|
||||
'password' => $password,
|
||||
'name' => $name,
|
||||
]);
|
||||
|
||||
$this->assertEquals(400, $response['headers']['status-code']);
|
||||
|
||||
/**
|
||||
* Create user
|
||||
*/
|
||||
$user = $this->client->call(Client::METHOD_POST, '/users', array_merge($this->getHeaders(), [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $id,
|
||||
'x-appwrite-mode' => 'admin',
|
||||
]), [
|
||||
'userId' => ID::unique(),
|
||||
'email' => uniqid() . 'user@localhost.test',
|
||||
'password' => 'password',
|
||||
'name' => 'Cristiano Ronaldo',
|
||||
]);
|
||||
$this->assertEquals(400, $response['headers']['status-code']);
|
||||
|
||||
$headers = array_merge($this->getHeaders(), [
|
||||
'x-appwrite-mode' => 'admin',
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $id,
|
||||
]);
|
||||
|
||||
$response = $this->client->call(Client::METHOD_PATCH, '/users/' . $userId . '/password', $headers, [
|
||||
'password' => $password,
|
||||
]);
|
||||
|
||||
$this->assertEquals(400, $response['headers']['status-code']);
|
||||
|
||||
|
||||
/**
|
||||
* Reset
|
||||
*/
|
||||
$response = $this->client->call(Client::METHOD_PATCH, '/projects/' . $id . '/auth/password-history', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'limit' => 0,
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertEquals(0, $response['body']['authPasswordHistory']);
|
||||
|
||||
/**
|
||||
* Reset
|
||||
*/
|
||||
$response = $this->client->call(Client::METHOD_PATCH, '/projects/' . $id . '/auth/password-dictionary', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'enabled' => false,
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertEquals(false, $response['body']['authPasswordDictionary']);
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
|
||||
public function testUpdateProjectServiceStatusAdmin(): array
|
||||
{
|
||||
$team = $this->client->call(Client::METHOD_POST, '/teams', array_merge([
|
||||
|
|
@ -1414,8 +1655,6 @@ class ProjectsConsoleClientTest extends Scope
|
|||
'events' => ['users.*.delete', 'users.*.sessions.*.delete', 'buckets.*.files.*.create'],
|
||||
'url' => 'https://appwrite.io/new',
|
||||
'security' => false,
|
||||
'httpUser' => '',
|
||||
'httpPass' => ''
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
|
|
@ -1462,8 +1701,6 @@ class ProjectsConsoleClientTest extends Scope
|
|||
'events' => ['users.*.delete', 'users.*.sessions.*.delete', 'buckets.*.files.*.unknown'],
|
||||
'url' => 'https://appwrite.io/new',
|
||||
'security' => false,
|
||||
'httpUser' => '',
|
||||
'httpPass' => '',
|
||||
]);
|
||||
|
||||
$this->assertEquals(400, $response['headers']['status-code']);
|
||||
|
|
@ -1476,8 +1713,6 @@ class ProjectsConsoleClientTest extends Scope
|
|||
'events' => ['users.*.delete', 'users.*.sessions.*.delete', 'buckets.*.files.*.create'],
|
||||
'url' => 'appwrite.io/new',
|
||||
'security' => false,
|
||||
'httpUser' => '',
|
||||
'httpPass' => '',
|
||||
]);
|
||||
|
||||
$this->assertEquals(400, $response['headers']['status-code']);
|
||||
|
|
@ -1849,8 +2084,6 @@ class ProjectsConsoleClientTest extends Scope
|
|||
], $this->getHeaders()), [
|
||||
'type' => 'web',
|
||||
'name' => 'Web App',
|
||||
'key' => '',
|
||||
'store' => '',
|
||||
'hostname' => 'localhost',
|
||||
]);
|
||||
|
||||
|
|
@ -1871,8 +2104,6 @@ class ProjectsConsoleClientTest extends Scope
|
|||
'type' => 'flutter-ios',
|
||||
'name' => 'Flutter App (iOS)',
|
||||
'key' => 'com.example.ios',
|
||||
'store' => '',
|
||||
'hostname' => '',
|
||||
]);
|
||||
|
||||
$this->assertEquals(201, $response['headers']['status-code']);
|
||||
|
|
@ -1892,8 +2123,6 @@ class ProjectsConsoleClientTest extends Scope
|
|||
'type' => 'flutter-android',
|
||||
'name' => 'Flutter App (Android)',
|
||||
'key' => 'com.example.android',
|
||||
'store' => '',
|
||||
'hostname' => '',
|
||||
]);
|
||||
|
||||
$this->assertEquals(201, $response['headers']['status-code']);
|
||||
|
|
@ -1912,8 +2141,6 @@ class ProjectsConsoleClientTest extends Scope
|
|||
], $this->getHeaders()), [
|
||||
'type' => 'flutter-web',
|
||||
'name' => 'Flutter App (Web)',
|
||||
'key' => '',
|
||||
'store' => '',
|
||||
'hostname' => 'flutter.appwrite.io',
|
||||
]);
|
||||
|
||||
|
|
@ -1934,8 +2161,6 @@ class ProjectsConsoleClientTest extends Scope
|
|||
'type' => 'apple-ios',
|
||||
'name' => 'iOS App',
|
||||
'key' => 'com.example.ios',
|
||||
'store' => '',
|
||||
'hostname' => '',
|
||||
]);
|
||||
|
||||
$this->assertEquals(201, $response['headers']['status-code']);
|
||||
|
|
@ -1955,8 +2180,6 @@ class ProjectsConsoleClientTest extends Scope
|
|||
'type' => 'apple-macos',
|
||||
'name' => 'macOS App',
|
||||
'key' => 'com.example.macos',
|
||||
'store' => '',
|
||||
'hostname' => '',
|
||||
]);
|
||||
|
||||
$this->assertEquals(201, $response['headers']['status-code']);
|
||||
|
|
@ -1976,8 +2199,6 @@ class ProjectsConsoleClientTest extends Scope
|
|||
'type' => 'apple-watchos',
|
||||
'name' => 'watchOS App',
|
||||
'key' => 'com.example.watchos',
|
||||
'store' => '',
|
||||
'hostname' => '',
|
||||
]);
|
||||
|
||||
$this->assertEquals(201, $response['headers']['status-code']);
|
||||
|
|
@ -1997,8 +2218,6 @@ class ProjectsConsoleClientTest extends Scope
|
|||
'type' => 'apple-tvos',
|
||||
'name' => 'tvOS App',
|
||||
'key' => 'com.example.tvos',
|
||||
'store' => '',
|
||||
'hostname' => '',
|
||||
]);
|
||||
|
||||
$this->assertEquals(201, $response['headers']['status-code']);
|
||||
|
|
@ -2020,8 +2239,6 @@ class ProjectsConsoleClientTest extends Scope
|
|||
], $this->getHeaders()), [
|
||||
'type' => 'unknown',
|
||||
'name' => 'Web App',
|
||||
'key' => '',
|
||||
'store' => '',
|
||||
'hostname' => 'localhost',
|
||||
]);
|
||||
|
||||
|
|
@ -2216,8 +2433,6 @@ class ProjectsConsoleClientTest extends Scope
|
|||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'name' => 'Web App 2',
|
||||
'key' => '',
|
||||
'store' => '',
|
||||
'hostname' => 'localhost-new',
|
||||
]);
|
||||
|
||||
|
|
@ -2238,8 +2453,6 @@ class ProjectsConsoleClientTest extends Scope
|
|||
], $this->getHeaders()), [
|
||||
'name' => 'Flutter App (iOS) 2',
|
||||
'key' => 'com.example.ios2',
|
||||
'store' => '',
|
||||
'hostname' => '',
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
|
|
@ -2259,8 +2472,6 @@ class ProjectsConsoleClientTest extends Scope
|
|||
], $this->getHeaders()), [
|
||||
'name' => 'Flutter App (Android) 2',
|
||||
'key' => 'com.example.android2',
|
||||
'store' => '',
|
||||
'hostname' => '',
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
|
|
@ -2279,8 +2490,6 @@ class ProjectsConsoleClientTest extends Scope
|
|||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'name' => 'Flutter App (Web) 2',
|
||||
'key' => '',
|
||||
'store' => '',
|
||||
'hostname' => 'flutter2.appwrite.io',
|
||||
]);
|
||||
|
||||
|
|
@ -2301,8 +2510,6 @@ class ProjectsConsoleClientTest extends Scope
|
|||
], $this->getHeaders()), [
|
||||
'name' => 'iOS App 2',
|
||||
'key' => 'com.example.ios2',
|
||||
'store' => '',
|
||||
'hostname' => '',
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
|
|
@ -2322,8 +2529,6 @@ class ProjectsConsoleClientTest extends Scope
|
|||
], $this->getHeaders()), [
|
||||
'name' => 'macOS App 2',
|
||||
'key' => 'com.example.macos2',
|
||||
'store' => '',
|
||||
'hostname' => '',
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
|
|
@ -2343,8 +2548,6 @@ class ProjectsConsoleClientTest extends Scope
|
|||
], $this->getHeaders()), [
|
||||
'name' => 'watchOS App 2',
|
||||
'key' => 'com.example.watchos2',
|
||||
'store' => '',
|
||||
'hostname' => '',
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
|
|
@ -2364,8 +2567,6 @@ class ProjectsConsoleClientTest extends Scope
|
|||
], $this->getHeaders()), [
|
||||
'name' => 'tvOS App 2',
|
||||
'key' => 'com.example.tvos2',
|
||||
'store' => '',
|
||||
'hostname' => '',
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
|
|
@ -2386,8 +2587,6 @@ class ProjectsConsoleClientTest extends Scope
|
|||
], $this->getHeaders()), [
|
||||
'name' => 'Flutter App (Android) 2',
|
||||
'key' => 'com.example.android2',
|
||||
'store' => '',
|
||||
'hostname' => '',
|
||||
]);
|
||||
|
||||
$this->assertEquals(404, $response['headers']['status-code']);
|
||||
|
|
@ -2595,8 +2794,6 @@ class ProjectsConsoleClientTest extends Scope
|
|||
], $this->getHeaders()), [
|
||||
'type' => 'web',
|
||||
'name' => 'Too Long Hostname',
|
||||
'key' => '',
|
||||
'store' => '',
|
||||
'hostname' => \str_repeat("bestdomain", 25) . '.com' // 250 + 4 chars total (exactly above limit)
|
||||
]);
|
||||
|
||||
|
|
|
|||
|
|
@ -443,7 +443,7 @@ class RealtimeConsoleClientTest extends Scope
|
|||
'users.*.delete',
|
||||
],
|
||||
'schedule' => '0 0 1 1 *',
|
||||
'timeout' => 10,
|
||||
'timeout' => 10
|
||||
]);
|
||||
|
||||
$functionId = $response1['body']['$id'] ?? '';
|
||||
|
|
@ -482,6 +482,7 @@ class RealtimeConsoleClientTest extends Scope
|
|||
], $this->getHeaders()), [
|
||||
'entrypoint' => 'index.php',
|
||||
'code' => new CURLFile($code, 'application/x-gzip', \basename($code)),
|
||||
'activate' => true
|
||||
]);
|
||||
|
||||
$deploymentId = $deployment['body']['$id'] ?? '';
|
||||
|
|
|
|||
|
|
@ -1284,7 +1284,8 @@ class RealtimeCustomClientTest extends Scope
|
|||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
]), [
|
||||
'entrypoint' => 'index.php',
|
||||
'code' => new CURLFile($code, 'application/x-gzip', basename($code))
|
||||
'code' => new CURLFile($code, 'application/x-gzip', basename($code)),
|
||||
'activate' => true
|
||||
]);
|
||||
|
||||
$deploymentId = $deployment['body']['$id'] ?? '';
|
||||
|
|
@ -1458,6 +1459,43 @@ class RealtimeCustomClientTest extends Scope
|
|||
$this->assertContains("teams.*", $response['data']['events']);
|
||||
$this->assertNotEmpty($response['data']['payload']);
|
||||
|
||||
/**
|
||||
* Test Team Update Prefs
|
||||
*/
|
||||
$team = $this->client->call(Client::METHOD_PUT, '/teams/' . $teamId . '/prefs', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), [
|
||||
'prefs' => [
|
||||
'funcKey1' => 'funcValue1',
|
||||
'funcKey2' => 'funcValue2',
|
||||
]
|
||||
]);
|
||||
|
||||
$this->assertEquals($team['headers']['status-code'], 200);
|
||||
$this->assertEquals($team['body']['funcKey1'], 'funcValue1');
|
||||
$this->assertEquals($team['body']['funcKey2'], 'funcValue2');
|
||||
|
||||
$response = json_decode($client->receive(), true);
|
||||
|
||||
$this->assertArrayHasKey('type', $response);
|
||||
$this->assertArrayHasKey('data', $response);
|
||||
$this->assertEquals('event', $response['type']);
|
||||
$this->assertNotEmpty($response['data']);
|
||||
$this->assertArrayHasKey('timestamp', $response['data']);
|
||||
$this->assertCount(2, $response['data']['channels']);
|
||||
$this->assertContains('teams', $response['data']['channels']);
|
||||
$this->assertContains("teams.{$teamId}", $response['data']['channels']);
|
||||
$this->assertContains("teams.{$teamId}.update", $response['data']['events']);
|
||||
$this->assertContains("teams.{$teamId}.update.prefs", $response['data']['events']);
|
||||
$this->assertContains("teams.{$teamId}", $response['data']['events']);
|
||||
$this->assertContains("teams.*.update.prefs", $response['data']['events']);
|
||||
$this->assertContains("teams.*.update", $response['data']['events']);
|
||||
$this->assertContains("teams.*", $response['data']['events']);
|
||||
$this->assertNotEmpty($response['data']['payload']);
|
||||
$this->assertEquals($response['data']['payload']['funcKey1'], 'funcValue1');
|
||||
$this->assertEquals($response['data']['payload']['funcKey2'], 'funcValue2');
|
||||
|
||||
$client->close();
|
||||
|
||||
return ['teamId' => $teamId];
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ use Tests\E2E\Client;
|
|||
use Utopia\Database\Helpers\ID;
|
||||
use Utopia\Database\Helpers\Permission;
|
||||
use Utopia\Database\Helpers\Role;
|
||||
use Utopia\Database\Validator\DatetimeValidator;
|
||||
use Utopia\Database\Validator\Datetime as DatetimeValidator;
|
||||
|
||||
trait StorageBase
|
||||
{
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ use Tests\E2E\Scopes\SideClient;
|
|||
use Utopia\Database\Helpers\ID;
|
||||
use Utopia\Database\Helpers\Permission;
|
||||
use Utopia\Database\Helpers\Role;
|
||||
use Utopia\Database\Validator\DatetimeValidator;
|
||||
use Utopia\Database\Validator\Datetime as DatetimeValidator;
|
||||
|
||||
class StorageCustomClientTest extends Scope
|
||||
{
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ use Tests\E2E\Scopes\ProjectCustom;
|
|||
use Tests\E2E\Scopes\Scope;
|
||||
use Tests\E2E\Scopes\SideServer;
|
||||
use Utopia\Database\Helpers\ID;
|
||||
use Utopia\Database\Validator\DatetimeValidator;
|
||||
use Utopia\Database\Validator\Datetime as DatetimeValidator;
|
||||
|
||||
class StorageCustomServerTest extends Scope
|
||||
{
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ namespace Tests\E2E\Services\Teams;
|
|||
|
||||
use Tests\E2E\Client;
|
||||
use Utopia\Database\Helpers\ID;
|
||||
use Utopia\Database\Validator\DatetimeValidator;
|
||||
use Utopia\Database\Validator\Datetime as DatetimeValidator;
|
||||
|
||||
trait TeamsBase
|
||||
{
|
||||
|
|
@ -27,6 +27,8 @@ trait TeamsBase
|
|||
$this->assertEquals('Arsenal', $response1['body']['name']);
|
||||
$this->assertGreaterThan(-1, $response1['body']['total']);
|
||||
$this->assertIsInt($response1['body']['total']);
|
||||
$this->assertArrayHasKey('prefs', $response1['body']);
|
||||
|
||||
$dateValidator = new DatetimeValidator();
|
||||
$this->assertEquals(true, $dateValidator->isValid($response1['body']['$createdAt']));
|
||||
|
||||
|
|
@ -48,6 +50,7 @@ trait TeamsBase
|
|||
$this->assertEquals('Manchester United', $response2['body']['name']);
|
||||
$this->assertGreaterThan(-1, $response2['body']['total']);
|
||||
$this->assertIsInt($response2['body']['total']);
|
||||
$this->assertArrayHasKey('prefs', $response2['body']);
|
||||
$this->assertEquals(true, $dateValidator->isValid($response2['body']['$createdAt']));
|
||||
|
||||
$response3 = $this->client->call(Client::METHOD_POST, '/teams', array_merge([
|
||||
|
|
@ -64,6 +67,7 @@ trait TeamsBase
|
|||
$this->assertGreaterThan(-1, $response3['body']['total']);
|
||||
$this->assertIsInt($response3['body']['total']);
|
||||
$this->assertEquals(true, $dateValidator->isValid($response3['body']['$createdAt']));
|
||||
|
||||
/**
|
||||
* Test for FAILURE
|
||||
*/
|
||||
|
|
@ -98,6 +102,7 @@ trait TeamsBase
|
|||
$this->assertEquals('Arsenal', $response['body']['name']);
|
||||
$this->assertGreaterThan(-1, $response['body']['total']);
|
||||
$this->assertIsInt($response['body']['total']);
|
||||
$this->assertArrayHasKey('prefs', $response['body']);
|
||||
$dateValidator = new DatetimeValidator();
|
||||
$this->assertEquals(true, $dateValidator->isValid($response['body']['$createdAt']));
|
||||
|
||||
|
|
@ -292,6 +297,7 @@ trait TeamsBase
|
|||
$this->assertEquals('Demo New', $response['body']['name']);
|
||||
$this->assertGreaterThan(-1, $response['body']['total']);
|
||||
$this->assertIsInt($response['body']['total']);
|
||||
$this->assertArrayHasKey('prefs', $response['body']);
|
||||
$this->assertEquals(true, $dateValidator->isValid($response['body']['$createdAt']));
|
||||
|
||||
/**
|
||||
|
|
@ -328,6 +334,7 @@ trait TeamsBase
|
|||
$this->assertEquals('Demo', $response['body']['name']);
|
||||
$this->assertGreaterThan(-1, $response['body']['total']);
|
||||
$this->assertIsInt($response['body']['total']);
|
||||
$this->assertArrayHasKey('prefs', $response['body']);
|
||||
$dateValidator = new DatetimeValidator();
|
||||
$this->assertEquals(true, $dateValidator->isValid($response['body']['$createdAt']));
|
||||
|
||||
|
|
@ -351,4 +358,63 @@ trait TeamsBase
|
|||
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testCreateTeam
|
||||
*/
|
||||
public function testUpdateAndGetUserPrefs(array $data): void
|
||||
{
|
||||
$id = $data['teamUid'] ?? '';
|
||||
|
||||
/**
|
||||
* Test for SUCCESS
|
||||
*/
|
||||
$team = $this->client->call(Client::METHOD_PUT, '/teams/' . $id . '/prefs', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'prefs' => [
|
||||
'funcKey1' => 'funcValue1',
|
||||
'funcKey2' => 'funcValue2',
|
||||
],
|
||||
]);
|
||||
|
||||
$this->assertEquals($team['headers']['status-code'], 200);
|
||||
$this->assertEquals($team['body']['funcKey1'], 'funcValue1');
|
||||
$this->assertEquals($team['body']['funcKey2'], 'funcValue2');
|
||||
|
||||
$team = $this->client->call(Client::METHOD_GET, '/teams/' . $id . '/prefs', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()));
|
||||
|
||||
$this->assertEquals($team['headers']['status-code'], 200);
|
||||
$this->assertEquals($team['body'], [
|
||||
'funcKey1' => 'funcValue1',
|
||||
'funcKey2' => 'funcValue2',
|
||||
]);
|
||||
|
||||
$team = $this->client->call(Client::METHOD_GET, '/teams/' . $id, array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()));
|
||||
|
||||
$this->assertEquals($team['headers']['status-code'], 200);
|
||||
$this->assertEquals($team['body']['prefs'], [
|
||||
'funcKey1' => 'funcValue1',
|
||||
'funcKey2' => 'funcValue2',
|
||||
]);
|
||||
|
||||
/**
|
||||
* Test for FAILURE
|
||||
*/
|
||||
$user = $this->client->call(Client::METHOD_PUT, '/teams/' . $id . '/prefs', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'prefs' => 'bad-string',
|
||||
]);
|
||||
|
||||
$this->assertEquals($user['headers']['status-code'], 400);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ namespace Tests\E2E\Services\Teams;
|
|||
|
||||
use Tests\E2E\Client;
|
||||
use Utopia\Database\Helpers\ID;
|
||||
use Utopia\Database\Validator\DatetimeValidator;
|
||||
use Utopia\Database\Validator\Datetime as DatetimeValidator;
|
||||
|
||||
trait TeamsBaseClient
|
||||
{
|
||||
|
|
@ -218,6 +218,69 @@ trait TeamsBaseClient
|
|||
$membershipUid = substr($lastEmail['text'], strpos($lastEmail['text'], '?membershipId=', 0) + 14, 20);
|
||||
$userUid = substr($lastEmail['text'], strpos($lastEmail['text'], '&userId=', 0) + 8, 20);
|
||||
|
||||
/**
|
||||
* Test with UserId
|
||||
* Create user
|
||||
*/
|
||||
$secondEmail = uniqid() . 'foe@localhost.test';
|
||||
$secondName = 'Another Foe';
|
||||
$response = $this->client->call(Client::METHOD_POST, '/account', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'userId' => 'unique()',
|
||||
'email' => $secondEmail,
|
||||
'password' => 'password',
|
||||
'name' => $secondName
|
||||
]);
|
||||
$this->assertEquals(201, $response['headers']['status-code']);
|
||||
$userId = $response['body']['$id'];
|
||||
|
||||
/**
|
||||
* Test for UserID
|
||||
* Failure
|
||||
*/
|
||||
$response = $this->client->call(Client::METHOD_POST, '/teams/' . $teamUid . '/memberships', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'userId' => 'abcdefdg',
|
||||
'roles' => ['admin', 'editor'],
|
||||
'url' => 'http://localhost:5000/join-us#title'
|
||||
]);
|
||||
|
||||
$this->assertEquals(404, $response['headers']['status-code']);
|
||||
|
||||
/**
|
||||
* Test for UserID
|
||||
* SUCCESS
|
||||
*/
|
||||
$response = $this->client->call(Client::METHOD_POST, '/teams/' . $teamUid . '/memberships', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'userId' => $userId,
|
||||
'roles' => ['admin', 'editor'],
|
||||
'url' => 'http://localhost:5000/join-us#title'
|
||||
]);
|
||||
|
||||
$this->assertEquals(201, $response['headers']['status-code']);
|
||||
$this->assertNotEmpty($response['body']['$id']);
|
||||
$this->assertNotEmpty($response['body']['userId']);
|
||||
$this->assertEquals($secondName, $response['body']['userName']);
|
||||
$this->assertEquals($secondEmail, $response['body']['userEmail']);
|
||||
$this->assertNotEmpty($response['body']['teamId']);
|
||||
$this->assertNotEmpty($response['body']['teamName']);
|
||||
$this->assertCount(2, $response['body']['roles']);
|
||||
$this->assertEquals(false, (new DateTimeValidator())->isValid($response['body']['joined'])); // is null in DB
|
||||
$this->assertEquals(false, $response['body']['confirm']);
|
||||
|
||||
$lastEmail = $this->getLastEmail();
|
||||
|
||||
$this->assertEquals($secondEmail, $lastEmail['to'][0]['address']);
|
||||
$this->assertEquals($secondName, $lastEmail['to'][0]['name']);
|
||||
$this->assertEquals('Invitation to ' . $teamName . ' Team at ' . $this->getProject()['name'], $lastEmail['subject']);
|
||||
|
||||
/**
|
||||
* Test for FAILURE
|
||||
*/
|
||||
|
|
@ -294,7 +357,7 @@ trait TeamsBaseClient
|
|||
$this->assertEquals(200, $memberships['headers']['status-code']);
|
||||
$this->assertIsInt($memberships['body']['total']);
|
||||
$this->assertNotEmpty($memberships['body']['memberships']);
|
||||
$this->assertCount(2, $memberships['body']['memberships']);
|
||||
$this->assertCount(3, $memberships['body']['memberships']);
|
||||
|
||||
$response = $this->client->call(Client::METHOD_GET, '/teams/' . $data['teamUid'] . '/memberships', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
|
|
@ -306,7 +369,7 @@ trait TeamsBaseClient
|
|||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertIsInt($response['body']['total']);
|
||||
$this->assertNotEmpty($response['body']['memberships']);
|
||||
$this->assertCount(1, $response['body']['memberships']);
|
||||
$this->assertCount(2, $response['body']['memberships']);
|
||||
$this->assertEquals($memberships['body']['memberships'][1]['$id'], $response['body']['memberships'][0]['$id']);
|
||||
}
|
||||
|
||||
|
|
@ -454,11 +517,22 @@ trait TeamsBaseClient
|
|||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
]), [
|
||||
'secret' => $secret,
|
||||
'userId' => ID::custom(''),
|
||||
'userId' => ID::custom('$notallowed'),
|
||||
]);
|
||||
|
||||
$this->assertEquals(400, $response['headers']['status-code']);
|
||||
|
||||
$response = $this->client->call(Client::METHOD_PATCH, '/teams/' . $teamUid . '/memberships/' . $membershipUid . '/status', array_merge([
|
||||
'origin' => 'http://localhost',
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
]), [
|
||||
'secret' => $secret,
|
||||
'userId' => ID::custom(''),
|
||||
]);
|
||||
|
||||
$this->assertEquals(401, $response['headers']['status-code']);
|
||||
|
||||
$response = $this->client->call(Client::METHOD_PATCH, '/teams/' . $teamUid . '/memberships/' . $membershipUid . '/status', array_merge([
|
||||
'origin' => 'http://localhost',
|
||||
'content-type' => 'application/json',
|
||||
|
|
@ -563,7 +637,7 @@ trait TeamsBaseClient
|
|||
], $this->getHeaders()));
|
||||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertEquals(2, $response['body']['total']);
|
||||
$this->assertEquals(3, $response['body']['total']);
|
||||
|
||||
$ownerMembershipUid = $response['body']['memberships'][0]['$id'];
|
||||
|
||||
|
|
@ -618,7 +692,7 @@ trait TeamsBaseClient
|
|||
], $this->getHeaders()));
|
||||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertEquals(1, $response['body']['total']);
|
||||
$this->assertEquals(2, $response['body']['total']);
|
||||
|
||||
/**
|
||||
* Test for when the owner tries to delete their membership
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
namespace Tests\E2E\Services\Teams;
|
||||
|
||||
use Tests\E2E\Client;
|
||||
use Utopia\Database\Validator\DatetimeValidator;
|
||||
use Utopia\Database\Validator\Datetime as DatetimeValidator;
|
||||
|
||||
trait TeamsBaseServer
|
||||
{
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ trait UsersBase
|
|||
$this->assertEquals(true, $res['body']['status']);
|
||||
$this->assertGreaterThan('2000-01-01 00:00:00', $res['body']['registration']);
|
||||
|
||||
/**
|
||||
/**
|
||||
* Test Create with hashed passwords
|
||||
*/
|
||||
$res = $this->client->call(Client::METHOD_POST, '/users/md5', array_merge([
|
||||
|
|
@ -180,7 +180,7 @@ trait UsersBase
|
|||
*/
|
||||
public function testCreateUserSessionHashed(array $data): void
|
||||
{
|
||||
$userIds = [ 'md5', 'bcrypt', 'argon2', 'sha512', 'scrypt', 'phpass', 'scrypt-modified' ];
|
||||
$userIds = ['md5', 'bcrypt', 'argon2', 'sha512', 'scrypt', 'phpass', 'scrypt-modified'];
|
||||
|
||||
foreach ($userIds as $userId) {
|
||||
// Ensure sessions can be created with hashed passwords
|
||||
|
|
@ -236,7 +236,7 @@ trait UsersBase
|
|||
{
|
||||
/**
|
||||
* Test for SUCCESS
|
||||
*/
|
||||
*/
|
||||
|
||||
// Email + password
|
||||
$response = $this->client->call(Client::METHOD_POST, '/users', array_merge([
|
||||
|
|
@ -967,7 +967,7 @@ trait UsersBase
|
|||
$this->assertEquals($user['headers']['status-code'], 200);
|
||||
$this->assertEquals($user['body']['phone'], $updatedNumber);
|
||||
|
||||
/**
|
||||
/**
|
||||
* Test for FAILURE
|
||||
*/
|
||||
|
||||
|
|
@ -1037,7 +1037,7 @@ trait UsersBase
|
|||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'queries' => [ 'limit(1)' ],
|
||||
'queries' => ['limit(1)'],
|
||||
]);
|
||||
|
||||
$this->assertEquals($logs['headers']['status-code'], 200);
|
||||
|
|
@ -1049,7 +1049,7 @@ trait UsersBase
|
|||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'queries' => [ 'offset(1)' ],
|
||||
'queries' => ['offset(1)'],
|
||||
]);
|
||||
|
||||
$this->assertEquals($logs['headers']['status-code'], 200);
|
||||
|
|
@ -1060,7 +1060,7 @@ trait UsersBase
|
|||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'queries' => [ 'limit(1)', 'offset(1)' ],
|
||||
'queries' => ['limit(1)', 'offset(1)'],
|
||||
]);
|
||||
|
||||
$this->assertEquals($logs['headers']['status-code'], 200);
|
||||
|
|
@ -1080,15 +1080,6 @@ trait UsersBase
|
|||
|
||||
$this->assertEquals($response['headers']['status-code'], 400);
|
||||
|
||||
$response = $this->client->call(Client::METHOD_GET, '/users/' . $data['userId'] . '/logs', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'queries' => ['limit(101)']
|
||||
]);
|
||||
|
||||
$this->assertEquals($response['headers']['status-code'], 400);
|
||||
|
||||
$response = $this->client->call(Client::METHOD_GET, '/users/' . $data['userId'] . '/logs', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
|
|
@ -1098,15 +1089,6 @@ trait UsersBase
|
|||
|
||||
$this->assertEquals($response['headers']['status-code'], 400);
|
||||
|
||||
$response = $this->client->call(Client::METHOD_GET, '/users/' . $data['userId'] . '/logs', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'queries' => ['offset(5001)']
|
||||
]);
|
||||
|
||||
$this->assertEquals($response['headers']['status-code'], 400);
|
||||
|
||||
$response = $this->client->call(Client::METHOD_GET, '/users/' . $data['userId'] . '/logs', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ use Tests\E2E\Client;
|
|||
use Utopia\Database\Helpers\ID;
|
||||
use Utopia\Database\Helpers\Permission;
|
||||
use Utopia\Database\Helpers\Role;
|
||||
use Utopia\Database\Validator\DatetimeValidator;
|
||||
use Utopia\Database\Validator\Datetime as DatetimeValidator;
|
||||
|
||||
trait WebhooksBase
|
||||
{
|
||||
|
|
@ -777,6 +777,53 @@ trait WebhooksBase
|
|||
return ['teamId' => $team['body']['$id']];
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testCreateTeam
|
||||
*/
|
||||
public function testUpdateTeamPrefs(array $data): array
|
||||
{
|
||||
$id = $data['teamId'] ?? '';
|
||||
|
||||
$team = $this->client->call(Client::METHOD_PUT, '/teams/' . $id . '/prefs', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'prefs' => [
|
||||
'prefKey1' => 'prefValue1',
|
||||
'prefKey2' => 'prefValue2',
|
||||
]
|
||||
]);
|
||||
|
||||
$this->assertEquals($team['headers']['status-code'], 200);
|
||||
$this->assertIsArray($team['body']);
|
||||
|
||||
$webhook = $this->getLastRequest();
|
||||
$signatureKey = $this->getProject()['signatureKey'];
|
||||
$payload = json_encode($webhook['data']);
|
||||
$url = $webhook['url'];
|
||||
$signatureExpected = base64_encode(hash_hmac('sha1', $url . $payload, $signatureKey, true));
|
||||
|
||||
$this->assertEquals($webhook['method'], 'POST');
|
||||
$this->assertEquals($webhook['headers']['Content-Type'], 'application/json');
|
||||
$this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io');
|
||||
$this->assertStringContainsString('teams.*', $webhook['headers']['X-Appwrite-Webhook-Events']);
|
||||
$this->assertStringContainsString('teams.*.update', $webhook['headers']['X-Appwrite-Webhook-Events']);
|
||||
$this->assertStringContainsString('teams.*.update.prefs', $webhook['headers']['X-Appwrite-Webhook-Events']);
|
||||
$this->assertStringContainsString("teams.{$id}", $webhook['headers']['X-Appwrite-Webhook-Events']);
|
||||
$this->assertStringContainsString("teams.{$id}.update", $webhook['headers']['X-Appwrite-Webhook-Events']);
|
||||
$this->assertStringContainsString("teams.{$id}.update.prefs", $webhook['headers']['X-Appwrite-Webhook-Events']);
|
||||
$this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], $signatureExpected);
|
||||
$this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']);
|
||||
$this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']);
|
||||
$this->assertEquals(empty($webhook['headers']['X-Appwrite-Webhook-User-Id'] ?? ''), ('server' === $this->getSide()));
|
||||
$this->assertEquals($webhook['data'], [
|
||||
'prefKey1' => 'prefValue1',
|
||||
'prefKey2' => 'prefValue2',
|
||||
]);
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
public function testDeleteTeam(): array
|
||||
{
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ use Tests\E2E\Scopes\Scope;
|
|||
use Tests\E2E\Scopes\ProjectCustom;
|
||||
use Tests\E2E\Scopes\SideClient;
|
||||
use Utopia\Database\Helpers\ID;
|
||||
use Utopia\Database\Validator\DatetimeValidator;
|
||||
use Utopia\Database\Validator\Datetime as DatetimeValidator;
|
||||
|
||||
class WebhooksCustomClientTest extends Scope
|
||||
{
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ use Utopia\CLI\Console;
|
|||
use Utopia\Database\Helpers\ID;
|
||||
use Utopia\Database\Helpers\Permission;
|
||||
use Utopia\Database\Helpers\Role;
|
||||
use Utopia\Database\Validator\DatetimeValidator;
|
||||
use Utopia\Database\Validator\Datetime as DatetimeValidator;
|
||||
|
||||
class WebhooksCustomServerTest extends Scope
|
||||
{
|
||||
|
|
@ -484,16 +484,6 @@ class WebhooksCustomServerTest extends Scope
|
|||
$this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']);
|
||||
$this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']);
|
||||
|
||||
$function = $this->client->call(Client::METHOD_PUT, '/functions/' . $data['functionId'], array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'name' => 'Test Failure',
|
||||
'execute' => [ 'not-valid-permission' ]
|
||||
]);
|
||||
|
||||
$this->assertEquals($function['headers']['status-code'], 400);
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
|
|
@ -516,7 +506,8 @@ class WebhooksCustomServerTest extends Scope
|
|||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'entrypoint' => 'index.php',
|
||||
'code' => new CURLFile($code, 'application/x-gzip', \basename($code))
|
||||
'code' => new CURLFile($code, 'application/x-gzip', \basename($code)),
|
||||
'activate' => true
|
||||
]);
|
||||
|
||||
$id = $data['functionId'] ?? '';
|
||||
|
|
|
|||
28
tests/unit/Auth/Validator/PasswordDictionaryTest.php
Normal file
28
tests/unit/Auth/Validator/PasswordDictionaryTest.php
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
<?php
|
||||
|
||||
namespace Tests\Unit\Auth\Validator;
|
||||
|
||||
use Appwrite\Auth\Validator\PasswordDictionary;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Utopia\Database\Document;
|
||||
|
||||
class PasswordDictionaryTest extends TestCase
|
||||
{
|
||||
protected ?PasswordDictionary $object = null;
|
||||
|
||||
public function setUp(): void
|
||||
{
|
||||
$this->object = new PasswordDictionary(
|
||||
['password' => true, '123456' => true],
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
public function testValues(): void
|
||||
{
|
||||
$this->assertEquals($this->object->isValid('1'), false); // to check parent is being called
|
||||
$this->assertEquals($this->object->isValid('123456'), false);
|
||||
$this->assertEquals($this->object->isValid('password'), false);
|
||||
$this->assertEquals($this->object->isValid('myPasswordIsRight'), true);
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue