From 64b4ea955171f1d27dc9e2af5ce212631447c5ad Mon Sep 17 00:00:00 2001 From: Harsh Mahajan Date: Mon, 10 Nov 2025 11:56:08 +0000 Subject: [PATCH 01/22] refactor(account): move MFA endpoints to module actions --- app/controllers/api/account.php | 939 ------------------ src/Appwrite/Platform/Appwrite.php | 2 + .../Http/Mfa/Authenticators/Create.php | 143 +++ .../Http/Mfa/Authenticators/Delete.php | 107 ++ .../Http/Mfa/Authenticators/Update.php | 135 +++ .../Account/Http/Mfa/Challenge/Create.php | 329 ++++++ .../Account/Http/Mfa/Challenge/Update.php | 160 +++ .../Account/Http/Mfa/Factors/XList.php | 89 ++ .../Account/Http/Mfa/RecoveryCodes/Create.php | 105 ++ .../Account/Http/Mfa/RecoveryCodes/Get.php | 86 ++ .../Account/Http/Mfa/RecoveryCodes/Update.php | 104 ++ .../Modules/Account/Http/Mfa/Update.php | 99 ++ .../Platform/Modules/Account/Module.php | 14 + .../Modules/Account/Services/Http.php | 34 + 14 files changed, 1407 insertions(+), 939 deletions(-) create mode 100644 src/Appwrite/Platform/Modules/Account/Http/Mfa/Authenticators/Create.php create mode 100644 src/Appwrite/Platform/Modules/Account/Http/Mfa/Authenticators/Delete.php create mode 100644 src/Appwrite/Platform/Modules/Account/Http/Mfa/Authenticators/Update.php create mode 100644 src/Appwrite/Platform/Modules/Account/Http/Mfa/Challenge/Create.php create mode 100644 src/Appwrite/Platform/Modules/Account/Http/Mfa/Challenge/Update.php create mode 100644 src/Appwrite/Platform/Modules/Account/Http/Mfa/Factors/XList.php create mode 100644 src/Appwrite/Platform/Modules/Account/Http/Mfa/RecoveryCodes/Create.php create mode 100644 src/Appwrite/Platform/Modules/Account/Http/Mfa/RecoveryCodes/Get.php create mode 100644 src/Appwrite/Platform/Modules/Account/Http/Mfa/RecoveryCodes/Update.php create mode 100644 src/Appwrite/Platform/Modules/Account/Http/Mfa/Update.php create mode 100644 src/Appwrite/Platform/Modules/Account/Module.php create mode 100644 src/Appwrite/Platform/Modules/Account/Services/Http.php diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index 9d1987591e..9d71985e67 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -2,9 +2,7 @@ use Ahc\Jwt\JWT; use Appwrite\Auth\Auth; -use Appwrite\Auth\MFA\Challenge; use Appwrite\Auth\MFA\Type; -use Appwrite\Auth\MFA\Type\TOTP; use Appwrite\Auth\OAuth2\Exception as OAuth2Exception; use Appwrite\Auth\Phrase; use Appwrite\Auth\Validator\Password; @@ -4077,943 +4075,6 @@ App::put('/v1/account/verifications/phone') $response->dynamic($verificationDocument, Response::MODEL_TOKEN); }); -App::patch('/v1/account/mfa') - ->desc('Update MFA') - ->groups(['api', 'account']) - ->label('event', 'users.[userId].update.mfa') - ->label('scope', 'account') - ->label('audits.event', 'user.update') - ->label('audits.resource', 'user/{response.$id}') - ->label('audits.userId', '{response.$id}') - ->label('sdk', new Method( - namespace: 'account', - group: 'mfa', - name: 'updateMFA', - description: '/docs/references/account/update-mfa.md', - auth: [AuthType::SESSION, AuthType::JWT], - responses: [ - new SDKResponse( - code: Response::STATUS_CODE_OK, - model: Response::MODEL_USER, - ) - ], - contentType: ContentType::JSON - )) - ->param('mfa', null, new Boolean(), 'Enable or disable MFA.') - ->inject('requestTimestamp') - ->inject('response') - ->inject('user') - ->inject('session') - ->inject('dbForProject') - ->inject('queueForEvents') - ->action(function (bool $mfa, ?\DateTime $requestTimestamp, Response $response, Document $user, Document $session, Database $dbForProject, Event $queueForEvents) { - - $user->setAttribute('mfa', $mfa); - - $user = $dbForProject->updateDocument('users', $user->getId(), $user); - - if ($mfa) { - $factors = $session->getAttribute('factors', []); - $totp = TOTP::getAuthenticatorFromUser($user); - if ($totp !== null && $totp->getAttribute('verified', false)) { - $factors[] = Type::TOTP; - } - if ($user->getAttribute('email', false) && $user->getAttribute('emailVerification', false)) { - $factors[] = Type::EMAIL; - } - if ($user->getAttribute('phone', false) && $user->getAttribute('phoneVerification', false)) { - $factors[] = Type::PHONE; - } - $factors = \array_values(\array_unique($factors)); - - $session->setAttribute('factors', $factors); - $dbForProject->updateDocument('sessions', $session->getId(), $session); - } - - $queueForEvents->setParam('userId', $user->getId()); - - $response->dynamic($user, Response::MODEL_ACCOUNT); - }); - -App::get('/v1/account/mfa/factors') - ->desc('List factors') - ->groups(['api', 'account', 'mfa']) - ->label('scope', 'account') - ->label('sdk', [ - new Method( - namespace: 'account', - group: 'mfa', - name: 'listMfaFactors', - description: '/docs/references/account/list-mfa-factors.md', - auth: [AuthType::SESSION, AuthType::JWT], - responses: [ - new SDKResponse( - code: Response::STATUS_CODE_OK, - model: Response::MODEL_MFA_FACTORS, - ) - ], - contentType: ContentType::JSON, - deprecated: new Deprecated( - since: '1.8.0', - replaceWith: 'account.listMFAFactors', - ), - ), - new Method( - namespace: 'account', - group: 'mfa', - name: 'listMFAFactors', - description: '/docs/references/account/list-mfa-factors.md', - auth: [AuthType::SESSION, AuthType::JWT], - responses: [ - new SDKResponse( - code: Response::STATUS_CODE_OK, - model: Response::MODEL_MFA_FACTORS, - ) - ], - contentType: ContentType::JSON - ) - ]) - ->inject('response') - ->inject('user') - ->action(function (Response $response, Document $user) { - - $mfaRecoveryCodes = $user->getAttribute('mfaRecoveryCodes', []); - $recoveryCodeEnabled = \is_array($mfaRecoveryCodes) && \count($mfaRecoveryCodes) > 0; - - $totp = TOTP::getAuthenticatorFromUser($user); - - $factors = new Document([ - Type::TOTP => $totp !== null && $totp->getAttribute('verified', false), - Type::EMAIL => $user->getAttribute('email', false) && $user->getAttribute('emailVerification', false), - Type::PHONE => $user->getAttribute('phone', false) && $user->getAttribute('phoneVerification', false), - Type::RECOVERY_CODE => $recoveryCodeEnabled - ]); - - $response->dynamic($factors, Response::MODEL_MFA_FACTORS); - }); - -App::post('/v1/account/mfa/authenticators/:type') - ->desc('Create authenticator') - ->groups(['api', 'account']) - ->label('event', 'users.[userId].update.mfa') - ->label('scope', 'account') - ->label('audits.event', 'user.update') - ->label('audits.resource', 'user/{response.$id}') - ->label('audits.userId', '{response.$id}') - ->label('sdk', [ - new Method( - namespace: 'account', - group: 'mfa', - name: 'createMfaAuthenticator', - description: '/docs/references/account/create-mfa-authenticator.md', - auth: [AuthType::SESSION, AuthType::JWT], - responses: [ - new SDKResponse( - code: Response::STATUS_CODE_OK, - model: Response::MODEL_MFA_TYPE, - ) - ], - contentType: ContentType::JSON, - deprecated: new Deprecated( - since: '1.8.0', - replaceWith: 'account.createMFAAuthenticator', - ), - ), - new Method( - namespace: 'account', - group: 'mfa', - name: 'createMFAAuthenticator', - description: '/docs/references/account/create-mfa-authenticator.md', - auth: [AuthType::SESSION, AuthType::JWT], - responses: [ - new SDKResponse( - code: Response::STATUS_CODE_OK, - model: Response::MODEL_MFA_TYPE, - ) - ], - contentType: ContentType::JSON - ) - ]) - ->param('type', null, new WhiteList([Type::TOTP]), 'Type of authenticator. Must be `' . Type::TOTP . '`') - ->inject('requestTimestamp') - ->inject('response') - ->inject('project') - ->inject('user') - ->inject('dbForProject') - ->inject('queueForEvents') - ->action(function (string $type, ?\DateTime $requestTimestamp, Response $response, Document $project, Document $user, Database $dbForProject, Event $queueForEvents) { - - $otp = (match ($type) { - Type::TOTP => new TOTP(), - default => throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'Unknown type.') // Ideally never happens if param validator stays always in sync - }); - - $otp->setLabel($user->getAttribute('email')); - $otp->setIssuer($project->getAttribute('name')); - - $authenticator = TOTP::getAuthenticatorFromUser($user); - - if ($authenticator) { - if ($authenticator->getAttribute('verified')) { - throw new Exception(Exception::USER_AUTHENTICATOR_ALREADY_VERIFIED); - } - $dbForProject->deleteDocument('authenticators', $authenticator->getId()); - } - - $authenticator = new Document([ - '$id' => ID::unique(), - 'userId' => $user->getId(), - 'userInternalId' => $user->getSequence(), - 'type' => Type::TOTP, - 'verified' => false, - 'data' => [ - 'secret' => $otp->getSecret(), - ], - '$permissions' => [ - Permission::read(Role::user($user->getId())), - Permission::update(Role::user($user->getId())), - Permission::delete(Role::user($user->getId())), - ] - ]); - - $model = new Document([ - 'secret' => $otp->getSecret(), - 'uri' => $otp->getProvisioningUri() - ]); - - $authenticator = $dbForProject->createDocument('authenticators', $authenticator); - $dbForProject->purgeCachedDocument('users', $user->getId()); - - $queueForEvents->setParam('userId', $user->getId()); - - $response->dynamic($model, Response::MODEL_MFA_TYPE); - }); - -App::put('/v1/account/mfa/authenticators/:type') - ->desc('Update authenticator (confirmation)') - ->groups(['api', 'account']) - ->label('event', 'users.[userId].update.mfa') - ->label('scope', 'account') - ->label('audits.event', 'user.update') - ->label('audits.resource', 'user/{response.$id}') - ->label('audits.userId', '{response.$id}') - ->label('sdk', [ - new Method( - namespace: 'account', - group: 'mfa', - name: 'updateMfaAuthenticator', - description: '/docs/references/account/update-mfa-authenticator.md', - auth: [AuthType::SESSION, AuthType::JWT], - responses: [ - new SDKResponse( - code: Response::STATUS_CODE_OK, - model: Response::MODEL_USER, - ) - ], - contentType: ContentType::JSON, - deprecated: new Deprecated( - since: '1.8.0', - replaceWith: 'account.updateMFAAuthenticator', - ), - ), - new Method( - namespace: 'account', - group: 'mfa', - name: 'updateMFAAuthenticator', - description: '/docs/references/account/update-mfa-authenticator.md', - auth: [AuthType::SESSION, AuthType::JWT], - responses: [ - new SDKResponse( - code: Response::STATUS_CODE_OK, - model: Response::MODEL_USER, - ) - ], - contentType: ContentType::JSON - ) - ]) - ->param('type', null, new WhiteList([Type::TOTP]), 'Type of authenticator.') - ->param('otp', '', new Text(256), 'Valid verification token.') - ->inject('response') - ->inject('user') - ->inject('session') - ->inject('dbForProject') - ->inject('queueForEvents') - ->action(function (string $type, string $otp, Response $response, Document $user, Document $session, Database $dbForProject, Event $queueForEvents) { - - $authenticator = (match ($type) { - Type::TOTP => TOTP::getAuthenticatorFromUser($user), - default => null - }); - - if ($authenticator === null) { - throw new Exception(Exception::USER_AUTHENTICATOR_NOT_FOUND); - } - - if ($authenticator->getAttribute('verified')) { - throw new Exception(Exception::USER_AUTHENTICATOR_ALREADY_VERIFIED); - } - - $success = (match ($type) { - Type::TOTP => Challenge\TOTP::verify($user, $otp), - default => false - }); - - if (!$success) { - throw new Exception(Exception::USER_INVALID_TOKEN); - } - - $authenticator->setAttribute('verified', true); - - $dbForProject->updateDocument('authenticators', $authenticator->getId(), $authenticator); - $dbForProject->purgeCachedDocument('users', $user->getId()); - - $factors = $session->getAttribute('factors', []); - $factors[] = $type; - $factors = \array_values(\array_unique($factors)); - - $session->setAttribute('factors', $factors); - $dbForProject->updateDocument('sessions', $session->getId(), $session); - - $queueForEvents->setParam('userId', $user->getId()); - - $response->dynamic($user, Response::MODEL_ACCOUNT); - }); - -App::post('/v1/account/mfa/recovery-codes') - ->desc('Create MFA recovery codes') - ->groups(['api', 'account']) - ->label('event', 'users.[userId].update.mfa') - ->label('scope', 'account') - ->label('audits.event', 'user.update') - ->label('audits.resource', 'user/{response.$id}') - ->label('audits.userId', '{response.$id}') - ->label('sdk', [ - new Method( - namespace: 'account', - group: 'mfa', - name: 'createMfaRecoveryCodes', - description: '/docs/references/account/create-mfa-recovery-codes.md', - auth: [AuthType::SESSION, AuthType::JWT], - responses: [ - new SDKResponse( - code: Response::STATUS_CODE_CREATED, - model: Response::MODEL_MFA_RECOVERY_CODES, - ) - ], - contentType: ContentType::JSON, - deprecated: new Deprecated( - since: '1.8.0', - replaceWith: 'account.createMFARecoveryCodes', - ), - ), - new Method( - namespace: 'account', - group: 'mfa', - name: 'createMFARecoveryCodes', - description: '/docs/references/account/create-mfa-recovery-codes.md', - auth: [AuthType::SESSION, AuthType::JWT], - responses: [ - new SDKResponse( - code: Response::STATUS_CODE_CREATED, - model: Response::MODEL_MFA_RECOVERY_CODES, - ) - ], - contentType: ContentType::JSON - ) - ]) - ->inject('response') - ->inject('user') - ->inject('dbForProject') - ->inject('queueForEvents') - ->action(function (Response $response, Document $user, Database $dbForProject, Event $queueForEvents) { - - $mfaRecoveryCodes = $user->getAttribute('mfaRecoveryCodes', []); - - if (!empty($mfaRecoveryCodes)) { - throw new Exception(Exception::USER_RECOVERY_CODES_ALREADY_EXISTS); - } - - $mfaRecoveryCodes = Type::generateBackupCodes(); - $user->setAttribute('mfaRecoveryCodes', $mfaRecoveryCodes); - $dbForProject->updateDocument('users', $user->getId(), $user); - - $queueForEvents->setParam('userId', $user->getId()); - - $document = new Document([ - 'recoveryCodes' => $mfaRecoveryCodes - ]); - - $response->dynamic($document, Response::MODEL_MFA_RECOVERY_CODES); - }); - -App::patch('/v1/account/mfa/recovery-codes') - ->desc('Update MFA recovery codes (regenerate)') - ->groups(['api', 'account', 'mfaProtected']) - ->label('event', 'users.[userId].update.mfa') - ->label('scope', 'account') - ->label('audits.event', 'user.update') - ->label('audits.resource', 'user/{response.$id}') - ->label('audits.userId', '{response.$id}') - ->label('sdk', [ - new Method( - namespace: 'account', - group: 'mfa', - name: 'updateMfaRecoveryCodes', - description: '/docs/references/account/update-mfa-recovery-codes.md', - auth: [AuthType::SESSION, AuthType::JWT], - responses: [ - new SDKResponse( - code: Response::STATUS_CODE_OK, - model: Response::MODEL_MFA_RECOVERY_CODES, - ) - ], - contentType: ContentType::JSON, - deprecated: new Deprecated( - since: '1.8.0', - replaceWith: 'account.updateMFARecoveryCodes', - ), - ), - new Method( - namespace: 'account', - group: 'mfa', - name: 'updateMFARecoveryCodes', - description: '/docs/references/account/update-mfa-recovery-codes.md', - auth: [AuthType::SESSION, AuthType::JWT], - responses: [ - new SDKResponse( - code: Response::STATUS_CODE_OK, - model: Response::MODEL_MFA_RECOVERY_CODES, - ) - ], - contentType: ContentType::JSON - ) - ]) - ->inject('dbForProject') - ->inject('response') - ->inject('user') - ->inject('queueForEvents') - ->action(function (Database $dbForProject, Response $response, Document $user, Event $queueForEvents) { - - $mfaRecoveryCodes = $user->getAttribute('mfaRecoveryCodes', []); - if (empty($mfaRecoveryCodes)) { - throw new Exception(Exception::USER_RECOVERY_CODES_NOT_FOUND); - } - - $mfaRecoveryCodes = Type::generateBackupCodes(); - $user->setAttribute('mfaRecoveryCodes', $mfaRecoveryCodes); - $dbForProject->updateDocument('users', $user->getId(), $user); - - $queueForEvents->setParam('userId', $user->getId()); - - $document = new Document([ - 'recoveryCodes' => $mfaRecoveryCodes - ]); - - $response->dynamic($document, Response::MODEL_MFA_RECOVERY_CODES); - }); - -App::get('/v1/account/mfa/recovery-codes') - ->desc('List MFA recovery codes') - ->groups(['api', 'account', 'mfaProtected']) - ->label('scope', 'account') - ->label('sdk', [ - new Method( - namespace: 'account', - group: 'mfa', - name: 'getMfaRecoveryCodes', - description: '/docs/references/account/get-mfa-recovery-codes.md', - auth: [AuthType::SESSION, AuthType::JWT], - responses: [ - new SDKResponse( - code: Response::STATUS_CODE_OK, - model: Response::MODEL_MFA_RECOVERY_CODES, - ) - ], - contentType: ContentType::JSON, - deprecated: new Deprecated( - since: '1.8.0', - replaceWith: 'account.getMFARecoveryCodes', - ), - ), - new Method( - namespace: 'account', - group: 'mfa', - name: 'getMFARecoveryCodes', - description: '/docs/references/account/get-mfa-recovery-codes.md', - auth: [AuthType::SESSION, AuthType::JWT], - responses: [ - new SDKResponse( - code: Response::STATUS_CODE_OK, - model: Response::MODEL_MFA_RECOVERY_CODES, - ) - ], - contentType: ContentType::JSON - ) - ]) - ->inject('response') - ->inject('user') - ->action(function (Response $response, Document $user) { - - $mfaRecoveryCodes = $user->getAttribute('mfaRecoveryCodes', []); - - if (empty($mfaRecoveryCodes)) { - throw new Exception(Exception::USER_RECOVERY_CODES_NOT_FOUND); - } - - $document = new Document([ - 'recoveryCodes' => $mfaRecoveryCodes - ]); - - $response->dynamic($document, Response::MODEL_MFA_RECOVERY_CODES); - }); - -App::delete('/v1/account/mfa/authenticators/:type') - ->desc('Delete authenticator') - ->groups(['api', 'account', 'mfaProtected']) - ->label('event', 'users.[userId].delete.mfa') - ->label('scope', 'account') - ->label('audits.event', 'user.update') - ->label('audits.resource', 'user/{response.$id}') - ->label('audits.userId', '{response.$id}') - ->label('sdk', [ - new Method( - namespace: 'account', - group: 'mfa', - name: 'deleteMfaAuthenticator', - description: '/docs/references/account/delete-mfa-authenticator.md', - auth: [AuthType::SESSION, AuthType::JWT], - responses: [ - new SDKResponse( - code: Response::STATUS_CODE_NOCONTENT, - model: Response::MODEL_NONE, - ) - ], - contentType: ContentType::NONE, - deprecated: new Deprecated( - since: '1.8.0', - replaceWith: 'account.deleteMFAAuthenticator', - ), - ), - new Method( - namespace: 'account', - group: 'mfa', - name: 'deleteMFAAuthenticator', - description: '/docs/references/account/delete-mfa-authenticator.md', - auth: [AuthType::SESSION, AuthType::JWT], - responses: [ - new SDKResponse( - code: Response::STATUS_CODE_NOCONTENT, - model: Response::MODEL_NONE, - ) - ], - contentType: ContentType::NONE - ) - ]) - ->param('type', null, new WhiteList([Type::TOTP]), 'Type of authenticator.') - ->inject('response') - ->inject('user') - ->inject('dbForProject') - ->inject('queueForEvents') - ->action(function (string $type, Response $response, Document $user, Database $dbForProject, Event $queueForEvents) { - - $authenticator = (match ($type) { - Type::TOTP => TOTP::getAuthenticatorFromUser($user), - default => null - }); - - if (!$authenticator) { - throw new Exception(Exception::USER_AUTHENTICATOR_NOT_FOUND); - } - - $dbForProject->deleteDocument('authenticators', $authenticator->getId()); - $dbForProject->purgeCachedDocument('users', $user->getId()); - - $queueForEvents->setParam('userId', $user->getId()); - - $response->noContent(); - }); - -App::post('/v1/account/mfa/challenge') - ->desc('Create MFA challenge') - ->groups(['api', 'account', 'mfa']) - ->label('scope', 'account') - ->label('event', 'users.[userId].challenges.[challengeId].create') - ->label('audits.event', 'challenge.create') - ->label('audits.resource', 'user/{response.userId}') - ->label('audits.userId', '{response.userId}') - ->label('sdk', [ - new Method( - namespace: 'account', - group: 'mfa', - name: 'createMfaChallenge', - description: '/docs/references/account/create-mfa-challenge.md', - auth: [], - responses: [ - new SDKResponse( - code: Response::STATUS_CODE_CREATED, - model: Response::MODEL_MFA_CHALLENGE, - ) - ], - contentType: ContentType::JSON, - deprecated: new Deprecated( - since: '1.8.0', - replaceWith: 'account.createMFAChallenge', - ), - ), - new Method( - namespace: 'account', - group: 'mfa', - name: 'createMFAChallenge', - description: '/docs/references/account/create-mfa-challenge.md', - auth: [], - responses: [ - new SDKResponse( - code: Response::STATUS_CODE_CREATED, - model: Response::MODEL_MFA_CHALLENGE, - ) - ], - contentType: ContentType::JSON - ) - ]) - ->label('abuse-limit', 10) - ->label('abuse-key', 'url:{url},userId:{userId}') - ->param('factor', '', new WhiteList([Type::EMAIL, Type::PHONE, Type::TOTP, Type::RECOVERY_CODE]), 'Factor used for verification. Must be one of following: `' . Type::EMAIL . '`, `' . Type::PHONE . '`, `' . Type::TOTP . '`, `' . Type::RECOVERY_CODE . '`.') - ->inject('response') - ->inject('dbForProject') - ->inject('user') - ->inject('locale') - ->inject('project') - ->inject('request') - ->inject('queueForEvents') - ->inject('queueForMessaging') - ->inject('queueForMails') - ->inject('timelimit') - ->inject('queueForStatsUsage') - ->inject('plan') - ->action(function (string $factor, Response $response, Database $dbForProject, Document $user, Locale $locale, Document $project, Request $request, Event $queueForEvents, Messaging $queueForMessaging, Mail $queueForMails, callable $timelimit, StatsUsage $queueForStatsUsage, array $plan) { - - $expire = DateTime::formatTz(DateTime::addSeconds(new \DateTime(), Auth::TOKEN_EXPIRATION_CONFIRM)); - $code = Auth::codeGenerator(); - $challenge = new Document([ - 'userId' => $user->getId(), - 'userInternalId' => $user->getSequence(), - 'type' => $factor, - 'token' => Auth::tokenGenerator(), - 'code' => $code, - 'expire' => $expire, - '$permissions' => [ - Permission::read(Role::user($user->getId())), - Permission::update(Role::user($user->getId())), - Permission::delete(Role::user($user->getId())), - ], - ]); - - $challenge = $dbForProject->createDocument('challenges', $challenge); - - switch ($factor) { - case Type::PHONE: - if (empty(System::getEnv('_APP_SMS_PROVIDER'))) { - throw new Exception(Exception::GENERAL_PHONE_DISABLED, 'Phone provider not configured'); - } - if (empty($user->getAttribute('phone'))) { - throw new Exception(Exception::USER_PHONE_NOT_FOUND); - } - if (!$user->getAttribute('phoneVerification')) { - throw new Exception(Exception::USER_PHONE_NOT_VERIFIED); - } - - $message = Template::fromFile(__DIR__ . '/../../config/locale/templates/sms-base.tpl'); - - $customTemplate = $project->getAttribute('templates', [])['sms.mfaChallenge-' . $locale->default] ?? []; - if (!empty($customTemplate)) { - $message = $customTemplate['message'] ?? $message; - } - - $messageContent = Template::fromString($locale->getText("sms.verification.body")); - $messageContent - ->setParam('{{project}}', $project->getAttribute('name')) - ->setParam('{{secret}}', $code); - $messageContent = \strip_tags($messageContent->render()); - $message = $message->setParam('{{token}}', $messageContent); - - $message = $message->render(); - - $phone = $user->getAttribute('phone'); - $queueForMessaging - ->setType(MESSAGE_SEND_TYPE_INTERNAL) - ->setMessage(new Document([ - '$id' => $challenge->getId(), - 'data' => [ - 'content' => $code, - ], - ])) - ->setRecipients([$phone]) - ->setProviderType(MESSAGE_TYPE_SMS); - - if (isset($plan['authPhone'])) { - $timelimit = $timelimit('organization:{organizationId}', $plan['authPhone'], 30 * 24 * 60 * 60); // 30 days - $timelimit - ->setParam('{organizationId}', $project->getAttribute('teamId')); - - $abuse = new Abuse($timelimit); - if ($abuse->check() && System::getEnv('_APP_OPTIONS_ABUSE', 'enabled') === 'enabled') { - $helper = PhoneNumberUtil::getInstance(); - $countryCode = $helper->parse($phone)->getCountryCode(); - - if (!empty($countryCode)) { - $queueForStatsUsage - ->addMetric(str_replace('{countryCode}', $countryCode, METRIC_AUTH_METHOD_PHONE_COUNTRY_CODE), 1); - } - } - $queueForStatsUsage - ->addMetric(METRIC_AUTH_METHOD_PHONE, 1) - ->setProject($project) - ->trigger(); - } - break; - case Type::EMAIL: - if (empty(System::getEnv('_APP_SMTP_HOST'))) { - throw new Exception(Exception::GENERAL_SMTP_DISABLED, 'SMTP disabled'); - } - if (empty($user->getAttribute('email'))) { - throw new Exception(Exception::USER_EMAIL_NOT_FOUND); - } - if (!$user->getAttribute('emailVerification')) { - throw new Exception(Exception::USER_EMAIL_NOT_VERIFIED); - } - - $subject = $locale->getText("emails.mfaChallenge.subject"); - $preview = $locale->getText("emails.mfaChallenge.preview"); - $heading = $locale->getText("emails.mfaChallenge.heading"); - - $customTemplate = $project->getAttribute('templates', [])['email.mfaChallenge-' . $locale->default] ?? []; - $smtpBaseTemplate = $project->getAttribute('smtpBaseTemplate', 'email-base'); - - $validator = new FileName(); - if (!$validator->isValid($smtpBaseTemplate)) { - throw new Exception(Exception::GENERAL_BAD_REQUEST, 'Invalid template path'); - } - - $bodyTemplate = __DIR__ . '/../../config/locale/templates/' . $smtpBaseTemplate . '.tpl'; - - $detector = new Detector($request->getUserAgent('UNKNOWN')); - $agentOs = $detector->getOS(); - $agentClient = $detector->getClient(); - $agentDevice = $detector->getDevice(); - - $message = Template::fromFile(__DIR__ . '/../../config/locale/templates/email-mfa-challenge.tpl'); - $message - ->setParam('{{hello}}', $locale->getText("emails.mfaChallenge.hello")) - ->setParam('{{description}}', $locale->getText("emails.mfaChallenge.description")) - ->setParam('{{clientInfo}}', $locale->getText("emails.mfaChallenge.clientInfo")) - ->setParam('{{thanks}}', $locale->getText("emails.mfaChallenge.thanks")) - ->setParam('{{signature}}', $locale->getText("emails.mfaChallenge.signature")); - - $body = $message->render(); - - $smtp = $project->getAttribute('smtp', []); - $smtpEnabled = $smtp['enabled'] ?? false; - - $senderEmail = System::getEnv('_APP_SYSTEM_EMAIL_ADDRESS', APP_EMAIL_TEAM); - $senderName = System::getEnv('_APP_SYSTEM_EMAIL_NAME', APP_NAME . ' Server'); - $replyTo = ""; - - if ($smtpEnabled) { - if (!empty($smtp['senderEmail'])) { - $senderEmail = $smtp['senderEmail']; - } - if (!empty($smtp['senderName'])) { - $senderName = $smtp['senderName']; - } - if (!empty($smtp['replyTo'])) { - $replyTo = $smtp['replyTo']; - } - - $queueForMails - ->setSmtpHost($smtp['host'] ?? '') - ->setSmtpPort($smtp['port'] ?? '') - ->setSmtpUsername($smtp['username'] ?? '') - ->setSmtpPassword($smtp['password'] ?? '') - ->setSmtpSecure($smtp['secure'] ?? ''); - - if (!empty($customTemplate)) { - if (!empty($customTemplate['senderEmail'])) { - $senderEmail = $customTemplate['senderEmail']; - } - if (!empty($customTemplate['senderName'])) { - $senderName = $customTemplate['senderName']; - } - if (!empty($customTemplate['replyTo'])) { - $replyTo = $customTemplate['replyTo']; - } - - $body = $customTemplate['message'] ?? ''; - $subject = $customTemplate['subject'] ?? $subject; - } - - $queueForMails - ->setSmtpReplyTo($replyTo) - ->setSmtpSenderEmail($senderEmail) - ->setSmtpSenderName($senderName); - } - - $emailVariables = [ - 'heading' => $heading, - 'direction' => $locale->getText('settings.direction'), - // {{user}}, {{project}} and {{otp}} are required in the templates - 'user' => $user->getAttribute('name'), - 'project' => $project->getAttribute('name'), - 'otp' => $code, - 'agentDevice' => $agentDevice['deviceBrand'] ?? $agentDevice['deviceBrand'] ?? 'UNKNOWN', - 'agentClient' => $agentClient['clientName'] ?? 'UNKNOWN', - 'agentOs' => $agentOs['osName'] ?? 'UNKNOWN', - ]; - - if ($smtpBaseTemplate === APP_BRANDED_EMAIL_BASE_TEMPLATE) { - $emailVariables = array_merge($emailVariables, [ - 'accentColor' => APP_EMAIL_ACCENT_COLOR, - 'logoUrl' => APP_EMAIL_LOGO_URL, - 'twitterUrl' => APP_SOCIAL_TWITTER, - 'discordUrl' => APP_SOCIAL_DISCORD, - 'githubUrl' => APP_SOCIAL_GITHUB_APPWRITE, - 'termsUrl' => APP_EMAIL_TERMS_URL, - 'privacyUrl' => APP_EMAIL_PRIVACY_URL, - ]); - } - - $queueForMails - ->setSubject($subject) - ->setPreview($preview) - ->setBody($body) - ->setBodyTemplate($bodyTemplate) - ->setVariables($emailVariables) - ->setRecipient($user->getAttribute('email')) - ->trigger(); - break; - } - - $queueForEvents - ->setParam('userId', $user->getId()) - ->setParam('challengeId', $challenge->getId()); - - $response->dynamic($challenge, Response::MODEL_MFA_CHALLENGE); - }); - -App::put('/v1/account/mfa/challenge') - ->desc('Update MFA challenge (confirmation)') - ->groups(['api', 'account', 'mfa']) - ->label('scope', 'account') - ->label('event', 'users.[userId].sessions.[sessionId].create') - ->label('audits.event', 'challenges.update') - ->label('audits.resource', 'user/{response.userId}') - ->label('audits.userId', '{response.userId}') - ->label('sdk', [ - new Method( - namespace: 'account', - group: 'mfa', - name: 'updateMfaChallenge', - description: '/docs/references/account/update-mfa-challenge.md', - auth: [AuthType::SESSION, AuthType::JWT], - responses: [ - new SDKResponse( - code: Response::STATUS_CODE_OK, - model: Response::MODEL_SESSION, - ) - ], - contentType: ContentType::JSON, - deprecated: new Deprecated( - since: '1.8.0', - replaceWith: 'account.updateMFAChallenge', - ), - ), - new Method( - namespace: 'account', - group: 'mfa', - name: 'updateMFAChallenge', - description: '/docs/references/account/update-mfa-challenge.md', - auth: [AuthType::SESSION, AuthType::JWT], - responses: [ - new SDKResponse( - code: Response::STATUS_CODE_OK, - model: Response::MODEL_SESSION, - ) - ], - contentType: ContentType::JSON - ) - ]) - ->label('abuse-limit', 10) - ->label('abuse-key', 'url:{url},challengeId:{param-challengeId}') - ->param('challengeId', '', new Text(256), 'ID of the challenge.') - ->param('otp', '', new Text(256), 'Valid verification token.') - ->inject('project') - ->inject('response') - ->inject('user') - ->inject('session') - ->inject('dbForProject') - ->inject('queueForEvents') - ->action(function (string $challengeId, string $otp, Document $project, Response $response, Document $user, Document $session, Database $dbForProject, Event $queueForEvents) { - - $challenge = $dbForProject->getDocument('challenges', $challengeId); - - if ($challenge->isEmpty()) { - throw new Exception(Exception::USER_INVALID_TOKEN); - } - - $type = $challenge->getAttribute('type'); - - $recoveryCodeChallenge = function (Document $challenge, Document $user, string $otp) use ($dbForProject) { - if ( - $challenge->isSet('type') && - $challenge->getAttribute('type') === \strtolower(Type::RECOVERY_CODE) - ) { - $mfaRecoveryCodes = $user->getAttribute('mfaRecoveryCodes', []); - if (in_array($otp, $mfaRecoveryCodes)) { - $mfaRecoveryCodes = array_diff($mfaRecoveryCodes, [$otp]); - $mfaRecoveryCodes = array_values($mfaRecoveryCodes); - $user->setAttribute('mfaRecoveryCodes', $mfaRecoveryCodes); - $dbForProject->updateDocument('users', $user->getId(), $user); - - return true; - } - - return false; - } - - return false; - }; - - $success = (match ($type) { - Type::TOTP => Challenge\TOTP::challenge($challenge, $user, $otp), - Type::PHONE => Challenge\Phone::challenge($challenge, $user, $otp), - Type::EMAIL => Challenge\Email::challenge($challenge, $user, $otp), - \strtolower(Type::RECOVERY_CODE) => $recoveryCodeChallenge($challenge, $user, $otp), - default => false - }); - - if (!$success) { - throw new Exception(Exception::USER_INVALID_TOKEN); - } - - $dbForProject->deleteDocument('challenges', $challengeId); - $dbForProject->purgeCachedDocument('users', $user->getId()); - - $factors = $session->getAttribute('factors', []); - $factors[] = $type; - $factors = \array_values(\array_unique($factors)); - - $session - ->setAttribute('factors', $factors) - ->setAttribute('mfaUpdatedAt', DateTime::now()); - - $dbForProject->updateDocument('sessions', $session->getId(), $session); - - $queueForEvents - ->setParam('userId', $user->getId()) - ->setParam('sessionId', $session->getId()); - - $response->dynamic($session, Response::MODEL_SESSION); - }); - App::post('/v1/account/targets/push') ->desc('Create push target') ->groups(['api', 'account']) diff --git a/src/Appwrite/Platform/Appwrite.php b/src/Appwrite/Platform/Appwrite.php index a77f894be7..4aa135c4f1 100644 --- a/src/Appwrite/Platform/Appwrite.php +++ b/src/Appwrite/Platform/Appwrite.php @@ -2,6 +2,7 @@ namespace Appwrite\Platform; +use Appwrite\Platform\Modules\Account; use Appwrite\Platform\Modules\Console; use Appwrite\Platform\Modules\Core; use Appwrite\Platform\Modules\Databases; @@ -17,6 +18,7 @@ class Appwrite extends Platform public function __construct() { parent::__construct(new Core()); + $this->addModule(new Account\Module()); $this->addModule(new Databases\Module()); $this->addModule(new Projects\Module()); $this->addModule(new Functions\Module()); diff --git a/src/Appwrite/Platform/Modules/Account/Http/Mfa/Authenticators/Create.php b/src/Appwrite/Platform/Modules/Account/Http/Mfa/Authenticators/Create.php new file mode 100644 index 0000000000..e024f06a6e --- /dev/null +++ b/src/Appwrite/Platform/Modules/Account/Http/Mfa/Authenticators/Create.php @@ -0,0 +1,143 @@ +setHttpMethod(Action::HTTP_REQUEST_METHOD_POST) + ->setHttpPath('/v1/account/mfa/authenticators/:type') + ->desc('Create authenticator') + ->groups(['api', 'account']) + ->label('event', 'users.[userId].update.mfa') + ->label('scope', 'account') + ->label('audits.event', 'user.update') + ->label('audits.resource', 'user/{response.$id}') + ->label('audits.userId', '{response.$id}') + ->label('sdk', [ + new Method( + namespace: 'account', + group: 'mfa', + name: 'createMfaAuthenticator', + description: '/docs/references/account/create-mfa-authenticator.md', + auth: [AuthType::SESSION, AuthType::JWT], + responses: [ + new SDKResponse( + code: Response::STATUS_CODE_OK, + model: Response::MODEL_MFA_TYPE, + ) + ], + contentType: ContentType::JSON, + deprecated: new Deprecated( + since: '1.8.0', + replaceWith: 'account.createMFAAuthenticator', + ), + ), + new Method( + namespace: 'account', + group: 'mfa', + name: 'createMFAAuthenticator', + description: '/docs/references/account/create-mfa-authenticator.md', + auth: [AuthType::SESSION, AuthType::JWT], + responses: [ + new SDKResponse( + code: Response::STATUS_CODE_OK, + model: Response::MODEL_MFA_TYPE, + ) + ], + contentType: ContentType::JSON + ) + ]) + ->param('type', null, new WhiteList([Type::TOTP]), 'Type of authenticator. Must be `' . Type::TOTP . '`') + ->inject('requestTimestamp') + ->inject('response') + ->inject('project') + ->inject('user') + ->inject('dbForProject') + ->inject('queueForEvents') + ->callback($this->action(...)); + } + + public function action( + string $type, + ?\DateTime $requestTimestamp, + Response $response, + Document $project, + Document $user, + Database $dbForProject, + Event $queueForEvents + ): void { + $otp = (match ($type) { + Type::TOTP => new TOTP(), + default => throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'Unknown type.') + }); + + $otp->setLabel($user->getAttribute('email')); + $otp->setIssuer($project->getAttribute('name')); + + $authenticator = TOTP::getAuthenticatorFromUser($user); + + if ($authenticator) { + if ($authenticator->getAttribute('verified')) { + throw new Exception(Exception::USER_AUTHENTICATOR_ALREADY_VERIFIED); + } + $dbForProject->deleteDocument('authenticators', $authenticator->getId()); + } + + $authenticator = new Document([ + '$id' => ID::unique(), + 'userId' => $user->getId(), + 'userInternalId' => $user->getSequence(), + 'type' => Type::TOTP, + 'verified' => false, + 'data' => [ + 'secret' => $otp->getSecret(), + ], + '$permissions' => [ + Permission::read(Role::user($user->getId())), + Permission::update(Role::user($user->getId())), + Permission::delete(Role::user($user->getId())), + ] + ]); + + $model = new Document([ + 'secret' => $otp->getSecret(), + 'uri' => $otp->getProvisioningUri() + ]); + + $authenticator = $dbForProject->createDocument('authenticators', $authenticator); + $dbForProject->purgeCachedDocument('users', $user->getId()); + + $queueForEvents->setParam('userId', $user->getId()); + + $response->dynamic($model, Response::MODEL_MFA_TYPE); + } +} diff --git a/src/Appwrite/Platform/Modules/Account/Http/Mfa/Authenticators/Delete.php b/src/Appwrite/Platform/Modules/Account/Http/Mfa/Authenticators/Delete.php new file mode 100644 index 0000000000..36e767dbaa --- /dev/null +++ b/src/Appwrite/Platform/Modules/Account/Http/Mfa/Authenticators/Delete.php @@ -0,0 +1,107 @@ +setHttpMethod(Action::HTTP_REQUEST_METHOD_DELETE) + ->setHttpPath('/v1/account/mfa/authenticators/:type') + ->desc('Delete authenticator') + ->groups(['api', 'account', 'mfaProtected']) + ->label('event', 'users.[userId].delete.mfa') + ->label('scope', 'account') + ->label('audits.event', 'user.update') + ->label('audits.resource', 'user/{response.$id}') + ->label('audits.userId', '{response.$id}') + ->label('sdk', [ + new Method( + namespace: 'account', + group: 'mfa', + name: 'deleteMfaAuthenticator', + description: '/docs/references/account/delete-mfa-authenticator.md', + auth: [AuthType::SESSION, AuthType::JWT], + responses: [ + new SDKResponse( + code: Response::STATUS_CODE_NOCONTENT, + model: Response::MODEL_NONE, + ) + ], + contentType: ContentType::NONE, + deprecated: new Deprecated( + since: '1.8.0', + replaceWith: 'account.deleteMFAAuthenticator', + ), + ), + new Method( + namespace: 'account', + group: 'mfa', + name: 'deleteMFAAuthenticator', + description: '/docs/references/account/delete-mfa-authenticator.md', + auth: [AuthType::SESSION, AuthType::JWT], + responses: [ + new SDKResponse( + code: Response::STATUS_CODE_NOCONTENT, + model: Response::MODEL_NONE, + ) + ], + contentType: ContentType::NONE + ) + ]) + ->param('type', null, new WhiteList([Type::TOTP]), 'Type of authenticator.') + ->inject('response') + ->inject('user') + ->inject('dbForProject') + ->inject('queueForEvents') + ->callback($this->action(...)); + } + + public function action( + string $type, + Response $response, + Document $user, + Database $dbForProject, + Event $queueForEvents + ): void { + $authenticator = (match ($type) { + Type::TOTP => TOTP::getAuthenticatorFromUser($user), + default => null + }); + + if (!$authenticator) { + throw new Exception(Exception::USER_AUTHENTICATOR_NOT_FOUND); + } + + $dbForProject->deleteDocument('authenticators', $authenticator->getId()); + $dbForProject->purgeCachedDocument('users', $user->getId()); + + $queueForEvents->setParam('userId', $user->getId()); + + $response->noContent(); + } +} diff --git a/src/Appwrite/Platform/Modules/Account/Http/Mfa/Authenticators/Update.php b/src/Appwrite/Platform/Modules/Account/Http/Mfa/Authenticators/Update.php new file mode 100644 index 0000000000..7134e3c59a --- /dev/null +++ b/src/Appwrite/Platform/Modules/Account/Http/Mfa/Authenticators/Update.php @@ -0,0 +1,135 @@ +setHttpMethod(Action::HTTP_REQUEST_METHOD_PUT) + ->setHttpPath('/v1/account/mfa/authenticators/:type') + ->desc('Update authenticator (confirmation)') + ->groups(['api', 'account']) + ->label('event', 'users.[userId].update.mfa') + ->label('scope', 'account') + ->label('audits.event', 'user.update') + ->label('audits.resource', 'user/{response.$id}') + ->label('audits.userId', '{response.$id}') + ->label('sdk', [ + new Method( + namespace: 'account', + group: 'mfa', + name: 'updateMfaAuthenticator', + description: '/docs/references/account/update-mfa-authenticator.md', + auth: [AuthType::SESSION, AuthType::JWT], + responses: [ + new SDKResponse( + code: Response::STATUS_CODE_OK, + model: Response::MODEL_USER, + ) + ], + contentType: ContentType::JSON, + deprecated: new Deprecated( + since: '1.8.0', + replaceWith: 'account.updateMFAAuthenticator', + ), + ), + new Method( + namespace: 'account', + group: 'mfa', + name: 'updateMFAAuthenticator', + description: '/docs/references/account/update-mfa-authenticator.md', + auth: [AuthType::SESSION, AuthType::JWT], + responses: [ + new SDKResponse( + code: Response::STATUS_CODE_OK, + model: Response::MODEL_USER, + ) + ], + contentType: ContentType::JSON + ) + ]) + ->param('type', null, new WhiteList([Type::TOTP]), 'Type of authenticator.') + ->param('otp', '', new Text(256), 'Valid verification token.') + ->inject('response') + ->inject('user') + ->inject('session') + ->inject('dbForProject') + ->inject('queueForEvents') + ->callback($this->action(...)); + } + + public function action( + string $type, + string $otp, + Response $response, + Document $user, + Document $session, + Database $dbForProject, + Event $queueForEvents + ): void { + $authenticator = (match ($type) { + Type::TOTP => TOTP::getAuthenticatorFromUser($user), + default => null + }); + + if ($authenticator === null) { + throw new Exception(Exception::USER_AUTHENTICATOR_NOT_FOUND); + } + + if ($authenticator->getAttribute('verified')) { + throw new Exception(Exception::USER_AUTHENTICATOR_ALREADY_VERIFIED); + } + + $success = (match ($type) { + Type::TOTP => Challenge\TOTP::verify($user, $otp), + default => false + }); + + if (!$success) { + throw new Exception(Exception::USER_INVALID_TOKEN); + } + + $authenticator->setAttribute('verified', true); + + $dbForProject->updateDocument('authenticators', $authenticator->getId(), $authenticator); + $dbForProject->purgeCachedDocument('users', $user->getId()); + + $factors = $session->getAttribute('factors', []); + $factors[] = $type; + $factors = \array_values(\array_unique($factors)); + + $session->setAttribute('factors', $factors); + $dbForProject->updateDocument('sessions', $session->getId(), $session); + + $queueForEvents->setParam('userId', $user->getId()); + + $response->dynamic($user, Response::MODEL_ACCOUNT); + } +} diff --git a/src/Appwrite/Platform/Modules/Account/Http/Mfa/Challenge/Create.php b/src/Appwrite/Platform/Modules/Account/Http/Mfa/Challenge/Create.php new file mode 100644 index 0000000000..6f99047477 --- /dev/null +++ b/src/Appwrite/Platform/Modules/Account/Http/Mfa/Challenge/Create.php @@ -0,0 +1,329 @@ +setHttpMethod(Action::HTTP_REQUEST_METHOD_POST) + ->setHttpPath('/v1/account/mfa/challenge') + ->desc('Create MFA challenge') + ->groups(['api', 'account', 'mfa']) + ->label('scope', 'account') + ->label('event', 'users.[userId].challenges.[challengeId].create') + ->label('audits.event', 'challenge.create') + ->label('audits.resource', 'user/{response.userId}') + ->label('audits.userId', '{response.userId}') + ->label('sdk', [ + new Method( + namespace: 'account', + group: 'mfa', + name: 'createMfaChallenge', + description: '/docs/references/account/create-mfa-challenge.md', + auth: [], + responses: [ + new SDKResponse( + code: Response::STATUS_CODE_CREATED, + model: Response::MODEL_MFA_CHALLENGE, + ) + ], + contentType: ContentType::JSON, + deprecated: new Deprecated( + since: '1.8.0', + replaceWith: 'account.createMFAChallenge', + ), + ), + new Method( + namespace: 'account', + group: 'mfa', + name: 'createMFAChallenge', + description: '/docs/references/account/create-mfa-challenge.md', + auth: [], + responses: [ + new SDKResponse( + code: Response::STATUS_CODE_CREATED, + model: Response::MODEL_MFA_CHALLENGE, + ) + ], + contentType: ContentType::JSON + ) + ]) + ->label('abuse-limit', 10) + ->label('abuse-key', 'url:{url},userId:{userId}') + ->param('factor', '', new WhiteList([Type::EMAIL, Type::PHONE, Type::TOTP, Type::RECOVERY_CODE]), 'Factor used for verification. Must be one of following: `' . Type::EMAIL . '`, `' . Type::PHONE . '`, `' . Type::TOTP . '`, `' . Type::RECOVERY_CODE . '`.') + ->inject('response') + ->inject('dbForProject') + ->inject('user') + ->inject('locale') + ->inject('project') + ->inject('request') + ->inject('queueForEvents') + ->inject('queueForMessaging') + ->inject('queueForMails') + ->inject('timelimit') + ->inject('queueForStatsUsage') + ->inject('plan') + ->callback($this->action(...)); + } + + public function action( + string $factor, + Response $response, + Database $dbForProject, + Document $user, + Locale $locale, + Document $project, + Request $request, + Event $queueForEvents, + Messaging $queueForMessaging, + Mail $queueForMails, + callable $timelimit, + StatsUsage $queueForStatsUsage, + array $plan + ): void { + $expire = DateTime::formatTz(DateTime::addSeconds(new \DateTime(), Auth::TOKEN_EXPIRATION_CONFIRM)); + $code = Auth::codeGenerator(); + $challenge = new Document([ + 'userId' => $user->getId(), + 'userInternalId' => $user->getSequence(), + 'type' => $factor, + 'token' => Auth::tokenGenerator(), + 'code' => $code, + 'expire' => $expire, + '$permissions' => [ + Permission::read(Role::user($user->getId())), + Permission::update(Role::user($user->getId())), + Permission::delete(Role::user($user->getId())), + ], + ]); + + $challenge = $dbForProject->createDocument('challenges', $challenge); + + $templatesPath = \dirname(__DIR__, 7) . '/app/config/locale/templates'; + + switch ($factor) { + case Type::PHONE: + if (empty(System::getEnv('_APP_SMS_PROVIDER'))) { + throw new Exception(Exception::GENERAL_PHONE_DISABLED, 'Phone provider not configured'); + } + if (empty($user->getAttribute('phone'))) { + throw new Exception(Exception::USER_PHONE_NOT_FOUND); + } + if (!$user->getAttribute('phoneVerification')) { + throw new Exception(Exception::USER_PHONE_NOT_VERIFIED); + } + + $message = Template::fromFile($templatesPath . '/sms-base.tpl'); + + $customTemplate = $project->getAttribute('templates', [])['sms.mfaChallenge-' . $locale->default] ?? []; + if (!empty($customTemplate)) { + $message = $customTemplate['message'] ?? $message; + } + + $messageContent = Template::fromString($locale->getText("sms.verification.body")); + $messageContent + ->setParam('{{project}}', $project->getAttribute('name')) + ->setParam('{{secret}}', $code); + $messageContent = \strip_tags($messageContent->render()); + $message = $message->setParam('{{token}}', $messageContent); + + $message = $message->render(); + + $phone = $user->getAttribute('phone'); + $queueForMessaging + ->setType(MESSAGE_SEND_TYPE_INTERNAL) + ->setMessage(new Document([ + '$id' => $challenge->getId(), + 'data' => [ + 'content' => $code, + ], + ])) + ->setRecipients([$phone]) + ->setProviderType(MESSAGE_TYPE_SMS); + + if (isset($plan['authPhone'])) { + $timelimit = $timelimit('organization:{organizationId}', $plan['authPhone'], 30 * 24 * 60 * 60); // 30 days + $timelimit + ->setParam('{organizationId}', $project->getAttribute('teamId')); + + $abuse = new Abuse($timelimit); + if ($abuse->check() && System::getEnv('_APP_OPTIONS_ABUSE', 'enabled') === 'enabled') { + $helper = PhoneNumberUtil::getInstance(); + $countryCode = $helper->parse($phone)->getCountryCode(); + + if (!empty($countryCode)) { + $queueForStatsUsage + ->addMetric(str_replace('{countryCode}', $countryCode, METRIC_AUTH_METHOD_PHONE_COUNTRY_CODE), 1); + } + } + $queueForStatsUsage + ->addMetric(METRIC_AUTH_METHOD_PHONE, 1) + ->setProject($project) + ->trigger(); + } + break; + case Type::EMAIL: + if (empty(System::getEnv('_APP_SMTP_HOST'))) { + throw new Exception(Exception::GENERAL_SMTP_DISABLED, 'SMTP disabled'); + } + if (empty($user->getAttribute('email'))) { + throw new Exception(Exception::USER_EMAIL_NOT_FOUND); + } + if (!$user->getAttribute('emailVerification')) { + throw new Exception(Exception::USER_EMAIL_NOT_VERIFIED); + } + + $subject = $locale->getText("emails.mfaChallenge.subject"); + $preview = $locale->getText("emails.mfaChallenge.preview"); + $heading = $locale->getText("emails.mfaChallenge.heading"); + + $customTemplate = $project->getAttribute('templates', [])['email.mfaChallenge-' . $locale->default] ?? []; + $smtpBaseTemplate = $project->getAttribute('smtpBaseTemplate', 'email-base'); + + $validator = new FileName(); + if (!$validator->isValid($smtpBaseTemplate)) { + throw new Exception(Exception::GENERAL_BAD_REQUEST, 'Invalid template path'); + } + + $bodyTemplate = $templatesPath . '/' . $smtpBaseTemplate . '.tpl'; + + $detector = new Detector($request->getUserAgent('UNKNOWN')); + $agentOs = $detector->getOS(); + $agentClient = $detector->getClient(); + $agentDevice = $detector->getDevice(); + + $message = Template::fromFile($templatesPath . '/email-mfa-challenge.tpl'); + $message + ->setParam('{{hello}}', $locale->getText("emails.mfaChallenge.hello")) + ->setParam('{{description}}', $locale->getText("emails.mfaChallenge.description")) + ->setParam('{{clientInfo}}', $locale->getText("emails.mfaChallenge.clientInfo")) + ->setParam('{{thanks}}', $locale->getText("emails.mfaChallenge.thanks")) + ->setParam('{{signature}}', $locale->getText("emails.mfaChallenge.signature")); + + $body = $message->render(); + + $smtp = $project->getAttribute('smtp', []); + $smtpEnabled = $smtp['enabled'] ?? false; + + $senderEmail = System::getEnv('_APP_SYSTEM_EMAIL_ADDRESS', APP_EMAIL_TEAM); + $senderName = System::getEnv('_APP_SYSTEM_EMAIL_NAME', APP_NAME . ' Server'); + $replyTo = ""; + + if ($smtpEnabled) { + if (!empty($smtp['senderEmail'])) { + $senderEmail = $smtp['senderEmail']; + } + if (!empty($smtp['senderName'])) { + $senderName = $smtp['senderName']; + } + if (!empty($smtp['replyTo'])) { + $replyTo = $smtp['replyTo']; + } + + $queueForMails + ->setSmtpHost($smtp['host'] ?? '') + ->setSmtpPort($smtp['port'] ?? '') + ->setSmtpUsername($smtp['username'] ?? '') + ->setSmtpPassword($smtp['password'] ?? '') + ->setSmtpSecure($smtp['secure'] ?? ''); + + if (!empty($customTemplate)) { + if (!empty($customTemplate['senderEmail'])) { + $senderEmail = $customTemplate['senderEmail']; + } + if (!empty($customTemplate['senderName'])) { + $senderName = $customTemplate['senderName']; + } + if (!empty($customTemplate['replyTo'])) { + $replyTo = $customTemplate['replyTo']; + } + + $body = $customTemplate['message'] ?? ''; + $subject = $customTemplate['subject'] ?? $subject; + } + + $queueForMails + ->setSmtpReplyTo($replyTo) + ->setSmtpSenderEmail($senderEmail) + ->setSmtpSenderName($senderName); + } + + $emailVariables = [ + 'heading' => $heading, + 'direction' => $locale->getText('settings.direction'), + 'user' => $user->getAttribute('name'), + 'project' => $project->getAttribute('name'), + 'otp' => $code, + 'agentDevice' => $agentDevice['deviceBrand'] ?? $agentDevice['deviceBrand'] ?? 'UNKNOWN', + 'agentClient' => $agentClient['clientName'] ?? 'UNKNOWN', + 'agentOs' => $agentOs['osName'] ?? 'UNKNOWN', + ]; + + if ($smtpBaseTemplate === APP_BRANDED_EMAIL_BASE_TEMPLATE) { + $emailVariables = array_merge($emailVariables, [ + 'accentColor' => APP_EMAIL_ACCENT_COLOR, + 'logoUrl' => APP_EMAIL_LOGO_URL, + 'twitterUrl' => APP_SOCIAL_TWITTER, + 'discordUrl' => APP_SOCIAL_DISCORD, + 'githubUrl' => APP_SOCIAL_GITHUB_APPWRITE, + 'termsUrl' => APP_EMAIL_TERMS_URL, + 'privacyUrl' => APP_EMAIL_PRIVACY_URL, + ]); + } + + $queueForMails + ->setSubject($subject) + ->setPreview($preview) + ->setBody($body) + ->setBodyTemplate($bodyTemplate) + ->setVariables($emailVariables) + ->setRecipient($user->getAttribute('email')) + ->trigger(); + break; + } + + $queueForEvents + ->setParam('userId', $user->getId()) + ->setParam('challengeId', $challenge->getId()); + + $response->dynamic($challenge, Response::MODEL_MFA_CHALLENGE); + } +} diff --git a/src/Appwrite/Platform/Modules/Account/Http/Mfa/Challenge/Update.php b/src/Appwrite/Platform/Modules/Account/Http/Mfa/Challenge/Update.php new file mode 100644 index 0000000000..d4bb6a1490 --- /dev/null +++ b/src/Appwrite/Platform/Modules/Account/Http/Mfa/Challenge/Update.php @@ -0,0 +1,160 @@ +setHttpMethod(Action::HTTP_REQUEST_METHOD_PUT) + ->setHttpPath('/v1/account/mfa/challenge') + ->desc('Update MFA challenge (confirmation)') + ->groups(['api', 'account', 'mfa']) + ->label('scope', 'account') + ->label('event', 'users.[userId].sessions.[sessionId].create') + ->label('audits.event', 'challenges.update') + ->label('audits.resource', 'user/{response.userId}') + ->label('audits.userId', '{response.userId}') + ->label('sdk', [ + new Method( + namespace: 'account', + group: 'mfa', + name: 'updateMfaChallenge', + description: '/docs/references/account/update-mfa-challenge.md', + auth: [AuthType::SESSION, AuthType::JWT], + responses: [ + new SDKResponse( + code: Response::STATUS_CODE_OK, + model: Response::MODEL_SESSION, + ) + ], + contentType: ContentType::JSON, + deprecated: new Deprecated( + since: '1.8.0', + replaceWith: 'account.updateMFAChallenge', + ), + ), + new Method( + namespace: 'account', + group: 'mfa', + name: 'updateMFAChallenge', + description: '/docs/references/account/update-mfa-challenge.md', + auth: [AuthType::SESSION, AuthType::JWT], + responses: [ + new SDKResponse( + code: Response::STATUS_CODE_OK, + model: Response::MODEL_SESSION, + ) + ], + contentType: ContentType::JSON + ) + ]) + ->label('abuse-limit', 10) + ->label('abuse-key', 'url:{url},challengeId:{param-challengeId}') + ->param('challengeId', '', new Text(256), 'ID of the challenge.') + ->param('otp', '', new Text(256), 'Valid verification token.') + ->inject('project') + ->inject('response') + ->inject('user') + ->inject('session') + ->inject('dbForProject') + ->inject('queueForEvents') + ->callback($this->action(...)); + } + + public function action( + string $challengeId, + string $otp, + Document $project, + Response $response, + Document $user, + Document $session, + Database $dbForProject, + Event $queueForEvents + ): void { + $challenge = $dbForProject->getDocument('challenges', $challengeId); + + if ($challenge->isEmpty()) { + throw new Exception(Exception::USER_INVALID_TOKEN); + } + + $type = $challenge->getAttribute('type'); + + $recoveryCodeChallenge = function (Document $challenge, Document $user, string $otp) use ($dbForProject) { + if ( + $challenge->isSet('type') && + $challenge->getAttribute('type') === \strtolower(Type::RECOVERY_CODE) + ) { + $mfaRecoveryCodes = $user->getAttribute('mfaRecoveryCodes', []); + if (\in_array($otp, $mfaRecoveryCodes)) { + $mfaRecoveryCodes = \array_diff($mfaRecoveryCodes, [$otp]); + $mfaRecoveryCodes = \array_values($mfaRecoveryCodes); + $user->setAttribute('mfaRecoveryCodes', $mfaRecoveryCodes); + $dbForProject->updateDocument('users', $user->getId(), $user); + + return true; + } + + return false; + } + + return false; + }; + + $success = (match ($type) { + Type::TOTP => Challenge\TOTP::challenge($challenge, $user, $otp), + Type::PHONE => Challenge\Phone::challenge($challenge, $user, $otp), + Type::EMAIL => Challenge\Email::challenge($challenge, $user, $otp), + \strtolower(Type::RECOVERY_CODE) => $recoveryCodeChallenge($challenge, $user, $otp), + default => false + }); + + if (!$success) { + throw new Exception(Exception::USER_INVALID_TOKEN); + } + + $dbForProject->deleteDocument('challenges', $challengeId); + $dbForProject->purgeCachedDocument('users', $user->getId()); + + $factors = $session->getAttribute('factors', []); + $factors[] = $type; + $factors = \array_values(\array_unique($factors)); + + $session + ->setAttribute('factors', $factors) + ->setAttribute('mfaUpdatedAt', DateTime::now()); + + $dbForProject->updateDocument('sessions', $session->getId(), $session); + + $queueForEvents + ->setParam('userId', $user->getId()) + ->setParam('sessionId', $session->getId()); + + $response->dynamic($session, Response::MODEL_SESSION); + } +} diff --git a/src/Appwrite/Platform/Modules/Account/Http/Mfa/Factors/XList.php b/src/Appwrite/Platform/Modules/Account/Http/Mfa/Factors/XList.php new file mode 100644 index 0000000000..8e99ad1283 --- /dev/null +++ b/src/Appwrite/Platform/Modules/Account/Http/Mfa/Factors/XList.php @@ -0,0 +1,89 @@ +setHttpMethod(Action::HTTP_REQUEST_METHOD_GET) + ->setHttpPath('/v1/account/mfa/factors') + ->desc('List factors') + ->groups(['api', 'account', 'mfa']) + ->label('scope', 'account') + ->label('sdk', [ + new Method( + namespace: 'account', + group: 'mfa', + name: 'listMfaFactors', + description: '/docs/references/account/list-mfa-factors.md', + auth: [AuthType::SESSION, AuthType::JWT], + responses: [ + new SDKResponse( + code: Response::STATUS_CODE_OK, + model: Response::MODEL_MFA_FACTORS, + ) + ], + contentType: ContentType::JSON, + deprecated: new Deprecated( + since: '1.8.0', + replaceWith: 'account.listMFAFactors', + ), + ), + new Method( + namespace: 'account', + group: 'mfa', + name: 'listMFAFactors', + description: '/docs/references/account/list-mfa-factors.md', + auth: [AuthType::SESSION, AuthType::JWT], + responses: [ + new SDKResponse( + code: Response::STATUS_CODE_OK, + model: Response::MODEL_MFA_FACTORS, + ) + ], + contentType: ContentType::JSON + ) + ]) + ->inject('response') + ->inject('user') + ->callback($this->action(...)); + } + + public function action(Response $response, Document $user): void + { + $mfaRecoveryCodes = $user->getAttribute('mfaRecoveryCodes', []); + $recoveryCodeEnabled = \is_array($mfaRecoveryCodes) && \count($mfaRecoveryCodes) > 0; + + $totp = TOTP::getAuthenticatorFromUser($user); + + $factors = new Document([ + Type::TOTP => $totp !== null && $totp->getAttribute('verified', false), + Type::EMAIL => $user->getAttribute('email', false) && $user->getAttribute('emailVerification', false), + Type::PHONE => $user->getAttribute('phone', false) && $user->getAttribute('phoneVerification', false), + Type::RECOVERY_CODE => $recoveryCodeEnabled + ]); + + $response->dynamic($factors, Response::MODEL_MFA_FACTORS); + } +} diff --git a/src/Appwrite/Platform/Modules/Account/Http/Mfa/RecoveryCodes/Create.php b/src/Appwrite/Platform/Modules/Account/Http/Mfa/RecoveryCodes/Create.php new file mode 100644 index 0000000000..3f3ad22c2e --- /dev/null +++ b/src/Appwrite/Platform/Modules/Account/Http/Mfa/RecoveryCodes/Create.php @@ -0,0 +1,105 @@ +setHttpMethod(Action::HTTP_REQUEST_METHOD_POST) + ->setHttpPath('/v1/account/mfa/recovery-codes') + ->desc('Create MFA recovery codes') + ->groups(['api', 'account']) + ->label('event', 'users.[userId].update.mfa') + ->label('scope', 'account') + ->label('audits.event', 'user.update') + ->label('audits.resource', 'user/{response.$id}') + ->label('audits.userId', '{response.$id}') + ->label('sdk', [ + new Method( + namespace: 'account', + group: 'mfa', + name: 'createMfaRecoveryCodes', + description: '/docs/references/account/create-mfa-recovery-codes.md', + auth: [AuthType::SESSION, AuthType::JWT], + responses: [ + new SDKResponse( + code: Response::STATUS_CODE_CREATED, + model: Response::MODEL_MFA_RECOVERY_CODES, + ) + ], + contentType: ContentType::JSON, + deprecated: new Deprecated( + since: '1.8.0', + replaceWith: 'account.createMFARecoveryCodes', + ), + ), + new Method( + namespace: 'account', + group: 'mfa', + name: 'createMFARecoveryCodes', + description: '/docs/references/account/create-mfa-recovery-codes.md', + auth: [AuthType::SESSION, AuthType::JWT], + responses: [ + new SDKResponse( + code: Response::STATUS_CODE_CREATED, + model: Response::MODEL_MFA_RECOVERY_CODES, + ) + ], + contentType: ContentType::JSON + ) + ]) + ->inject('response') + ->inject('user') + ->inject('dbForProject') + ->inject('queueForEvents') + ->callback($this->action(...)); + } + + public function action( + Response $response, + Document $user, + Database $dbForProject, + Event $queueForEvents + ): void { + $mfaRecoveryCodes = $user->getAttribute('mfaRecoveryCodes', []); + + if (!empty($mfaRecoveryCodes)) { + throw new Exception(Exception::USER_RECOVERY_CODES_ALREADY_EXISTS); + } + + $mfaRecoveryCodes = Type::generateBackupCodes(); + $user->setAttribute('mfaRecoveryCodes', $mfaRecoveryCodes); + $dbForProject->updateDocument('users', $user->getId(), $user); + + $queueForEvents->setParam('userId', $user->getId()); + + $document = new Document([ + 'recoveryCodes' => $mfaRecoveryCodes + ]); + + $response->dynamic($document, Response::MODEL_MFA_RECOVERY_CODES); + } +} diff --git a/src/Appwrite/Platform/Modules/Account/Http/Mfa/RecoveryCodes/Get.php b/src/Appwrite/Platform/Modules/Account/Http/Mfa/RecoveryCodes/Get.php new file mode 100644 index 0000000000..93ce7d4b7b --- /dev/null +++ b/src/Appwrite/Platform/Modules/Account/Http/Mfa/RecoveryCodes/Get.php @@ -0,0 +1,86 @@ +setHttpMethod(Action::HTTP_REQUEST_METHOD_GET) + ->setHttpPath('/v1/account/mfa/recovery-codes') + ->desc('List MFA recovery codes') + ->groups(['api', 'account', 'mfaProtected']) + ->label('scope', 'account') + ->label('sdk', [ + new Method( + namespace: 'account', + group: 'mfa', + name: 'getMfaRecoveryCodes', + description: '/docs/references/account/get-mfa-recovery-codes.md', + auth: [AuthType::SESSION, AuthType::JWT], + responses: [ + new SDKResponse( + code: Response::STATUS_CODE_OK, + model: Response::MODEL_MFA_RECOVERY_CODES, + ) + ], + contentType: ContentType::JSON, + deprecated: new Deprecated( + since: '1.8.0', + replaceWith: 'account.getMFARecoveryCodes', + ), + ), + new Method( + namespace: 'account', + group: 'mfa', + name: 'getMFARecoveryCodes', + description: '/docs/references/account/get-mfa-recovery-codes.md', + auth: [AuthType::SESSION, AuthType::JWT], + responses: [ + new SDKResponse( + code: Response::STATUS_CODE_OK, + model: Response::MODEL_MFA_RECOVERY_CODES, + ) + ], + contentType: ContentType::JSON + ) + ]) + ->inject('response') + ->inject('user') + ->callback($this->action(...)); + } + + public function action(Response $response, Document $user): void + { + $mfaRecoveryCodes = $user->getAttribute('mfaRecoveryCodes', []); + + if (empty($mfaRecoveryCodes)) { + throw new Exception(Exception::USER_RECOVERY_CODES_NOT_FOUND); + } + + $document = new Document([ + 'recoveryCodes' => $mfaRecoveryCodes + ]); + + $response->dynamic($document, Response::MODEL_MFA_RECOVERY_CODES); + } +} diff --git a/src/Appwrite/Platform/Modules/Account/Http/Mfa/RecoveryCodes/Update.php b/src/Appwrite/Platform/Modules/Account/Http/Mfa/RecoveryCodes/Update.php new file mode 100644 index 0000000000..e4b1490dc9 --- /dev/null +++ b/src/Appwrite/Platform/Modules/Account/Http/Mfa/RecoveryCodes/Update.php @@ -0,0 +1,104 @@ +setHttpMethod(Action::HTTP_REQUEST_METHOD_PATCH) + ->setHttpPath('/v1/account/mfa/recovery-codes') + ->desc('Update MFA recovery codes (regenerate)') + ->groups(['api', 'account', 'mfaProtected']) + ->label('event', 'users.[userId].update.mfa') + ->label('scope', 'account') + ->label('audits.event', 'user.update') + ->label('audits.resource', 'user/{response.$id}') + ->label('audits.userId', '{response.$id}') + ->label('sdk', [ + new Method( + namespace: 'account', + group: 'mfa', + name: 'updateMfaRecoveryCodes', + description: '/docs/references/account/update-mfa-recovery-codes.md', + auth: [AuthType::SESSION, AuthType::JWT], + responses: [ + new SDKResponse( + code: Response::STATUS_CODE_OK, + model: Response::MODEL_MFA_RECOVERY_CODES, + ) + ], + contentType: ContentType::JSON, + deprecated: new Deprecated( + since: '1.8.0', + replaceWith: 'account.updateMFARecoveryCodes', + ), + ), + new Method( + namespace: 'account', + group: 'mfa', + name: 'updateMFARecoveryCodes', + description: '/docs/references/account/update-mfa-recovery-codes.md', + auth: [AuthType::SESSION, AuthType::JWT], + responses: [ + new SDKResponse( + code: Response::STATUS_CODE_OK, + model: Response::MODEL_MFA_RECOVERY_CODES, + ) + ], + contentType: ContentType::JSON + ) + ]) + ->inject('dbForProject') + ->inject('response') + ->inject('user') + ->inject('queueForEvents') + ->callback($this->action(...)); + } + + public function action( + Database $dbForProject, + Response $response, + Document $user, + Event $queueForEvents + ): void { + $mfaRecoveryCodes = $user->getAttribute('mfaRecoveryCodes', []); + if (empty($mfaRecoveryCodes)) { + throw new Exception(Exception::USER_RECOVERY_CODES_NOT_FOUND); + } + + $mfaRecoveryCodes = Type::generateBackupCodes(); + $user->setAttribute('mfaRecoveryCodes', $mfaRecoveryCodes); + $dbForProject->updateDocument('users', $user->getId(), $user); + + $queueForEvents->setParam('userId', $user->getId()); + + $document = new Document([ + 'recoveryCodes' => $mfaRecoveryCodes + ]); + + $response->dynamic($document, Response::MODEL_MFA_RECOVERY_CODES); + } +} diff --git a/src/Appwrite/Platform/Modules/Account/Http/Mfa/Update.php b/src/Appwrite/Platform/Modules/Account/Http/Mfa/Update.php new file mode 100644 index 0000000000..bd889985ff --- /dev/null +++ b/src/Appwrite/Platform/Modules/Account/Http/Mfa/Update.php @@ -0,0 +1,99 @@ +setHttpMethod(Action::HTTP_REQUEST_METHOD_PATCH) + ->setHttpPath('/v1/account/mfa') + ->desc('Update MFA') + ->groups(['api', 'account']) + ->label('event', 'users.[userId].update.mfa') + ->label('scope', 'account') + ->label('audits.event', 'user.update') + ->label('audits.resource', 'user/{response.$id}') + ->label('audits.userId', '{response.$id}') + ->label('sdk', new Method( + namespace: 'account', + group: 'mfa', + name: 'updateMFA', + description: '/docs/references/account/update-mfa.md', + auth: [AuthType::SESSION, AuthType::JWT], + responses: [ + new SDKResponse( + code: Response::STATUS_CODE_OK, + model: Response::MODEL_USER, + ) + ], + contentType: ContentType::JSON + )) + ->param('mfa', null, new Boolean(), 'Enable or disable MFA.') + ->inject('requestTimestamp') + ->inject('response') + ->inject('user') + ->inject('session') + ->inject('dbForProject') + ->inject('queueForEvents') + ->callback($this->action(...)); + } + + public function action( + bool $mfa, + ?\DateTime $requestTimestamp, + Response $response, + Document $user, + Document $session, + Database $dbForProject, + Event $queueForEvents + ): void { + $user->setAttribute('mfa', $mfa); + + $user = $dbForProject->updateDocument('users', $user->getId(), $user); + + if ($mfa) { + $factors = $session->getAttribute('factors', []); + $totp = TOTP::getAuthenticatorFromUser($user); + if ($totp !== null && $totp->getAttribute('verified', false)) { + $factors[] = Type::TOTP; + } + if ($user->getAttribute('email', false) && $user->getAttribute('emailVerification', false)) { + $factors[] = Type::EMAIL; + } + if ($user->getAttribute('phone', false) && $user->getAttribute('phoneVerification', false)) { + $factors[] = Type::PHONE; + } + $factors = \array_values(\array_unique($factors)); + + $session->setAttribute('factors', $factors); + $dbForProject->updateDocument('sessions', $session->getId(), $session); + } + + $queueForEvents->setParam('userId', $user->getId()); + + $response->dynamic($user, Response::MODEL_ACCOUNT); + } +} diff --git a/src/Appwrite/Platform/Modules/Account/Module.php b/src/Appwrite/Platform/Modules/Account/Module.php new file mode 100644 index 0000000000..3ad50d388a --- /dev/null +++ b/src/Appwrite/Platform/Modules/Account/Module.php @@ -0,0 +1,14 @@ +addService('http', new Http()); + } +} diff --git a/src/Appwrite/Platform/Modules/Account/Services/Http.php b/src/Appwrite/Platform/Modules/Account/Services/Http.php new file mode 100644 index 0000000000..454a8d7c35 --- /dev/null +++ b/src/Appwrite/Platform/Modules/Account/Services/Http.php @@ -0,0 +1,34 @@ +type = Service::TYPE_HTTP; + $this + ->addAction(UpdateMfa::getName(), new UpdateMfa()) + ->addAction(ListFactors::getName(), new ListFactors()) + ->addAction(CreateAuthenticator::getName(), new CreateAuthenticator()) + ->addAction(UpdateAuthenticator::getName(), new UpdateAuthenticator()) + ->addAction(DeleteAuthenticator::getName(), new DeleteAuthenticator()) + ->addAction(CreateRecoveryCodes::getName(), new CreateRecoveryCodes()) + ->addAction(UpdateRecoveryCodes::getName(), new UpdateRecoveryCodes()) + ->addAction(GetRecoveryCodes::getName(), new GetRecoveryCodes()) + ->addAction(CreateChallenge::getName(), new CreateChallenge()) + ->addAction(UpdateChallenge::getName(), new UpdateChallenge()); + } +} From b20c493fa69b36b96feb271ca7d02b2cf6ae300c Mon Sep 17 00:00:00 2001 From: Harsh Mahajan Date: Tue, 11 Nov 2025 07:50:42 +0000 Subject: [PATCH 02/22] changes --- .../MFA}/Authenticators/Create.php | 2 +- .../MFA}/Authenticators/Delete.php | 2 +- .../MFA}/Authenticators/Update.php | 2 +- .../{Mfa => Account/MFA}/Challenge/Create.php | 5 +- .../{Mfa => Account/MFA}/Challenge/Update.php | 5 +- .../Http/Account/MFA/Challenges/Create.php | 330 ++++++++++++++++++ .../Http/Account/MFA/Challenges/Update.php | 161 +++++++++ .../{Mfa => Account/MFA}/Factors/XList.php | 2 +- .../MFA}/RecoveryCodes/Create.php | 2 +- .../MFA}/RecoveryCodes/Get.php | 2 +- .../MFA}/RecoveryCodes/Update.php | 2 +- .../Http/{Mfa => Account/MFA}/Update.php | 2 +- .../Modules/Account/Services/Http.php | 20 +- 13 files changed, 515 insertions(+), 22 deletions(-) rename src/Appwrite/Platform/Modules/Account/Http/{Mfa => Account/MFA}/Authenticators/Create.php (98%) rename src/Appwrite/Platform/Modules/Account/Http/{Mfa => Account/MFA}/Authenticators/Delete.php (97%) rename src/Appwrite/Platform/Modules/Account/Http/{Mfa => Account/MFA}/Authenticators/Update.php (98%) rename src/Appwrite/Platform/Modules/Account/Http/{Mfa => Account/MFA}/Challenge/Create.php (98%) rename src/Appwrite/Platform/Modules/Account/Http/{Mfa => Account/MFA}/Challenge/Update.php (97%) create mode 100644 src/Appwrite/Platform/Modules/Account/Http/Account/MFA/Challenges/Create.php create mode 100644 src/Appwrite/Platform/Modules/Account/Http/Account/MFA/Challenges/Update.php rename src/Appwrite/Platform/Modules/Account/Http/{Mfa => Account/MFA}/Factors/XList.php (97%) rename src/Appwrite/Platform/Modules/Account/Http/{Mfa => Account/MFA}/RecoveryCodes/Create.php (97%) rename src/Appwrite/Platform/Modules/Account/Http/{Mfa => Account/MFA}/RecoveryCodes/Get.php (97%) rename src/Appwrite/Platform/Modules/Account/Http/{Mfa => Account/MFA}/RecoveryCodes/Update.php (97%) rename src/Appwrite/Platform/Modules/Account/Http/{Mfa => Account/MFA}/Update.php (98%) diff --git a/src/Appwrite/Platform/Modules/Account/Http/Mfa/Authenticators/Create.php b/src/Appwrite/Platform/Modules/Account/Http/Account/MFA/Authenticators/Create.php similarity index 98% rename from src/Appwrite/Platform/Modules/Account/Http/Mfa/Authenticators/Create.php rename to src/Appwrite/Platform/Modules/Account/Http/Account/MFA/Authenticators/Create.php index e024f06a6e..a7bec0ec53 100644 --- a/src/Appwrite/Platform/Modules/Account/Http/Mfa/Authenticators/Create.php +++ b/src/Appwrite/Platform/Modules/Account/Http/Account/MFA/Authenticators/Create.php @@ -1,6 +1,6 @@ setHttpMethod(Action::HTTP_REQUEST_METHOD_POST) - ->setHttpPath('/v1/account/mfa/challenge') + ->setHttpPath('/v1/account/mfa/challenges') + ->httpAlias('/v1/account/mfa/challenge') ->desc('Create MFA challenge') ->groups(['api', 'account', 'mfa']) ->label('scope', 'account') diff --git a/src/Appwrite/Platform/Modules/Account/Http/Mfa/Challenge/Update.php b/src/Appwrite/Platform/Modules/Account/Http/Account/MFA/Challenge/Update.php similarity index 97% rename from src/Appwrite/Platform/Modules/Account/Http/Mfa/Challenge/Update.php rename to src/Appwrite/Platform/Modules/Account/Http/Account/MFA/Challenge/Update.php index d4bb6a1490..c0139d8c4f 100644 --- a/src/Appwrite/Platform/Modules/Account/Http/Mfa/Challenge/Update.php +++ b/src/Appwrite/Platform/Modules/Account/Http/Account/MFA/Challenge/Update.php @@ -1,6 +1,6 @@ setHttpMethod(Action::HTTP_REQUEST_METHOD_PUT) - ->setHttpPath('/v1/account/mfa/challenge') + ->setHttpPath('/v1/account/mfa/challenges') + ->httpAlias('/v1/account/mfa/challenge') ->desc('Update MFA challenge (confirmation)') ->groups(['api', 'account', 'mfa']) ->label('scope', 'account') diff --git a/src/Appwrite/Platform/Modules/Account/Http/Account/MFA/Challenges/Create.php b/src/Appwrite/Platform/Modules/Account/Http/Account/MFA/Challenges/Create.php new file mode 100644 index 0000000000..10d3ce53e3 --- /dev/null +++ b/src/Appwrite/Platform/Modules/Account/Http/Account/MFA/Challenges/Create.php @@ -0,0 +1,330 @@ +setHttpMethod(Action::HTTP_REQUEST_METHOD_POST) + ->setHttpPath('/v1/account/mfa/challenges') + ->httpAlias('/v1/account/mfa/challenge') + ->desc('Create MFA challenge') + ->groups(['api', 'account', 'mfa']) + ->label('scope', 'account') + ->label('event', 'users.[userId].challenges.[challengeId].create') + ->label('audits.event', 'challenge.create') + ->label('audits.resource', 'user/{response.userId}') + ->label('audits.userId', '{response.userId}') + ->label('sdk', [ + new Method( + namespace: 'account', + group: 'mfa', + name: 'createMfaChallenge', + description: '/docs/references/account/create-mfa-challenge.md', + auth: [], + responses: [ + new SDKResponse( + code: Response::STATUS_CODE_CREATED, + model: Response::MODEL_MFA_CHALLENGE, + ) + ], + contentType: ContentType::JSON, + deprecated: new Deprecated( + since: '1.8.0', + replaceWith: 'account.createMFAChallenge', + ), + ), + new Method( + namespace: 'account', + group: 'mfa', + name: 'createMFAChallenge', + description: '/docs/references/account/create-mfa-challenge.md', + auth: [], + responses: [ + new SDKResponse( + code: Response::STATUS_CODE_CREATED, + model: Response::MODEL_MFA_CHALLENGE, + ) + ], + contentType: ContentType::JSON + ) + ]) + ->label('abuse-limit', 10) + ->label('abuse-key', 'url:{url},userId:{userId}') + ->param('factor', '', new WhiteList([Type::EMAIL, Type::PHONE, Type::TOTP, Type::RECOVERY_CODE]), 'Factor used for verification. Must be one of following: `' . Type::EMAIL . '`, `' . Type::PHONE . '`, `' . Type::TOTP . '`, `' . Type::RECOVERY_CODE . '`.') + ->inject('response') + ->inject('dbForProject') + ->inject('user') + ->inject('locale') + ->inject('project') + ->inject('request') + ->inject('queueForEvents') + ->inject('queueForMessaging') + ->inject('queueForMails') + ->inject('timelimit') + ->inject('queueForStatsUsage') + ->inject('plan') + ->callback($this->action(...)); + } + + public function action( + string $factor, + Response $response, + Database $dbForProject, + Document $user, + Locale $locale, + Document $project, + Request $request, + Event $queueForEvents, + Messaging $queueForMessaging, + Mail $queueForMails, + callable $timelimit, + StatsUsage $queueForStatsUsage, + array $plan + ): void { + $expire = DateTime::formatTz(DateTime::addSeconds(new \DateTime(), Auth::TOKEN_EXPIRATION_CONFIRM)); + $code = Auth::codeGenerator(); + $challenge = new Document([ + 'userId' => $user->getId(), + 'userInternalId' => $user->getSequence(), + 'type' => $factor, + 'token' => Auth::tokenGenerator(), + 'code' => $code, + 'expire' => $expire, + '$permissions' => [ + Permission::read(Role::user($user->getId())), + Permission::update(Role::user($user->getId())), + Permission::delete(Role::user($user->getId())), + ], + ]); + + $challenge = $dbForProject->createDocument('challenges', $challenge); + + $templatesPath = \dirname(__DIR__, 7) . '/app/config/locale/templates'; + + switch ($factor) { + case Type::PHONE: + if (empty(System::getEnv('_APP_SMS_PROVIDER'))) { + throw new Exception(Exception::GENERAL_PHONE_DISABLED, 'Phone provider not configured'); + } + if (empty($user->getAttribute('phone'))) { + throw new Exception(Exception::USER_PHONE_NOT_FOUND); + } + if (!$user->getAttribute('phoneVerification')) { + throw new Exception(Exception::USER_PHONE_NOT_VERIFIED); + } + + $message = Template::fromFile($templatesPath . '/sms-base.tpl'); + + $customTemplate = $project->getAttribute('templates', [])['sms.mfaChallenge-' . $locale->default] ?? []; + if (!empty($customTemplate)) { + $message = $customTemplate['message'] ?? $message; + } + + $messageContent = Template::fromString($locale->getText("sms.verification.body")); + $messageContent + ->setParam('{{project}}', $project->getAttribute('name')) + ->setParam('{{secret}}', $code); + $messageContent = \strip_tags($messageContent->render()); + $message = $message->setParam('{{token}}', $messageContent); + + $message = $message->render(); + + $phone = $user->getAttribute('phone'); + $queueForMessaging + ->setType(MESSAGE_SEND_TYPE_INTERNAL) + ->setMessage(new Document([ + '$id' => $challenge->getId(), + 'data' => [ + 'content' => $code, + ], + ])) + ->setRecipients([$phone]) + ->setProviderType(MESSAGE_TYPE_SMS); + + if (isset($plan['authPhone'])) { + $timelimit = $timelimit('organization:{organizationId}', $plan['authPhone'], 30 * 24 * 60 * 60); // 30 days + $timelimit + ->setParam('{organizationId}', $project->getAttribute('teamId')); + + $abuse = new Abuse($timelimit); + if ($abuse->check() && System::getEnv('_APP_OPTIONS_ABUSE', 'enabled') === 'enabled') { + $helper = PhoneNumberUtil::getInstance(); + $countryCode = $helper->parse($phone)->getCountryCode(); + + if (!empty($countryCode)) { + $queueForStatsUsage + ->addMetric(str_replace('{countryCode}', $countryCode, METRIC_AUTH_METHOD_PHONE_COUNTRY_CODE), 1); + } + } + $queueForStatsUsage + ->addMetric(METRIC_AUTH_METHOD_PHONE, 1) + ->setProject($project) + ->trigger(); + } + break; + case Type::EMAIL: + if (empty(System::getEnv('_APP_SMTP_HOST'))) { + throw new Exception(Exception::GENERAL_SMTP_DISABLED, 'SMTP disabled'); + } + if (empty($user->getAttribute('email'))) { + throw new Exception(Exception::USER_EMAIL_NOT_FOUND); + } + if (!$user->getAttribute('emailVerification')) { + throw new Exception(Exception::USER_EMAIL_NOT_VERIFIED); + } + + $subject = $locale->getText("emails.mfaChallenge.subject"); + $preview = $locale->getText("emails.mfaChallenge.preview"); + $heading = $locale->getText("emails.mfaChallenge.heading"); + + $customTemplate = $project->getAttribute('templates', [])['email.mfaChallenge-' . $locale->default] ?? []; + $smtpBaseTemplate = $project->getAttribute('smtpBaseTemplate', 'email-base'); + + $validator = new FileName(); + if (!$validator->isValid($smtpBaseTemplate)) { + throw new Exception(Exception::GENERAL_BAD_REQUEST, 'Invalid template path'); + } + + $bodyTemplate = $templatesPath . '/' . $smtpBaseTemplate . '.tpl'; + + $detector = new Detector($request->getUserAgent('UNKNOWN')); + $agentOs = $detector->getOS(); + $agentClient = $detector->getClient(); + $agentDevice = $detector->getDevice(); + + $message = Template::fromFile($templatesPath . '/email-mfa-challenge.tpl'); + $message + ->setParam('{{hello}}', $locale->getText("emails.mfaChallenge.hello")) + ->setParam('{{description}}', $locale->getText("emails.mfaChallenge.description")) + ->setParam('{{clientInfo}}', $locale->getText("emails.mfaChallenge.clientInfo")) + ->setParam('{{thanks}}', $locale->getText("emails.mfaChallenge.thanks")) + ->setParam('{{signature}}', $locale->getText("emails.mfaChallenge.signature")); + + $body = $message->render(); + + $smtp = $project->getAttribute('smtp', []); + $smtpEnabled = $smtp['enabled'] ?? false; + + $senderEmail = System::getEnv('_APP_SYSTEM_EMAIL_ADDRESS', APP_EMAIL_TEAM); + $senderName = System::getEnv('_APP_SYSTEM_EMAIL_NAME', APP_NAME . ' Server'); + $replyTo = ""; + + if ($smtpEnabled) { + if (!empty($smtp['senderEmail'])) { + $senderEmail = $smtp['senderEmail']; + } + if (!empty($smtp['senderName'])) { + $senderName = $smtp['senderName']; + } + if (!empty($smtp['replyTo'])) { + $replyTo = $smtp['replyTo']; + } + + $queueForMails + ->setSmtpHost($smtp['host'] ?? '') + ->setSmtpPort($smtp['port'] ?? '') + ->setSmtpUsername($smtp['username'] ?? '') + ->setSmtpPassword($smtp['password'] ?? '') + ->setSmtpSecure($smtp['secure'] ?? ''); + + if (!empty($customTemplate)) { + if (!empty($customTemplate['senderEmail'])) { + $senderEmail = $customTemplate['senderEmail']; + } + if (!empty($customTemplate['senderName'])) { + $senderName = $customTemplate['senderName']; + } + if (!empty($customTemplate['replyTo'])) { + $replyTo = $customTemplate['replyTo']; + } + + $body = $customTemplate['message'] ?? ''; + $subject = $customTemplate['subject'] ?? $subject; + } + + $queueForMails + ->setSmtpReplyTo($replyTo) + ->setSmtpSenderEmail($senderEmail) + ->setSmtpSenderName($senderName); + } + + $emailVariables = [ + 'heading' => $heading, + 'direction' => $locale->getText('settings.direction'), + 'user' => $user->getAttribute('name'), + 'project' => $project->getAttribute('name'), + 'otp' => $code, + 'agentDevice' => $agentDevice['deviceBrand'] ?? $agentDevice['deviceBrand'] ?? 'UNKNOWN', + 'agentClient' => $agentClient['clientName'] ?? 'UNKNOWN', + 'agentOs' => $agentOs['osName'] ?? 'UNKNOWN', + ]; + + if ($smtpBaseTemplate === APP_BRANDED_EMAIL_BASE_TEMPLATE) { + $emailVariables = array_merge($emailVariables, [ + 'accentColor' => APP_EMAIL_ACCENT_COLOR, + 'logoUrl' => APP_EMAIL_LOGO_URL, + 'twitterUrl' => APP_SOCIAL_TWITTER, + 'discordUrl' => APP_SOCIAL_DISCORD, + 'githubUrl' => APP_SOCIAL_GITHUB_APPWRITE, + 'termsUrl' => APP_EMAIL_TERMS_URL, + 'privacyUrl' => APP_EMAIL_PRIVACY_URL, + ]); + } + + $queueForMails + ->setSubject($subject) + ->setPreview($preview) + ->setBody($body) + ->setBodyTemplate($bodyTemplate) + ->setVariables($emailVariables) + ->setRecipient($user->getAttribute('email')) + ->trigger(); + break; + } + + $queueForEvents + ->setParam('userId', $user->getId()) + ->setParam('challengeId', $challenge->getId()); + + $response->dynamic($challenge, Response::MODEL_MFA_CHALLENGE); + } +} diff --git a/src/Appwrite/Platform/Modules/Account/Http/Account/MFA/Challenges/Update.php b/src/Appwrite/Platform/Modules/Account/Http/Account/MFA/Challenges/Update.php new file mode 100644 index 0000000000..40d17afa18 --- /dev/null +++ b/src/Appwrite/Platform/Modules/Account/Http/Account/MFA/Challenges/Update.php @@ -0,0 +1,161 @@ +setHttpMethod(Action::HTTP_REQUEST_METHOD_PUT) + ->setHttpPath('/v1/account/mfa/challenges') + ->httpAlias('/v1/account/mfa/challenge') + ->desc('Update MFA challenge (confirmation)') + ->groups(['api', 'account', 'mfa']) + ->label('scope', 'account') + ->label('event', 'users.[userId].sessions.[sessionId].create') + ->label('audits.event', 'challenges.update') + ->label('audits.resource', 'user/{response.userId}') + ->label('audits.userId', '{response.userId}') + ->label('sdk', [ + new Method( + namespace: 'account', + group: 'mfa', + name: 'updateMfaChallenge', + description: '/docs/references/account/update-mfa-challenge.md', + auth: [AuthType::SESSION, AuthType::JWT], + responses: [ + new SDKResponse( + code: Response::STATUS_CODE_OK, + model: Response::MODEL_SESSION, + ) + ], + contentType: ContentType::JSON, + deprecated: new Deprecated( + since: '1.8.0', + replaceWith: 'account.updateMFAChallenge', + ), + ), + new Method( + namespace: 'account', + group: 'mfa', + name: 'updateMFAChallenge', + description: '/docs/references/account/update-mfa-challenge.md', + auth: [AuthType::SESSION, AuthType::JWT], + responses: [ + new SDKResponse( + code: Response::STATUS_CODE_OK, + model: Response::MODEL_SESSION, + ) + ], + contentType: ContentType::JSON + ) + ]) + ->label('abuse-limit', 10) + ->label('abuse-key', 'url:{url},challengeId:{param-challengeId}') + ->param('challengeId', '', new Text(256), 'ID of the challenge.') + ->param('otp', '', new Text(256), 'Valid verification token.') + ->inject('project') + ->inject('response') + ->inject('user') + ->inject('session') + ->inject('dbForProject') + ->inject('queueForEvents') + ->callback($this->action(...)); + } + + public function action( + string $challengeId, + string $otp, + Document $project, + Response $response, + Document $user, + Document $session, + Database $dbForProject, + Event $queueForEvents + ): void { + $challenge = $dbForProject->getDocument('challenges', $challengeId); + + if ($challenge->isEmpty()) { + throw new Exception(Exception::USER_INVALID_TOKEN); + } + + $type = $challenge->getAttribute('type'); + + $recoveryCodeChallenge = function (Document $challenge, Document $user, string $otp) use ($dbForProject) { + if ( + $challenge->isSet('type') && + $challenge->getAttribute('type') === \strtolower(Type::RECOVERY_CODE) + ) { + $mfaRecoveryCodes = $user->getAttribute('mfaRecoveryCodes', []); + if (\in_array($otp, $mfaRecoveryCodes)) { + $mfaRecoveryCodes = \array_diff($mfaRecoveryCodes, [$otp]); + $mfaRecoveryCodes = \array_values($mfaRecoveryCodes); + $user->setAttribute('mfaRecoveryCodes', $mfaRecoveryCodes); + $dbForProject->updateDocument('users', $user->getId(), $user); + + return true; + } + + return false; + } + + return false; + }; + + $success = (match ($type) { + Type::TOTP => Challenge\TOTP::challenge($challenge, $user, $otp), + Type::PHONE => Challenge\Phone::challenge($challenge, $user, $otp), + Type::EMAIL => Challenge\Email::challenge($challenge, $user, $otp), + \strtolower(Type::RECOVERY_CODE) => $recoveryCodeChallenge($challenge, $user, $otp), + default => false + }); + + if (!$success) { + throw new Exception(Exception::USER_INVALID_TOKEN); + } + + $dbForProject->deleteDocument('challenges', $challengeId); + $dbForProject->purgeCachedDocument('users', $user->getId()); + + $factors = $session->getAttribute('factors', []); + $factors[] = $type; + $factors = \array_values(\array_unique($factors)); + + $session + ->setAttribute('factors', $factors) + ->setAttribute('mfaUpdatedAt', DateTime::now()); + + $dbForProject->updateDocument('sessions', $session->getId(), $session); + + $queueForEvents + ->setParam('userId', $user->getId()) + ->setParam('sessionId', $session->getId()); + + $response->dynamic($session, Response::MODEL_SESSION); + } +} diff --git a/src/Appwrite/Platform/Modules/Account/Http/Mfa/Factors/XList.php b/src/Appwrite/Platform/Modules/Account/Http/Account/MFA/Factors/XList.php similarity index 97% rename from src/Appwrite/Platform/Modules/Account/Http/Mfa/Factors/XList.php rename to src/Appwrite/Platform/Modules/Account/Http/Account/MFA/Factors/XList.php index 8e99ad1283..c60f599cfc 100644 --- a/src/Appwrite/Platform/Modules/Account/Http/Mfa/Factors/XList.php +++ b/src/Appwrite/Platform/Modules/Account/Http/Account/MFA/Factors/XList.php @@ -1,6 +1,6 @@ Date: Fri, 14 Nov 2025 20:44:48 +0530 Subject: [PATCH 03/22] Update deploymentId in sites rule for first deployment --- .../Platform/Modules/Compute/Base.php | 30 +++++++++++++++++++ .../Modules/Sites/Http/Deployments/Create.php | 5 +++- .../Http/Deployments/Template/Create.php | 2 ++ tests/e2e/Services/Sites/SitesBase.php | 23 ++++++++++++++ .../Services/Sites/SitesCustomServerTest.php | 14 +++++++++ 5 files changed, 73 insertions(+), 1 deletion(-) diff --git a/src/Appwrite/Platform/Modules/Compute/Base.php b/src/Appwrite/Platform/Modules/Compute/Base.php index a538eb1497..5fae52ff45 100644 --- a/src/Appwrite/Platform/Modules/Compute/Base.php +++ b/src/Appwrite/Platform/Modules/Compute/Base.php @@ -14,6 +14,7 @@ use Utopia\Database\Helpers\ID; use Utopia\Database\Helpers\Permission; use Utopia\Database\Helpers\Role; use Utopia\Database\Validator\Authorization; +use Utopia\Database\Query; use Utopia\Swoole\Request; use Utopia\System\System; use Utopia\VCS\Adapter\Git\GitHub; @@ -335,4 +336,33 @@ class Base extends Action return $deployment; } + + /** + * Update manual rule for new site deployment. + * In case of fresh site, deployment ID will be empty in the rules, so we need to update it here. + * + * @param \Utopia\Database\Document $project + * @param \Utopia\Database\Document $site + * @param \Utopia\Database\Document $deployment + * @param \Utopia\Database\Database $dbForPlatform + * @return void + */ + public function updateManualRuleForNewSiteDeployment(Document $project, Document $site, Document $deployment, Database $dbForPlatform) + { + $queries = [ + Query::equal('projectInternalId', [$project->getSequence()]), + Query::equal('type', ['deployment']), + Query::equal('deploymentResourceInternalId', [$site->getSequence()]), + Query::equal('deploymentResourceType', ['site']), + Query::equal('trigger', ['manual']), + ]; + $dbForPlatform->forEach('rules', function (Document $rule) use ($deployment, $dbForPlatform) { + if (empty($rule->getAttribute('deploymentId', ''))) { + Authorization::skip(fn () => $dbForPlatform->updateDocument('rules', $rule->getId(), new Document([ + 'deploymentId' => $deployment->getId(), + 'deploymentInternalId' => $deployment->getSequence(), + ]))); + } + }, $queries); + } } diff --git a/src/Appwrite/Platform/Modules/Sites/Http/Deployments/Create.php b/src/Appwrite/Platform/Modules/Sites/Http/Deployments/Create.php index 4bd3afa1f5..70546bf3f7 100644 --- a/src/Appwrite/Platform/Modules/Sites/Http/Deployments/Create.php +++ b/src/Appwrite/Platform/Modules/Sites/Http/Deployments/Create.php @@ -5,6 +5,7 @@ namespace Appwrite\Platform\Modules\Sites\Http\Deployments; use Appwrite\Event\Build; use Appwrite\Event\Event; use Appwrite\Extend\Exception; +use Appwrite\Platform\Modules\Compute\Base; use Appwrite\SDK\AuthType; use Appwrite\SDK\ContentType; use Appwrite\SDK\Method; @@ -31,7 +32,7 @@ use Utopia\System\System; use Utopia\Validator\Boolean; use Utopia\Validator\Text; -class Create extends Action +class Create extends Base { use HTTP; @@ -364,6 +365,8 @@ class Create extends Action } } + $this->updateManualRuleForNewSiteDeployment($project, $site, $deployment, $dbForPlatform); + $metadata = null; $queueForEvents diff --git a/src/Appwrite/Platform/Modules/Sites/Http/Deployments/Template/Create.php b/src/Appwrite/Platform/Modules/Sites/Http/Deployments/Template/Create.php index dc90045b0c..2407becd68 100644 --- a/src/Appwrite/Platform/Modules/Sites/Http/Deployments/Template/Create.php +++ b/src/Appwrite/Platform/Modules/Sites/Http/Deployments/Template/Create.php @@ -196,6 +196,8 @@ class Create extends Base ])) ); + $this->updateManualRuleForNewSiteDeployment($project, $site, $deployment, $dbForPlatform); + $queueForBuilds ->setType(BUILD_TYPE_DEPLOYMENT) ->setResource($site) diff --git a/tests/e2e/Services/Sites/SitesBase.php b/tests/e2e/Services/Sites/SitesBase.php index 93c55b82b7..3f82a42dca 100644 --- a/tests/e2e/Services/Sites/SitesBase.php +++ b/tests/e2e/Services/Sites/SitesBase.php @@ -450,4 +450,27 @@ trait SitesBase return $specifications; } + + protected function createRule(string $siteId, string $domain): mixed + { + $rule = $this->client->call(Client::METHOD_POST, '/proxy/rules/site', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'siteId' => $siteId, + 'domain' => $domain, + ]); + + return $rule; + } + + protected function getRule(string $ruleId): mixed + { + $rule = $this->client->call(Client::METHOD_GET, '/proxy/rules/' . $ruleId, array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + return $rule; + } } diff --git a/tests/e2e/Services/Sites/SitesCustomServerTest.php b/tests/e2e/Services/Sites/SitesCustomServerTest.php index 8591514796..df29231562 100644 --- a/tests/e2e/Services/Sites/SitesCustomServerTest.php +++ b/tests/e2e/Services/Sites/SitesCustomServerTest.php @@ -861,6 +861,9 @@ class SitesCustomServerTest extends Scope $this->assertNotNull($siteId); + $rule = $this->createRule($siteId, \uniqid() . '.myapp.com'); + $this->assertEquals(201, $rule['headers']['status-code']); + $deployment = $this->createDeployment($siteId, [ 'siteId' => $siteId, 'code' => $this->packageSite('static-single-file'), @@ -872,6 +875,10 @@ class SitesCustomServerTest extends Scope $this->assertEquals('waiting', $deployment['body']['status']); $this->assertEquals(true, (new DatetimeValidator())->isValid($deployment['body']['$createdAt'])); + $rule = $this->getRule($rule['body']['$id']); + $this->assertEquals(200, $rule['headers']['status-code']); + $this->assertEquals($deployment['body']['$id'], $rule['body']['deploymentId']); + $deploymentIdActive = $deployment['body']['$id'] ?? ''; $this->assertEventually(function () use ($siteId, $deploymentIdActive) { @@ -1563,6 +1570,9 @@ class SitesCustomServerTest extends Scope $this->assertNotEmpty($siteId); + $rule = $this->createRule($siteId, \uniqid() . '.myapp.com'); + $this->assertEquals(201, $rule['headers']['status-code']); + $deployment = $this->createTemplateDeployment($siteId, [ 'repository' => $template['providerRepositoryId'], 'owner' => $template['providerOwner'], @@ -1574,6 +1584,10 @@ class SitesCustomServerTest extends Scope $this->assertEquals(202, $deployment['headers']['status-code']); $this->assertNotEmpty($deployment['body']['$id']); + $rule = $this->getRule($rule['body']['$id']); + $this->assertEquals(200, $rule['headers']['status-code']); + $this->assertEquals($deployment['body']['$id'], $rule['body']['deploymentId']); + $deployment = $this->getDeployment($siteId, $deployment['body']['$id']); $this->assertEquals(200, $deployment['headers']['status-code']); $this->assertEquals(0, $deployment['body']['sourceSize']); From d36e42377b859f7dbdd5dd32b1def240f43b4ee5 Mon Sep 17 00:00:00 2001 From: Hemachandar Date: Fri, 14 Nov 2025 20:49:06 +0530 Subject: [PATCH 04/22] include VCS --- src/Appwrite/Platform/Modules/Compute/Base.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Appwrite/Platform/Modules/Compute/Base.php b/src/Appwrite/Platform/Modules/Compute/Base.php index 5fae52ff45..af06ca8000 100644 --- a/src/Appwrite/Platform/Modules/Compute/Base.php +++ b/src/Appwrite/Platform/Modules/Compute/Base.php @@ -328,6 +328,8 @@ class Base extends Action } } + $this->updateManualRuleForNewSiteDeployment($project, $site, $deployment, $dbForPlatform); + $queueForBuilds ->setType(BUILD_TYPE_DEPLOYMENT) ->setResource($site) From ba542554ceacfbbe4ea7afe4648570ddd080f3e6 Mon Sep 17 00:00:00 2001 From: Hemachandar Date: Mon, 17 Nov 2025 14:53:26 +0530 Subject: [PATCH 05/22] functions and more tests --- .../Platform/Modules/Compute/Base.php | 28 ++++++++--------- .../Functions/Http/Deployments/Create.php | 7 ++++- .../Http/Deployments/Template/Create.php | 4 +++ .../Functions/Http/Deployments/Vcs/Create.php | 1 + .../Functions/Http/Functions/Create.php | 1 + .../Functions/Http/Functions/Update.php | 2 +- .../Modules/Sites/Http/Deployments/Create.php | 2 +- .../Http/Deployments/Template/Create.php | 2 +- .../Modules/Sites/Http/Sites/Update.php | 2 +- .../e2e/Services/Functions/FunctionsBase.php | 23 ++++++++++++++ .../Functions/FunctionsCustomServerTest.php | 31 +++++++++++++++++++ tests/e2e/Services/Sites/SitesBase.php | 4 +-- .../Services/Sites/SitesCustomServerTest.php | 30 ++++++++++++------ 13 files changed, 107 insertions(+), 30 deletions(-) diff --git a/src/Appwrite/Platform/Modules/Compute/Base.php b/src/Appwrite/Platform/Modules/Compute/Base.php index af06ca8000..02650b9b85 100644 --- a/src/Appwrite/Platform/Modules/Compute/Base.php +++ b/src/Appwrite/Platform/Modules/Compute/Base.php @@ -13,8 +13,8 @@ use Utopia\Database\Exception\Duplicate; use Utopia\Database\Helpers\ID; use Utopia\Database\Helpers\Permission; use Utopia\Database\Helpers\Role; -use Utopia\Database\Validator\Authorization; use Utopia\Database\Query; +use Utopia\Database\Validator\Authorization; use Utopia\Swoole\Request; use Utopia\System\System; use Utopia\VCS\Adapter\Git\GitHub; @@ -53,7 +53,7 @@ class Base extends Action return $allowedSpecifications[0] ?? APP_COMPUTE_SPECIFICATION_DEFAULT; } - public function redeployVcsFunction(Request $request, Document $function, Document $project, Document $installation, Database $dbForProject, Build $queueForBuilds, Document $template, GitHub $github, bool $activate, string $referenceType = 'branch', string $reference = ''): Document + public function redeployVcsFunction(Request $request, Document $function, Document $project, Document $installation, Database $dbForProject, Database $dbForPlatform, Build $queueForBuilds, Document $template, GitHub $github, bool $activate, string $referenceType = 'branch', string $reference = ''): Document { $deploymentId = ID::unique(); $entrypoint = $function->getAttribute('entrypoint', ''); @@ -134,6 +134,8 @@ class Base extends Action ->setAttribute('latestDeploymentStatus', $deployment->getAttribute('status', '')); $dbForProject->updateDocument('functions', $function->getId(), $function); + $this->updateEmptyManualRule($project, $function, $deployment, $dbForPlatform); + $queueForBuilds ->setType(BUILD_TYPE_DEPLOYMENT) ->setResource($function) @@ -328,7 +330,7 @@ class Base extends Action } } - $this->updateManualRuleForNewSiteDeployment($project, $site, $deployment, $dbForPlatform); + $this->updateEmptyManualRule($project, $site, $deployment, $dbForPlatform); $queueForBuilds ->setType(BUILD_TYPE_DEPLOYMENT) @@ -340,8 +342,8 @@ class Base extends Action } /** - * Update manual rule for new site deployment. - * In case of fresh site, deployment ID will be empty in the rules, so we need to update it here. + * Update empty manual rule for deployment. + * In case of first deployment, deployment ID will be empty in the rules, so we need to update it here. * * @param \Utopia\Database\Document $project * @param \Utopia\Database\Document $site @@ -349,22 +351,20 @@ class Base extends Action * @param \Utopia\Database\Database $dbForPlatform * @return void */ - public function updateManualRuleForNewSiteDeployment(Document $project, Document $site, Document $deployment, Database $dbForPlatform) + public function updateEmptyManualRule(Document $project, Document $resource, Document $deployment, Database $dbForPlatform) { $queries = [ Query::equal('projectInternalId', [$project->getSequence()]), + Query::equal('deploymentResourceInternalId', [$resource->getSequence()]), + Query::equal('deploymentId', ['']), Query::equal('type', ['deployment']), - Query::equal('deploymentResourceInternalId', [$site->getSequence()]), - Query::equal('deploymentResourceType', ['site']), Query::equal('trigger', ['manual']), ]; $dbForPlatform->forEach('rules', function (Document $rule) use ($deployment, $dbForPlatform) { - if (empty($rule->getAttribute('deploymentId', ''))) { - Authorization::skip(fn () => $dbForPlatform->updateDocument('rules', $rule->getId(), new Document([ - 'deploymentId' => $deployment->getId(), - 'deploymentInternalId' => $deployment->getSequence(), - ]))); - } + Authorization::skip(fn () => $dbForPlatform->updateDocument('rules', $rule->getId(), new Document([ + 'deploymentId' => $deployment->getId(), + 'deploymentInternalId' => $deployment->getSequence(), + ]))); }, $queries); } } diff --git a/src/Appwrite/Platform/Modules/Functions/Http/Deployments/Create.php b/src/Appwrite/Platform/Modules/Functions/Http/Deployments/Create.php index f64a960507..76a5380f07 100644 --- a/src/Appwrite/Platform/Modules/Functions/Http/Deployments/Create.php +++ b/src/Appwrite/Platform/Modules/Functions/Http/Deployments/Create.php @@ -5,6 +5,7 @@ namespace Appwrite\Platform\Modules\Functions\Http\Deployments; use Appwrite\Event\Build; use Appwrite\Event\Event; use Appwrite\Extend\Exception; +use Appwrite\Platform\Modules\Compute\Base; use Appwrite\SDK\AuthType; use Appwrite\SDK\ContentType; use Appwrite\SDK\Method; @@ -30,7 +31,7 @@ use Utopia\System\System; use Utopia\Validator\Boolean; use Utopia\Validator\Text; -class Create extends Action +class Create extends Base { use HTTP; @@ -81,6 +82,7 @@ class Create extends Action ->inject('request') ->inject('response') ->inject('dbForProject') + ->inject('dbForPlatform') ->inject('queueForEvents') ->inject('project') ->inject('deviceForFunctions') @@ -99,6 +101,7 @@ class Create extends Action Request $request, Response $response, Database $dbForProject, + Database $dbForPlatform, Event $queueForEvents, Document $project, Device $deviceForFunctions, @@ -300,6 +303,8 @@ class Create extends Action } } + $this->updateEmptyManualRule($project, $function, $deployment, $dbForPlatform); + $metadata = null; $queueForEvents diff --git a/src/Appwrite/Platform/Modules/Functions/Http/Deployments/Template/Create.php b/src/Appwrite/Platform/Modules/Functions/Http/Deployments/Template/Create.php index bbe84c56ee..e452ebf974 100644 --- a/src/Appwrite/Platform/Modules/Functions/Http/Deployments/Template/Create.php +++ b/src/Appwrite/Platform/Modules/Functions/Http/Deployments/Template/Create.php @@ -116,6 +116,7 @@ class Create extends Base project: $project, installation: $installation, dbForProject: $dbForProject, + dbForPlatform: $dbForPlatform, queueForBuilds: $queueForBuilds, template: $template, github: $github, @@ -157,6 +158,9 @@ class Create extends Base ->setAttribute('latestDeploymentStatus', $deployment->getAttribute('status', '')); $dbForProject->updateDocument('functions', $function->getId(), $function); + + $this->updateEmptyManualRule($project, $function, $deployment, $dbForPlatform); + $queueForBuilds ->setType(BUILD_TYPE_DEPLOYMENT) ->setResource($function) diff --git a/src/Appwrite/Platform/Modules/Functions/Http/Deployments/Vcs/Create.php b/src/Appwrite/Platform/Modules/Functions/Http/Deployments/Vcs/Create.php index 0ad9852722..79e2e215ad 100644 --- a/src/Appwrite/Platform/Modules/Functions/Http/Deployments/Vcs/Create.php +++ b/src/Appwrite/Platform/Modules/Functions/Http/Deployments/Vcs/Create.php @@ -105,6 +105,7 @@ class Create extends Base project: $project, installation: $installation, dbForProject: $dbForProject, + dbForPlatform: $dbForPlatform, queueForBuilds: $queueForBuilds, template: $template, github: $github, diff --git a/src/Appwrite/Platform/Modules/Functions/Http/Functions/Create.php b/src/Appwrite/Platform/Modules/Functions/Http/Functions/Create.php index ec2a4baac5..b4300c4913 100644 --- a/src/Appwrite/Platform/Modules/Functions/Http/Functions/Create.php +++ b/src/Appwrite/Platform/Modules/Functions/Http/Functions/Create.php @@ -311,6 +311,7 @@ class Create extends Base project: $project, installation: $installation, dbForProject: $dbForProject, + dbForPlatform: $dbForPlatform, queueForBuilds: $queueForBuilds, template: $template, github: $github, diff --git a/src/Appwrite/Platform/Modules/Functions/Http/Functions/Update.php b/src/Appwrite/Platform/Modules/Functions/Http/Functions/Update.php index 318c2a2032..4bc3dca613 100644 --- a/src/Appwrite/Platform/Modules/Functions/Http/Functions/Update.php +++ b/src/Appwrite/Platform/Modules/Functions/Http/Functions/Update.php @@ -273,7 +273,7 @@ class Update extends Base // Redeploy logic if (!$isConnected && !empty($providerRepositoryId)) { - $this->redeployVcsFunction($request, $function, $project, $installation, $dbForProject, $queueForBuilds, new Document(), $github, true); + $this->redeployVcsFunction($request, $function, $project, $installation, $dbForProject, $dbForPlatform, $queueForBuilds, new Document(), $github, true); } // Inform scheduler if function is still active diff --git a/src/Appwrite/Platform/Modules/Sites/Http/Deployments/Create.php b/src/Appwrite/Platform/Modules/Sites/Http/Deployments/Create.php index 70546bf3f7..b2d48162e4 100644 --- a/src/Appwrite/Platform/Modules/Sites/Http/Deployments/Create.php +++ b/src/Appwrite/Platform/Modules/Sites/Http/Deployments/Create.php @@ -365,7 +365,7 @@ class Create extends Base } } - $this->updateManualRuleForNewSiteDeployment($project, $site, $deployment, $dbForPlatform); + $this->updateEmptyManualRule($project, $site, $deployment, $dbForPlatform); $metadata = null; diff --git a/src/Appwrite/Platform/Modules/Sites/Http/Deployments/Template/Create.php b/src/Appwrite/Platform/Modules/Sites/Http/Deployments/Template/Create.php index 2407becd68..daf41e4c6d 100644 --- a/src/Appwrite/Platform/Modules/Sites/Http/Deployments/Template/Create.php +++ b/src/Appwrite/Platform/Modules/Sites/Http/Deployments/Template/Create.php @@ -196,7 +196,7 @@ class Create extends Base ])) ); - $this->updateManualRuleForNewSiteDeployment($project, $site, $deployment, $dbForPlatform); + $this->updateEmptyManualRule($project, $site, $deployment, $dbForPlatform); $queueForBuilds ->setType(BUILD_TYPE_DEPLOYMENT) diff --git a/src/Appwrite/Platform/Modules/Sites/Http/Sites/Update.php b/src/Appwrite/Platform/Modules/Sites/Http/Sites/Update.php index 72ec04a2a5..61d59d4da0 100644 --- a/src/Appwrite/Platform/Modules/Sites/Http/Sites/Update.php +++ b/src/Appwrite/Platform/Modules/Sites/Http/Sites/Update.php @@ -272,7 +272,7 @@ class Update extends Base // Redeploy logic if (!$isConnected && !empty($providerRepositoryId)) { - $this->redeployVcsFunction($request, $site, $project, $installation, $dbForProject, $queueForBuilds, new Document(), $github, true); + $this->redeployVcsFunction($request, $site, $project, $installation, $dbForProject, $dbForPlatform, $queueForBuilds, new Document(), $github, true); } $queueForEvents->setParam('siteId', $site->getId()); diff --git a/tests/e2e/Services/Functions/FunctionsBase.php b/tests/e2e/Services/Functions/FunctionsBase.php index 27b67d851d..1e817c49f9 100644 --- a/tests/e2e/Services/Functions/FunctionsBase.php +++ b/tests/e2e/Services/Functions/FunctionsBase.php @@ -409,4 +409,27 @@ trait FunctionsBase return $specifications; } + + protected function createFunctionRule(string $functionId, string $domain): mixed + { + $rule = $this->client->call(Client::METHOD_POST, '/proxy/rules/function', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'functionId' => $functionId, + 'domain' => $domain, + ]); + + return $rule; + } + + protected function getFunctionRule(string $ruleId): mixed + { + $rule = $this->client->call(Client::METHOD_GET, '/proxy/rules/' . $ruleId, array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + return $rule; + } } diff --git a/tests/e2e/Services/Functions/FunctionsCustomServerTest.php b/tests/e2e/Services/Functions/FunctionsCustomServerTest.php index f5846af959..ee15a1d9e1 100644 --- a/tests/e2e/Services/Functions/FunctionsCustomServerTest.php +++ b/tests/e2e/Services/Functions/FunctionsCustomServerTest.php @@ -14,6 +14,7 @@ use Utopia\Database\Helpers\ID; use Utopia\Database\Helpers\Role; use Utopia\Database\Query; use Utopia\Database\Validator\Datetime as DatetimeValidator; +use Utopia\System\System; class FunctionsCustomServerTest extends Scope { @@ -392,6 +393,10 @@ class FunctionsCustomServerTest extends Scope $functionId = $function['body']['$id'] ?? ''; + $domain = ID::unique() . '.' . System::getEnv('_APP_DOMAIN_FUNCTIONS', ''); + $rule = $this->createFunctionRule($functionId, $domain); + $this->assertEquals(201, $rule['headers']['status-code']); + $deployment = $this->createTemplateDeployment( $functionId, [ @@ -407,6 +412,17 @@ class FunctionsCustomServerTest extends Scope $this->assertEquals(202, $deployment['headers']['status-code']); $this->assertNotEmpty($deployment['body']['$id']); + $rule = $this->getFunctionRule($rule['body']['$id']); + $this->assertEquals(200, $rule['headers']['status-code']); + $this->assertEquals($deployment['body']['$id'], $rule['body']['deploymentId']); + + $proxyClient = new Client(); + $proxyClient->setEndpoint('http://' . $domain); + + $response = $proxyClient->call(Client::METHOD_GET, '/'); + $this->assertEquals(400, $response['headers']['status-code']); + $this->assertStringContainsString("Deployment is still building", $response['body']); + $deployment = $this->getDeployment($functionId, $deployment['body']['$id']); $this->assertEquals(200, $deployment['headers']['status-code']); $this->assertEquals(0, $deployment['body']['sourceSize']); @@ -524,6 +540,10 @@ class FunctionsCustomServerTest extends Scope */ $functionId = $data['functionId']; + $domain = ID::unique() . '.' . System::getEnv('_APP_DOMAIN_FUNCTIONS', ''); + $rule = $this->createFunctionRule($functionId, $domain); + $this->assertEquals(201, $rule['headers']['status-code']); + $deployment = $this->createDeployment($functionId, [ 'code' => $this->packageFunction('basic'), 'activate' => true @@ -535,6 +555,17 @@ class FunctionsCustomServerTest extends Scope $this->assertEquals(true, (new DatetimeValidator())->isValid($deployment['body']['$createdAt'])); $this->assertEquals('index.js', $deployment['body']['entrypoint']); + $rule = $this->getFunctionRule($rule['body']['$id']); + $this->assertEquals(200, $rule['headers']['status-code']); + $this->assertEquals($deployment['body']['$id'], $rule['body']['deploymentId']); + + $proxyClient = new Client(); + $proxyClient->setEndpoint('http://' . $domain); + + $response = $proxyClient->call(Client::METHOD_GET, '/'); + $this->assertEquals(400, $response['headers']['status-code']); + $this->assertStringContainsString("Deployment is still building", $response['body']); + $deploymentIdActive = $deployment['body']['$id'] ?? ''; $this->assertEventually(function () use ($functionId, $deploymentIdActive) { diff --git a/tests/e2e/Services/Sites/SitesBase.php b/tests/e2e/Services/Sites/SitesBase.php index 3f82a42dca..f0a3ffb6dc 100644 --- a/tests/e2e/Services/Sites/SitesBase.php +++ b/tests/e2e/Services/Sites/SitesBase.php @@ -451,7 +451,7 @@ trait SitesBase return $specifications; } - protected function createRule(string $siteId, string $domain): mixed + protected function createSiteRule(string $siteId, string $domain): mixed { $rule = $this->client->call(Client::METHOD_POST, '/proxy/rules/site', array_merge([ 'content-type' => 'application/json', @@ -464,7 +464,7 @@ trait SitesBase return $rule; } - protected function getRule(string $ruleId): mixed + protected function getSiteRule(string $ruleId): mixed { $rule = $this->client->call(Client::METHOD_GET, '/proxy/rules/' . $ruleId, array_merge([ 'content-type' => 'application/json', diff --git a/tests/e2e/Services/Sites/SitesCustomServerTest.php b/tests/e2e/Services/Sites/SitesCustomServerTest.php index df29231562..3784747c73 100644 --- a/tests/e2e/Services/Sites/SitesCustomServerTest.php +++ b/tests/e2e/Services/Sites/SitesCustomServerTest.php @@ -861,7 +861,8 @@ class SitesCustomServerTest extends Scope $this->assertNotNull($siteId); - $rule = $this->createRule($siteId, \uniqid() . '.myapp.com'); + $domain = ID::unique() . '.' . System::getEnv('_APP_DOMAIN_SITES', ''); + $rule = $this->createSiteRule($siteId, $domain); $this->assertEquals(201, $rule['headers']['status-code']); $deployment = $this->createDeployment($siteId, [ @@ -875,10 +876,17 @@ class SitesCustomServerTest extends Scope $this->assertEquals('waiting', $deployment['body']['status']); $this->assertEquals(true, (new DatetimeValidator())->isValid($deployment['body']['$createdAt'])); - $rule = $this->getRule($rule['body']['$id']); + $rule = $this->getSiteRule($rule['body']['$id']); $this->assertEquals(200, $rule['headers']['status-code']); $this->assertEquals($deployment['body']['$id'], $rule['body']['deploymentId']); + $proxyClient = new Client(); + $proxyClient->setEndpoint('http://' . $domain); + + $response = $proxyClient->call(Client::METHOD_GET, '/'); + $this->assertEquals(400, $response['headers']['status-code']); + $this->assertStringContainsString("Deployment is still building", $response['body']); + $deploymentIdActive = $deployment['body']['$id'] ?? ''; $this->assertEventually(function () use ($siteId, $deploymentIdActive) { @@ -1548,7 +1556,7 @@ class SitesCustomServerTest extends Scope $this->cleanupSite($siteId); } - #[Retry(count: 3)] + // #[Retry(count: 3)] public function testSiteTemplate(): void { $template = $this->getTemplate('playground-for-astro'); @@ -1570,7 +1578,8 @@ class SitesCustomServerTest extends Scope $this->assertNotEmpty($siteId); - $rule = $this->createRule($siteId, \uniqid() . '.myapp.com'); + $domain = ID::unique() . '.' . System::getEnv('_APP_DOMAIN_SITES', ''); + $rule = $this->createSiteRule($siteId, $domain); $this->assertEquals(201, $rule['headers']['status-code']); $deployment = $this->createTemplateDeployment($siteId, [ @@ -1584,10 +1593,17 @@ class SitesCustomServerTest extends Scope $this->assertEquals(202, $deployment['headers']['status-code']); $this->assertNotEmpty($deployment['body']['$id']); - $rule = $this->getRule($rule['body']['$id']); + $rule = $this->getSiteRule($rule['body']['$id']); $this->assertEquals(200, $rule['headers']['status-code']); $this->assertEquals($deployment['body']['$id'], $rule['body']['deploymentId']); + $proxyClient = new Client(); + $proxyClient->setEndpoint('http://' . $domain); + + $response = $proxyClient->call(Client::METHOD_GET, '/'); + $this->assertEquals(400, $response['headers']['status-code']); + $this->assertStringContainsString("Deployment is still building", $response['body']); + $deployment = $this->getDeployment($siteId, $deployment['body']['$id']); $this->assertEquals(200, $deployment['headers']['status-code']); $this->assertEquals(0, $deployment['body']['sourceSize']); @@ -1599,10 +1615,6 @@ class SitesCustomServerTest extends Scope $this->assertNotEmpty($site['body']['deploymentId']); }, 50000, 500); - $domain = $this->setupSiteDomain($siteId); - $proxyClient = new Client(); - $proxyClient->setEndpoint('http://' . $domain); - $response = $proxyClient->call(Client::METHOD_GET, '/'); $this->assertEquals(200, $response['headers']['status-code']); From 73f5b4227d2c12da741ba0a9ff0f9e074aea3b1d Mon Sep 17 00:00:00 2001 From: Hemachandar Date: Mon, 17 Nov 2025 14:58:04 +0530 Subject: [PATCH 06/22] nit --- src/Appwrite/Platform/Modules/Compute/Base.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Appwrite/Platform/Modules/Compute/Base.php b/src/Appwrite/Platform/Modules/Compute/Base.php index 02650b9b85..f88aec9101 100644 --- a/src/Appwrite/Platform/Modules/Compute/Base.php +++ b/src/Appwrite/Platform/Modules/Compute/Base.php @@ -346,7 +346,7 @@ class Base extends Action * In case of first deployment, deployment ID will be empty in the rules, so we need to update it here. * * @param \Utopia\Database\Document $project - * @param \Utopia\Database\Document $site + * @param \Utopia\Database\Document $resource * @param \Utopia\Database\Document $deployment * @param \Utopia\Database\Database $dbForPlatform * @return void From 0718f9040525912a97c4e8adb64610b9bd9d3378 Mon Sep 17 00:00:00 2001 From: Hemachandar Date: Mon, 17 Nov 2025 15:51:36 +0530 Subject: [PATCH 07/22] test failures due to merge --- .../Functions/FunctionsCustomServerTest.php | 14 +++--- .../Services/Sites/SitesCustomServerTest.php | 44 +++++++++---------- 2 files changed, 28 insertions(+), 30 deletions(-) diff --git a/tests/e2e/Services/Functions/FunctionsCustomServerTest.php b/tests/e2e/Services/Functions/FunctionsCustomServerTest.php index 71c9826f3f..b49d9ba7e0 100644 --- a/tests/e2e/Services/Functions/FunctionsCustomServerTest.php +++ b/tests/e2e/Services/Functions/FunctionsCustomServerTest.php @@ -423,6 +423,7 @@ class FunctionsCustomServerTest extends Scope $response = $proxyClient->call(Client::METHOD_GET, '/'); $this->assertEquals(400, $response['headers']['status-code']); $this->assertStringContainsString("Deployment is still building", $response['body']); + $this->assertStringContainsString("The page will update after the build completes.", $response['body']); $deployment = $this->getDeployment($functionId, $deployment['body']['$id']); $this->assertEquals(200, $deployment['headers']['status-code']); @@ -703,6 +704,7 @@ class FunctionsCustomServerTest extends Scope $response = $proxyClient->call(Client::METHOD_GET, '/'); $this->assertEquals(400, $response['headers']['status-code']); $this->assertStringContainsString("Deployment is still building", $response['body']); + $this->assertStringContainsString('The page will update after the build completes.', $response['body']); $deploymentIdActive = $deployment['body']['$id'] ?? ''; @@ -2435,9 +2437,9 @@ class FunctionsCustomServerTest extends Scope 'x-appwrite-project' => $this->getProject()['$id'] ])); - $this->assertEquals(404, $response['headers']['status-code']); - $this->assertStringContainsString('No active deployments', $response['body']); - $this->assertStringContainsString('View deployments', $response['body']); + $this->assertEquals(400, $response['headers']['status-code']); + $this->assertStringContainsString('Deployment is still building', $response['body']); + $this->assertStringContainsString('The page will update after the build completes.', $response['body']); // canceled deployment $deployment = $this->createDeployment($functionId, [ @@ -2457,9 +2459,9 @@ class FunctionsCustomServerTest extends Scope 'x-appwrite-project' => $this->getProject()['$id'] ])); - $this->assertEquals(404, $response['headers']['status-code']); - $this->assertStringContainsString('No active deployments', $response['body']); - $this->assertStringContainsString('View deployments', $response['body']); + $this->assertEquals(400, $response['headers']['status-code']); + $this->assertStringContainsString('Deployment build canceled', $response['body']); + $this->assertStringContainsString('This build was canceled and won\'t be deployed.', $response['body']); $this->cleanupFunction($functionId); } diff --git a/tests/e2e/Services/Sites/SitesCustomServerTest.php b/tests/e2e/Services/Sites/SitesCustomServerTest.php index a48bdf7fc8..4b67169c03 100644 --- a/tests/e2e/Services/Sites/SitesCustomServerTest.php +++ b/tests/e2e/Services/Sites/SitesCustomServerTest.php @@ -886,6 +886,7 @@ class SitesCustomServerTest extends Scope $response = $proxyClient->call(Client::METHOD_GET, '/'); $this->assertEquals(400, $response['headers']['status-code']); $this->assertStringContainsString("Deployment is still building", $response['body']); + $this->assertStringContainsString('The page will update after the build completes.', $response['body']); $deploymentIdActive = $deployment['body']['$id'] ?? ''; @@ -1556,7 +1557,7 @@ class SitesCustomServerTest extends Scope $this->cleanupSite($siteId); } - // #[Retry(count: 3)] + #[Retry(count: 3)] public function testSiteTemplate(): void { $template = $this->getTemplate('playground-for-astro'); @@ -1594,6 +1595,18 @@ class SitesCustomServerTest extends Scope $this->assertEquals(202, $deployment['headers']['status-code']); $this->assertNotEmpty($deployment['body']['$id']); + $rule = $this->getSiteRule($rule['body']['$id']); + $this->assertEquals(200, $rule['headers']['status-code']); + $this->assertEquals($deployment['body']['$id'], $rule['body']['deploymentId']); + + $proxyClient = new Client(); + $proxyClient->setEndpoint('http://' . $domain); + + $response = $proxyClient->call(Client::METHOD_GET, '/'); + $this->assertEquals(400, $response['headers']['status-code']); + $this->assertStringContainsString("Deployment is still building", $response['body']); + $this->assertStringContainsString('The page will update after the build completes.', $response['body']); + $deployment = $this->getDeployment($siteId, $deployment['body']['$id']); $this->assertEquals(200, $deployment['headers']['status-code']); $this->assertEquals(0, $deployment['body']['sourceSize']); @@ -1605,10 +1618,6 @@ class SitesCustomServerTest extends Scope $this->assertNotEmpty($site['body']['deploymentId']); }, 50000, 500); - $domain = $this->setupSiteDomain($siteId); - $proxyClient = new Client(); - $proxyClient->setEndpoint('http://' . $domain); - $response = $proxyClient->call(Client::METHOD_GET, '/'); $this->assertEquals(200, $response['headers']['status-code']); @@ -1743,17 +1752,6 @@ class SitesCustomServerTest extends Scope $this->assertEquals(202, $deployment['headers']['status-code']); $this->assertNotEmpty($deployment['body']['$id']); - $rule = $this->getSiteRule($rule['body']['$id']); - $this->assertEquals(200, $rule['headers']['status-code']); - $this->assertEquals($deployment['body']['$id'], $rule['body']['deploymentId']); - - $proxyClient = new Client(); - $proxyClient->setEndpoint('http://' . $domain); - - $response = $proxyClient->call(Client::METHOD_GET, '/'); - $this->assertEquals(400, $response['headers']['status-code']); - $this->assertStringContainsString("Deployment is still building", $response['body']); - $deployment = $this->getDeployment($siteId, $deployment['body']['$id']); $this->assertEquals(200, $deployment['headers']['status-code']); $this->assertEquals(0, $deployment['body']['sourceSize']); @@ -1765,6 +1763,10 @@ class SitesCustomServerTest extends Scope $this->assertNotEmpty($site['body']['deploymentId']); }, 50000, 500); + $domain = $this->setupSiteDomain($siteId); + $proxyClient = new Client(); + $proxyClient->setEndpoint('http://' . $domain); + $response = $proxyClient->call(Client::METHOD_GET, '/'); $this->assertEquals(200, $response['headers']['status-code']); @@ -2610,7 +2612,8 @@ class SitesCustomServerTest extends Scope }, 100000, 500); $response = $proxyClient->call(Client::METHOD_GET, '/'); - $this->assertStringContainsString('This page is empty, activate a deployment to make it live.', $response['body']); + $this->assertEquals(400, $response['headers']['status-code']); + $this->assertStringContainsString('Deployment build failed', $response['body']); $this->cleanupSite($siteId); } @@ -2740,13 +2743,6 @@ class SitesCustomServerTest extends Scope $this->assertStringContainsString("Deployment build canceled", $response['body']); $this->assertStringContainsString("View deployments", $response['body']); - // check site domain for no active deployments - $proxyClient->setEndpoint('http://' . $domain); - $response = $proxyClient->call(Client::METHOD_GET, '/'); - $this->assertEquals(404, $response['headers']['status-code']); - $this->assertStringContainsString('No active deployments', $response['body']); - $this->assertStringContainsString('View deployments', $response['body']); - $deployment = $this->createDeployment($siteId, [ 'code' => $this->packageSite('static-single-file'), 'activate' => 'true' From d2000ee9d8c394d84a1147ae06124db9b605f6a3 Mon Sep 17 00:00:00 2001 From: Hemachandar Date: Mon, 17 Nov 2025 17:58:15 +0530 Subject: [PATCH 08/22] test fix --- tests/e2e/Services/Functions/FunctionsCustomServerTest.php | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/e2e/Services/Functions/FunctionsCustomServerTest.php b/tests/e2e/Services/Functions/FunctionsCustomServerTest.php index b49d9ba7e0..cd4a130d30 100644 --- a/tests/e2e/Services/Functions/FunctionsCustomServerTest.php +++ b/tests/e2e/Services/Functions/FunctionsCustomServerTest.php @@ -435,6 +435,7 @@ class FunctionsCustomServerTest extends Scope }, 50000, 500); // Verify deployment sizes + $deployment = $this->getDeployment($functionId, $deploymentId); $this->assertGreaterThan(0, $deployment['body']['sourceSize']); $this->assertGreaterThan(0, $deployment['body']['buildSize']); $totalSize = $deployment['body']['sourceSize'] + $deployment['body']['buildSize']; From 3d69998f9caafbf86fceb8b1440d552ae98e45e3 Mon Sep 17 00:00:00 2001 From: Hemachandar Date: Mon, 17 Nov 2025 18:41:28 +0530 Subject: [PATCH 09/22] another test fix --- .../Services/Functions/FunctionsCustomServerTest.php | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/tests/e2e/Services/Functions/FunctionsCustomServerTest.php b/tests/e2e/Services/Functions/FunctionsCustomServerTest.php index cd4a130d30..355fd01c8e 100644 --- a/tests/e2e/Services/Functions/FunctionsCustomServerTest.php +++ b/tests/e2e/Services/Functions/FunctionsCustomServerTest.php @@ -2443,6 +2443,17 @@ class FunctionsCustomServerTest extends Scope $this->assertStringContainsString('The page will update after the build completes.', $response['body']); // canceled deployment + $functionId = $this->setupFunction([ + 'functionId' => ID::unique(), + 'name' => 'Test Error Pages', + 'runtime' => 'node-22', + 'entrypoint' => 'index.js', + 'timeout' => 15, + 'commands' => 'cd non-existing-directory', + 'execute' => ['any'] + ]); + $domain = $this->setupFunctionDomain($functionId); + $proxyClient->setEndpoint('http://' . $domain); $deployment = $this->createDeployment($functionId, [ 'code' => $this->packageFunction('basic'), 'activate' => true From 9f7e3146a4dbcea54fe74a82938551a35f70bf71 Mon Sep 17 00:00:00 2001 From: Hemachandar Date: Tue, 18 Nov 2025 20:08:43 +0530 Subject: [PATCH 10/22] feedback --- app/controllers/api/vcs.php | 3 +++ src/Appwrite/Platform/Modules/Compute/Base.php | 5 ++++- .../Platform/Modules/Functions/Http/Functions/Create.php | 1 + tests/e2e/Services/Functions/FunctionsCustomServerTest.php | 6 ++++++ tests/e2e/Services/Sites/SitesCustomServerTest.php | 6 ++++++ 5 files changed, 20 insertions(+), 1 deletion(-) diff --git a/app/controllers/api/vcs.php b/app/controllers/api/vcs.php index 2ac6cd0b26..56bc72e708 100644 --- a/app/controllers/api/vcs.php +++ b/app/controllers/api/vcs.php @@ -5,6 +5,7 @@ use Appwrite\Event\Build; use Appwrite\Event\Delete; use Appwrite\Extend\Exception; use Appwrite\Network\Validator\Redirect; +use Appwrite\Platform\Modules\Compute\Base as ComputeBase; use Appwrite\SDK\AuthType; use Appwrite\SDK\ContentType; use Appwrite\SDK\Method; @@ -431,6 +432,8 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId } } + ComputeBase::updateEmptyManualRule($project, $resource, $deployment, $dbForPlatform); + if ($resource->getCollection() === 'sites' && !empty($latestCommentId) && !empty($previewRuleId)) { $retries = 0; $lockAcquired = false; diff --git a/src/Appwrite/Platform/Modules/Compute/Base.php b/src/Appwrite/Platform/Modules/Compute/Base.php index f88aec9101..cc9b403925 100644 --- a/src/Appwrite/Platform/Modules/Compute/Base.php +++ b/src/Appwrite/Platform/Modules/Compute/Base.php @@ -351,11 +351,14 @@ class Base extends Action * @param \Utopia\Database\Database $dbForPlatform * @return void */ - public function updateEmptyManualRule(Document $project, Document $resource, Document $deployment, Database $dbForPlatform) + public static function updateEmptyManualRule(Document $project, Document $resource, Document $deployment, Database $dbForPlatform) { + $resourceType = $resource->getCollection() === 'sites' ? 'site' : 'function'; + $queries = [ Query::equal('projectInternalId', [$project->getSequence()]), Query::equal('deploymentResourceInternalId', [$resource->getSequence()]), + Query::equal('deploymentResourceType', [$resourceType]), Query::equal('deploymentId', ['']), Query::equal('type', ['deployment']), Query::equal('trigger', ['manual']), diff --git a/src/Appwrite/Platform/Modules/Functions/Http/Functions/Create.php b/src/Appwrite/Platform/Modules/Functions/Http/Functions/Create.php index b4300c4913..b22710253e 100644 --- a/src/Appwrite/Platform/Modules/Functions/Http/Functions/Create.php +++ b/src/Appwrite/Platform/Modules/Functions/Http/Functions/Create.php @@ -305,6 +305,7 @@ class Create extends Base $template = new Document(); $installation = $dbForPlatform->getDocument('installations', $function->getAttribute('installationId')); + // TODO: Is this still needed? Can this be removed? $deployment = $this->redeployVcsFunction( request: $request, function: $function, diff --git a/tests/e2e/Services/Functions/FunctionsCustomServerTest.php b/tests/e2e/Services/Functions/FunctionsCustomServerTest.php index 355fd01c8e..74b6ed3e8d 100644 --- a/tests/e2e/Services/Functions/FunctionsCustomServerTest.php +++ b/tests/e2e/Services/Functions/FunctionsCustomServerTest.php @@ -2426,6 +2426,12 @@ class FunctionsCustomServerTest extends Scope $domain = $this->setupFunctionDomain($functionId); $proxyClient->setEndpoint('http://' . $domain); + $response = $proxyClient->call(Client::METHOD_GET, '/'); + + $this->assertEquals(404, $response['headers']['status-code']); + $this->assertStringContainsString('No active deployments', $response['body']); + $this->assertStringContainsString('View deployments', $response['body']); + $deployment = $this->createDeployment($functionId, [ 'code' => $this->packageFunction('basic'), 'activate' => true diff --git a/tests/e2e/Services/Sites/SitesCustomServerTest.php b/tests/e2e/Services/Sites/SitesCustomServerTest.php index 4b67169c03..a3b3680b5c 100644 --- a/tests/e2e/Services/Sites/SitesCustomServerTest.php +++ b/tests/e2e/Services/Sites/SitesCustomServerTest.php @@ -2708,6 +2708,12 @@ class SitesCustomServerTest extends Scope $this->assertNotEmpty($siteId); $domain = $this->setupSiteDomain($siteId); + $proxyClient->setEndpoint('http://' . $domain); + $response = $proxyClient->call(Client::METHOD_GET, '/'); + + $this->assertEquals(404, $response['headers']['status-code']); + $this->assertStringContainsString('No active deployments', $response['body']); + $this->assertStringContainsString('View deployments', $response['body']); // test canceled deployment error page $deployment = $this->createDeployment($siteId, [ From 9b68b5474330530b898b6d145d083487327fcd38 Mon Sep 17 00:00:00 2001 From: Darshan Date: Thu, 20 Nov 2025 20:18:54 +0530 Subject: [PATCH 11/22] bump: browser version for tests. --- app/views/install/compose.phtml | 2 +- docker-compose.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/install/compose.phtml b/app/views/install/compose.phtml index 9a2669fb5c..16e34b58ea 100644 --- a/app/views/install/compose.phtml +++ b/app/views/install/compose.phtml @@ -859,7 +859,7 @@ $image = $this->getParam('image', ''); - _APP_ASSISTANT_OPENAI_API_KEY appwrite-browser: - image: appwrite/browser:0.3.1 + image: appwrite/browser:0.3.2.RC.1 container_name: appwrite-browser <<: *x-logging restart: unless-stopped diff --git a/docker-compose.yml b/docker-compose.yml index ede1011af3..5df71f16f1 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -958,7 +958,7 @@ services: appwrite-browser: container_name: appwrite-browser - image: appwrite/browser:0.3.1 + image: appwrite/browser:0.3.2.RC.1 networks: - appwrite From 3347573f176a42abf92c3b316f6c045dffd06fe7 Mon Sep 17 00:00:00 2001 From: Darshan Date: Fri, 21 Nov 2025 12:09:33 +0530 Subject: [PATCH 12/22] bump: browser version for tests. --- app/views/install/compose.phtml | 2 +- docker-compose.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/install/compose.phtml b/app/views/install/compose.phtml index 16e34b58ea..e3ca65fa97 100644 --- a/app/views/install/compose.phtml +++ b/app/views/install/compose.phtml @@ -859,7 +859,7 @@ $image = $this->getParam('image', ''); - _APP_ASSISTANT_OPENAI_API_KEY appwrite-browser: - image: appwrite/browser:0.3.2.RC.1 + image: appwrite/browser:0.3.2 container_name: appwrite-browser <<: *x-logging restart: unless-stopped diff --git a/docker-compose.yml b/docker-compose.yml index 5df71f16f1..32e81ea5e3 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -958,7 +958,7 @@ services: appwrite-browser: container_name: appwrite-browser - image: appwrite/browser:0.3.2.RC.1 + image: appwrite/browser:0.3.2 networks: - appwrite From e2758691af11cb227312d85be8264176834ca387 Mon Sep 17 00:00:00 2001 From: Harsh Mahajan Date: Fri, 21 Nov 2025 06:49:09 +0000 Subject: [PATCH 13/22] removed singular folder --- .../Http/Account/MFA/Challenge/Create.php | 330 ------------------ .../Http/Account/MFA/Challenge/Update.php | 161 --------- 2 files changed, 491 deletions(-) delete mode 100644 src/Appwrite/Platform/Modules/Account/Http/Account/MFA/Challenge/Create.php delete mode 100644 src/Appwrite/Platform/Modules/Account/Http/Account/MFA/Challenge/Update.php diff --git a/src/Appwrite/Platform/Modules/Account/Http/Account/MFA/Challenge/Create.php b/src/Appwrite/Platform/Modules/Account/Http/Account/MFA/Challenge/Create.php deleted file mode 100644 index 3811ee6301..0000000000 --- a/src/Appwrite/Platform/Modules/Account/Http/Account/MFA/Challenge/Create.php +++ /dev/null @@ -1,330 +0,0 @@ -setHttpMethod(Action::HTTP_REQUEST_METHOD_POST) - ->setHttpPath('/v1/account/mfa/challenges') - ->httpAlias('/v1/account/mfa/challenge') - ->desc('Create MFA challenge') - ->groups(['api', 'account', 'mfa']) - ->label('scope', 'account') - ->label('event', 'users.[userId].challenges.[challengeId].create') - ->label('audits.event', 'challenge.create') - ->label('audits.resource', 'user/{response.userId}') - ->label('audits.userId', '{response.userId}') - ->label('sdk', [ - new Method( - namespace: 'account', - group: 'mfa', - name: 'createMfaChallenge', - description: '/docs/references/account/create-mfa-challenge.md', - auth: [], - responses: [ - new SDKResponse( - code: Response::STATUS_CODE_CREATED, - model: Response::MODEL_MFA_CHALLENGE, - ) - ], - contentType: ContentType::JSON, - deprecated: new Deprecated( - since: '1.8.0', - replaceWith: 'account.createMFAChallenge', - ), - ), - new Method( - namespace: 'account', - group: 'mfa', - name: 'createMFAChallenge', - description: '/docs/references/account/create-mfa-challenge.md', - auth: [], - responses: [ - new SDKResponse( - code: Response::STATUS_CODE_CREATED, - model: Response::MODEL_MFA_CHALLENGE, - ) - ], - contentType: ContentType::JSON - ) - ]) - ->label('abuse-limit', 10) - ->label('abuse-key', 'url:{url},userId:{userId}') - ->param('factor', '', new WhiteList([Type::EMAIL, Type::PHONE, Type::TOTP, Type::RECOVERY_CODE]), 'Factor used for verification. Must be one of following: `' . Type::EMAIL . '`, `' . Type::PHONE . '`, `' . Type::TOTP . '`, `' . Type::RECOVERY_CODE . '`.') - ->inject('response') - ->inject('dbForProject') - ->inject('user') - ->inject('locale') - ->inject('project') - ->inject('request') - ->inject('queueForEvents') - ->inject('queueForMessaging') - ->inject('queueForMails') - ->inject('timelimit') - ->inject('queueForStatsUsage') - ->inject('plan') - ->callback($this->action(...)); - } - - public function action( - string $factor, - Response $response, - Database $dbForProject, - Document $user, - Locale $locale, - Document $project, - Request $request, - Event $queueForEvents, - Messaging $queueForMessaging, - Mail $queueForMails, - callable $timelimit, - StatsUsage $queueForStatsUsage, - array $plan - ): void { - $expire = DateTime::formatTz(DateTime::addSeconds(new \DateTime(), Auth::TOKEN_EXPIRATION_CONFIRM)); - $code = Auth::codeGenerator(); - $challenge = new Document([ - 'userId' => $user->getId(), - 'userInternalId' => $user->getSequence(), - 'type' => $factor, - 'token' => Auth::tokenGenerator(), - 'code' => $code, - 'expire' => $expire, - '$permissions' => [ - Permission::read(Role::user($user->getId())), - Permission::update(Role::user($user->getId())), - Permission::delete(Role::user($user->getId())), - ], - ]); - - $challenge = $dbForProject->createDocument('challenges', $challenge); - - $templatesPath = \dirname(__DIR__, 7) . '/app/config/locale/templates'; - - switch ($factor) { - case Type::PHONE: - if (empty(System::getEnv('_APP_SMS_PROVIDER'))) { - throw new Exception(Exception::GENERAL_PHONE_DISABLED, 'Phone provider not configured'); - } - if (empty($user->getAttribute('phone'))) { - throw new Exception(Exception::USER_PHONE_NOT_FOUND); - } - if (!$user->getAttribute('phoneVerification')) { - throw new Exception(Exception::USER_PHONE_NOT_VERIFIED); - } - - $message = Template::fromFile($templatesPath . '/sms-base.tpl'); - - $customTemplate = $project->getAttribute('templates', [])['sms.mfaChallenge-' . $locale->default] ?? []; - if (!empty($customTemplate)) { - $message = $customTemplate['message'] ?? $message; - } - - $messageContent = Template::fromString($locale->getText("sms.verification.body")); - $messageContent - ->setParam('{{project}}', $project->getAttribute('name')) - ->setParam('{{secret}}', $code); - $messageContent = \strip_tags($messageContent->render()); - $message = $message->setParam('{{token}}', $messageContent); - - $message = $message->render(); - - $phone = $user->getAttribute('phone'); - $queueForMessaging - ->setType(MESSAGE_SEND_TYPE_INTERNAL) - ->setMessage(new Document([ - '$id' => $challenge->getId(), - 'data' => [ - 'content' => $code, - ], - ])) - ->setRecipients([$phone]) - ->setProviderType(MESSAGE_TYPE_SMS); - - if (isset($plan['authPhone'])) { - $timelimit = $timelimit('organization:{organizationId}', $plan['authPhone'], 30 * 24 * 60 * 60); // 30 days - $timelimit - ->setParam('{organizationId}', $project->getAttribute('teamId')); - - $abuse = new Abuse($timelimit); - if ($abuse->check() && System::getEnv('_APP_OPTIONS_ABUSE', 'enabled') === 'enabled') { - $helper = PhoneNumberUtil::getInstance(); - $countryCode = $helper->parse($phone)->getCountryCode(); - - if (!empty($countryCode)) { - $queueForStatsUsage - ->addMetric(str_replace('{countryCode}', $countryCode, METRIC_AUTH_METHOD_PHONE_COUNTRY_CODE), 1); - } - } - $queueForStatsUsage - ->addMetric(METRIC_AUTH_METHOD_PHONE, 1) - ->setProject($project) - ->trigger(); - } - break; - case Type::EMAIL: - if (empty(System::getEnv('_APP_SMTP_HOST'))) { - throw new Exception(Exception::GENERAL_SMTP_DISABLED, 'SMTP disabled'); - } - if (empty($user->getAttribute('email'))) { - throw new Exception(Exception::USER_EMAIL_NOT_FOUND); - } - if (!$user->getAttribute('emailVerification')) { - throw new Exception(Exception::USER_EMAIL_NOT_VERIFIED); - } - - $subject = $locale->getText("emails.mfaChallenge.subject"); - $preview = $locale->getText("emails.mfaChallenge.preview"); - $heading = $locale->getText("emails.mfaChallenge.heading"); - - $customTemplate = $project->getAttribute('templates', [])['email.mfaChallenge-' . $locale->default] ?? []; - $smtpBaseTemplate = $project->getAttribute('smtpBaseTemplate', 'email-base'); - - $validator = new FileName(); - if (!$validator->isValid($smtpBaseTemplate)) { - throw new Exception(Exception::GENERAL_BAD_REQUEST, 'Invalid template path'); - } - - $bodyTemplate = $templatesPath . '/' . $smtpBaseTemplate . '.tpl'; - - $detector = new Detector($request->getUserAgent('UNKNOWN')); - $agentOs = $detector->getOS(); - $agentClient = $detector->getClient(); - $agentDevice = $detector->getDevice(); - - $message = Template::fromFile($templatesPath . '/email-mfa-challenge.tpl'); - $message - ->setParam('{{hello}}', $locale->getText("emails.mfaChallenge.hello")) - ->setParam('{{description}}', $locale->getText("emails.mfaChallenge.description")) - ->setParam('{{clientInfo}}', $locale->getText("emails.mfaChallenge.clientInfo")) - ->setParam('{{thanks}}', $locale->getText("emails.mfaChallenge.thanks")) - ->setParam('{{signature}}', $locale->getText("emails.mfaChallenge.signature")); - - $body = $message->render(); - - $smtp = $project->getAttribute('smtp', []); - $smtpEnabled = $smtp['enabled'] ?? false; - - $senderEmail = System::getEnv('_APP_SYSTEM_EMAIL_ADDRESS', APP_EMAIL_TEAM); - $senderName = System::getEnv('_APP_SYSTEM_EMAIL_NAME', APP_NAME . ' Server'); - $replyTo = ""; - - if ($smtpEnabled) { - if (!empty($smtp['senderEmail'])) { - $senderEmail = $smtp['senderEmail']; - } - if (!empty($smtp['senderName'])) { - $senderName = $smtp['senderName']; - } - if (!empty($smtp['replyTo'])) { - $replyTo = $smtp['replyTo']; - } - - $queueForMails - ->setSmtpHost($smtp['host'] ?? '') - ->setSmtpPort($smtp['port'] ?? '') - ->setSmtpUsername($smtp['username'] ?? '') - ->setSmtpPassword($smtp['password'] ?? '') - ->setSmtpSecure($smtp['secure'] ?? ''); - - if (!empty($customTemplate)) { - if (!empty($customTemplate['senderEmail'])) { - $senderEmail = $customTemplate['senderEmail']; - } - if (!empty($customTemplate['senderName'])) { - $senderName = $customTemplate['senderName']; - } - if (!empty($customTemplate['replyTo'])) { - $replyTo = $customTemplate['replyTo']; - } - - $body = $customTemplate['message'] ?? ''; - $subject = $customTemplate['subject'] ?? $subject; - } - - $queueForMails - ->setSmtpReplyTo($replyTo) - ->setSmtpSenderEmail($senderEmail) - ->setSmtpSenderName($senderName); - } - - $emailVariables = [ - 'heading' => $heading, - 'direction' => $locale->getText('settings.direction'), - 'user' => $user->getAttribute('name'), - 'project' => $project->getAttribute('name'), - 'otp' => $code, - 'agentDevice' => $agentDevice['deviceBrand'] ?? $agentDevice['deviceBrand'] ?? 'UNKNOWN', - 'agentClient' => $agentClient['clientName'] ?? 'UNKNOWN', - 'agentOs' => $agentOs['osName'] ?? 'UNKNOWN', - ]; - - if ($smtpBaseTemplate === APP_BRANDED_EMAIL_BASE_TEMPLATE) { - $emailVariables = array_merge($emailVariables, [ - 'accentColor' => APP_EMAIL_ACCENT_COLOR, - 'logoUrl' => APP_EMAIL_LOGO_URL, - 'twitterUrl' => APP_SOCIAL_TWITTER, - 'discordUrl' => APP_SOCIAL_DISCORD, - 'githubUrl' => APP_SOCIAL_GITHUB_APPWRITE, - 'termsUrl' => APP_EMAIL_TERMS_URL, - 'privacyUrl' => APP_EMAIL_PRIVACY_URL, - ]); - } - - $queueForMails - ->setSubject($subject) - ->setPreview($preview) - ->setBody($body) - ->setBodyTemplate($bodyTemplate) - ->setVariables($emailVariables) - ->setRecipient($user->getAttribute('email')) - ->trigger(); - break; - } - - $queueForEvents - ->setParam('userId', $user->getId()) - ->setParam('challengeId', $challenge->getId()); - - $response->dynamic($challenge, Response::MODEL_MFA_CHALLENGE); - } -} diff --git a/src/Appwrite/Platform/Modules/Account/Http/Account/MFA/Challenge/Update.php b/src/Appwrite/Platform/Modules/Account/Http/Account/MFA/Challenge/Update.php deleted file mode 100644 index c0139d8c4f..0000000000 --- a/src/Appwrite/Platform/Modules/Account/Http/Account/MFA/Challenge/Update.php +++ /dev/null @@ -1,161 +0,0 @@ -setHttpMethod(Action::HTTP_REQUEST_METHOD_PUT) - ->setHttpPath('/v1/account/mfa/challenges') - ->httpAlias('/v1/account/mfa/challenge') - ->desc('Update MFA challenge (confirmation)') - ->groups(['api', 'account', 'mfa']) - ->label('scope', 'account') - ->label('event', 'users.[userId].sessions.[sessionId].create') - ->label('audits.event', 'challenges.update') - ->label('audits.resource', 'user/{response.userId}') - ->label('audits.userId', '{response.userId}') - ->label('sdk', [ - new Method( - namespace: 'account', - group: 'mfa', - name: 'updateMfaChallenge', - description: '/docs/references/account/update-mfa-challenge.md', - auth: [AuthType::SESSION, AuthType::JWT], - responses: [ - new SDKResponse( - code: Response::STATUS_CODE_OK, - model: Response::MODEL_SESSION, - ) - ], - contentType: ContentType::JSON, - deprecated: new Deprecated( - since: '1.8.0', - replaceWith: 'account.updateMFAChallenge', - ), - ), - new Method( - namespace: 'account', - group: 'mfa', - name: 'updateMFAChallenge', - description: '/docs/references/account/update-mfa-challenge.md', - auth: [AuthType::SESSION, AuthType::JWT], - responses: [ - new SDKResponse( - code: Response::STATUS_CODE_OK, - model: Response::MODEL_SESSION, - ) - ], - contentType: ContentType::JSON - ) - ]) - ->label('abuse-limit', 10) - ->label('abuse-key', 'url:{url},challengeId:{param-challengeId}') - ->param('challengeId', '', new Text(256), 'ID of the challenge.') - ->param('otp', '', new Text(256), 'Valid verification token.') - ->inject('project') - ->inject('response') - ->inject('user') - ->inject('session') - ->inject('dbForProject') - ->inject('queueForEvents') - ->callback($this->action(...)); - } - - public function action( - string $challengeId, - string $otp, - Document $project, - Response $response, - Document $user, - Document $session, - Database $dbForProject, - Event $queueForEvents - ): void { - $challenge = $dbForProject->getDocument('challenges', $challengeId); - - if ($challenge->isEmpty()) { - throw new Exception(Exception::USER_INVALID_TOKEN); - } - - $type = $challenge->getAttribute('type'); - - $recoveryCodeChallenge = function (Document $challenge, Document $user, string $otp) use ($dbForProject) { - if ( - $challenge->isSet('type') && - $challenge->getAttribute('type') === \strtolower(Type::RECOVERY_CODE) - ) { - $mfaRecoveryCodes = $user->getAttribute('mfaRecoveryCodes', []); - if (\in_array($otp, $mfaRecoveryCodes)) { - $mfaRecoveryCodes = \array_diff($mfaRecoveryCodes, [$otp]); - $mfaRecoveryCodes = \array_values($mfaRecoveryCodes); - $user->setAttribute('mfaRecoveryCodes', $mfaRecoveryCodes); - $dbForProject->updateDocument('users', $user->getId(), $user); - - return true; - } - - return false; - } - - return false; - }; - - $success = (match ($type) { - Type::TOTP => Challenge\TOTP::challenge($challenge, $user, $otp), - Type::PHONE => Challenge\Phone::challenge($challenge, $user, $otp), - Type::EMAIL => Challenge\Email::challenge($challenge, $user, $otp), - \strtolower(Type::RECOVERY_CODE) => $recoveryCodeChallenge($challenge, $user, $otp), - default => false - }); - - if (!$success) { - throw new Exception(Exception::USER_INVALID_TOKEN); - } - - $dbForProject->deleteDocument('challenges', $challengeId); - $dbForProject->purgeCachedDocument('users', $user->getId()); - - $factors = $session->getAttribute('factors', []); - $factors[] = $type; - $factors = \array_values(\array_unique($factors)); - - $session - ->setAttribute('factors', $factors) - ->setAttribute('mfaUpdatedAt', DateTime::now()); - - $dbForProject->updateDocument('sessions', $session->getId(), $session); - - $queueForEvents - ->setParam('userId', $user->getId()) - ->setParam('sessionId', $session->getId()); - - $response->dynamic($session, Response::MODEL_SESSION); - } -} From e5fa23ef6ce384a9ad7b4e055c3f23cc7879cc19 Mon Sep 17 00:00:00 2001 From: Darshan Date: Fri, 21 Nov 2025 20:05:31 +0530 Subject: [PATCH 14/22] add: 1:1 comparison tests. --- tests/e2e/Services/Avatars/AvatarsBase.php | 28 ++++ .../Services/Sites/SitesConsoleClientTest.php | 14 +- .../avatars/screenshot-example-com.png | Bin 0 -> 29457 bytes .../resources/sites/static-themed/index.html | 142 +++++++++++++++++- .../sites/static-themed/screenshot-dark.png | Bin 0 -> 40811 bytes .../sites/static-themed/screenshot-light.png | Bin 0 -> 40460 bytes 6 files changed, 170 insertions(+), 14 deletions(-) create mode 100644 tests/resources/avatars/screenshot-example-com.png create mode 100644 tests/resources/sites/static-themed/screenshot-dark.png create mode 100644 tests/resources/sites/static-themed/screenshot-light.png diff --git a/tests/e2e/Services/Avatars/AvatarsBase.php b/tests/e2e/Services/Avatars/AvatarsBase.php index cc626be99a..aca3af6dfa 100644 --- a/tests/e2e/Services/Avatars/AvatarsBase.php +++ b/tests/e2e/Services/Avatars/AvatarsBase.php @@ -1293,4 +1293,32 @@ trait AvatarsBase return []; } + + public function testGetScreenshotComparison(): array + { + /** + * Test screenshot comparison with stable domain (example.com) + * This test captures a screenshot of example.com and compares it + * against a reference image to ensure consistent rendering. + */ + $response = $this->client->call(Client::METHOD_GET, '/avatars/screenshots', [ + 'x-appwrite-project' => $this->getProject()['$id'], + ], [ + 'url' => 'https://example.com', + 'width' => 800, + 'height' => 600, + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertEquals('image/png', $response['headers']['content-type']); + $this->assertNotEmpty($response['body']); + + // Compare with reference screenshot + $referencePath = \realpath(__DIR__ . '/../../../resources/avatars'); + $referenceScreenshot = $referencePath . '/screenshot-example-com.png'; + $this->assertFileExists($referenceScreenshot, 'Reference example.com screenshot not found'); + $this->assertSamePixels($referenceScreenshot, $response['body']); + + return []; + } } diff --git a/tests/e2e/Services/Sites/SitesConsoleClientTest.php b/tests/e2e/Services/Sites/SitesConsoleClientTest.php index 227e36a50e..2b75402b25 100644 --- a/tests/e2e/Services/Sites/SitesConsoleClientTest.php +++ b/tests/e2e/Services/Sites/SitesConsoleClientTest.php @@ -74,8 +74,11 @@ class SitesConsoleClientTest extends Scope $this->assertGreaterThan(1, $file['headers']['content-length']); $this->assertEquals('image/png', $file['headers']['content-type']); - $screenshotHash = \md5($file['body']); - $this->assertNotEmpty($screenshotHash); + // Compare with reference screenshots + $referencePath = \realpath(__DIR__ . '/../../../resources/sites/static-themed'); + $referenceScreenshotLight = $referencePath . '/screenshot-light.png'; + $this->assertFileExists($referenceScreenshotLight, 'Reference light screenshot not found'); + $this->assertSamePixels($referenceScreenshotLight, $file['body']); $screenshotId = $deployment['body']['screenshotDark']; $file = $this->client->call(Client::METHOD_GET, "/storage/buckets/screenshots/files/$screenshotId/view?project=console", array_merge($this->getHeaders(), [ @@ -87,10 +90,9 @@ class SitesConsoleClientTest extends Scope $this->assertGreaterThan(1, $file['headers']['content-length']); $this->assertEquals('image/png', $file['headers']['content-type']); - $screenshotDarkHash = \md5($file['body']); - $this->assertNotEmpty($screenshotDarkHash); - - $this->assertNotEquals($screenshotDarkHash, $screenshotHash); + $referenceScreenshotDark = $referencePath . '/screenshot-dark.png'; + $this->assertFileExists($referenceScreenshotDark, 'Reference dark screenshot not found'); + $this->assertSamePixels($referenceScreenshotDark, $file['body']); $screenshotId = $deployment['body']['screenshotLight']; $file = $this->client->call(Client::METHOD_GET, "/storage/buckets/screenshots/files/$screenshotId/view?project=console"); diff --git a/tests/resources/avatars/screenshot-example-com.png b/tests/resources/avatars/screenshot-example-com.png new file mode 100644 index 0000000000000000000000000000000000000000..2f8af920e0821fd3687caef3aeedd64036fbb248 GIT binary patch literal 29457 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 Themed website - + +
+
+

Themed website

+

Adaptive light & dark mode showcase

+
+ +
+

About This Site

+

This is a test deployment showcasing automatic theme switching based on your system preferences.

+ Appwrite Sites +
+ +
+

Features

+
    +
  • 🎨 Automatic theme detection
  • +
  • 🌓 Light and dark mode support
  • +
  • 📱 Responsive design
  • +
  • âš¡ Fast static deployment
  • +
+
+ +
+

Powered by Appwrite Sites • E2E Test

+
+
+ diff --git a/tests/resources/sites/static-themed/screenshot-dark.png b/tests/resources/sites/static-themed/screenshot-dark.png new file mode 100644 index 0000000000000000000000000000000000000000..bcab317265dbd9f8adc3e491f440108a16952262 GIT binary patch literal 40811 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 n9-MrMch`OV=LVvGr6lcddTAGWT@s=d66vOhYt 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

`zaW&c`@X%TV`T9vP(>_$REd6Rd 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 literal 0 HcmV?d00001 From 9ac594c537a808fed66d1766537001a9dc417795 Mon Sep 17 00:00:00 2001 From: Harsh Mahajan <127186841+HarshMN2345@users.noreply.github.com> Date: Mon, 24 Nov 2025 17:44:03 +0530 Subject: [PATCH 15/22] Update src/Appwrite/Platform/Modules/Account/Http/Account/MFA/Challenges/Create.php Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- .../Modules/Account/Http/Account/MFA/Challenges/Create.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Appwrite/Platform/Modules/Account/Http/Account/MFA/Challenges/Create.php b/src/Appwrite/Platform/Modules/Account/Http/Account/MFA/Challenges/Create.php index 10d3ce53e3..2ac53c2d46 100644 --- a/src/Appwrite/Platform/Modules/Account/Http/Account/MFA/Challenges/Create.php +++ b/src/Appwrite/Platform/Modules/Account/Http/Account/MFA/Challenges/Create.php @@ -293,7 +293,7 @@ class Create extends Action 'user' => $user->getAttribute('name'), 'project' => $project->getAttribute('name'), 'otp' => $code, - 'agentDevice' => $agentDevice['deviceBrand'] ?? $agentDevice['deviceBrand'] ?? 'UNKNOWN', + 'agentDevice' => $agentDevice['deviceBrand'] ?? 'UNKNOWN', 'agentClient' => $agentClient['clientName'] ?? 'UNKNOWN', 'agentOs' => $agentOs['osName'] ?? 'UNKNOWN', ]; From b1f5b6946a7bc55fd3d7d9f4bb3eddd51f11b9cf Mon Sep 17 00:00:00 2001 From: Harsh Mahajan <127186841+HarshMN2345@users.noreply.github.com> Date: Mon, 24 Nov 2025 17:44:25 +0530 Subject: [PATCH 16/22] Update src/Appwrite/Platform/Modules/Account/Http/Account/MFA/Authenticators/Create.php Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- .../Modules/Account/Http/Account/MFA/Authenticators/Create.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Appwrite/Platform/Modules/Account/Http/Account/MFA/Authenticators/Create.php b/src/Appwrite/Platform/Modules/Account/Http/Account/MFA/Authenticators/Create.php index a7bec0ec53..2d83599964 100644 --- a/src/Appwrite/Platform/Modules/Account/Http/Account/MFA/Authenticators/Create.php +++ b/src/Appwrite/Platform/Modules/Account/Http/Account/MFA/Authenticators/Create.php @@ -77,7 +77,6 @@ class Create extends Action ) ]) ->param('type', null, new WhiteList([Type::TOTP]), 'Type of authenticator. Must be `' . Type::TOTP . '`') - ->inject('requestTimestamp') ->inject('response') ->inject('project') ->inject('user') @@ -88,7 +87,6 @@ class Create extends Action public function action( string $type, - ?\DateTime $requestTimestamp, Response $response, Document $project, Document $user, From 10171c80da52e750949b1ff917944ab474bf8df5 Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Sun, 23 Nov 2025 12:09:28 +0530 Subject: [PATCH 17/22] Update SDK generator and regenerate Java/Kotlin documentation examples Updated SDK generator to fix Java examples Kotlin syntax issues and regenerated all affected documentation examples across multiple SDK versions. --- composer.lock | 16 +++++++-------- .../java/account/create-o-auth-2-session.md | 2 +- .../java/account/create-o-auth-2-token.md | 2 +- .../java/account/list-identities.md | 2 +- .../client-android/java/account/list-logs.md | 2 +- .../java/account/update-prefs.md | 8 ++++---- .../java/avatars/get-screenshot.md | 4 ++-- .../java/databases/create-document.md | 16 +++++++-------- .../java/databases/create-operations.md | 20 +++++++++---------- .../java/databases/get-document.md | 2 +- .../java/databases/list-documents.md | 2 +- .../java/databases/list-transactions.md | 2 +- .../java/databases/update-document.md | 6 +++--- .../java/databases/upsert-document.md | 6 +++--- .../java/functions/create-execution.md | 2 +- .../java/functions/list-executions.md | 2 +- .../client-android/java/graphql/mutation.md | 2 +- .../client-android/java/graphql/query.md | 2 +- .../java/storage/create-file.md | 4 ++-- .../client-android/java/storage/list-files.md | 2 +- .../java/storage/update-file.md | 4 ++-- .../java/tablesdb/create-operations.md | 20 +++++++++---------- .../java/tablesdb/create-row.md | 16 +++++++-------- .../client-android/java/tablesdb/get-row.md | 2 +- .../client-android/java/tablesdb/list-rows.md | 2 +- .../java/tablesdb/list-transactions.md | 2 +- .../java/tablesdb/update-row.md | 6 +++--- .../java/tablesdb/upsert-row.md | 6 +++--- .../java/teams/create-membership.md | 2 +- .../1.8.x/client-android/java/teams/create.md | 2 +- .../java/teams/list-memberships.md | 2 +- .../1.8.x/client-android/java/teams/list.md | 2 +- .../java/teams/update-membership.md | 2 +- .../client-android/java/teams/update-prefs.md | 2 +- .../kotlin/databases/create-operations.md | 20 +++++++++---------- .../kotlin/tablesdb/create-operations.md | 20 +++++++++---------- .../functions/create-template-deployment.md | 4 ++-- .../functions/create-vcs-deployment.md | 4 ++-- .../sites/create-template-deployment.md | 4 ++-- .../examples/sites/create-vcs-deployment.md | 4 ++-- .../functions/create-template-deployment.md | 2 +- .../functions/create-vcs-deployment.md | 2 +- .../sites/create-template-deployment.md | 2 +- .../examples/sites/create-vcs-deployment.md | 2 +- .../functions/create-template-deployment.md | 2 +- .../functions/create-vcs-deployment.md | 2 +- .../sites/create-template-deployment.md | 2 +- .../examples/sites/create-vcs-deployment.md | 2 +- .../java/account/create-o-auth-2-token.md | 2 +- .../java/account/list-identities.md | 2 +- .../server-kotlin/java/account/list-logs.md | 2 +- .../java/account/update-prefs.md | 8 ++++---- .../java/avatars/get-screenshot.md | 4 ++-- .../java/databases/create-collection.md | 4 ++-- .../java/databases/create-document.md | 16 +++++++-------- .../java/databases/create-documents.md | 2 +- .../java/databases/create-enum-attribute.md | 2 +- .../java/databases/create-index.md | 6 +++--- .../java/databases/create-line-attribute.md | 2 +- .../java/databases/create-operations.md | 20 +++++++++---------- .../java/databases/create-point-attribute.md | 2 +- .../databases/create-polygon-attribute.md | 2 +- .../java/databases/delete-documents.md | 2 +- .../java/databases/get-document.md | 2 +- .../java/databases/list-attributes.md | 2 +- .../java/databases/list-collections.md | 2 +- .../java/databases/list-documents.md | 2 +- .../java/databases/list-indexes.md | 2 +- .../java/databases/list-transactions.md | 2 +- .../server-kotlin/java/databases/list.md | 2 +- .../java/databases/update-collection.md | 4 ++-- .../java/databases/update-document.md | 6 +++--- .../java/databases/update-documents.md | 4 ++-- .../java/databases/update-enum-attribute.md | 2 +- .../java/databases/update-line-attribute.md | 2 +- .../java/databases/update-point-attribute.md | 2 +- .../databases/update-polygon-attribute.md | 2 +- .../java/databases/upsert-document.md | 6 +++--- .../java/databases/upsert-documents.md | 2 +- .../java/functions/create-execution.md | 2 +- .../functions/create-template-deployment.md | 4 ++-- .../java/functions/create-vcs-deployment.md | 4 ++-- .../server-kotlin/java/functions/create.md | 6 +++--- .../java/functions/list-deployments.md | 2 +- .../java/functions/list-executions.md | 2 +- .../server-kotlin/java/functions/list.md | 2 +- .../server-kotlin/java/functions/update.md | 6 +++--- .../server-kotlin/java/graphql/mutation.md | 2 +- .../1.8.x/server-kotlin/java/graphql/query.md | 2 +- .../java/messaging/create-email.md | 12 +++++------ .../java/messaging/create-fcm-provider.md | 2 +- .../java/messaging/create-push.md | 8 ++++---- .../java/messaging/create-sms.md | 6 +++--- .../java/messaging/create-topic.md | 2 +- .../java/messaging/list-message-logs.md | 2 +- .../java/messaging/list-messages.md | 2 +- .../java/messaging/list-provider-logs.md | 2 +- .../java/messaging/list-providers.md | 2 +- .../java/messaging/list-subscriber-logs.md | 2 +- .../java/messaging/list-subscribers.md | 2 +- .../java/messaging/list-targets.md | 2 +- .../java/messaging/list-topic-logs.md | 2 +- .../java/messaging/list-topics.md | 2 +- .../java/messaging/update-email.md | 12 +++++------ .../java/messaging/update-fcm-provider.md | 2 +- .../java/messaging/update-push.md | 8 ++++---- .../java/messaging/update-sms.md | 6 +++--- .../java/messaging/update-topic.md | 2 +- .../java/sites/create-template-deployment.md | 4 ++-- .../java/sites/create-vcs-deployment.md | 4 ++-- .../java/sites/list-deployments.md | 2 +- .../server-kotlin/java/sites/list-logs.md | 2 +- .../1.8.x/server-kotlin/java/sites/list.md | 2 +- .../java/storage/create-bucket.md | 8 ++++---- .../server-kotlin/java/storage/create-file.md | 4 ++-- .../java/storage/list-buckets.md | 2 +- .../server-kotlin/java/storage/list-files.md | 2 +- .../java/storage/update-bucket.md | 8 ++++---- .../server-kotlin/java/storage/update-file.md | 4 ++-- .../java/tablesdb/create-enum-column.md | 2 +- .../java/tablesdb/create-index.md | 6 +++--- .../java/tablesdb/create-line-column.md | 2 +- .../java/tablesdb/create-operations.md | 20 +++++++++---------- .../java/tablesdb/create-point-column.md | 2 +- .../java/tablesdb/create-polygon-column.md | 2 +- .../server-kotlin/java/tablesdb/create-row.md | 16 +++++++-------- .../java/tablesdb/create-rows.md | 2 +- .../java/tablesdb/create-table.md | 4 ++-- .../java/tablesdb/delete-rows.md | 2 +- .../server-kotlin/java/tablesdb/get-row.md | 2 +- .../java/tablesdb/list-columns.md | 2 +- .../java/tablesdb/list-indexes.md | 2 +- .../server-kotlin/java/tablesdb/list-rows.md | 2 +- .../java/tablesdb/list-tables.md | 2 +- .../java/tablesdb/list-transactions.md | 2 +- .../1.8.x/server-kotlin/java/tablesdb/list.md | 2 +- .../java/tablesdb/update-enum-column.md | 2 +- .../java/tablesdb/update-line-column.md | 2 +- .../java/tablesdb/update-point-column.md | 2 +- .../java/tablesdb/update-polygon-column.md | 2 +- .../server-kotlin/java/tablesdb/update-row.md | 6 +++--- .../java/tablesdb/update-rows.md | 4 ++-- .../java/tablesdb/update-table.md | 4 ++-- .../server-kotlin/java/tablesdb/upsert-row.md | 6 +++--- .../java/tablesdb/upsert-rows.md | 2 +- .../java/teams/create-membership.md | 2 +- .../1.8.x/server-kotlin/java/teams/create.md | 2 +- .../java/teams/list-memberships.md | 2 +- .../1.8.x/server-kotlin/java/teams/list.md | 2 +- .../java/teams/update-membership.md | 2 +- .../server-kotlin/java/teams/update-prefs.md | 2 +- .../1.8.x/server-kotlin/java/tokens/list.md | 2 +- .../java/users/list-identities.md | 2 +- .../server-kotlin/java/users/list-logs.md | 2 +- .../java/users/list-memberships.md | 2 +- .../server-kotlin/java/users/list-targets.md | 2 +- .../1.8.x/server-kotlin/java/users/list.md | 2 +- .../server-kotlin/java/users/update-labels.md | 2 +- .../server-kotlin/java/users/update-prefs.md | 2 +- .../kotlin/databases/create-line-attribute.md | 2 +- .../kotlin/databases/create-operations.md | 20 +++++++++---------- .../databases/create-polygon-attribute.md | 2 +- .../kotlin/databases/update-line-attribute.md | 2 +- .../databases/update-polygon-attribute.md | 2 +- .../functions/create-template-deployment.md | 4 ++-- .../kotlin/functions/create-vcs-deployment.md | 4 ++-- .../sites/create-template-deployment.md | 4 ++-- .../kotlin/sites/create-vcs-deployment.md | 4 ++-- .../kotlin/tablesdb/create-line-column.md | 2 +- .../kotlin/tablesdb/create-operations.md | 20 +++++++++---------- .../kotlin/tablesdb/create-polygon-column.md | 2 +- .../kotlin/tablesdb/update-line-column.md | 2 +- .../kotlin/tablesdb/update-polygon-column.md | 2 +- .../functions/create-template-deployment.md | 2 +- .../functions/create-vcs-deployment.md | 2 +- .../sites/create-template-deployment.md | 2 +- .../examples/sites/create-vcs-deployment.md | 2 +- .../functions/create-template-deployment.md | 4 ++-- .../functions/create-vcs-deployment.md | 4 ++-- .../sites/create-template-deployment.md | 4 ++-- .../examples/sites/create-vcs-deployment.md | 4 ++-- .../functions/create-template-deployment.md | 4 ++-- .../functions/create-vcs-deployment.md | 4 ++-- .../sites/create-template-deployment.md | 4 ++-- .../examples/sites/create-vcs-deployment.md | 4 ++-- .../functions/create-template-deployment.md | 2 +- .../functions/create-vcs-deployment.md | 2 +- .../sites/create-template-deployment.md | 2 +- .../examples/sites/create-vcs-deployment.md | 2 +- 189 files changed, 376 insertions(+), 392 deletions(-) diff --git a/composer.lock b/composer.lock index d66945e3fc..605f5d6ff1 100644 --- a/composer.lock +++ b/composer.lock @@ -5379,16 +5379,16 @@ "packages-dev": [ { "name": "appwrite/sdk-generator", - "version": "1.5.7", + "version": "1.5.9", "source": { "type": "git", "url": "https://github.com/appwrite/sdk-generator.git", - "reference": "dc6720ba92ed98e2c62b2a319d4371f167ccc808" + "reference": "ee434aa00a9185380b9a39bb46bf86d7104d3a93" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/dc6720ba92ed98e2c62b2a319d4371f167ccc808", - "reference": "dc6720ba92ed98e2c62b2a319d4371f167ccc808", + "url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/ee434aa00a9185380b9a39bb46bf86d7104d3a93", + "reference": "ee434aa00a9185380b9a39bb46bf86d7104d3a93", "shasum": "" }, "require": { @@ -5424,9 +5424,9 @@ "description": "Appwrite PHP library for generating API SDKs for multiple programming languages and platforms", "support": { "issues": "https://github.com/appwrite/sdk-generator/issues", - "source": "https://github.com/appwrite/sdk-generator/tree/1.5.7" + "source": "https://github.com/appwrite/sdk-generator/tree/1.5.9" }, - "time": "2025-11-18T05:57:01+00:00" + "time": "2025-11-25T05:22:25+00:00" }, { "name": "doctrine/annotations", @@ -8893,7 +8893,7 @@ ], "aliases": [], "minimum-stability": "stable", - "stability-flags": [], + "stability-flags": {}, "prefer-stable": false, "prefer-lowest": false, "platform": { @@ -8917,5 +8917,5 @@ "platform-overrides": { "php": "8.3" }, - "plugin-api-version": "2.3.0" + "plugin-api-version": "2.9.0" } diff --git a/docs/examples/1.8.x/client-android/java/account/create-o-auth-2-session.md b/docs/examples/1.8.x/client-android/java/account/create-o-auth-2-session.md index 4420859ce3..5ada9368ea 100644 --- a/docs/examples/1.8.x/client-android/java/account/create-o-auth-2-session.md +++ b/docs/examples/1.8.x/client-android/java/account/create-o-auth-2-session.md @@ -13,7 +13,7 @@ account.createOAuth2Session( OAuthProvider.AMAZON, // provider "https://example.com", // success (optional) "https://example.com", // failure (optional) - listOf(), // scopes (optional) + List.of(), // scopes (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); diff --git a/docs/examples/1.8.x/client-android/java/account/create-o-auth-2-token.md b/docs/examples/1.8.x/client-android/java/account/create-o-auth-2-token.md index e5590c8ceb..f1122dc8f3 100644 --- a/docs/examples/1.8.x/client-android/java/account/create-o-auth-2-token.md +++ b/docs/examples/1.8.x/client-android/java/account/create-o-auth-2-token.md @@ -13,7 +13,7 @@ account.createOAuth2Token( OAuthProvider.AMAZON, // provider "https://example.com", // success (optional) "https://example.com", // failure (optional) - listOf(), // scopes (optional) + List.of(), // scopes (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); diff --git a/docs/examples/1.8.x/client-android/java/account/list-identities.md b/docs/examples/1.8.x/client-android/java/account/list-identities.md index 327fe39537..e5dd44d9b7 100644 --- a/docs/examples/1.8.x/client-android/java/account/list-identities.md +++ b/docs/examples/1.8.x/client-android/java/account/list-identities.md @@ -9,7 +9,7 @@ Client client = new Client(context) Account account = new Account(client); account.listIdentities( - listOf(), // queries (optional) + List.of(), // queries (optional) false, // total (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { diff --git a/docs/examples/1.8.x/client-android/java/account/list-logs.md b/docs/examples/1.8.x/client-android/java/account/list-logs.md index 4562ecc3ba..8b6ec85067 100644 --- a/docs/examples/1.8.x/client-android/java/account/list-logs.md +++ b/docs/examples/1.8.x/client-android/java/account/list-logs.md @@ -9,7 +9,7 @@ Client client = new Client(context) Account account = new Account(client); account.listLogs( - listOf(), // queries (optional) + List.of(), // queries (optional) false, // total (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { diff --git a/docs/examples/1.8.x/client-android/java/account/update-prefs.md b/docs/examples/1.8.x/client-android/java/account/update-prefs.md index 4bd6940c61..2682fe3aeb 100644 --- a/docs/examples/1.8.x/client-android/java/account/update-prefs.md +++ b/docs/examples/1.8.x/client-android/java/account/update-prefs.md @@ -9,10 +9,10 @@ Client client = new Client(context) Account account = new Account(client); account.updatePrefs( - mapOf( - "language" to "en", - "timezone" to "UTC", - "darkTheme" to true + Map.of( + "language", "en", + "timezone", "UTC", + "darkTheme", true ), // prefs new CoroutineCallback<>((result, error) -> { if (error != null) { diff --git a/docs/examples/1.8.x/client-android/java/avatars/get-screenshot.md b/docs/examples/1.8.x/client-android/java/avatars/get-screenshot.md index 48babdca20..0f6e41451a 100644 --- a/docs/examples/1.8.x/client-android/java/avatars/get-screenshot.md +++ b/docs/examples/1.8.x/client-android/java/avatars/get-screenshot.md @@ -13,7 +13,7 @@ Avatars avatars = new Avatars(client); avatars.getScreenshot( "https://example.com", // url - mapOf( "a" to "b" ), // headers (optional) + Map.of("a", "b"), // headers (optional) 1, // viewportWidth (optional) 1, // viewportHeight (optional) 0.1, // scale (optional) @@ -26,7 +26,7 @@ avatars.getScreenshot( -180, // longitude (optional) 0, // accuracy (optional) false, // touch (optional) - listOf(), // permissions (optional) + List.of(), // permissions (optional) 0, // sleep (optional) 0, // width (optional) 0, // height (optional) diff --git a/docs/examples/1.8.x/client-android/java/databases/create-document.md b/docs/examples/1.8.x/client-android/java/databases/create-document.md index bfd4601ab4..d95db8a9b0 100644 --- a/docs/examples/1.8.x/client-android/java/databases/create-document.md +++ b/docs/examples/1.8.x/client-android/java/databases/create-document.md @@ -1,8 +1,8 @@ import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; -import io.appwrite.services.Databases; import io.appwrite.Permission; import io.appwrite.Role; +import io.appwrite.services.Databases; Client client = new Client(context) .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint @@ -14,14 +14,14 @@ databases.createDocument( "", // databaseId "", // collectionId "", // documentId - mapOf( - "username" to "walter.obrien", - "email" to "walter.obrien@example.com", - "fullName" to "Walter O'Brien", - "age" to 30, - "isAdmin" to false + Map.of( + "username", "walter.obrien", + "email", "walter.obrien@example.com", + "fullName", "Walter O'Brien", + "age", 30, + "isAdmin", false ), // data - listOf(Permission.read(Role.any())), // permissions (optional) + List.of(Permission.read(Role.any())), // permissions (optional) "", // transactionId (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { diff --git a/docs/examples/1.8.x/client-android/java/databases/create-operations.md b/docs/examples/1.8.x/client-android/java/databases/create-operations.md index def58af773..a8635d81e9 100644 --- a/docs/examples/1.8.x/client-android/java/databases/create-operations.md +++ b/docs/examples/1.8.x/client-android/java/databases/create-operations.md @@ -10,17 +10,15 @@ Databases databases = new Databases(client); databases.createOperations( "", // transactionId - listOf( - { - "action": "create", - "databaseId": "", - "collectionId": "", - "documentId": "", - "data": { - "name": "Walter O'Brien" - } - } - ), // operations (optional) + List.of(Map.of( + "action", "create", + "databaseId", "", + "collectionId", "", + "documentId", "", + "data", Map.of( + "name", "Walter O'Brien" + ) + )), // operations (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); diff --git a/docs/examples/1.8.x/client-android/java/databases/get-document.md b/docs/examples/1.8.x/client-android/java/databases/get-document.md index 92d6b5ed23..85f9bb9b13 100644 --- a/docs/examples/1.8.x/client-android/java/databases/get-document.md +++ b/docs/examples/1.8.x/client-android/java/databases/get-document.md @@ -12,7 +12,7 @@ databases.getDocument( "", // databaseId "", // collectionId "", // documentId - listOf(), // queries (optional) + List.of(), // queries (optional) "", // transactionId (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { diff --git a/docs/examples/1.8.x/client-android/java/databases/list-documents.md b/docs/examples/1.8.x/client-android/java/databases/list-documents.md index e4102ec542..5440f786cc 100644 --- a/docs/examples/1.8.x/client-android/java/databases/list-documents.md +++ b/docs/examples/1.8.x/client-android/java/databases/list-documents.md @@ -11,7 +11,7 @@ Databases databases = new Databases(client); databases.listDocuments( "", // databaseId "", // collectionId - listOf(), // queries (optional) + List.of(), // queries (optional) "", // transactionId (optional) false, // total (optional) new CoroutineCallback<>((result, error) -> { diff --git a/docs/examples/1.8.x/client-android/java/databases/list-transactions.md b/docs/examples/1.8.x/client-android/java/databases/list-transactions.md index 39f58f3490..899a3ea181 100644 --- a/docs/examples/1.8.x/client-android/java/databases/list-transactions.md +++ b/docs/examples/1.8.x/client-android/java/databases/list-transactions.md @@ -9,7 +9,7 @@ Client client = new Client(context) Databases databases = new Databases(client); databases.listTransactions( - listOf(), // queries (optional) + List.of(), // queries (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); diff --git a/docs/examples/1.8.x/client-android/java/databases/update-document.md b/docs/examples/1.8.x/client-android/java/databases/update-document.md index d3a3967d5b..0f13f74e08 100644 --- a/docs/examples/1.8.x/client-android/java/databases/update-document.md +++ b/docs/examples/1.8.x/client-android/java/databases/update-document.md @@ -1,8 +1,8 @@ import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; -import io.appwrite.services.Databases; import io.appwrite.Permission; import io.appwrite.Role; +import io.appwrite.services.Databases; Client client = new Client(context) .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint @@ -14,8 +14,8 @@ databases.updateDocument( "", // databaseId "", // collectionId "", // documentId - mapOf( "a" to "b" ), // data (optional) - listOf(Permission.read(Role.any())), // permissions (optional) + Map.of("a", "b"), // data (optional) + List.of(Permission.read(Role.any())), // permissions (optional) "", // transactionId (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { diff --git a/docs/examples/1.8.x/client-android/java/databases/upsert-document.md b/docs/examples/1.8.x/client-android/java/databases/upsert-document.md index e46afa10a9..8c72734f98 100644 --- a/docs/examples/1.8.x/client-android/java/databases/upsert-document.md +++ b/docs/examples/1.8.x/client-android/java/databases/upsert-document.md @@ -1,8 +1,8 @@ import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; -import io.appwrite.services.Databases; import io.appwrite.Permission; import io.appwrite.Role; +import io.appwrite.services.Databases; Client client = new Client(context) .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint @@ -14,8 +14,8 @@ databases.upsertDocument( "", // databaseId "", // collectionId "", // documentId - mapOf( "a" to "b" ), // data - listOf(Permission.read(Role.any())), // permissions (optional) + Map.of("a", "b"), // data + List.of(Permission.read(Role.any())), // permissions (optional) "", // transactionId (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { diff --git a/docs/examples/1.8.x/client-android/java/functions/create-execution.md b/docs/examples/1.8.x/client-android/java/functions/create-execution.md index 758881d13c..4aea0929f1 100644 --- a/docs/examples/1.8.x/client-android/java/functions/create-execution.md +++ b/docs/examples/1.8.x/client-android/java/functions/create-execution.md @@ -15,7 +15,7 @@ functions.createExecution( false, // async (optional) "", // path (optional) ExecutionMethod.GET, // method (optional) - mapOf( "a" to "b" ), // headers (optional) + Map.of("a", "b"), // headers (optional) "", // scheduledAt (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { diff --git a/docs/examples/1.8.x/client-android/java/functions/list-executions.md b/docs/examples/1.8.x/client-android/java/functions/list-executions.md index c9a1df107e..893d098998 100644 --- a/docs/examples/1.8.x/client-android/java/functions/list-executions.md +++ b/docs/examples/1.8.x/client-android/java/functions/list-executions.md @@ -10,7 +10,7 @@ Functions functions = new Functions(client); functions.listExecutions( "", // functionId - listOf(), // queries (optional) + List.of(), // queries (optional) false, // total (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { diff --git a/docs/examples/1.8.x/client-android/java/graphql/mutation.md b/docs/examples/1.8.x/client-android/java/graphql/mutation.md index 25f095e1b1..445195275a 100644 --- a/docs/examples/1.8.x/client-android/java/graphql/mutation.md +++ b/docs/examples/1.8.x/client-android/java/graphql/mutation.md @@ -9,7 +9,7 @@ Client client = new Client(context) Graphql graphql = new Graphql(client); graphql.mutation( - mapOf( "a" to "b" ), // query + Map.of("a", "b"), // query new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); diff --git a/docs/examples/1.8.x/client-android/java/graphql/query.md b/docs/examples/1.8.x/client-android/java/graphql/query.md index 6b2a04d0b6..b39f128165 100644 --- a/docs/examples/1.8.x/client-android/java/graphql/query.md +++ b/docs/examples/1.8.x/client-android/java/graphql/query.md @@ -9,7 +9,7 @@ Client client = new Client(context) Graphql graphql = new Graphql(client); graphql.query( - mapOf( "a" to "b" ), // query + Map.of("a", "b"), // query new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); diff --git a/docs/examples/1.8.x/client-android/java/storage/create-file.md b/docs/examples/1.8.x/client-android/java/storage/create-file.md index 8de00099b0..518bbc9d29 100644 --- a/docs/examples/1.8.x/client-android/java/storage/create-file.md +++ b/docs/examples/1.8.x/client-android/java/storage/create-file.md @@ -1,9 +1,9 @@ import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.models.InputFile; -import io.appwrite.services.Storage; import io.appwrite.Permission; import io.appwrite.Role; +import io.appwrite.services.Storage; Client client = new Client(context) .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint @@ -15,7 +15,7 @@ storage.createFile( "", // bucketId "", // fileId InputFile.fromPath("file.png"), // file - listOf(Permission.read(Role.any())), // permissions (optional) + List.of(Permission.read(Role.any())), // permissions (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); diff --git a/docs/examples/1.8.x/client-android/java/storage/list-files.md b/docs/examples/1.8.x/client-android/java/storage/list-files.md index 178027cc5d..05292a6465 100644 --- a/docs/examples/1.8.x/client-android/java/storage/list-files.md +++ b/docs/examples/1.8.x/client-android/java/storage/list-files.md @@ -10,7 +10,7 @@ Storage storage = new Storage(client); storage.listFiles( "", // bucketId - listOf(), // queries (optional) + List.of(), // queries (optional) "", // search (optional) false, // total (optional) new CoroutineCallback<>((result, error) -> { diff --git a/docs/examples/1.8.x/client-android/java/storage/update-file.md b/docs/examples/1.8.x/client-android/java/storage/update-file.md index 1e21b3fae1..40cc61b1bf 100644 --- a/docs/examples/1.8.x/client-android/java/storage/update-file.md +++ b/docs/examples/1.8.x/client-android/java/storage/update-file.md @@ -1,8 +1,8 @@ import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; -import io.appwrite.services.Storage; import io.appwrite.Permission; import io.appwrite.Role; +import io.appwrite.services.Storage; Client client = new Client(context) .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint @@ -14,7 +14,7 @@ storage.updateFile( "", // bucketId "", // fileId "", // name (optional) - listOf(Permission.read(Role.any())), // permissions (optional) + List.of(Permission.read(Role.any())), // permissions (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); diff --git a/docs/examples/1.8.x/client-android/java/tablesdb/create-operations.md b/docs/examples/1.8.x/client-android/java/tablesdb/create-operations.md index 7ca288c2ae..0e8cb4b067 100644 --- a/docs/examples/1.8.x/client-android/java/tablesdb/create-operations.md +++ b/docs/examples/1.8.x/client-android/java/tablesdb/create-operations.md @@ -10,17 +10,15 @@ TablesDB tablesDB = new TablesDB(client); tablesDB.createOperations( "", // transactionId - listOf( - { - "action": "create", - "databaseId": "", - "tableId": "", - "rowId": "", - "data": { - "name": "Walter O'Brien" - } - } - ), // operations (optional) + List.of(Map.of( + "action", "create", + "databaseId", "", + "tableId", "", + "rowId", "", + "data", Map.of( + "name", "Walter O'Brien" + ) + )), // operations (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); diff --git a/docs/examples/1.8.x/client-android/java/tablesdb/create-row.md b/docs/examples/1.8.x/client-android/java/tablesdb/create-row.md index f7aa10e5c7..b3e4f310db 100644 --- a/docs/examples/1.8.x/client-android/java/tablesdb/create-row.md +++ b/docs/examples/1.8.x/client-android/java/tablesdb/create-row.md @@ -1,8 +1,8 @@ import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; -import io.appwrite.services.TablesDB; import io.appwrite.Permission; import io.appwrite.Role; +import io.appwrite.services.TablesDB; Client client = new Client(context) .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint @@ -14,14 +14,14 @@ tablesDB.createRow( "", // databaseId "", // tableId "", // rowId - mapOf( - "username" to "walter.obrien", - "email" to "walter.obrien@example.com", - "fullName" to "Walter O'Brien", - "age" to 30, - "isAdmin" to false + Map.of( + "username", "walter.obrien", + "email", "walter.obrien@example.com", + "fullName", "Walter O'Brien", + "age", 30, + "isAdmin", false ), // data - listOf(Permission.read(Role.any())), // permissions (optional) + List.of(Permission.read(Role.any())), // permissions (optional) "", // transactionId (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { diff --git a/docs/examples/1.8.x/client-android/java/tablesdb/get-row.md b/docs/examples/1.8.x/client-android/java/tablesdb/get-row.md index 45efc6b061..2bf847284a 100644 --- a/docs/examples/1.8.x/client-android/java/tablesdb/get-row.md +++ b/docs/examples/1.8.x/client-android/java/tablesdb/get-row.md @@ -12,7 +12,7 @@ tablesDB.getRow( "", // databaseId "", // tableId "", // rowId - listOf(), // queries (optional) + List.of(), // queries (optional) "", // transactionId (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { diff --git a/docs/examples/1.8.x/client-android/java/tablesdb/list-rows.md b/docs/examples/1.8.x/client-android/java/tablesdb/list-rows.md index 3bd1e1c77a..0833929eb1 100644 --- a/docs/examples/1.8.x/client-android/java/tablesdb/list-rows.md +++ b/docs/examples/1.8.x/client-android/java/tablesdb/list-rows.md @@ -11,7 +11,7 @@ TablesDB tablesDB = new TablesDB(client); tablesDB.listRows( "", // databaseId "", // tableId - listOf(), // queries (optional) + List.of(), // queries (optional) "", // transactionId (optional) false, // total (optional) new CoroutineCallback<>((result, error) -> { diff --git a/docs/examples/1.8.x/client-android/java/tablesdb/list-transactions.md b/docs/examples/1.8.x/client-android/java/tablesdb/list-transactions.md index c37ccf1931..ce0147b036 100644 --- a/docs/examples/1.8.x/client-android/java/tablesdb/list-transactions.md +++ b/docs/examples/1.8.x/client-android/java/tablesdb/list-transactions.md @@ -9,7 +9,7 @@ Client client = new Client(context) TablesDB tablesDB = new TablesDB(client); tablesDB.listTransactions( - listOf(), // queries (optional) + List.of(), // queries (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); diff --git a/docs/examples/1.8.x/client-android/java/tablesdb/update-row.md b/docs/examples/1.8.x/client-android/java/tablesdb/update-row.md index 3abaf90603..89f2646d9d 100644 --- a/docs/examples/1.8.x/client-android/java/tablesdb/update-row.md +++ b/docs/examples/1.8.x/client-android/java/tablesdb/update-row.md @@ -1,8 +1,8 @@ import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; -import io.appwrite.services.TablesDB; import io.appwrite.Permission; import io.appwrite.Role; +import io.appwrite.services.TablesDB; Client client = new Client(context) .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint @@ -14,8 +14,8 @@ tablesDB.updateRow( "", // databaseId "", // tableId "", // rowId - mapOf( "a" to "b" ), // data (optional) - listOf(Permission.read(Role.any())), // permissions (optional) + Map.of("a", "b"), // data (optional) + List.of(Permission.read(Role.any())), // permissions (optional) "", // transactionId (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { diff --git a/docs/examples/1.8.x/client-android/java/tablesdb/upsert-row.md b/docs/examples/1.8.x/client-android/java/tablesdb/upsert-row.md index 6f979fc126..c6cfe6d1bc 100644 --- a/docs/examples/1.8.x/client-android/java/tablesdb/upsert-row.md +++ b/docs/examples/1.8.x/client-android/java/tablesdb/upsert-row.md @@ -1,8 +1,8 @@ import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; -import io.appwrite.services.TablesDB; import io.appwrite.Permission; import io.appwrite.Role; +import io.appwrite.services.TablesDB; Client client = new Client(context) .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint @@ -14,8 +14,8 @@ tablesDB.upsertRow( "", // databaseId "", // tableId "", // rowId - mapOf( "a" to "b" ), // data (optional) - listOf(Permission.read(Role.any())), // permissions (optional) + Map.of("a", "b"), // data (optional) + List.of(Permission.read(Role.any())), // permissions (optional) "", // transactionId (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { diff --git a/docs/examples/1.8.x/client-android/java/teams/create-membership.md b/docs/examples/1.8.x/client-android/java/teams/create-membership.md index bb5293ef63..e5eee207dc 100644 --- a/docs/examples/1.8.x/client-android/java/teams/create-membership.md +++ b/docs/examples/1.8.x/client-android/java/teams/create-membership.md @@ -10,7 +10,7 @@ Teams teams = new Teams(client); teams.createMembership( "", // teamId - listOf(), // roles + List.of(), // roles "email@example.com", // email (optional) "", // userId (optional) "+12065550100", // phone (optional) diff --git a/docs/examples/1.8.x/client-android/java/teams/create.md b/docs/examples/1.8.x/client-android/java/teams/create.md index ae2fdf32c8..232d3b38ee 100644 --- a/docs/examples/1.8.x/client-android/java/teams/create.md +++ b/docs/examples/1.8.x/client-android/java/teams/create.md @@ -11,7 +11,7 @@ Teams teams = new Teams(client); teams.create( "", // teamId "", // name - listOf(), // roles (optional) + List.of(), // roles (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); diff --git a/docs/examples/1.8.x/client-android/java/teams/list-memberships.md b/docs/examples/1.8.x/client-android/java/teams/list-memberships.md index ae5cc69b4d..c8d3b610dc 100644 --- a/docs/examples/1.8.x/client-android/java/teams/list-memberships.md +++ b/docs/examples/1.8.x/client-android/java/teams/list-memberships.md @@ -10,7 +10,7 @@ Teams teams = new Teams(client); teams.listMemberships( "", // teamId - listOf(), // queries (optional) + List.of(), // queries (optional) "", // search (optional) false, // total (optional) new CoroutineCallback<>((result, error) -> { diff --git a/docs/examples/1.8.x/client-android/java/teams/list.md b/docs/examples/1.8.x/client-android/java/teams/list.md index fff14e2992..1db3e67a2e 100644 --- a/docs/examples/1.8.x/client-android/java/teams/list.md +++ b/docs/examples/1.8.x/client-android/java/teams/list.md @@ -9,7 +9,7 @@ Client client = new Client(context) Teams teams = new Teams(client); teams.list( - listOf(), // queries (optional) + List.of(), // queries (optional) "", // search (optional) false, // total (optional) new CoroutineCallback<>((result, error) -> { diff --git a/docs/examples/1.8.x/client-android/java/teams/update-membership.md b/docs/examples/1.8.x/client-android/java/teams/update-membership.md index 481be43107..f8adcf1fb8 100644 --- a/docs/examples/1.8.x/client-android/java/teams/update-membership.md +++ b/docs/examples/1.8.x/client-android/java/teams/update-membership.md @@ -11,7 +11,7 @@ Teams teams = new Teams(client); teams.updateMembership( "", // teamId "", // membershipId - listOf(), // roles + List.of(), // roles new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); diff --git a/docs/examples/1.8.x/client-android/java/teams/update-prefs.md b/docs/examples/1.8.x/client-android/java/teams/update-prefs.md index 5a0186ff31..9ea1487bd4 100644 --- a/docs/examples/1.8.x/client-android/java/teams/update-prefs.md +++ b/docs/examples/1.8.x/client-android/java/teams/update-prefs.md @@ -10,7 +10,7 @@ Teams teams = new Teams(client); teams.updatePrefs( "", // teamId - mapOf( "a" to "b" ), // prefs + Map.of("a", "b"), // prefs new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); diff --git a/docs/examples/1.8.x/client-android/kotlin/databases/create-operations.md b/docs/examples/1.8.x/client-android/kotlin/databases/create-operations.md index 62c93351e7..f3a419448b 100644 --- a/docs/examples/1.8.x/client-android/kotlin/databases/create-operations.md +++ b/docs/examples/1.8.x/client-android/kotlin/databases/create-operations.md @@ -10,15 +10,13 @@ val databases = Databases(client) val result = databases.createOperations( transactionId = "", - operations = listOf( - { - "action": "create", - "databaseId": "", - "collectionId": "", - "documentId": "", - "data": { - "name": "Walter O'Brien" - } - } - ), // (optional) + operations = listOf(mapOf( + "action" to "create", + "databaseId" to "", + "collectionId" to "", + "documentId" to "", + "data" to mapOf( + "name" to "Walter O'Brien" + ) + )), // (optional) ) \ No newline at end of file diff --git a/docs/examples/1.8.x/client-android/kotlin/tablesdb/create-operations.md b/docs/examples/1.8.x/client-android/kotlin/tablesdb/create-operations.md index 3073a00bca..7807102f27 100644 --- a/docs/examples/1.8.x/client-android/kotlin/tablesdb/create-operations.md +++ b/docs/examples/1.8.x/client-android/kotlin/tablesdb/create-operations.md @@ -10,15 +10,13 @@ val tablesDB = TablesDB(client) val result = tablesDB.createOperations( transactionId = "", - operations = listOf( - { - "action": "create", - "databaseId": "", - "tableId": "", - "rowId": "", - "data": { - "name": "Walter O'Brien" - } - } - ), // (optional) + operations = listOf(mapOf( + "action" to "create", + "databaseId" to "", + "tableId" to "", + "rowId" to "", + "data" to mapOf( + "name" to "Walter O'Brien" + ) + )), // (optional) ) \ No newline at end of file diff --git a/docs/examples/1.8.x/console-web/examples/functions/create-template-deployment.md b/docs/examples/1.8.x/console-web/examples/functions/create-template-deployment.md index 5bf812c1bc..414a0d0bfb 100644 --- a/docs/examples/1.8.x/console-web/examples/functions/create-template-deployment.md +++ b/docs/examples/1.8.x/console-web/examples/functions/create-template-deployment.md @@ -1,4 +1,4 @@ -import { Client, Functions, } from "@appwrite.io/console"; +import { Client, Functions, TemplateReferenceType } from "@appwrite.io/console"; const client = new Client() .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint @@ -11,7 +11,7 @@ const result = await functions.createTemplateDeployment({ repository: '', owner: '', rootDirectory: '', - type: .Commit, + type: TemplateReferenceType.Commit, reference: '', activate: false // optional }); diff --git a/docs/examples/1.8.x/console-web/examples/functions/create-vcs-deployment.md b/docs/examples/1.8.x/console-web/examples/functions/create-vcs-deployment.md index 33da9bfd70..d6f4e765e3 100644 --- a/docs/examples/1.8.x/console-web/examples/functions/create-vcs-deployment.md +++ b/docs/examples/1.8.x/console-web/examples/functions/create-vcs-deployment.md @@ -1,4 +1,4 @@ -import { Client, Functions, VCSDeploymentType } from "@appwrite.io/console"; +import { Client, Functions, VCSReferenceType } from "@appwrite.io/console"; const client = new Client() .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint @@ -8,7 +8,7 @@ const functions = new Functions(client); const result = await functions.createVcsDeployment({ functionId: '', - type: VCSDeploymentType.Branch, + type: VCSReferenceType.Branch, reference: '', activate: false // optional }); diff --git a/docs/examples/1.8.x/console-web/examples/sites/create-template-deployment.md b/docs/examples/1.8.x/console-web/examples/sites/create-template-deployment.md index 4f1d0184f7..1bfaeb6a7e 100644 --- a/docs/examples/1.8.x/console-web/examples/sites/create-template-deployment.md +++ b/docs/examples/1.8.x/console-web/examples/sites/create-template-deployment.md @@ -1,4 +1,4 @@ -import { Client, Sites, } from "@appwrite.io/console"; +import { Client, Sites, TemplateReferenceType } from "@appwrite.io/console"; const client = new Client() .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint @@ -11,7 +11,7 @@ const result = await sites.createTemplateDeployment({ repository: '', owner: '', rootDirectory: '', - type: .Branch, + type: TemplateReferenceType.Branch, reference: '', activate: false // optional }); diff --git a/docs/examples/1.8.x/console-web/examples/sites/create-vcs-deployment.md b/docs/examples/1.8.x/console-web/examples/sites/create-vcs-deployment.md index cc1fd1d301..80d9403ccb 100644 --- a/docs/examples/1.8.x/console-web/examples/sites/create-vcs-deployment.md +++ b/docs/examples/1.8.x/console-web/examples/sites/create-vcs-deployment.md @@ -1,4 +1,4 @@ -import { Client, Sites, VCSDeploymentType } from "@appwrite.io/console"; +import { Client, Sites, VCSReferenceType } from "@appwrite.io/console"; const client = new Client() .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint @@ -8,7 +8,7 @@ const sites = new Sites(client); const result = await sites.createVcsDeployment({ siteId: '', - type: VCSDeploymentType.Branch, + type: VCSReferenceType.Branch, reference: '', activate: false // optional }); diff --git a/docs/examples/1.8.x/server-dart/examples/functions/create-template-deployment.md b/docs/examples/1.8.x/server-dart/examples/functions/create-template-deployment.md index bfd94b1963..f5b6cdeb1f 100644 --- a/docs/examples/1.8.x/server-dart/examples/functions/create-template-deployment.md +++ b/docs/examples/1.8.x/server-dart/examples/functions/create-template-deployment.md @@ -12,7 +12,7 @@ Deployment result = await functions.createTemplateDeployment( repository: '', owner: '', rootDirectory: '', - type: .commit, + type: TemplateReferenceType.commit, reference: '', activate: false, // (optional) ); diff --git a/docs/examples/1.8.x/server-dart/examples/functions/create-vcs-deployment.md b/docs/examples/1.8.x/server-dart/examples/functions/create-vcs-deployment.md index ed315a54e3..0c12315ffc 100644 --- a/docs/examples/1.8.x/server-dart/examples/functions/create-vcs-deployment.md +++ b/docs/examples/1.8.x/server-dart/examples/functions/create-vcs-deployment.md @@ -9,7 +9,7 @@ Functions functions = Functions(client); Deployment result = await functions.createVcsDeployment( functionId: '', - type: VCSDeploymentType.branch, + type: VCSReferenceType.branch, reference: '', activate: false, // (optional) ); diff --git a/docs/examples/1.8.x/server-dart/examples/sites/create-template-deployment.md b/docs/examples/1.8.x/server-dart/examples/sites/create-template-deployment.md index 93c9b1d283..8826b1f462 100644 --- a/docs/examples/1.8.x/server-dart/examples/sites/create-template-deployment.md +++ b/docs/examples/1.8.x/server-dart/examples/sites/create-template-deployment.md @@ -12,7 +12,7 @@ Deployment result = await sites.createTemplateDeployment( repository: '', owner: '', rootDirectory: '', - type: .branch, + type: TemplateReferenceType.branch, reference: '', activate: false, // (optional) ); diff --git a/docs/examples/1.8.x/server-dart/examples/sites/create-vcs-deployment.md b/docs/examples/1.8.x/server-dart/examples/sites/create-vcs-deployment.md index 50f65b9603..52133a535e 100644 --- a/docs/examples/1.8.x/server-dart/examples/sites/create-vcs-deployment.md +++ b/docs/examples/1.8.x/server-dart/examples/sites/create-vcs-deployment.md @@ -9,7 +9,7 @@ Sites sites = Sites(client); Deployment result = await sites.createVcsDeployment( siteId: '', - type: VCSDeploymentType.branch, + type: VCSReferenceType.branch, reference: '', activate: false, // (optional) ); diff --git a/docs/examples/1.8.x/server-dotnet/examples/functions/create-template-deployment.md b/docs/examples/1.8.x/server-dotnet/examples/functions/create-template-deployment.md index 125eeda5cc..6fcf2398dd 100644 --- a/docs/examples/1.8.x/server-dotnet/examples/functions/create-template-deployment.md +++ b/docs/examples/1.8.x/server-dotnet/examples/functions/create-template-deployment.md @@ -15,7 +15,7 @@ Deployment result = await functions.CreateTemplateDeployment( repository: "", owner: "", rootDirectory: "", - type: .Commit, + type: TemplateReferenceType.Commit, reference: "", activate: false // optional ); \ No newline at end of file diff --git a/docs/examples/1.8.x/server-dotnet/examples/functions/create-vcs-deployment.md b/docs/examples/1.8.x/server-dotnet/examples/functions/create-vcs-deployment.md index 9651365912..a7403ff116 100644 --- a/docs/examples/1.8.x/server-dotnet/examples/functions/create-vcs-deployment.md +++ b/docs/examples/1.8.x/server-dotnet/examples/functions/create-vcs-deployment.md @@ -12,7 +12,7 @@ Functions functions = new Functions(client); Deployment result = await functions.CreateVcsDeployment( functionId: "", - type: VCSDeploymentType.Branch, + type: VCSReferenceType.Branch, reference: "", activate: false // optional ); \ No newline at end of file diff --git a/docs/examples/1.8.x/server-dotnet/examples/sites/create-template-deployment.md b/docs/examples/1.8.x/server-dotnet/examples/sites/create-template-deployment.md index 9775cb659f..5353b004b8 100644 --- a/docs/examples/1.8.x/server-dotnet/examples/sites/create-template-deployment.md +++ b/docs/examples/1.8.x/server-dotnet/examples/sites/create-template-deployment.md @@ -15,7 +15,7 @@ Deployment result = await sites.CreateTemplateDeployment( repository: "", owner: "", rootDirectory: "", - type: .Branch, + type: TemplateReferenceType.Branch, reference: "", activate: false // optional ); \ No newline at end of file diff --git a/docs/examples/1.8.x/server-dotnet/examples/sites/create-vcs-deployment.md b/docs/examples/1.8.x/server-dotnet/examples/sites/create-vcs-deployment.md index 4d3e685176..229549befa 100644 --- a/docs/examples/1.8.x/server-dotnet/examples/sites/create-vcs-deployment.md +++ b/docs/examples/1.8.x/server-dotnet/examples/sites/create-vcs-deployment.md @@ -12,7 +12,7 @@ Sites sites = new Sites(client); Deployment result = await sites.CreateVcsDeployment( siteId: "", - type: VCSDeploymentType.Branch, + type: VCSReferenceType.Branch, reference: "", activate: false // optional ); \ No newline at end of file diff --git a/docs/examples/1.8.x/server-kotlin/java/account/create-o-auth-2-token.md b/docs/examples/1.8.x/server-kotlin/java/account/create-o-auth-2-token.md index 5b325f5c61..376d943533 100644 --- a/docs/examples/1.8.x/server-kotlin/java/account/create-o-auth-2-token.md +++ b/docs/examples/1.8.x/server-kotlin/java/account/create-o-auth-2-token.md @@ -13,7 +13,7 @@ account.createOAuth2Token( OAuthProvider.AMAZON, // provider "https://example.com", // success (optional) "https://example.com", // failure (optional) - listOf(), // scopes (optional) + List.of(), // scopes (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); diff --git a/docs/examples/1.8.x/server-kotlin/java/account/list-identities.md b/docs/examples/1.8.x/server-kotlin/java/account/list-identities.md index 8d204d5920..97cdf99b62 100644 --- a/docs/examples/1.8.x/server-kotlin/java/account/list-identities.md +++ b/docs/examples/1.8.x/server-kotlin/java/account/list-identities.md @@ -10,7 +10,7 @@ Client client = new Client() Account account = new Account(client); account.listIdentities( - listOf(), // queries (optional) + List.of(), // queries (optional) false, // total (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { diff --git a/docs/examples/1.8.x/server-kotlin/java/account/list-logs.md b/docs/examples/1.8.x/server-kotlin/java/account/list-logs.md index 4b301a11e0..6c41c7f073 100644 --- a/docs/examples/1.8.x/server-kotlin/java/account/list-logs.md +++ b/docs/examples/1.8.x/server-kotlin/java/account/list-logs.md @@ -10,7 +10,7 @@ Client client = new Client() Account account = new Account(client); account.listLogs( - listOf(), // queries (optional) + List.of(), // queries (optional) false, // total (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { diff --git a/docs/examples/1.8.x/server-kotlin/java/account/update-prefs.md b/docs/examples/1.8.x/server-kotlin/java/account/update-prefs.md index 0b6893916b..4fc1cb5439 100644 --- a/docs/examples/1.8.x/server-kotlin/java/account/update-prefs.md +++ b/docs/examples/1.8.x/server-kotlin/java/account/update-prefs.md @@ -10,10 +10,10 @@ Client client = new Client() Account account = new Account(client); account.updatePrefs( - mapOf( - "language" to "en", - "timezone" to "UTC", - "darkTheme" to true + Map.of( + "language", "en", + "timezone", "UTC", + "darkTheme", true ), // prefs new CoroutineCallback<>((result, error) -> { if (error != null) { diff --git a/docs/examples/1.8.x/server-kotlin/java/avatars/get-screenshot.md b/docs/examples/1.8.x/server-kotlin/java/avatars/get-screenshot.md index 0f1cb9c2f0..1874358337 100644 --- a/docs/examples/1.8.x/server-kotlin/java/avatars/get-screenshot.md +++ b/docs/examples/1.8.x/server-kotlin/java/avatars/get-screenshot.md @@ -14,7 +14,7 @@ Avatars avatars = new Avatars(client); avatars.getScreenshot( "https://example.com", // url - mapOf( "a" to "b" ), // headers (optional) + Map.of("a", "b"), // headers (optional) 1, // viewportWidth (optional) 1, // viewportHeight (optional) 0.1, // scale (optional) @@ -27,7 +27,7 @@ avatars.getScreenshot( -180, // longitude (optional) 0, // accuracy (optional) false, // touch (optional) - listOf(), // permissions (optional) + List.of(), // permissions (optional) 0, // sleep (optional) 0, // width (optional) 0, // height (optional) diff --git a/docs/examples/1.8.x/server-kotlin/java/databases/create-collection.md b/docs/examples/1.8.x/server-kotlin/java/databases/create-collection.md index 10eed04a4f..eea55558cc 100644 --- a/docs/examples/1.8.x/server-kotlin/java/databases/create-collection.md +++ b/docs/examples/1.8.x/server-kotlin/java/databases/create-collection.md @@ -1,8 +1,8 @@ import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; -import io.appwrite.services.Databases; import io.appwrite.Permission; import io.appwrite.Role; +import io.appwrite.services.Databases; Client client = new Client() .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint @@ -15,7 +15,7 @@ databases.createCollection( "", // databaseId "", // collectionId "", // name - listOf(Permission.read(Role.any())), // permissions (optional) + List.of(Permission.read(Role.any())), // permissions (optional) false, // documentSecurity (optional) false, // enabled (optional) new CoroutineCallback<>((result, error) -> { diff --git a/docs/examples/1.8.x/server-kotlin/java/databases/create-document.md b/docs/examples/1.8.x/server-kotlin/java/databases/create-document.md index aa6c9ea203..7ff195f613 100644 --- a/docs/examples/1.8.x/server-kotlin/java/databases/create-document.md +++ b/docs/examples/1.8.x/server-kotlin/java/databases/create-document.md @@ -1,8 +1,8 @@ import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; -import io.appwrite.services.Databases; import io.appwrite.Permission; import io.appwrite.Role; +import io.appwrite.services.Databases; Client client = new Client() .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint @@ -15,14 +15,14 @@ databases.createDocument( "", // databaseId "", // collectionId "", // documentId - mapOf( - "username" to "walter.obrien", - "email" to "walter.obrien@example.com", - "fullName" to "Walter O'Brien", - "age" to 30, - "isAdmin" to false + Map.of( + "username", "walter.obrien", + "email", "walter.obrien@example.com", + "fullName", "Walter O'Brien", + "age", 30, + "isAdmin", false ), // data - listOf(Permission.read(Role.any())), // permissions (optional) + List.of(Permission.read(Role.any())), // permissions (optional) "", // transactionId (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { diff --git a/docs/examples/1.8.x/server-kotlin/java/databases/create-documents.md b/docs/examples/1.8.x/server-kotlin/java/databases/create-documents.md index 3a4540974b..be695fe80b 100644 --- a/docs/examples/1.8.x/server-kotlin/java/databases/create-documents.md +++ b/docs/examples/1.8.x/server-kotlin/java/databases/create-documents.md @@ -12,7 +12,7 @@ Databases databases = new Databases(client); databases.createDocuments( "", // databaseId "", // collectionId - listOf(), // documents + List.of(), // documents "", // transactionId (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { diff --git a/docs/examples/1.8.x/server-kotlin/java/databases/create-enum-attribute.md b/docs/examples/1.8.x/server-kotlin/java/databases/create-enum-attribute.md index 44202086b0..b8666666da 100644 --- a/docs/examples/1.8.x/server-kotlin/java/databases/create-enum-attribute.md +++ b/docs/examples/1.8.x/server-kotlin/java/databases/create-enum-attribute.md @@ -13,7 +13,7 @@ databases.createEnumAttribute( "", // databaseId "", // collectionId "", // key - listOf(), // elements + List.of(), // elements false, // required "", // default (optional) false, // array (optional) diff --git a/docs/examples/1.8.x/server-kotlin/java/databases/create-index.md b/docs/examples/1.8.x/server-kotlin/java/databases/create-index.md index fe2d9bf66d..1fb1efb826 100644 --- a/docs/examples/1.8.x/server-kotlin/java/databases/create-index.md +++ b/docs/examples/1.8.x/server-kotlin/java/databases/create-index.md @@ -15,9 +15,9 @@ databases.createIndex( "", // collectionId "", // key IndexType.KEY, // type - listOf(), // attributes - listOf(), // orders (optional) - listOf(), // lengths (optional) + List.of(), // attributes + List.of(), // orders (optional) + List.of(), // lengths (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); diff --git a/docs/examples/1.8.x/server-kotlin/java/databases/create-line-attribute.md b/docs/examples/1.8.x/server-kotlin/java/databases/create-line-attribute.md index ad988b8773..c0daeac6c3 100644 --- a/docs/examples/1.8.x/server-kotlin/java/databases/create-line-attribute.md +++ b/docs/examples/1.8.x/server-kotlin/java/databases/create-line-attribute.md @@ -14,7 +14,7 @@ databases.createLineAttribute( "", // collectionId "", // key false, // required - listOf([1, 2], [3, 4], [5, 6]), // default (optional) + List.of(List.of(1, 2), List.of(3, 4), List.of(5, 6)), // default (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); diff --git a/docs/examples/1.8.x/server-kotlin/java/databases/create-operations.md b/docs/examples/1.8.x/server-kotlin/java/databases/create-operations.md index 2dad8a15ac..c935f82a9a 100644 --- a/docs/examples/1.8.x/server-kotlin/java/databases/create-operations.md +++ b/docs/examples/1.8.x/server-kotlin/java/databases/create-operations.md @@ -11,17 +11,15 @@ Databases databases = new Databases(client); databases.createOperations( "", // transactionId - listOf( - { - "action": "create", - "databaseId": "", - "collectionId": "", - "documentId": "", - "data": { - "name": "Walter O'Brien" - } - } - ), // operations (optional) + List.of(Map.of( + "action", "create", + "databaseId", "", + "collectionId", "", + "documentId", "", + "data", Map.of( + "name", "Walter O'Brien" + ) + )), // operations (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); diff --git a/docs/examples/1.8.x/server-kotlin/java/databases/create-point-attribute.md b/docs/examples/1.8.x/server-kotlin/java/databases/create-point-attribute.md index 89d7cc7177..c3b5d503b2 100644 --- a/docs/examples/1.8.x/server-kotlin/java/databases/create-point-attribute.md +++ b/docs/examples/1.8.x/server-kotlin/java/databases/create-point-attribute.md @@ -14,7 +14,7 @@ databases.createPointAttribute( "", // collectionId "", // key false, // required - listOf(1, 2), // default (optional) + List.of(1, 2), // default (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); diff --git a/docs/examples/1.8.x/server-kotlin/java/databases/create-polygon-attribute.md b/docs/examples/1.8.x/server-kotlin/java/databases/create-polygon-attribute.md index 556fb38481..4f8fe0369e 100644 --- a/docs/examples/1.8.x/server-kotlin/java/databases/create-polygon-attribute.md +++ b/docs/examples/1.8.x/server-kotlin/java/databases/create-polygon-attribute.md @@ -14,7 +14,7 @@ databases.createPolygonAttribute( "", // collectionId "", // key false, // required - listOf([[1, 2], [3, 4], [5, 6], [1, 2]]), // default (optional) + List.of(List.of(List.of(1, 2), List.of(3, 4), List.of(5, 6), List.of(1, 2))), // default (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); diff --git a/docs/examples/1.8.x/server-kotlin/java/databases/delete-documents.md b/docs/examples/1.8.x/server-kotlin/java/databases/delete-documents.md index 958c40c382..f535ae7780 100644 --- a/docs/examples/1.8.x/server-kotlin/java/databases/delete-documents.md +++ b/docs/examples/1.8.x/server-kotlin/java/databases/delete-documents.md @@ -12,7 +12,7 @@ Databases databases = new Databases(client); databases.deleteDocuments( "", // databaseId "", // collectionId - listOf(), // queries (optional) + List.of(), // queries (optional) "", // transactionId (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { diff --git a/docs/examples/1.8.x/server-kotlin/java/databases/get-document.md b/docs/examples/1.8.x/server-kotlin/java/databases/get-document.md index 489447f599..d3e8b8b3d4 100644 --- a/docs/examples/1.8.x/server-kotlin/java/databases/get-document.md +++ b/docs/examples/1.8.x/server-kotlin/java/databases/get-document.md @@ -13,7 +13,7 @@ databases.getDocument( "", // databaseId "", // collectionId "", // documentId - listOf(), // queries (optional) + List.of(), // queries (optional) "", // transactionId (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { diff --git a/docs/examples/1.8.x/server-kotlin/java/databases/list-attributes.md b/docs/examples/1.8.x/server-kotlin/java/databases/list-attributes.md index b1b3bd1b9c..dd883e232f 100644 --- a/docs/examples/1.8.x/server-kotlin/java/databases/list-attributes.md +++ b/docs/examples/1.8.x/server-kotlin/java/databases/list-attributes.md @@ -12,7 +12,7 @@ Databases databases = new Databases(client); databases.listAttributes( "", // databaseId "", // collectionId - listOf(), // queries (optional) + List.of(), // queries (optional) false, // total (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { diff --git a/docs/examples/1.8.x/server-kotlin/java/databases/list-collections.md b/docs/examples/1.8.x/server-kotlin/java/databases/list-collections.md index efb0e7f89b..ddb47c9989 100644 --- a/docs/examples/1.8.x/server-kotlin/java/databases/list-collections.md +++ b/docs/examples/1.8.x/server-kotlin/java/databases/list-collections.md @@ -11,7 +11,7 @@ Databases databases = new Databases(client); databases.listCollections( "", // databaseId - listOf(), // queries (optional) + List.of(), // queries (optional) "", // search (optional) false, // total (optional) new CoroutineCallback<>((result, error) -> { diff --git a/docs/examples/1.8.x/server-kotlin/java/databases/list-documents.md b/docs/examples/1.8.x/server-kotlin/java/databases/list-documents.md index 472d15ba27..b8ef0717ea 100644 --- a/docs/examples/1.8.x/server-kotlin/java/databases/list-documents.md +++ b/docs/examples/1.8.x/server-kotlin/java/databases/list-documents.md @@ -12,7 +12,7 @@ Databases databases = new Databases(client); databases.listDocuments( "", // databaseId "", // collectionId - listOf(), // queries (optional) + List.of(), // queries (optional) "", // transactionId (optional) false, // total (optional) new CoroutineCallback<>((result, error) -> { diff --git a/docs/examples/1.8.x/server-kotlin/java/databases/list-indexes.md b/docs/examples/1.8.x/server-kotlin/java/databases/list-indexes.md index 5715af7d47..c701904157 100644 --- a/docs/examples/1.8.x/server-kotlin/java/databases/list-indexes.md +++ b/docs/examples/1.8.x/server-kotlin/java/databases/list-indexes.md @@ -12,7 +12,7 @@ Databases databases = new Databases(client); databases.listIndexes( "", // databaseId "", // collectionId - listOf(), // queries (optional) + List.of(), // queries (optional) false, // total (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { diff --git a/docs/examples/1.8.x/server-kotlin/java/databases/list-transactions.md b/docs/examples/1.8.x/server-kotlin/java/databases/list-transactions.md index 281fc1205b..8a6f60544f 100644 --- a/docs/examples/1.8.x/server-kotlin/java/databases/list-transactions.md +++ b/docs/examples/1.8.x/server-kotlin/java/databases/list-transactions.md @@ -10,7 +10,7 @@ Client client = new Client() Databases databases = new Databases(client); databases.listTransactions( - listOf(), // queries (optional) + List.of(), // queries (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); diff --git a/docs/examples/1.8.x/server-kotlin/java/databases/list.md b/docs/examples/1.8.x/server-kotlin/java/databases/list.md index a3f2d51fed..32ef454a73 100644 --- a/docs/examples/1.8.x/server-kotlin/java/databases/list.md +++ b/docs/examples/1.8.x/server-kotlin/java/databases/list.md @@ -10,7 +10,7 @@ Client client = new Client() Databases databases = new Databases(client); databases.list( - listOf(), // queries (optional) + List.of(), // queries (optional) "", // search (optional) false, // total (optional) new CoroutineCallback<>((result, error) -> { diff --git a/docs/examples/1.8.x/server-kotlin/java/databases/update-collection.md b/docs/examples/1.8.x/server-kotlin/java/databases/update-collection.md index 24d312da8c..898f1aafe5 100644 --- a/docs/examples/1.8.x/server-kotlin/java/databases/update-collection.md +++ b/docs/examples/1.8.x/server-kotlin/java/databases/update-collection.md @@ -1,8 +1,8 @@ import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; -import io.appwrite.services.Databases; import io.appwrite.Permission; import io.appwrite.Role; +import io.appwrite.services.Databases; Client client = new Client() .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint @@ -15,7 +15,7 @@ databases.updateCollection( "", // databaseId "", // collectionId "", // name - listOf(Permission.read(Role.any())), // permissions (optional) + List.of(Permission.read(Role.any())), // permissions (optional) false, // documentSecurity (optional) false, // enabled (optional) new CoroutineCallback<>((result, error) -> { diff --git a/docs/examples/1.8.x/server-kotlin/java/databases/update-document.md b/docs/examples/1.8.x/server-kotlin/java/databases/update-document.md index 749de99fce..0638225bfd 100644 --- a/docs/examples/1.8.x/server-kotlin/java/databases/update-document.md +++ b/docs/examples/1.8.x/server-kotlin/java/databases/update-document.md @@ -1,8 +1,8 @@ import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; -import io.appwrite.services.Databases; import io.appwrite.Permission; import io.appwrite.Role; +import io.appwrite.services.Databases; Client client = new Client() .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint @@ -15,8 +15,8 @@ databases.updateDocument( "", // databaseId "", // collectionId "", // documentId - mapOf( "a" to "b" ), // data (optional) - listOf(Permission.read(Role.any())), // permissions (optional) + Map.of("a", "b"), // data (optional) + List.of(Permission.read(Role.any())), // permissions (optional) "", // transactionId (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { diff --git a/docs/examples/1.8.x/server-kotlin/java/databases/update-documents.md b/docs/examples/1.8.x/server-kotlin/java/databases/update-documents.md index a685ac81fc..57a82f56f9 100644 --- a/docs/examples/1.8.x/server-kotlin/java/databases/update-documents.md +++ b/docs/examples/1.8.x/server-kotlin/java/databases/update-documents.md @@ -12,8 +12,8 @@ Databases databases = new Databases(client); databases.updateDocuments( "", // databaseId "", // collectionId - mapOf( "a" to "b" ), // data (optional) - listOf(), // queries (optional) + Map.of("a", "b"), // data (optional) + List.of(), // queries (optional) "", // transactionId (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { diff --git a/docs/examples/1.8.x/server-kotlin/java/databases/update-enum-attribute.md b/docs/examples/1.8.x/server-kotlin/java/databases/update-enum-attribute.md index 89606806d9..8870e372a6 100644 --- a/docs/examples/1.8.x/server-kotlin/java/databases/update-enum-attribute.md +++ b/docs/examples/1.8.x/server-kotlin/java/databases/update-enum-attribute.md @@ -13,7 +13,7 @@ databases.updateEnumAttribute( "", // databaseId "", // collectionId "", // key - listOf(), // elements + List.of(), // elements false, // required "", // default "", // newKey (optional) diff --git a/docs/examples/1.8.x/server-kotlin/java/databases/update-line-attribute.md b/docs/examples/1.8.x/server-kotlin/java/databases/update-line-attribute.md index 6a4265bbda..14d83eeee5 100644 --- a/docs/examples/1.8.x/server-kotlin/java/databases/update-line-attribute.md +++ b/docs/examples/1.8.x/server-kotlin/java/databases/update-line-attribute.md @@ -14,7 +14,7 @@ databases.updateLineAttribute( "", // collectionId "", // key false, // required - listOf([1, 2], [3, 4], [5, 6]), // default (optional) + List.of(List.of(1, 2), List.of(3, 4), List.of(5, 6)), // default (optional) "", // newKey (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { diff --git a/docs/examples/1.8.x/server-kotlin/java/databases/update-point-attribute.md b/docs/examples/1.8.x/server-kotlin/java/databases/update-point-attribute.md index 38d48c27e8..5d44d7a190 100644 --- a/docs/examples/1.8.x/server-kotlin/java/databases/update-point-attribute.md +++ b/docs/examples/1.8.x/server-kotlin/java/databases/update-point-attribute.md @@ -14,7 +14,7 @@ databases.updatePointAttribute( "", // collectionId "", // key false, // required - listOf(1, 2), // default (optional) + List.of(1, 2), // default (optional) "", // newKey (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { diff --git a/docs/examples/1.8.x/server-kotlin/java/databases/update-polygon-attribute.md b/docs/examples/1.8.x/server-kotlin/java/databases/update-polygon-attribute.md index 6e6fd08575..2c530b9aae 100644 --- a/docs/examples/1.8.x/server-kotlin/java/databases/update-polygon-attribute.md +++ b/docs/examples/1.8.x/server-kotlin/java/databases/update-polygon-attribute.md @@ -14,7 +14,7 @@ databases.updatePolygonAttribute( "", // collectionId "", // key false, // required - listOf([[1, 2], [3, 4], [5, 6], [1, 2]]), // default (optional) + List.of(List.of(List.of(1, 2), List.of(3, 4), List.of(5, 6), List.of(1, 2))), // default (optional) "", // newKey (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { diff --git a/docs/examples/1.8.x/server-kotlin/java/databases/upsert-document.md b/docs/examples/1.8.x/server-kotlin/java/databases/upsert-document.md index 4f156bbf8b..ec99390e15 100644 --- a/docs/examples/1.8.x/server-kotlin/java/databases/upsert-document.md +++ b/docs/examples/1.8.x/server-kotlin/java/databases/upsert-document.md @@ -1,8 +1,8 @@ import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; -import io.appwrite.services.Databases; import io.appwrite.Permission; import io.appwrite.Role; +import io.appwrite.services.Databases; Client client = new Client() .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint @@ -15,8 +15,8 @@ databases.upsertDocument( "", // databaseId "", // collectionId "", // documentId - mapOf( "a" to "b" ), // data - listOf(Permission.read(Role.any())), // permissions (optional) + Map.of("a", "b"), // data + List.of(Permission.read(Role.any())), // permissions (optional) "", // transactionId (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { diff --git a/docs/examples/1.8.x/server-kotlin/java/databases/upsert-documents.md b/docs/examples/1.8.x/server-kotlin/java/databases/upsert-documents.md index b8fcd8781a..ee4450fc29 100644 --- a/docs/examples/1.8.x/server-kotlin/java/databases/upsert-documents.md +++ b/docs/examples/1.8.x/server-kotlin/java/databases/upsert-documents.md @@ -12,7 +12,7 @@ Databases databases = new Databases(client); databases.upsertDocuments( "", // databaseId "", // collectionId - listOf(), // documents + List.of(), // documents "", // transactionId (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { diff --git a/docs/examples/1.8.x/server-kotlin/java/functions/create-execution.md b/docs/examples/1.8.x/server-kotlin/java/functions/create-execution.md index 4abfe236b3..98a4d1b572 100644 --- a/docs/examples/1.8.x/server-kotlin/java/functions/create-execution.md +++ b/docs/examples/1.8.x/server-kotlin/java/functions/create-execution.md @@ -16,7 +16,7 @@ functions.createExecution( false, // async (optional) "", // path (optional) ExecutionMethod.GET, // method (optional) - mapOf( "a" to "b" ), // headers (optional) + Map.of("a", "b"), // headers (optional) "", // scheduledAt (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { diff --git a/docs/examples/1.8.x/server-kotlin/java/functions/create-template-deployment.md b/docs/examples/1.8.x/server-kotlin/java/functions/create-template-deployment.md index 9a0642b04e..59c105dfb1 100644 --- a/docs/examples/1.8.x/server-kotlin/java/functions/create-template-deployment.md +++ b/docs/examples/1.8.x/server-kotlin/java/functions/create-template-deployment.md @@ -1,7 +1,7 @@ import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Functions; -import io.appwrite.enums.Type; +import io.appwrite.enums.TemplateReferenceType; Client client = new Client() .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint @@ -15,7 +15,7 @@ functions.createTemplateDeployment( "", // repository "", // owner "", // rootDirectory - .COMMIT, // type + TemplateReferenceType.COMMIT, // type "", // reference false, // activate (optional) new CoroutineCallback<>((result, error) -> { diff --git a/docs/examples/1.8.x/server-kotlin/java/functions/create-vcs-deployment.md b/docs/examples/1.8.x/server-kotlin/java/functions/create-vcs-deployment.md index 9274cd88c7..25f2f28485 100644 --- a/docs/examples/1.8.x/server-kotlin/java/functions/create-vcs-deployment.md +++ b/docs/examples/1.8.x/server-kotlin/java/functions/create-vcs-deployment.md @@ -1,7 +1,7 @@ import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Functions; -import io.appwrite.enums.VCSDeploymentType; +import io.appwrite.enums.VCSReferenceType; Client client = new Client() .setEndpoint("https://.cloud.appwrite.io/v1") // Your API Endpoint @@ -12,7 +12,7 @@ Functions functions = new Functions(client); functions.createVcsDeployment( "", // functionId - VCSDeploymentType.BRANCH, // type + VCSReferenceType.BRANCH, // type "", // reference false, // activate (optional) new CoroutineCallback<>((result, error) -> { diff --git a/docs/examples/1.8.x/server-kotlin/java/functions/create.md b/docs/examples/1.8.x/server-kotlin/java/functions/create.md index 587fd54b9b..17e291140a 100644 --- a/docs/examples/1.8.x/server-kotlin/java/functions/create.md +++ b/docs/examples/1.8.x/server-kotlin/java/functions/create.md @@ -14,15 +14,15 @@ functions.create( "", // functionId "", // name Runtime.NODE_14_5, // runtime - listOf("any"), // execute (optional) - listOf(), // events (optional) + List.of("any"), // execute (optional) + List.of(), // events (optional) "", // schedule (optional) 1, // timeout (optional) false, // enabled (optional) false, // logging (optional) "", // entrypoint (optional) "", // commands (optional) - listOf(), // scopes (optional) + List.of(), // scopes (optional) "", // installationId (optional) "", // providerRepositoryId (optional) "", // providerBranch (optional) diff --git a/docs/examples/1.8.x/server-kotlin/java/functions/list-deployments.md b/docs/examples/1.8.x/server-kotlin/java/functions/list-deployments.md index a0ea8b68b3..a92cf0792a 100644 --- a/docs/examples/1.8.x/server-kotlin/java/functions/list-deployments.md +++ b/docs/examples/1.8.x/server-kotlin/java/functions/list-deployments.md @@ -11,7 +11,7 @@ Functions functions = new Functions(client); functions.listDeployments( "", // functionId - listOf(), // queries (optional) + List.of(), // queries (optional) "", // search (optional) false, // total (optional) new CoroutineCallback<>((result, error) -> { diff --git a/docs/examples/1.8.x/server-kotlin/java/functions/list-executions.md b/docs/examples/1.8.x/server-kotlin/java/functions/list-executions.md index 8026d4730b..2b97ab3be6 100644 --- a/docs/examples/1.8.x/server-kotlin/java/functions/list-executions.md +++ b/docs/examples/1.8.x/server-kotlin/java/functions/list-executions.md @@ -11,7 +11,7 @@ Functions functions = new Functions(client); functions.listExecutions( "", // functionId - listOf(), // queries (optional) + List.of(), // queries (optional) false, // total (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { diff --git a/docs/examples/1.8.x/server-kotlin/java/functions/list.md b/docs/examples/1.8.x/server-kotlin/java/functions/list.md index 5d0f59c3b8..712510db4c 100644 --- a/docs/examples/1.8.x/server-kotlin/java/functions/list.md +++ b/docs/examples/1.8.x/server-kotlin/java/functions/list.md @@ -10,7 +10,7 @@ Client client = new Client() Functions functions = new Functions(client); functions.list( - listOf(), // queries (optional) + List.of(), // queries (optional) "", // search (optional) false, // total (optional) new CoroutineCallback<>((result, error) -> { diff --git a/docs/examples/1.8.x/server-kotlin/java/functions/update.md b/docs/examples/1.8.x/server-kotlin/java/functions/update.md index 5cb560e200..4b54a6361d 100644 --- a/docs/examples/1.8.x/server-kotlin/java/functions/update.md +++ b/docs/examples/1.8.x/server-kotlin/java/functions/update.md @@ -14,15 +14,15 @@ functions.update( "", // functionId "", // name Runtime.NODE_14_5, // runtime (optional) - listOf("any"), // execute (optional) - listOf(), // events (optional) + List.of("any"), // execute (optional) + List.of(), // events (optional) "", // schedule (optional) 1, // timeout (optional) false, // enabled (optional) false, // logging (optional) "", // entrypoint (optional) "", // commands (optional) - listOf(), // scopes (optional) + List.of(), // scopes (optional) "", // installationId (optional) "", // providerRepositoryId (optional) "", // providerBranch (optional) diff --git a/docs/examples/1.8.x/server-kotlin/java/graphql/mutation.md b/docs/examples/1.8.x/server-kotlin/java/graphql/mutation.md index 778892457b..baf41a8a65 100644 --- a/docs/examples/1.8.x/server-kotlin/java/graphql/mutation.md +++ b/docs/examples/1.8.x/server-kotlin/java/graphql/mutation.md @@ -10,7 +10,7 @@ Client client = new Client() Graphql graphql = new Graphql(client); graphql.mutation( - mapOf( "a" to "b" ), // query + Map.of("a", "b"), // query new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); diff --git a/docs/examples/1.8.x/server-kotlin/java/graphql/query.md b/docs/examples/1.8.x/server-kotlin/java/graphql/query.md index e109d523f8..381da3fb5a 100644 --- a/docs/examples/1.8.x/server-kotlin/java/graphql/query.md +++ b/docs/examples/1.8.x/server-kotlin/java/graphql/query.md @@ -10,7 +10,7 @@ Client client = new Client() Graphql graphql = new Graphql(client); graphql.query( - mapOf( "a" to "b" ), // query + Map.of("a", "b"), // query new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); diff --git a/docs/examples/1.8.x/server-kotlin/java/messaging/create-email.md b/docs/examples/1.8.x/server-kotlin/java/messaging/create-email.md index d6ab5ee1bf..ca654e6a08 100644 --- a/docs/examples/1.8.x/server-kotlin/java/messaging/create-email.md +++ b/docs/examples/1.8.x/server-kotlin/java/messaging/create-email.md @@ -13,12 +13,12 @@ messaging.createEmail( "", // messageId "", // subject "", // content - listOf(), // topics (optional) - listOf(), // users (optional) - listOf(), // targets (optional) - listOf(), // cc (optional) - listOf(), // bcc (optional) - listOf(), // attachments (optional) + List.of(), // topics (optional) + List.of(), // users (optional) + List.of(), // targets (optional) + List.of(), // cc (optional) + List.of(), // bcc (optional) + List.of(), // attachments (optional) false, // draft (optional) false, // html (optional) "", // scheduledAt (optional) diff --git a/docs/examples/1.8.x/server-kotlin/java/messaging/create-fcm-provider.md b/docs/examples/1.8.x/server-kotlin/java/messaging/create-fcm-provider.md index 0d67e28cf0..554ab3cbcf 100644 --- a/docs/examples/1.8.x/server-kotlin/java/messaging/create-fcm-provider.md +++ b/docs/examples/1.8.x/server-kotlin/java/messaging/create-fcm-provider.md @@ -12,7 +12,7 @@ Messaging messaging = new Messaging(client); messaging.createFCMProvider( "", // providerId "", // name - mapOf( "a" to "b" ), // serviceAccountJSON (optional) + Map.of("a", "b"), // serviceAccountJSON (optional) false, // enabled (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { diff --git a/docs/examples/1.8.x/server-kotlin/java/messaging/create-push.md b/docs/examples/1.8.x/server-kotlin/java/messaging/create-push.md index 94119e6a93..7ab3541a7d 100644 --- a/docs/examples/1.8.x/server-kotlin/java/messaging/create-push.md +++ b/docs/examples/1.8.x/server-kotlin/java/messaging/create-push.md @@ -14,10 +14,10 @@ messaging.createPush( "", // messageId "", // title (optional) "<BODY>", // body (optional) - listOf(), // topics (optional) - listOf(), // users (optional) - listOf(), // targets (optional) - mapOf( "a" to "b" ), // data (optional) + List.of(), // topics (optional) + List.of(), // users (optional) + List.of(), // targets (optional) + Map.of("a", "b"), // data (optional) "<ACTION>", // action (optional) "<ID1:ID2>", // image (optional) "<ICON>", // icon (optional) diff --git a/docs/examples/1.8.x/server-kotlin/java/messaging/create-sms.md b/docs/examples/1.8.x/server-kotlin/java/messaging/create-sms.md index ca40cc33a8..5ac95079f7 100644 --- a/docs/examples/1.8.x/server-kotlin/java/messaging/create-sms.md +++ b/docs/examples/1.8.x/server-kotlin/java/messaging/create-sms.md @@ -12,9 +12,9 @@ Messaging messaging = new Messaging(client); messaging.createSMS( "<MESSAGE_ID>", // messageId "<CONTENT>", // content - listOf(), // topics (optional) - listOf(), // users (optional) - listOf(), // targets (optional) + List.of(), // topics (optional) + List.of(), // users (optional) + List.of(), // targets (optional) false, // draft (optional) "", // scheduledAt (optional) new CoroutineCallback<>((result, error) -> { diff --git a/docs/examples/1.8.x/server-kotlin/java/messaging/create-topic.md b/docs/examples/1.8.x/server-kotlin/java/messaging/create-topic.md index 63a24b467d..ec55053d42 100644 --- a/docs/examples/1.8.x/server-kotlin/java/messaging/create-topic.md +++ b/docs/examples/1.8.x/server-kotlin/java/messaging/create-topic.md @@ -12,7 +12,7 @@ Messaging messaging = new Messaging(client); messaging.createTopic( "<TOPIC_ID>", // topicId "<NAME>", // name - listOf("any"), // subscribe (optional) + List.of("any"), // subscribe (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); diff --git a/docs/examples/1.8.x/server-kotlin/java/messaging/list-message-logs.md b/docs/examples/1.8.x/server-kotlin/java/messaging/list-message-logs.md index 253299ccec..30469ffc09 100644 --- a/docs/examples/1.8.x/server-kotlin/java/messaging/list-message-logs.md +++ b/docs/examples/1.8.x/server-kotlin/java/messaging/list-message-logs.md @@ -11,7 +11,7 @@ Messaging messaging = new Messaging(client); messaging.listMessageLogs( "<MESSAGE_ID>", // messageId - listOf(), // queries (optional) + List.of(), // queries (optional) false, // total (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { diff --git a/docs/examples/1.8.x/server-kotlin/java/messaging/list-messages.md b/docs/examples/1.8.x/server-kotlin/java/messaging/list-messages.md index 6535222999..8af8085d8e 100644 --- a/docs/examples/1.8.x/server-kotlin/java/messaging/list-messages.md +++ b/docs/examples/1.8.x/server-kotlin/java/messaging/list-messages.md @@ -10,7 +10,7 @@ Client client = new Client() Messaging messaging = new Messaging(client); messaging.listMessages( - listOf(), // queries (optional) + List.of(), // queries (optional) "<SEARCH>", // search (optional) false, // total (optional) new CoroutineCallback<>((result, error) -> { diff --git a/docs/examples/1.8.x/server-kotlin/java/messaging/list-provider-logs.md b/docs/examples/1.8.x/server-kotlin/java/messaging/list-provider-logs.md index 3bce21144d..e62dedf89d 100644 --- a/docs/examples/1.8.x/server-kotlin/java/messaging/list-provider-logs.md +++ b/docs/examples/1.8.x/server-kotlin/java/messaging/list-provider-logs.md @@ -11,7 +11,7 @@ Messaging messaging = new Messaging(client); messaging.listProviderLogs( "<PROVIDER_ID>", // providerId - listOf(), // queries (optional) + List.of(), // queries (optional) false, // total (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { diff --git a/docs/examples/1.8.x/server-kotlin/java/messaging/list-providers.md b/docs/examples/1.8.x/server-kotlin/java/messaging/list-providers.md index 115cd41745..b52408c02d 100644 --- a/docs/examples/1.8.x/server-kotlin/java/messaging/list-providers.md +++ b/docs/examples/1.8.x/server-kotlin/java/messaging/list-providers.md @@ -10,7 +10,7 @@ Client client = new Client() Messaging messaging = new Messaging(client); messaging.listProviders( - listOf(), // queries (optional) + List.of(), // queries (optional) "<SEARCH>", // search (optional) false, // total (optional) new CoroutineCallback<>((result, error) -> { diff --git a/docs/examples/1.8.x/server-kotlin/java/messaging/list-subscriber-logs.md b/docs/examples/1.8.x/server-kotlin/java/messaging/list-subscriber-logs.md index e2e1e94667..317db32986 100644 --- a/docs/examples/1.8.x/server-kotlin/java/messaging/list-subscriber-logs.md +++ b/docs/examples/1.8.x/server-kotlin/java/messaging/list-subscriber-logs.md @@ -11,7 +11,7 @@ Messaging messaging = new Messaging(client); messaging.listSubscriberLogs( "<SUBSCRIBER_ID>", // subscriberId - listOf(), // queries (optional) + List.of(), // queries (optional) false, // total (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { diff --git a/docs/examples/1.8.x/server-kotlin/java/messaging/list-subscribers.md b/docs/examples/1.8.x/server-kotlin/java/messaging/list-subscribers.md index 3b87ed865d..cc552179de 100644 --- a/docs/examples/1.8.x/server-kotlin/java/messaging/list-subscribers.md +++ b/docs/examples/1.8.x/server-kotlin/java/messaging/list-subscribers.md @@ -11,7 +11,7 @@ Messaging messaging = new Messaging(client); messaging.listSubscribers( "<TOPIC_ID>", // topicId - listOf(), // queries (optional) + List.of(), // queries (optional) "<SEARCH>", // search (optional) false, // total (optional) new CoroutineCallback<>((result, error) -> { diff --git a/docs/examples/1.8.x/server-kotlin/java/messaging/list-targets.md b/docs/examples/1.8.x/server-kotlin/java/messaging/list-targets.md index db6aee1f68..b123218d22 100644 --- a/docs/examples/1.8.x/server-kotlin/java/messaging/list-targets.md +++ b/docs/examples/1.8.x/server-kotlin/java/messaging/list-targets.md @@ -11,7 +11,7 @@ Messaging messaging = new Messaging(client); messaging.listTargets( "<MESSAGE_ID>", // messageId - listOf(), // queries (optional) + List.of(), // queries (optional) false, // total (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { diff --git a/docs/examples/1.8.x/server-kotlin/java/messaging/list-topic-logs.md b/docs/examples/1.8.x/server-kotlin/java/messaging/list-topic-logs.md index 800bb6b7d2..d2d8809575 100644 --- a/docs/examples/1.8.x/server-kotlin/java/messaging/list-topic-logs.md +++ b/docs/examples/1.8.x/server-kotlin/java/messaging/list-topic-logs.md @@ -11,7 +11,7 @@ Messaging messaging = new Messaging(client); messaging.listTopicLogs( "<TOPIC_ID>", // topicId - listOf(), // queries (optional) + List.of(), // queries (optional) false, // total (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { diff --git a/docs/examples/1.8.x/server-kotlin/java/messaging/list-topics.md b/docs/examples/1.8.x/server-kotlin/java/messaging/list-topics.md index bd53b41d21..ea258c2759 100644 --- a/docs/examples/1.8.x/server-kotlin/java/messaging/list-topics.md +++ b/docs/examples/1.8.x/server-kotlin/java/messaging/list-topics.md @@ -10,7 +10,7 @@ Client client = new Client() Messaging messaging = new Messaging(client); messaging.listTopics( - listOf(), // queries (optional) + List.of(), // queries (optional) "<SEARCH>", // search (optional) false, // total (optional) new CoroutineCallback<>((result, error) -> { diff --git a/docs/examples/1.8.x/server-kotlin/java/messaging/update-email.md b/docs/examples/1.8.x/server-kotlin/java/messaging/update-email.md index 56e9767861..1bed63b313 100644 --- a/docs/examples/1.8.x/server-kotlin/java/messaging/update-email.md +++ b/docs/examples/1.8.x/server-kotlin/java/messaging/update-email.md @@ -11,17 +11,17 @@ Messaging messaging = new Messaging(client); messaging.updateEmail( "<MESSAGE_ID>", // messageId - listOf(), // topics (optional) - listOf(), // users (optional) - listOf(), // targets (optional) + List.of(), // topics (optional) + List.of(), // users (optional) + List.of(), // targets (optional) "<SUBJECT>", // subject (optional) "<CONTENT>", // content (optional) false, // draft (optional) false, // html (optional) - listOf(), // cc (optional) - listOf(), // bcc (optional) + List.of(), // cc (optional) + List.of(), // bcc (optional) "", // scheduledAt (optional) - listOf(), // attachments (optional) + List.of(), // attachments (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); diff --git a/docs/examples/1.8.x/server-kotlin/java/messaging/update-fcm-provider.md b/docs/examples/1.8.x/server-kotlin/java/messaging/update-fcm-provider.md index dd92f9321f..2976ff5a53 100644 --- a/docs/examples/1.8.x/server-kotlin/java/messaging/update-fcm-provider.md +++ b/docs/examples/1.8.x/server-kotlin/java/messaging/update-fcm-provider.md @@ -13,7 +13,7 @@ messaging.updateFCMProvider( "<PROVIDER_ID>", // providerId "<NAME>", // name (optional) false, // enabled (optional) - mapOf( "a" to "b" ), // serviceAccountJSON (optional) + Map.of("a", "b"), // serviceAccountJSON (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); diff --git a/docs/examples/1.8.x/server-kotlin/java/messaging/update-push.md b/docs/examples/1.8.x/server-kotlin/java/messaging/update-push.md index 80ad6fcd30..80f70130e5 100644 --- a/docs/examples/1.8.x/server-kotlin/java/messaging/update-push.md +++ b/docs/examples/1.8.x/server-kotlin/java/messaging/update-push.md @@ -12,12 +12,12 @@ Messaging messaging = new Messaging(client); messaging.updatePush( "<MESSAGE_ID>", // messageId - listOf(), // topics (optional) - listOf(), // users (optional) - listOf(), // targets (optional) + List.of(), // topics (optional) + List.of(), // users (optional) + List.of(), // targets (optional) "<TITLE>", // title (optional) "<BODY>", // body (optional) - mapOf( "a" to "b" ), // data (optional) + Map.of("a", "b"), // data (optional) "<ACTION>", // action (optional) "<ID1:ID2>", // image (optional) "<ICON>", // icon (optional) diff --git a/docs/examples/1.8.x/server-kotlin/java/messaging/update-sms.md b/docs/examples/1.8.x/server-kotlin/java/messaging/update-sms.md index c59505b68a..4df9588f55 100644 --- a/docs/examples/1.8.x/server-kotlin/java/messaging/update-sms.md +++ b/docs/examples/1.8.x/server-kotlin/java/messaging/update-sms.md @@ -11,9 +11,9 @@ Messaging messaging = new Messaging(client); messaging.updateSMS( "<MESSAGE_ID>", // messageId - listOf(), // topics (optional) - listOf(), // users (optional) - listOf(), // targets (optional) + List.of(), // topics (optional) + List.of(), // users (optional) + List.of(), // targets (optional) "<CONTENT>", // content (optional) false, // draft (optional) "", // scheduledAt (optional) diff --git a/docs/examples/1.8.x/server-kotlin/java/messaging/update-topic.md b/docs/examples/1.8.x/server-kotlin/java/messaging/update-topic.md index be9c44dc23..0d651c7895 100644 --- a/docs/examples/1.8.x/server-kotlin/java/messaging/update-topic.md +++ b/docs/examples/1.8.x/server-kotlin/java/messaging/update-topic.md @@ -12,7 +12,7 @@ Messaging messaging = new Messaging(client); messaging.updateTopic( "<TOPIC_ID>", // topicId "<NAME>", // name (optional) - listOf("any"), // subscribe (optional) + List.of("any"), // subscribe (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); diff --git a/docs/examples/1.8.x/server-kotlin/java/sites/create-template-deployment.md b/docs/examples/1.8.x/server-kotlin/java/sites/create-template-deployment.md index a501461a2c..e7fee585a7 100644 --- a/docs/examples/1.8.x/server-kotlin/java/sites/create-template-deployment.md +++ b/docs/examples/1.8.x/server-kotlin/java/sites/create-template-deployment.md @@ -1,7 +1,7 @@ import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Sites; -import io.appwrite.enums.Type; +import io.appwrite.enums.TemplateReferenceType; Client client = new Client() .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint @@ -15,7 +15,7 @@ sites.createTemplateDeployment( "<REPOSITORY>", // repository "<OWNER>", // owner "<ROOT_DIRECTORY>", // rootDirectory - .BRANCH, // type + TemplateReferenceType.BRANCH, // type "<REFERENCE>", // reference false, // activate (optional) new CoroutineCallback<>((result, error) -> { diff --git a/docs/examples/1.8.x/server-kotlin/java/sites/create-vcs-deployment.md b/docs/examples/1.8.x/server-kotlin/java/sites/create-vcs-deployment.md index 754eb26419..8a1dca95cc 100644 --- a/docs/examples/1.8.x/server-kotlin/java/sites/create-vcs-deployment.md +++ b/docs/examples/1.8.x/server-kotlin/java/sites/create-vcs-deployment.md @@ -1,7 +1,7 @@ import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.services.Sites; -import io.appwrite.enums.VCSDeploymentType; +import io.appwrite.enums.VCSReferenceType; Client client = new Client() .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint @@ -12,7 +12,7 @@ Sites sites = new Sites(client); sites.createVcsDeployment( "<SITE_ID>", // siteId - VCSDeploymentType.BRANCH, // type + VCSReferenceType.BRANCH, // type "<REFERENCE>", // reference false, // activate (optional) new CoroutineCallback<>((result, error) -> { diff --git a/docs/examples/1.8.x/server-kotlin/java/sites/list-deployments.md b/docs/examples/1.8.x/server-kotlin/java/sites/list-deployments.md index a19f4ecf09..a1953cdf6e 100644 --- a/docs/examples/1.8.x/server-kotlin/java/sites/list-deployments.md +++ b/docs/examples/1.8.x/server-kotlin/java/sites/list-deployments.md @@ -11,7 +11,7 @@ Sites sites = new Sites(client); sites.listDeployments( "<SITE_ID>", // siteId - listOf(), // queries (optional) + List.of(), // queries (optional) "<SEARCH>", // search (optional) false, // total (optional) new CoroutineCallback<>((result, error) -> { diff --git a/docs/examples/1.8.x/server-kotlin/java/sites/list-logs.md b/docs/examples/1.8.x/server-kotlin/java/sites/list-logs.md index 85c2171aa2..095f0ae97a 100644 --- a/docs/examples/1.8.x/server-kotlin/java/sites/list-logs.md +++ b/docs/examples/1.8.x/server-kotlin/java/sites/list-logs.md @@ -11,7 +11,7 @@ Sites sites = new Sites(client); sites.listLogs( "<SITE_ID>", // siteId - listOf(), // queries (optional) + List.of(), // queries (optional) false, // total (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { diff --git a/docs/examples/1.8.x/server-kotlin/java/sites/list.md b/docs/examples/1.8.x/server-kotlin/java/sites/list.md index 20b5533c7a..d8c69419fe 100644 --- a/docs/examples/1.8.x/server-kotlin/java/sites/list.md +++ b/docs/examples/1.8.x/server-kotlin/java/sites/list.md @@ -10,7 +10,7 @@ Client client = new Client() Sites sites = new Sites(client); sites.list( - listOf(), // queries (optional) + List.of(), // queries (optional) "<SEARCH>", // search (optional) false, // total (optional) new CoroutineCallback<>((result, error) -> { diff --git a/docs/examples/1.8.x/server-kotlin/java/storage/create-bucket.md b/docs/examples/1.8.x/server-kotlin/java/storage/create-bucket.md index b228408933..0af282a0ca 100644 --- a/docs/examples/1.8.x/server-kotlin/java/storage/create-bucket.md +++ b/docs/examples/1.8.x/server-kotlin/java/storage/create-bucket.md @@ -1,9 +1,9 @@ import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; -import io.appwrite.services.Storage; -import io.appwrite.enums.Compression; import io.appwrite.Permission; import io.appwrite.Role; +import io.appwrite.services.Storage; +import io.appwrite.enums.Compression; Client client = new Client() .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint @@ -15,11 +15,11 @@ Storage storage = new Storage(client); storage.createBucket( "<BUCKET_ID>", // bucketId "<NAME>", // name - listOf(Permission.read(Role.any())), // permissions (optional) + List.of(Permission.read(Role.any())), // permissions (optional) false, // fileSecurity (optional) false, // enabled (optional) 1, // maximumFileSize (optional) - listOf(), // allowedFileExtensions (optional) + List.of(), // allowedFileExtensions (optional) Compression.NONE, // compression (optional) false, // encryption (optional) false, // antivirus (optional) diff --git a/docs/examples/1.8.x/server-kotlin/java/storage/create-file.md b/docs/examples/1.8.x/server-kotlin/java/storage/create-file.md index 7ddf8913e1..abaeee3eca 100644 --- a/docs/examples/1.8.x/server-kotlin/java/storage/create-file.md +++ b/docs/examples/1.8.x/server-kotlin/java/storage/create-file.md @@ -1,9 +1,9 @@ import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; import io.appwrite.models.InputFile; -import io.appwrite.services.Storage; import io.appwrite.Permission; import io.appwrite.Role; +import io.appwrite.services.Storage; Client client = new Client() .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint @@ -16,7 +16,7 @@ storage.createFile( "<BUCKET_ID>", // bucketId "<FILE_ID>", // fileId InputFile.fromPath("file.png"), // file - listOf(Permission.read(Role.any())), // permissions (optional) + List.of(Permission.read(Role.any())), // permissions (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); diff --git a/docs/examples/1.8.x/server-kotlin/java/storage/list-buckets.md b/docs/examples/1.8.x/server-kotlin/java/storage/list-buckets.md index 504cfb7ce2..3fde32c73c 100644 --- a/docs/examples/1.8.x/server-kotlin/java/storage/list-buckets.md +++ b/docs/examples/1.8.x/server-kotlin/java/storage/list-buckets.md @@ -10,7 +10,7 @@ Client client = new Client() Storage storage = new Storage(client); storage.listBuckets( - listOf(), // queries (optional) + List.of(), // queries (optional) "<SEARCH>", // search (optional) false, // total (optional) new CoroutineCallback<>((result, error) -> { diff --git a/docs/examples/1.8.x/server-kotlin/java/storage/list-files.md b/docs/examples/1.8.x/server-kotlin/java/storage/list-files.md index 6397ca07bc..712c420011 100644 --- a/docs/examples/1.8.x/server-kotlin/java/storage/list-files.md +++ b/docs/examples/1.8.x/server-kotlin/java/storage/list-files.md @@ -11,7 +11,7 @@ Storage storage = new Storage(client); storage.listFiles( "<BUCKET_ID>", // bucketId - listOf(), // queries (optional) + List.of(), // queries (optional) "<SEARCH>", // search (optional) false, // total (optional) new CoroutineCallback<>((result, error) -> { diff --git a/docs/examples/1.8.x/server-kotlin/java/storage/update-bucket.md b/docs/examples/1.8.x/server-kotlin/java/storage/update-bucket.md index 26758cf19a..47b322e449 100644 --- a/docs/examples/1.8.x/server-kotlin/java/storage/update-bucket.md +++ b/docs/examples/1.8.x/server-kotlin/java/storage/update-bucket.md @@ -1,9 +1,9 @@ import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; -import io.appwrite.services.Storage; -import io.appwrite.enums.Compression; import io.appwrite.Permission; import io.appwrite.Role; +import io.appwrite.services.Storage; +import io.appwrite.enums.Compression; Client client = new Client() .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint @@ -15,11 +15,11 @@ Storage storage = new Storage(client); storage.updateBucket( "<BUCKET_ID>", // bucketId "<NAME>", // name - listOf(Permission.read(Role.any())), // permissions (optional) + List.of(Permission.read(Role.any())), // permissions (optional) false, // fileSecurity (optional) false, // enabled (optional) 1, // maximumFileSize (optional) - listOf(), // allowedFileExtensions (optional) + List.of(), // allowedFileExtensions (optional) Compression.NONE, // compression (optional) false, // encryption (optional) false, // antivirus (optional) diff --git a/docs/examples/1.8.x/server-kotlin/java/storage/update-file.md b/docs/examples/1.8.x/server-kotlin/java/storage/update-file.md index d534e0eb64..b394fd5672 100644 --- a/docs/examples/1.8.x/server-kotlin/java/storage/update-file.md +++ b/docs/examples/1.8.x/server-kotlin/java/storage/update-file.md @@ -1,8 +1,8 @@ import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; -import io.appwrite.services.Storage; import io.appwrite.Permission; import io.appwrite.Role; +import io.appwrite.services.Storage; Client client = new Client() .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint @@ -15,7 +15,7 @@ storage.updateFile( "<BUCKET_ID>", // bucketId "<FILE_ID>", // fileId "<NAME>", // name (optional) - listOf(Permission.read(Role.any())), // permissions (optional) + List.of(Permission.read(Role.any())), // permissions (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); diff --git a/docs/examples/1.8.x/server-kotlin/java/tablesdb/create-enum-column.md b/docs/examples/1.8.x/server-kotlin/java/tablesdb/create-enum-column.md index bc31bbe79e..ea0452f250 100644 --- a/docs/examples/1.8.x/server-kotlin/java/tablesdb/create-enum-column.md +++ b/docs/examples/1.8.x/server-kotlin/java/tablesdb/create-enum-column.md @@ -13,7 +13,7 @@ tablesDB.createEnumColumn( "<DATABASE_ID>", // databaseId "<TABLE_ID>", // tableId "", // key - listOf(), // elements + List.of(), // elements false, // required "<DEFAULT>", // default (optional) false, // array (optional) diff --git a/docs/examples/1.8.x/server-kotlin/java/tablesdb/create-index.md b/docs/examples/1.8.x/server-kotlin/java/tablesdb/create-index.md index 991bd3429b..07076d9763 100644 --- a/docs/examples/1.8.x/server-kotlin/java/tablesdb/create-index.md +++ b/docs/examples/1.8.x/server-kotlin/java/tablesdb/create-index.md @@ -15,9 +15,9 @@ tablesDB.createIndex( "<TABLE_ID>", // tableId "", // key IndexType.KEY, // type - listOf(), // columns - listOf(), // orders (optional) - listOf(), // lengths (optional) + List.of(), // columns + List.of(), // orders (optional) + List.of(), // lengths (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); diff --git a/docs/examples/1.8.x/server-kotlin/java/tablesdb/create-line-column.md b/docs/examples/1.8.x/server-kotlin/java/tablesdb/create-line-column.md index afe029ebe8..f8351cac79 100644 --- a/docs/examples/1.8.x/server-kotlin/java/tablesdb/create-line-column.md +++ b/docs/examples/1.8.x/server-kotlin/java/tablesdb/create-line-column.md @@ -14,7 +14,7 @@ tablesDB.createLineColumn( "<TABLE_ID>", // tableId "", // key false, // required - listOf([1, 2], [3, 4], [5, 6]), // default (optional) + List.of(List.of(1, 2), List.of(3, 4), List.of(5, 6)), // default (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); diff --git a/docs/examples/1.8.x/server-kotlin/java/tablesdb/create-operations.md b/docs/examples/1.8.x/server-kotlin/java/tablesdb/create-operations.md index 9504f623b3..a7c3a1bcb8 100644 --- a/docs/examples/1.8.x/server-kotlin/java/tablesdb/create-operations.md +++ b/docs/examples/1.8.x/server-kotlin/java/tablesdb/create-operations.md @@ -11,17 +11,15 @@ TablesDB tablesDB = new TablesDB(client); tablesDB.createOperations( "<TRANSACTION_ID>", // transactionId - listOf( - { - "action": "create", - "databaseId": "<DATABASE_ID>", - "tableId": "<TABLE_ID>", - "rowId": "<ROW_ID>", - "data": { - "name": "Walter O'Brien" - } - } - ), // operations (optional) + List.of(Map.of( + "action", "create", + "databaseId", "<DATABASE_ID>", + "tableId", "<TABLE_ID>", + "rowId", "<ROW_ID>", + "data", Map.of( + "name", "Walter O'Brien" + ) + )), // operations (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); diff --git a/docs/examples/1.8.x/server-kotlin/java/tablesdb/create-point-column.md b/docs/examples/1.8.x/server-kotlin/java/tablesdb/create-point-column.md index 2c9941b09c..2f6ae0e937 100644 --- a/docs/examples/1.8.x/server-kotlin/java/tablesdb/create-point-column.md +++ b/docs/examples/1.8.x/server-kotlin/java/tablesdb/create-point-column.md @@ -14,7 +14,7 @@ tablesDB.createPointColumn( "<TABLE_ID>", // tableId "", // key false, // required - listOf(1, 2), // default (optional) + List.of(1, 2), // default (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); diff --git a/docs/examples/1.8.x/server-kotlin/java/tablesdb/create-polygon-column.md b/docs/examples/1.8.x/server-kotlin/java/tablesdb/create-polygon-column.md index 58ca798381..58caa8a791 100644 --- a/docs/examples/1.8.x/server-kotlin/java/tablesdb/create-polygon-column.md +++ b/docs/examples/1.8.x/server-kotlin/java/tablesdb/create-polygon-column.md @@ -14,7 +14,7 @@ tablesDB.createPolygonColumn( "<TABLE_ID>", // tableId "", // key false, // required - listOf([[1, 2], [3, 4], [5, 6], [1, 2]]), // default (optional) + List.of(List.of(List.of(1, 2), List.of(3, 4), List.of(5, 6), List.of(1, 2))), // default (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); diff --git a/docs/examples/1.8.x/server-kotlin/java/tablesdb/create-row.md b/docs/examples/1.8.x/server-kotlin/java/tablesdb/create-row.md index 9e47167cd1..3da8c3248e 100644 --- a/docs/examples/1.8.x/server-kotlin/java/tablesdb/create-row.md +++ b/docs/examples/1.8.x/server-kotlin/java/tablesdb/create-row.md @@ -1,8 +1,8 @@ import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; -import io.appwrite.services.TablesDB; import io.appwrite.Permission; import io.appwrite.Role; +import io.appwrite.services.TablesDB; Client client = new Client() .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint @@ -15,14 +15,14 @@ tablesDB.createRow( "<DATABASE_ID>", // databaseId "<TABLE_ID>", // tableId "<ROW_ID>", // rowId - mapOf( - "username" to "walter.obrien", - "email" to "walter.obrien@example.com", - "fullName" to "Walter O'Brien", - "age" to 30, - "isAdmin" to false + Map.of( + "username", "walter.obrien", + "email", "walter.obrien@example.com", + "fullName", "Walter O'Brien", + "age", 30, + "isAdmin", false ), // data - listOf(Permission.read(Role.any())), // permissions (optional) + List.of(Permission.read(Role.any())), // permissions (optional) "<TRANSACTION_ID>", // transactionId (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { diff --git a/docs/examples/1.8.x/server-kotlin/java/tablesdb/create-rows.md b/docs/examples/1.8.x/server-kotlin/java/tablesdb/create-rows.md index 956d812165..e99ea04da4 100644 --- a/docs/examples/1.8.x/server-kotlin/java/tablesdb/create-rows.md +++ b/docs/examples/1.8.x/server-kotlin/java/tablesdb/create-rows.md @@ -12,7 +12,7 @@ TablesDB tablesDB = new TablesDB(client); tablesDB.createRows( "<DATABASE_ID>", // databaseId "<TABLE_ID>", // tableId - listOf(), // rows + List.of(), // rows "<TRANSACTION_ID>", // transactionId (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { diff --git a/docs/examples/1.8.x/server-kotlin/java/tablesdb/create-table.md b/docs/examples/1.8.x/server-kotlin/java/tablesdb/create-table.md index 1f9fd10d5c..615278a2d4 100644 --- a/docs/examples/1.8.x/server-kotlin/java/tablesdb/create-table.md +++ b/docs/examples/1.8.x/server-kotlin/java/tablesdb/create-table.md @@ -1,8 +1,8 @@ import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; -import io.appwrite.services.TablesDB; import io.appwrite.Permission; import io.appwrite.Role; +import io.appwrite.services.TablesDB; Client client = new Client() .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint @@ -15,7 +15,7 @@ tablesDB.createTable( "<DATABASE_ID>", // databaseId "<TABLE_ID>", // tableId "<NAME>", // name - listOf(Permission.read(Role.any())), // permissions (optional) + List.of(Permission.read(Role.any())), // permissions (optional) false, // rowSecurity (optional) false, // enabled (optional) new CoroutineCallback<>((result, error) -> { diff --git a/docs/examples/1.8.x/server-kotlin/java/tablesdb/delete-rows.md b/docs/examples/1.8.x/server-kotlin/java/tablesdb/delete-rows.md index 80ca0bb40f..f90789d276 100644 --- a/docs/examples/1.8.x/server-kotlin/java/tablesdb/delete-rows.md +++ b/docs/examples/1.8.x/server-kotlin/java/tablesdb/delete-rows.md @@ -12,7 +12,7 @@ TablesDB tablesDB = new TablesDB(client); tablesDB.deleteRows( "<DATABASE_ID>", // databaseId "<TABLE_ID>", // tableId - listOf(), // queries (optional) + List.of(), // queries (optional) "<TRANSACTION_ID>", // transactionId (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { diff --git a/docs/examples/1.8.x/server-kotlin/java/tablesdb/get-row.md b/docs/examples/1.8.x/server-kotlin/java/tablesdb/get-row.md index d642ebcaf1..5b18e8f879 100644 --- a/docs/examples/1.8.x/server-kotlin/java/tablesdb/get-row.md +++ b/docs/examples/1.8.x/server-kotlin/java/tablesdb/get-row.md @@ -13,7 +13,7 @@ tablesDB.getRow( "<DATABASE_ID>", // databaseId "<TABLE_ID>", // tableId "<ROW_ID>", // rowId - listOf(), // queries (optional) + List.of(), // queries (optional) "<TRANSACTION_ID>", // transactionId (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { diff --git a/docs/examples/1.8.x/server-kotlin/java/tablesdb/list-columns.md b/docs/examples/1.8.x/server-kotlin/java/tablesdb/list-columns.md index bdf376cb33..f7e37f970b 100644 --- a/docs/examples/1.8.x/server-kotlin/java/tablesdb/list-columns.md +++ b/docs/examples/1.8.x/server-kotlin/java/tablesdb/list-columns.md @@ -12,7 +12,7 @@ TablesDB tablesDB = new TablesDB(client); tablesDB.listColumns( "<DATABASE_ID>", // databaseId "<TABLE_ID>", // tableId - listOf(), // queries (optional) + List.of(), // queries (optional) false, // total (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { diff --git a/docs/examples/1.8.x/server-kotlin/java/tablesdb/list-indexes.md b/docs/examples/1.8.x/server-kotlin/java/tablesdb/list-indexes.md index 5b73204314..ca792c5470 100644 --- a/docs/examples/1.8.x/server-kotlin/java/tablesdb/list-indexes.md +++ b/docs/examples/1.8.x/server-kotlin/java/tablesdb/list-indexes.md @@ -12,7 +12,7 @@ TablesDB tablesDB = new TablesDB(client); tablesDB.listIndexes( "<DATABASE_ID>", // databaseId "<TABLE_ID>", // tableId - listOf(), // queries (optional) + List.of(), // queries (optional) false, // total (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { diff --git a/docs/examples/1.8.x/server-kotlin/java/tablesdb/list-rows.md b/docs/examples/1.8.x/server-kotlin/java/tablesdb/list-rows.md index 8d7956bbc6..7903da4b32 100644 --- a/docs/examples/1.8.x/server-kotlin/java/tablesdb/list-rows.md +++ b/docs/examples/1.8.x/server-kotlin/java/tablesdb/list-rows.md @@ -12,7 +12,7 @@ TablesDB tablesDB = new TablesDB(client); tablesDB.listRows( "<DATABASE_ID>", // databaseId "<TABLE_ID>", // tableId - listOf(), // queries (optional) + List.of(), // queries (optional) "<TRANSACTION_ID>", // transactionId (optional) false, // total (optional) new CoroutineCallback<>((result, error) -> { diff --git a/docs/examples/1.8.x/server-kotlin/java/tablesdb/list-tables.md b/docs/examples/1.8.x/server-kotlin/java/tablesdb/list-tables.md index 646be0f4a4..9b4c88bdc0 100644 --- a/docs/examples/1.8.x/server-kotlin/java/tablesdb/list-tables.md +++ b/docs/examples/1.8.x/server-kotlin/java/tablesdb/list-tables.md @@ -11,7 +11,7 @@ TablesDB tablesDB = new TablesDB(client); tablesDB.listTables( "<DATABASE_ID>", // databaseId - listOf(), // queries (optional) + List.of(), // queries (optional) "<SEARCH>", // search (optional) false, // total (optional) new CoroutineCallback<>((result, error) -> { diff --git a/docs/examples/1.8.x/server-kotlin/java/tablesdb/list-transactions.md b/docs/examples/1.8.x/server-kotlin/java/tablesdb/list-transactions.md index acc4902da4..f0b4db05db 100644 --- a/docs/examples/1.8.x/server-kotlin/java/tablesdb/list-transactions.md +++ b/docs/examples/1.8.x/server-kotlin/java/tablesdb/list-transactions.md @@ -10,7 +10,7 @@ Client client = new Client() TablesDB tablesDB = new TablesDB(client); tablesDB.listTransactions( - listOf(), // queries (optional) + List.of(), // queries (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); diff --git a/docs/examples/1.8.x/server-kotlin/java/tablesdb/list.md b/docs/examples/1.8.x/server-kotlin/java/tablesdb/list.md index 98784df806..cf23036591 100644 --- a/docs/examples/1.8.x/server-kotlin/java/tablesdb/list.md +++ b/docs/examples/1.8.x/server-kotlin/java/tablesdb/list.md @@ -10,7 +10,7 @@ Client client = new Client() TablesDB tablesDB = new TablesDB(client); tablesDB.list( - listOf(), // queries (optional) + List.of(), // queries (optional) "<SEARCH>", // search (optional) false, // total (optional) new CoroutineCallback<>((result, error) -> { diff --git a/docs/examples/1.8.x/server-kotlin/java/tablesdb/update-enum-column.md b/docs/examples/1.8.x/server-kotlin/java/tablesdb/update-enum-column.md index 5a8036aebc..0f182b76f0 100644 --- a/docs/examples/1.8.x/server-kotlin/java/tablesdb/update-enum-column.md +++ b/docs/examples/1.8.x/server-kotlin/java/tablesdb/update-enum-column.md @@ -13,7 +13,7 @@ tablesDB.updateEnumColumn( "<DATABASE_ID>", // databaseId "<TABLE_ID>", // tableId "", // key - listOf(), // elements + List.of(), // elements false, // required "<DEFAULT>", // default "", // newKey (optional) diff --git a/docs/examples/1.8.x/server-kotlin/java/tablesdb/update-line-column.md b/docs/examples/1.8.x/server-kotlin/java/tablesdb/update-line-column.md index 4c65a907f7..4ef648fbc9 100644 --- a/docs/examples/1.8.x/server-kotlin/java/tablesdb/update-line-column.md +++ b/docs/examples/1.8.x/server-kotlin/java/tablesdb/update-line-column.md @@ -14,7 +14,7 @@ tablesDB.updateLineColumn( "<TABLE_ID>", // tableId "", // key false, // required - listOf([1, 2], [3, 4], [5, 6]), // default (optional) + List.of(List.of(1, 2), List.of(3, 4), List.of(5, 6)), // default (optional) "", // newKey (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { diff --git a/docs/examples/1.8.x/server-kotlin/java/tablesdb/update-point-column.md b/docs/examples/1.8.x/server-kotlin/java/tablesdb/update-point-column.md index 56ac86a6f0..729595dfa6 100644 --- a/docs/examples/1.8.x/server-kotlin/java/tablesdb/update-point-column.md +++ b/docs/examples/1.8.x/server-kotlin/java/tablesdb/update-point-column.md @@ -14,7 +14,7 @@ tablesDB.updatePointColumn( "<TABLE_ID>", // tableId "", // key false, // required - listOf(1, 2), // default (optional) + List.of(1, 2), // default (optional) "", // newKey (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { diff --git a/docs/examples/1.8.x/server-kotlin/java/tablesdb/update-polygon-column.md b/docs/examples/1.8.x/server-kotlin/java/tablesdb/update-polygon-column.md index 189d473175..bc6ccc93b5 100644 --- a/docs/examples/1.8.x/server-kotlin/java/tablesdb/update-polygon-column.md +++ b/docs/examples/1.8.x/server-kotlin/java/tablesdb/update-polygon-column.md @@ -14,7 +14,7 @@ tablesDB.updatePolygonColumn( "<TABLE_ID>", // tableId "", // key false, // required - listOf([[1, 2], [3, 4], [5, 6], [1, 2]]), // default (optional) + List.of(List.of(List.of(1, 2), List.of(3, 4), List.of(5, 6), List.of(1, 2))), // default (optional) "", // newKey (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { diff --git a/docs/examples/1.8.x/server-kotlin/java/tablesdb/update-row.md b/docs/examples/1.8.x/server-kotlin/java/tablesdb/update-row.md index 835f63b19a..5585274cbb 100644 --- a/docs/examples/1.8.x/server-kotlin/java/tablesdb/update-row.md +++ b/docs/examples/1.8.x/server-kotlin/java/tablesdb/update-row.md @@ -1,8 +1,8 @@ import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; -import io.appwrite.services.TablesDB; import io.appwrite.Permission; import io.appwrite.Role; +import io.appwrite.services.TablesDB; Client client = new Client() .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint @@ -15,8 +15,8 @@ tablesDB.updateRow( "<DATABASE_ID>", // databaseId "<TABLE_ID>", // tableId "<ROW_ID>", // rowId - mapOf( "a" to "b" ), // data (optional) - listOf(Permission.read(Role.any())), // permissions (optional) + Map.of("a", "b"), // data (optional) + List.of(Permission.read(Role.any())), // permissions (optional) "<TRANSACTION_ID>", // transactionId (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { diff --git a/docs/examples/1.8.x/server-kotlin/java/tablesdb/update-rows.md b/docs/examples/1.8.x/server-kotlin/java/tablesdb/update-rows.md index 7d39e4422c..b479613856 100644 --- a/docs/examples/1.8.x/server-kotlin/java/tablesdb/update-rows.md +++ b/docs/examples/1.8.x/server-kotlin/java/tablesdb/update-rows.md @@ -12,8 +12,8 @@ TablesDB tablesDB = new TablesDB(client); tablesDB.updateRows( "<DATABASE_ID>", // databaseId "<TABLE_ID>", // tableId - mapOf( "a" to "b" ), // data (optional) - listOf(), // queries (optional) + Map.of("a", "b"), // data (optional) + List.of(), // queries (optional) "<TRANSACTION_ID>", // transactionId (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { diff --git a/docs/examples/1.8.x/server-kotlin/java/tablesdb/update-table.md b/docs/examples/1.8.x/server-kotlin/java/tablesdb/update-table.md index 257803b984..cf0c2fbc2b 100644 --- a/docs/examples/1.8.x/server-kotlin/java/tablesdb/update-table.md +++ b/docs/examples/1.8.x/server-kotlin/java/tablesdb/update-table.md @@ -1,8 +1,8 @@ import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; -import io.appwrite.services.TablesDB; import io.appwrite.Permission; import io.appwrite.Role; +import io.appwrite.services.TablesDB; Client client = new Client() .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint @@ -15,7 +15,7 @@ tablesDB.updateTable( "<DATABASE_ID>", // databaseId "<TABLE_ID>", // tableId "<NAME>", // name - listOf(Permission.read(Role.any())), // permissions (optional) + List.of(Permission.read(Role.any())), // permissions (optional) false, // rowSecurity (optional) false, // enabled (optional) new CoroutineCallback<>((result, error) -> { diff --git a/docs/examples/1.8.x/server-kotlin/java/tablesdb/upsert-row.md b/docs/examples/1.8.x/server-kotlin/java/tablesdb/upsert-row.md index 6ea29e3e8d..adb2095f34 100644 --- a/docs/examples/1.8.x/server-kotlin/java/tablesdb/upsert-row.md +++ b/docs/examples/1.8.x/server-kotlin/java/tablesdb/upsert-row.md @@ -1,8 +1,8 @@ import io.appwrite.Client; import io.appwrite.coroutines.CoroutineCallback; -import io.appwrite.services.TablesDB; import io.appwrite.Permission; import io.appwrite.Role; +import io.appwrite.services.TablesDB; Client client = new Client() .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint @@ -15,8 +15,8 @@ tablesDB.upsertRow( "<DATABASE_ID>", // databaseId "<TABLE_ID>", // tableId "<ROW_ID>", // rowId - mapOf( "a" to "b" ), // data (optional) - listOf(Permission.read(Role.any())), // permissions (optional) + Map.of("a", "b"), // data (optional) + List.of(Permission.read(Role.any())), // permissions (optional) "<TRANSACTION_ID>", // transactionId (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { diff --git a/docs/examples/1.8.x/server-kotlin/java/tablesdb/upsert-rows.md b/docs/examples/1.8.x/server-kotlin/java/tablesdb/upsert-rows.md index c4b2bf3857..e16ecb35c9 100644 --- a/docs/examples/1.8.x/server-kotlin/java/tablesdb/upsert-rows.md +++ b/docs/examples/1.8.x/server-kotlin/java/tablesdb/upsert-rows.md @@ -12,7 +12,7 @@ TablesDB tablesDB = new TablesDB(client); tablesDB.upsertRows( "<DATABASE_ID>", // databaseId "<TABLE_ID>", // tableId - listOf(), // rows + List.of(), // rows "<TRANSACTION_ID>", // transactionId (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { diff --git a/docs/examples/1.8.x/server-kotlin/java/teams/create-membership.md b/docs/examples/1.8.x/server-kotlin/java/teams/create-membership.md index 89e9d96ef6..e71ea2e578 100644 --- a/docs/examples/1.8.x/server-kotlin/java/teams/create-membership.md +++ b/docs/examples/1.8.x/server-kotlin/java/teams/create-membership.md @@ -11,7 +11,7 @@ Teams teams = new Teams(client); teams.createMembership( "<TEAM_ID>", // teamId - listOf(), // roles + List.of(), // roles "email@example.com", // email (optional) "<USER_ID>", // userId (optional) "+12065550100", // phone (optional) diff --git a/docs/examples/1.8.x/server-kotlin/java/teams/create.md b/docs/examples/1.8.x/server-kotlin/java/teams/create.md index 28cc3dada1..bc82cb47e1 100644 --- a/docs/examples/1.8.x/server-kotlin/java/teams/create.md +++ b/docs/examples/1.8.x/server-kotlin/java/teams/create.md @@ -12,7 +12,7 @@ Teams teams = new Teams(client); teams.create( "<TEAM_ID>", // teamId "<NAME>", // name - listOf(), // roles (optional) + List.of(), // roles (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); diff --git a/docs/examples/1.8.x/server-kotlin/java/teams/list-memberships.md b/docs/examples/1.8.x/server-kotlin/java/teams/list-memberships.md index bfbf519db8..5437e6048c 100644 --- a/docs/examples/1.8.x/server-kotlin/java/teams/list-memberships.md +++ b/docs/examples/1.8.x/server-kotlin/java/teams/list-memberships.md @@ -11,7 +11,7 @@ Teams teams = new Teams(client); teams.listMemberships( "<TEAM_ID>", // teamId - listOf(), // queries (optional) + List.of(), // queries (optional) "<SEARCH>", // search (optional) false, // total (optional) new CoroutineCallback<>((result, error) -> { diff --git a/docs/examples/1.8.x/server-kotlin/java/teams/list.md b/docs/examples/1.8.x/server-kotlin/java/teams/list.md index 7ff98ad86f..06f0034bfe 100644 --- a/docs/examples/1.8.x/server-kotlin/java/teams/list.md +++ b/docs/examples/1.8.x/server-kotlin/java/teams/list.md @@ -10,7 +10,7 @@ Client client = new Client() Teams teams = new Teams(client); teams.list( - listOf(), // queries (optional) + List.of(), // queries (optional) "<SEARCH>", // search (optional) false, // total (optional) new CoroutineCallback<>((result, error) -> { diff --git a/docs/examples/1.8.x/server-kotlin/java/teams/update-membership.md b/docs/examples/1.8.x/server-kotlin/java/teams/update-membership.md index d4816c57f1..2e0de4e3b0 100644 --- a/docs/examples/1.8.x/server-kotlin/java/teams/update-membership.md +++ b/docs/examples/1.8.x/server-kotlin/java/teams/update-membership.md @@ -12,7 +12,7 @@ Teams teams = new Teams(client); teams.updateMembership( "<TEAM_ID>", // teamId "<MEMBERSHIP_ID>", // membershipId - listOf(), // roles + List.of(), // roles new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); diff --git a/docs/examples/1.8.x/server-kotlin/java/teams/update-prefs.md b/docs/examples/1.8.x/server-kotlin/java/teams/update-prefs.md index 2ef05222df..85f18aef1e 100644 --- a/docs/examples/1.8.x/server-kotlin/java/teams/update-prefs.md +++ b/docs/examples/1.8.x/server-kotlin/java/teams/update-prefs.md @@ -11,7 +11,7 @@ Teams teams = new Teams(client); teams.updatePrefs( "<TEAM_ID>", // teamId - mapOf( "a" to "b" ), // prefs + Map.of("a", "b"), // prefs new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); diff --git a/docs/examples/1.8.x/server-kotlin/java/tokens/list.md b/docs/examples/1.8.x/server-kotlin/java/tokens/list.md index 23c51a64ef..1147f6f536 100644 --- a/docs/examples/1.8.x/server-kotlin/java/tokens/list.md +++ b/docs/examples/1.8.x/server-kotlin/java/tokens/list.md @@ -12,7 +12,7 @@ Tokens tokens = new Tokens(client); tokens.list( "<BUCKET_ID>", // bucketId "<FILE_ID>", // fileId - listOf(), // queries (optional) + List.of(), // queries (optional) false, // total (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { diff --git a/docs/examples/1.8.x/server-kotlin/java/users/list-identities.md b/docs/examples/1.8.x/server-kotlin/java/users/list-identities.md index fc95c8e48c..2dfa297592 100644 --- a/docs/examples/1.8.x/server-kotlin/java/users/list-identities.md +++ b/docs/examples/1.8.x/server-kotlin/java/users/list-identities.md @@ -10,7 +10,7 @@ Client client = new Client() Users users = new Users(client); users.listIdentities( - listOf(), // queries (optional) + List.of(), // queries (optional) "<SEARCH>", // search (optional) false, // total (optional) new CoroutineCallback<>((result, error) -> { diff --git a/docs/examples/1.8.x/server-kotlin/java/users/list-logs.md b/docs/examples/1.8.x/server-kotlin/java/users/list-logs.md index 4a2e549ed4..ebaa749f56 100644 --- a/docs/examples/1.8.x/server-kotlin/java/users/list-logs.md +++ b/docs/examples/1.8.x/server-kotlin/java/users/list-logs.md @@ -11,7 +11,7 @@ Users users = new Users(client); users.listLogs( "<USER_ID>", // userId - listOf(), // queries (optional) + List.of(), // queries (optional) false, // total (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { diff --git a/docs/examples/1.8.x/server-kotlin/java/users/list-memberships.md b/docs/examples/1.8.x/server-kotlin/java/users/list-memberships.md index 36e67ae322..3335e18bc7 100644 --- a/docs/examples/1.8.x/server-kotlin/java/users/list-memberships.md +++ b/docs/examples/1.8.x/server-kotlin/java/users/list-memberships.md @@ -11,7 +11,7 @@ Users users = new Users(client); users.listMemberships( "<USER_ID>", // userId - listOf(), // queries (optional) + List.of(), // queries (optional) "<SEARCH>", // search (optional) false, // total (optional) new CoroutineCallback<>((result, error) -> { diff --git a/docs/examples/1.8.x/server-kotlin/java/users/list-targets.md b/docs/examples/1.8.x/server-kotlin/java/users/list-targets.md index 156aaefa7e..02fd291cb5 100644 --- a/docs/examples/1.8.x/server-kotlin/java/users/list-targets.md +++ b/docs/examples/1.8.x/server-kotlin/java/users/list-targets.md @@ -11,7 +11,7 @@ Users users = new Users(client); users.listTargets( "<USER_ID>", // userId - listOf(), // queries (optional) + List.of(), // queries (optional) false, // total (optional) new CoroutineCallback<>((result, error) -> { if (error != null) { diff --git a/docs/examples/1.8.x/server-kotlin/java/users/list.md b/docs/examples/1.8.x/server-kotlin/java/users/list.md index ec038afded..65ed4b00f8 100644 --- a/docs/examples/1.8.x/server-kotlin/java/users/list.md +++ b/docs/examples/1.8.x/server-kotlin/java/users/list.md @@ -10,7 +10,7 @@ Client client = new Client() Users users = new Users(client); users.list( - listOf(), // queries (optional) + List.of(), // queries (optional) "<SEARCH>", // search (optional) false, // total (optional) new CoroutineCallback<>((result, error) -> { diff --git a/docs/examples/1.8.x/server-kotlin/java/users/update-labels.md b/docs/examples/1.8.x/server-kotlin/java/users/update-labels.md index 379200a56b..953f466290 100644 --- a/docs/examples/1.8.x/server-kotlin/java/users/update-labels.md +++ b/docs/examples/1.8.x/server-kotlin/java/users/update-labels.md @@ -11,7 +11,7 @@ Users users = new Users(client); users.updateLabels( "<USER_ID>", // userId - listOf(), // labels + List.of(), // labels new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); diff --git a/docs/examples/1.8.x/server-kotlin/java/users/update-prefs.md b/docs/examples/1.8.x/server-kotlin/java/users/update-prefs.md index c5a9677a20..5a128aa716 100644 --- a/docs/examples/1.8.x/server-kotlin/java/users/update-prefs.md +++ b/docs/examples/1.8.x/server-kotlin/java/users/update-prefs.md @@ -11,7 +11,7 @@ Users users = new Users(client); users.updatePrefs( "<USER_ID>", // userId - mapOf( "a" to "b" ), // prefs + Map.of("a", "b"), // prefs new CoroutineCallback<>((result, error) -> { if (error != null) { error.printStackTrace(); diff --git a/docs/examples/1.8.x/server-kotlin/kotlin/databases/create-line-attribute.md b/docs/examples/1.8.x/server-kotlin/kotlin/databases/create-line-attribute.md index af9a4d2425..8f1322b3fd 100644 --- a/docs/examples/1.8.x/server-kotlin/kotlin/databases/create-line-attribute.md +++ b/docs/examples/1.8.x/server-kotlin/kotlin/databases/create-line-attribute.md @@ -14,5 +14,5 @@ val response = databases.createLineAttribute( collectionId = "<COLLECTION_ID>", key = "", required = false, - default = listOf([1, 2], [3, 4], [5, 6]) // optional + default = listOf(listOf(1, 2), listOf(3, 4), listOf(5, 6)) // optional ) diff --git a/docs/examples/1.8.x/server-kotlin/kotlin/databases/create-operations.md b/docs/examples/1.8.x/server-kotlin/kotlin/databases/create-operations.md index 1c741818b9..eae10ab609 100644 --- a/docs/examples/1.8.x/server-kotlin/kotlin/databases/create-operations.md +++ b/docs/examples/1.8.x/server-kotlin/kotlin/databases/create-operations.md @@ -11,15 +11,13 @@ val databases = Databases(client) val response = databases.createOperations( transactionId = "<TRANSACTION_ID>", - operations = listOf( - { - "action": "create", - "databaseId": "<DATABASE_ID>", - "collectionId": "<COLLECTION_ID>", - "documentId": "<DOCUMENT_ID>", - "data": { - "name": "Walter O'Brien" - } - } - ) // optional + operations = listOf(mapOf( + "action" to "create", + "databaseId" to "<DATABASE_ID>", + "collectionId" to "<COLLECTION_ID>", + "documentId" to "<DOCUMENT_ID>", + "data" to mapOf( + "name" to "Walter O'Brien" + ) + )) // optional ) diff --git a/docs/examples/1.8.x/server-kotlin/kotlin/databases/create-polygon-attribute.md b/docs/examples/1.8.x/server-kotlin/kotlin/databases/create-polygon-attribute.md index ffeb3c4398..5a3491c414 100644 --- a/docs/examples/1.8.x/server-kotlin/kotlin/databases/create-polygon-attribute.md +++ b/docs/examples/1.8.x/server-kotlin/kotlin/databases/create-polygon-attribute.md @@ -14,5 +14,5 @@ val response = databases.createPolygonAttribute( collectionId = "<COLLECTION_ID>", key = "", required = false, - default = listOf([[1, 2], [3, 4], [5, 6], [1, 2]]) // optional + default = listOf(listOf(listOf(1, 2), listOf(3, 4), listOf(5, 6), listOf(1, 2))) // optional ) diff --git a/docs/examples/1.8.x/server-kotlin/kotlin/databases/update-line-attribute.md b/docs/examples/1.8.x/server-kotlin/kotlin/databases/update-line-attribute.md index 0d6b40a2d8..0a8b50a332 100644 --- a/docs/examples/1.8.x/server-kotlin/kotlin/databases/update-line-attribute.md +++ b/docs/examples/1.8.x/server-kotlin/kotlin/databases/update-line-attribute.md @@ -14,6 +14,6 @@ val response = databases.updateLineAttribute( collectionId = "<COLLECTION_ID>", key = "", required = false, - default = listOf([1, 2], [3, 4], [5, 6]), // optional + default = listOf(listOf(1, 2), listOf(3, 4), listOf(5, 6)), // optional newKey = "" // optional ) diff --git a/docs/examples/1.8.x/server-kotlin/kotlin/databases/update-polygon-attribute.md b/docs/examples/1.8.x/server-kotlin/kotlin/databases/update-polygon-attribute.md index 66bbdea11c..37df6acee0 100644 --- a/docs/examples/1.8.x/server-kotlin/kotlin/databases/update-polygon-attribute.md +++ b/docs/examples/1.8.x/server-kotlin/kotlin/databases/update-polygon-attribute.md @@ -14,6 +14,6 @@ val response = databases.updatePolygonAttribute( collectionId = "<COLLECTION_ID>", key = "", required = false, - default = listOf([[1, 2], [3, 4], [5, 6], [1, 2]]), // optional + default = listOf(listOf(listOf(1, 2), listOf(3, 4), listOf(5, 6), listOf(1, 2))), // optional newKey = "" // optional ) diff --git a/docs/examples/1.8.x/server-kotlin/kotlin/functions/create-template-deployment.md b/docs/examples/1.8.x/server-kotlin/kotlin/functions/create-template-deployment.md index b8eb44b4fa..176620e19d 100644 --- a/docs/examples/1.8.x/server-kotlin/kotlin/functions/create-template-deployment.md +++ b/docs/examples/1.8.x/server-kotlin/kotlin/functions/create-template-deployment.md @@ -1,7 +1,7 @@ import io.appwrite.Client import io.appwrite.coroutines.CoroutineCallback import io.appwrite.services.Functions -import io.appwrite.enums.Type +import io.appwrite.enums.TemplateReferenceType val client = Client() .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint @@ -15,7 +15,7 @@ val response = functions.createTemplateDeployment( repository = "<REPOSITORY>", owner = "<OWNER>", rootDirectory = "<ROOT_DIRECTORY>", - type = .COMMIT, + type = TemplateReferenceType.COMMIT, reference = "<REFERENCE>", activate = false // optional ) diff --git a/docs/examples/1.8.x/server-kotlin/kotlin/functions/create-vcs-deployment.md b/docs/examples/1.8.x/server-kotlin/kotlin/functions/create-vcs-deployment.md index 08bb5a3097..4e1c21daeb 100644 --- a/docs/examples/1.8.x/server-kotlin/kotlin/functions/create-vcs-deployment.md +++ b/docs/examples/1.8.x/server-kotlin/kotlin/functions/create-vcs-deployment.md @@ -1,7 +1,7 @@ import io.appwrite.Client import io.appwrite.coroutines.CoroutineCallback import io.appwrite.services.Functions -import io.appwrite.enums.VCSDeploymentType +import io.appwrite.enums.VCSReferenceType val client = Client() .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint @@ -12,7 +12,7 @@ val functions = Functions(client) val response = functions.createVcsDeployment( functionId = "<FUNCTION_ID>", - type = VCSDeploymentType.BRANCH, + type = VCSReferenceType.BRANCH, reference = "<REFERENCE>", activate = false // optional ) diff --git a/docs/examples/1.8.x/server-kotlin/kotlin/sites/create-template-deployment.md b/docs/examples/1.8.x/server-kotlin/kotlin/sites/create-template-deployment.md index 1f3d7d386a..6ef7414bc4 100644 --- a/docs/examples/1.8.x/server-kotlin/kotlin/sites/create-template-deployment.md +++ b/docs/examples/1.8.x/server-kotlin/kotlin/sites/create-template-deployment.md @@ -1,7 +1,7 @@ import io.appwrite.Client import io.appwrite.coroutines.CoroutineCallback import io.appwrite.services.Sites -import io.appwrite.enums.Type +import io.appwrite.enums.TemplateReferenceType val client = Client() .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint @@ -15,7 +15,7 @@ val response = sites.createTemplateDeployment( repository = "<REPOSITORY>", owner = "<OWNER>", rootDirectory = "<ROOT_DIRECTORY>", - type = .BRANCH, + type = TemplateReferenceType.BRANCH, reference = "<REFERENCE>", activate = false // optional ) diff --git a/docs/examples/1.8.x/server-kotlin/kotlin/sites/create-vcs-deployment.md b/docs/examples/1.8.x/server-kotlin/kotlin/sites/create-vcs-deployment.md index 141cf3e658..c11e890fc0 100644 --- a/docs/examples/1.8.x/server-kotlin/kotlin/sites/create-vcs-deployment.md +++ b/docs/examples/1.8.x/server-kotlin/kotlin/sites/create-vcs-deployment.md @@ -1,7 +1,7 @@ import io.appwrite.Client import io.appwrite.coroutines.CoroutineCallback import io.appwrite.services.Sites -import io.appwrite.enums.VCSDeploymentType +import io.appwrite.enums.VCSReferenceType val client = Client() .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint @@ -12,7 +12,7 @@ val sites = Sites(client) val response = sites.createVcsDeployment( siteId = "<SITE_ID>", - type = VCSDeploymentType.BRANCH, + type = VCSReferenceType.BRANCH, reference = "<REFERENCE>", activate = false // optional ) diff --git a/docs/examples/1.8.x/server-kotlin/kotlin/tablesdb/create-line-column.md b/docs/examples/1.8.x/server-kotlin/kotlin/tablesdb/create-line-column.md index 3dd9ebd083..cede289439 100644 --- a/docs/examples/1.8.x/server-kotlin/kotlin/tablesdb/create-line-column.md +++ b/docs/examples/1.8.x/server-kotlin/kotlin/tablesdb/create-line-column.md @@ -14,5 +14,5 @@ val response = tablesDB.createLineColumn( tableId = "<TABLE_ID>", key = "", required = false, - default = listOf([1, 2], [3, 4], [5, 6]) // optional + default = listOf(listOf(1, 2), listOf(3, 4), listOf(5, 6)) // optional ) diff --git a/docs/examples/1.8.x/server-kotlin/kotlin/tablesdb/create-operations.md b/docs/examples/1.8.x/server-kotlin/kotlin/tablesdb/create-operations.md index 40c98d1c81..5b05949715 100644 --- a/docs/examples/1.8.x/server-kotlin/kotlin/tablesdb/create-operations.md +++ b/docs/examples/1.8.x/server-kotlin/kotlin/tablesdb/create-operations.md @@ -11,15 +11,13 @@ val tablesDB = TablesDB(client) val response = tablesDB.createOperations( transactionId = "<TRANSACTION_ID>", - operations = listOf( - { - "action": "create", - "databaseId": "<DATABASE_ID>", - "tableId": "<TABLE_ID>", - "rowId": "<ROW_ID>", - "data": { - "name": "Walter O'Brien" - } - } - ) // optional + operations = listOf(mapOf( + "action" to "create", + "databaseId" to "<DATABASE_ID>", + "tableId" to "<TABLE_ID>", + "rowId" to "<ROW_ID>", + "data" to mapOf( + "name" to "Walter O'Brien" + ) + )) // optional ) diff --git a/docs/examples/1.8.x/server-kotlin/kotlin/tablesdb/create-polygon-column.md b/docs/examples/1.8.x/server-kotlin/kotlin/tablesdb/create-polygon-column.md index 218b4cba33..c4282d1b67 100644 --- a/docs/examples/1.8.x/server-kotlin/kotlin/tablesdb/create-polygon-column.md +++ b/docs/examples/1.8.x/server-kotlin/kotlin/tablesdb/create-polygon-column.md @@ -14,5 +14,5 @@ val response = tablesDB.createPolygonColumn( tableId = "<TABLE_ID>", key = "", required = false, - default = listOf([[1, 2], [3, 4], [5, 6], [1, 2]]) // optional + default = listOf(listOf(listOf(1, 2), listOf(3, 4), listOf(5, 6), listOf(1, 2))) // optional ) diff --git a/docs/examples/1.8.x/server-kotlin/kotlin/tablesdb/update-line-column.md b/docs/examples/1.8.x/server-kotlin/kotlin/tablesdb/update-line-column.md index 571d25206c..4b975c0ee0 100644 --- a/docs/examples/1.8.x/server-kotlin/kotlin/tablesdb/update-line-column.md +++ b/docs/examples/1.8.x/server-kotlin/kotlin/tablesdb/update-line-column.md @@ -14,6 +14,6 @@ val response = tablesDB.updateLineColumn( tableId = "<TABLE_ID>", key = "", required = false, - default = listOf([1, 2], [3, 4], [5, 6]), // optional + default = listOf(listOf(1, 2), listOf(3, 4), listOf(5, 6)), // optional newKey = "" // optional ) diff --git a/docs/examples/1.8.x/server-kotlin/kotlin/tablesdb/update-polygon-column.md b/docs/examples/1.8.x/server-kotlin/kotlin/tablesdb/update-polygon-column.md index db3a46bc6c..4042847176 100644 --- a/docs/examples/1.8.x/server-kotlin/kotlin/tablesdb/update-polygon-column.md +++ b/docs/examples/1.8.x/server-kotlin/kotlin/tablesdb/update-polygon-column.md @@ -14,6 +14,6 @@ val response = tablesDB.updatePolygonColumn( tableId = "<TABLE_ID>", key = "", required = false, - default = listOf([[1, 2], [3, 4], [5, 6], [1, 2]]), // optional + default = listOf(listOf(listOf(1, 2), listOf(3, 4), listOf(5, 6), listOf(1, 2))), // optional newKey = "" // optional ) diff --git a/docs/examples/1.8.x/server-nodejs/examples/functions/create-template-deployment.md b/docs/examples/1.8.x/server-nodejs/examples/functions/create-template-deployment.md index 999c029f8f..f1efd7b199 100644 --- a/docs/examples/1.8.x/server-nodejs/examples/functions/create-template-deployment.md +++ b/docs/examples/1.8.x/server-nodejs/examples/functions/create-template-deployment.md @@ -12,7 +12,7 @@ const result = await functions.createTemplateDeployment({ repository: '<REPOSITORY>', owner: '<OWNER>', rootDirectory: '<ROOT_DIRECTORY>', - type: sdk..Commit, + type: sdk.TemplateReferenceType.Commit, reference: '<REFERENCE>', activate: false // optional }); diff --git a/docs/examples/1.8.x/server-nodejs/examples/functions/create-vcs-deployment.md b/docs/examples/1.8.x/server-nodejs/examples/functions/create-vcs-deployment.md index 0aabfcff8a..c648625531 100644 --- a/docs/examples/1.8.x/server-nodejs/examples/functions/create-vcs-deployment.md +++ b/docs/examples/1.8.x/server-nodejs/examples/functions/create-vcs-deployment.md @@ -9,7 +9,7 @@ const functions = new sdk.Functions(client); const result = await functions.createVcsDeployment({ functionId: '<FUNCTION_ID>', - type: sdk.VCSDeploymentType.Branch, + type: sdk.VCSReferenceType.Branch, reference: '<REFERENCE>', activate: false // optional }); diff --git a/docs/examples/1.8.x/server-nodejs/examples/sites/create-template-deployment.md b/docs/examples/1.8.x/server-nodejs/examples/sites/create-template-deployment.md index dc9736fa80..3728f7f846 100644 --- a/docs/examples/1.8.x/server-nodejs/examples/sites/create-template-deployment.md +++ b/docs/examples/1.8.x/server-nodejs/examples/sites/create-template-deployment.md @@ -12,7 +12,7 @@ const result = await sites.createTemplateDeployment({ repository: '<REPOSITORY>', owner: '<OWNER>', rootDirectory: '<ROOT_DIRECTORY>', - type: sdk..Branch, + type: sdk.TemplateReferenceType.Branch, reference: '<REFERENCE>', activate: false // optional }); diff --git a/docs/examples/1.8.x/server-nodejs/examples/sites/create-vcs-deployment.md b/docs/examples/1.8.x/server-nodejs/examples/sites/create-vcs-deployment.md index 7f7c2196ba..95cefea9ae 100644 --- a/docs/examples/1.8.x/server-nodejs/examples/sites/create-vcs-deployment.md +++ b/docs/examples/1.8.x/server-nodejs/examples/sites/create-vcs-deployment.md @@ -9,7 +9,7 @@ const sites = new sdk.Sites(client); const result = await sites.createVcsDeployment({ siteId: '<SITE_ID>', - type: sdk.VCSDeploymentType.Branch, + type: sdk.VCSReferenceType.Branch, reference: '<REFERENCE>', activate: false // optional }); diff --git a/docs/examples/1.8.x/server-php/examples/functions/create-template-deployment.md b/docs/examples/1.8.x/server-php/examples/functions/create-template-deployment.md index 5888396312..a1272523f7 100644 --- a/docs/examples/1.8.x/server-php/examples/functions/create-template-deployment.md +++ b/docs/examples/1.8.x/server-php/examples/functions/create-template-deployment.md @@ -2,7 +2,7 @@ use Appwrite\Client; use Appwrite\Services\Functions; -use Appwrite\Enums\Type; +use Appwrite\Enums\TemplateReferenceType; $client = (new Client()) ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint @@ -16,7 +16,7 @@ $result = $functions->createTemplateDeployment( repository: '<REPOSITORY>', owner: '<OWNER>', rootDirectory: '<ROOT_DIRECTORY>', - type: Type::COMMIT(), + type: TemplateReferenceType::COMMIT(), reference: '<REFERENCE>', activate: false // optional ); \ No newline at end of file diff --git a/docs/examples/1.8.x/server-php/examples/functions/create-vcs-deployment.md b/docs/examples/1.8.x/server-php/examples/functions/create-vcs-deployment.md index bb4622e67a..f044219541 100644 --- a/docs/examples/1.8.x/server-php/examples/functions/create-vcs-deployment.md +++ b/docs/examples/1.8.x/server-php/examples/functions/create-vcs-deployment.md @@ -2,7 +2,7 @@ use Appwrite\Client; use Appwrite\Services\Functions; -use Appwrite\Enums\VCSDeploymentType; +use Appwrite\Enums\VCSReferenceType; $client = (new Client()) ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint @@ -13,7 +13,7 @@ $functions = new Functions($client); $result = $functions->createVcsDeployment( functionId: '<FUNCTION_ID>', - type: VCSDeploymentType::BRANCH(), + type: VCSReferenceType::BRANCH(), reference: '<REFERENCE>', activate: false // optional ); \ No newline at end of file diff --git a/docs/examples/1.8.x/server-php/examples/sites/create-template-deployment.md b/docs/examples/1.8.x/server-php/examples/sites/create-template-deployment.md index 314ffdd1b9..1661bbb407 100644 --- a/docs/examples/1.8.x/server-php/examples/sites/create-template-deployment.md +++ b/docs/examples/1.8.x/server-php/examples/sites/create-template-deployment.md @@ -2,7 +2,7 @@ use Appwrite\Client; use Appwrite\Services\Sites; -use Appwrite\Enums\Type; +use Appwrite\Enums\TemplateReferenceType; $client = (new Client()) ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint @@ -16,7 +16,7 @@ $result = $sites->createTemplateDeployment( repository: '<REPOSITORY>', owner: '<OWNER>', rootDirectory: '<ROOT_DIRECTORY>', - type: Type::BRANCH(), + type: TemplateReferenceType::BRANCH(), reference: '<REFERENCE>', activate: false // optional ); \ No newline at end of file diff --git a/docs/examples/1.8.x/server-php/examples/sites/create-vcs-deployment.md b/docs/examples/1.8.x/server-php/examples/sites/create-vcs-deployment.md index 7f63dffdf8..015bf09d7d 100644 --- a/docs/examples/1.8.x/server-php/examples/sites/create-vcs-deployment.md +++ b/docs/examples/1.8.x/server-php/examples/sites/create-vcs-deployment.md @@ -2,7 +2,7 @@ use Appwrite\Client; use Appwrite\Services\Sites; -use Appwrite\Enums\VCSDeploymentType; +use Appwrite\Enums\VCSReferenceType; $client = (new Client()) ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint @@ -13,7 +13,7 @@ $sites = new Sites($client); $result = $sites->createVcsDeployment( siteId: '<SITE_ID>', - type: VCSDeploymentType::BRANCH(), + type: VCSReferenceType::BRANCH(), reference: '<REFERENCE>', activate: false // optional ); \ No newline at end of file diff --git a/docs/examples/1.8.x/server-python/examples/functions/create-template-deployment.md b/docs/examples/1.8.x/server-python/examples/functions/create-template-deployment.md index d9f8f70a82..db2058a87a 100644 --- a/docs/examples/1.8.x/server-python/examples/functions/create-template-deployment.md +++ b/docs/examples/1.8.x/server-python/examples/functions/create-template-deployment.md @@ -1,6 +1,6 @@ from appwrite.client import Client from appwrite.services.functions import Functions -from appwrite.enums import +from appwrite.enums import TemplateReferenceType client = Client() client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint @@ -14,7 +14,7 @@ result = functions.create_template_deployment( repository = '<REPOSITORY>', owner = '<OWNER>', root_directory = '<ROOT_DIRECTORY>', - type = .COMMIT, + type = TemplateReferenceType.COMMIT, reference = '<REFERENCE>', activate = False # optional ) diff --git a/docs/examples/1.8.x/server-python/examples/functions/create-vcs-deployment.md b/docs/examples/1.8.x/server-python/examples/functions/create-vcs-deployment.md index 4004baec27..43e198b4b8 100644 --- a/docs/examples/1.8.x/server-python/examples/functions/create-vcs-deployment.md +++ b/docs/examples/1.8.x/server-python/examples/functions/create-vcs-deployment.md @@ -1,6 +1,6 @@ from appwrite.client import Client from appwrite.services.functions import Functions -from appwrite.enums import VCSDeploymentType +from appwrite.enums import VCSReferenceType client = Client() client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint @@ -11,7 +11,7 @@ functions = Functions(client) result = functions.create_vcs_deployment( function_id = '<FUNCTION_ID>', - type = VCSDeploymentType.BRANCH, + type = VCSReferenceType.BRANCH, reference = '<REFERENCE>', activate = False # optional ) diff --git a/docs/examples/1.8.x/server-python/examples/sites/create-template-deployment.md b/docs/examples/1.8.x/server-python/examples/sites/create-template-deployment.md index fb47cb1453..700ca44d1b 100644 --- a/docs/examples/1.8.x/server-python/examples/sites/create-template-deployment.md +++ b/docs/examples/1.8.x/server-python/examples/sites/create-template-deployment.md @@ -1,6 +1,6 @@ from appwrite.client import Client from appwrite.services.sites import Sites -from appwrite.enums import +from appwrite.enums import TemplateReferenceType client = Client() client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint @@ -14,7 +14,7 @@ result = sites.create_template_deployment( repository = '<REPOSITORY>', owner = '<OWNER>', root_directory = '<ROOT_DIRECTORY>', - type = .BRANCH, + type = TemplateReferenceType.BRANCH, reference = '<REFERENCE>', activate = False # optional ) diff --git a/docs/examples/1.8.x/server-python/examples/sites/create-vcs-deployment.md b/docs/examples/1.8.x/server-python/examples/sites/create-vcs-deployment.md index 089e6c8141..ec02f31c00 100644 --- a/docs/examples/1.8.x/server-python/examples/sites/create-vcs-deployment.md +++ b/docs/examples/1.8.x/server-python/examples/sites/create-vcs-deployment.md @@ -1,6 +1,6 @@ from appwrite.client import Client from appwrite.services.sites import Sites -from appwrite.enums import VCSDeploymentType +from appwrite.enums import VCSReferenceType client = Client() client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint @@ -11,7 +11,7 @@ sites = Sites(client) result = sites.create_vcs_deployment( site_id = '<SITE_ID>', - type = VCSDeploymentType.BRANCH, + type = VCSReferenceType.BRANCH, reference = '<REFERENCE>', activate = False # optional ) diff --git a/docs/examples/1.8.x/server-ruby/examples/functions/create-template-deployment.md b/docs/examples/1.8.x/server-ruby/examples/functions/create-template-deployment.md index cfe0adc263..e4c759f3fc 100644 --- a/docs/examples/1.8.x/server-ruby/examples/functions/create-template-deployment.md +++ b/docs/examples/1.8.x/server-ruby/examples/functions/create-template-deployment.md @@ -15,7 +15,7 @@ result = functions.create_template_deployment( repository: '<REPOSITORY>', owner: '<OWNER>', root_directory: '<ROOT_DIRECTORY>', - type: ::COMMIT, + type: TemplateReferenceType::COMMIT, reference: '<REFERENCE>', activate: false # optional ) diff --git a/docs/examples/1.8.x/server-ruby/examples/functions/create-vcs-deployment.md b/docs/examples/1.8.x/server-ruby/examples/functions/create-vcs-deployment.md index 75bd3c49f5..930ec6dc76 100644 --- a/docs/examples/1.8.x/server-ruby/examples/functions/create-vcs-deployment.md +++ b/docs/examples/1.8.x/server-ruby/examples/functions/create-vcs-deployment.md @@ -12,7 +12,7 @@ functions = Functions.new(client) result = functions.create_vcs_deployment( function_id: '<FUNCTION_ID>', - type: VCSDeploymentType::BRANCH, + type: VCSReferenceType::BRANCH, reference: '<REFERENCE>', activate: false # optional ) diff --git a/docs/examples/1.8.x/server-ruby/examples/sites/create-template-deployment.md b/docs/examples/1.8.x/server-ruby/examples/sites/create-template-deployment.md index 9e1cc93986..4fb81779fe 100644 --- a/docs/examples/1.8.x/server-ruby/examples/sites/create-template-deployment.md +++ b/docs/examples/1.8.x/server-ruby/examples/sites/create-template-deployment.md @@ -15,7 +15,7 @@ result = sites.create_template_deployment( repository: '<REPOSITORY>', owner: '<OWNER>', root_directory: '<ROOT_DIRECTORY>', - type: ::BRANCH, + type: TemplateReferenceType::BRANCH, reference: '<REFERENCE>', activate: false # optional ) diff --git a/docs/examples/1.8.x/server-ruby/examples/sites/create-vcs-deployment.md b/docs/examples/1.8.x/server-ruby/examples/sites/create-vcs-deployment.md index 2e72b6e3f1..e0a6ff3af0 100644 --- a/docs/examples/1.8.x/server-ruby/examples/sites/create-vcs-deployment.md +++ b/docs/examples/1.8.x/server-ruby/examples/sites/create-vcs-deployment.md @@ -12,7 +12,7 @@ sites = Sites.new(client) result = sites.create_vcs_deployment( site_id: '<SITE_ID>', - type: VCSDeploymentType::BRANCH, + type: VCSReferenceType::BRANCH, reference: '<REFERENCE>', activate: false # optional ) From f0ed75f5854d015210d867e62643a81fb3e796eb Mon Sep 17 00:00:00 2001 From: Harsh Mahajan <harshmahajan2345@gmail.com> Date: Tue, 25 Nov 2025 06:08:10 +0000 Subject: [PATCH 18/22] update MFA challenge examples to use plural path --- .../1.5.x/client-rest/examples/account/create-challenge.md | 2 +- .../1.5.x/client-rest/examples/account/create-mfa-challenge.md | 2 +- .../1.5.x/client-rest/examples/account/create2f-a-challenge.md | 2 +- .../1.5.x/client-rest/examples/account/update-challenge.md | 2 +- .../1.5.x/client-rest/examples/account/update-mfa-challenge.md | 2 +- .../1.5.x/server-rest/examples/account/create-challenge.md | 2 +- .../1.5.x/server-rest/examples/account/create-mfa-challenge.md | 2 +- .../1.5.x/server-rest/examples/account/create2f-a-challenge.md | 2 +- .../1.5.x/server-rest/examples/account/update-challenge.md | 2 +- .../1.5.x/server-rest/examples/account/update-mfa-challenge.md | 2 +- .../1.6.x/client-rest/examples/account/create-mfa-challenge.md | 2 +- .../1.6.x/client-rest/examples/account/update-mfa-challenge.md | 2 +- .../1.6.x/server-rest/examples/account/create-mfa-challenge.md | 2 +- .../1.6.x/server-rest/examples/account/update-mfa-challenge.md | 2 +- .../1.7.x/client-rest/examples/account/create-mfa-challenge.md | 2 +- .../1.7.x/client-rest/examples/account/update-mfa-challenge.md | 2 +- .../1.7.x/server-rest/examples/account/create-mfa-challenge.md | 2 +- .../1.7.x/server-rest/examples/account/update-mfa-challenge.md | 2 +- .../1.8.x/client-rest/examples/account/create-mfa-challenge.md | 2 +- .../1.8.x/client-rest/examples/account/update-mfa-challenge.md | 2 +- .../1.8.x/server-rest/examples/account/create-mfa-challenge.md | 2 +- .../1.8.x/server-rest/examples/account/update-mfa-challenge.md | 2 +- 22 files changed, 22 insertions(+), 22 deletions(-) diff --git a/docs/examples/1.5.x/client-rest/examples/account/create-challenge.md b/docs/examples/1.5.x/client-rest/examples/account/create-challenge.md index dbdbc1f16a..8d803e56a9 100644 --- a/docs/examples/1.5.x/client-rest/examples/account/create-challenge.md +++ b/docs/examples/1.5.x/client-rest/examples/account/create-challenge.md @@ -1,4 +1,4 @@ -POST /v1/account/mfa/challenge HTTP/1.1 +POST /v1/account/mfa/challenges HTTP/1.1 Host: cloud.appwrite.io Content-Type: application/json X-Appwrite-Response-Format: 1.5.0 diff --git a/docs/examples/1.5.x/client-rest/examples/account/create-mfa-challenge.md b/docs/examples/1.5.x/client-rest/examples/account/create-mfa-challenge.md index 2019a1e52c..0553c4b5ba 100644 --- a/docs/examples/1.5.x/client-rest/examples/account/create-mfa-challenge.md +++ b/docs/examples/1.5.x/client-rest/examples/account/create-mfa-challenge.md @@ -1,4 +1,4 @@ -POST /v1/account/mfa/challenge HTTP/1.1 +POST /v1/account/mfa/challenges HTTP/1.1 Host: <REGION>.cloud.appwrite.io Content-Type: application/json X-Appwrite-Response-Format: 1.6.0 diff --git a/docs/examples/1.5.x/client-rest/examples/account/create2f-a-challenge.md b/docs/examples/1.5.x/client-rest/examples/account/create2f-a-challenge.md index dbdbc1f16a..8d803e56a9 100644 --- a/docs/examples/1.5.x/client-rest/examples/account/create2f-a-challenge.md +++ b/docs/examples/1.5.x/client-rest/examples/account/create2f-a-challenge.md @@ -1,4 +1,4 @@ -POST /v1/account/mfa/challenge HTTP/1.1 +POST /v1/account/mfa/challenges HTTP/1.1 Host: cloud.appwrite.io Content-Type: application/json X-Appwrite-Response-Format: 1.5.0 diff --git a/docs/examples/1.5.x/client-rest/examples/account/update-challenge.md b/docs/examples/1.5.x/client-rest/examples/account/update-challenge.md index 5e50a0e88d..00c85da373 100644 --- a/docs/examples/1.5.x/client-rest/examples/account/update-challenge.md +++ b/docs/examples/1.5.x/client-rest/examples/account/update-challenge.md @@ -1,4 +1,4 @@ -PUT /v1/account/mfa/challenge HTTP/1.1 +PUT /v1/account/mfa/challenges HTTP/1.1 Host: cloud.appwrite.io Content-Type: application/json X-Appwrite-Response-Format: 1.5.0 diff --git a/docs/examples/1.5.x/client-rest/examples/account/update-mfa-challenge.md b/docs/examples/1.5.x/client-rest/examples/account/update-mfa-challenge.md index 520b587562..093cc37930 100644 --- a/docs/examples/1.5.x/client-rest/examples/account/update-mfa-challenge.md +++ b/docs/examples/1.5.x/client-rest/examples/account/update-mfa-challenge.md @@ -1,4 +1,4 @@ -PUT /v1/account/mfa/challenge HTTP/1.1 +PUT /v1/account/mfa/challenges HTTP/1.1 Host: <REGION>.cloud.appwrite.io Content-Type: application/json X-Appwrite-Response-Format: 1.6.0 diff --git a/docs/examples/1.5.x/server-rest/examples/account/create-challenge.md b/docs/examples/1.5.x/server-rest/examples/account/create-challenge.md index dbdbc1f16a..8d803e56a9 100644 --- a/docs/examples/1.5.x/server-rest/examples/account/create-challenge.md +++ b/docs/examples/1.5.x/server-rest/examples/account/create-challenge.md @@ -1,4 +1,4 @@ -POST /v1/account/mfa/challenge HTTP/1.1 +POST /v1/account/mfa/challenges HTTP/1.1 Host: cloud.appwrite.io Content-Type: application/json X-Appwrite-Response-Format: 1.5.0 diff --git a/docs/examples/1.5.x/server-rest/examples/account/create-mfa-challenge.md b/docs/examples/1.5.x/server-rest/examples/account/create-mfa-challenge.md index 2019a1e52c..0553c4b5ba 100644 --- a/docs/examples/1.5.x/server-rest/examples/account/create-mfa-challenge.md +++ b/docs/examples/1.5.x/server-rest/examples/account/create-mfa-challenge.md @@ -1,4 +1,4 @@ -POST /v1/account/mfa/challenge HTTP/1.1 +POST /v1/account/mfa/challenges HTTP/1.1 Host: <REGION>.cloud.appwrite.io Content-Type: application/json X-Appwrite-Response-Format: 1.6.0 diff --git a/docs/examples/1.5.x/server-rest/examples/account/create2f-a-challenge.md b/docs/examples/1.5.x/server-rest/examples/account/create2f-a-challenge.md index dbdbc1f16a..8d803e56a9 100644 --- a/docs/examples/1.5.x/server-rest/examples/account/create2f-a-challenge.md +++ b/docs/examples/1.5.x/server-rest/examples/account/create2f-a-challenge.md @@ -1,4 +1,4 @@ -POST /v1/account/mfa/challenge HTTP/1.1 +POST /v1/account/mfa/challenges HTTP/1.1 Host: cloud.appwrite.io Content-Type: application/json X-Appwrite-Response-Format: 1.5.0 diff --git a/docs/examples/1.5.x/server-rest/examples/account/update-challenge.md b/docs/examples/1.5.x/server-rest/examples/account/update-challenge.md index 5e50a0e88d..00c85da373 100644 --- a/docs/examples/1.5.x/server-rest/examples/account/update-challenge.md +++ b/docs/examples/1.5.x/server-rest/examples/account/update-challenge.md @@ -1,4 +1,4 @@ -PUT /v1/account/mfa/challenge HTTP/1.1 +PUT /v1/account/mfa/challenges HTTP/1.1 Host: cloud.appwrite.io Content-Type: application/json X-Appwrite-Response-Format: 1.5.0 diff --git a/docs/examples/1.5.x/server-rest/examples/account/update-mfa-challenge.md b/docs/examples/1.5.x/server-rest/examples/account/update-mfa-challenge.md index 520b587562..093cc37930 100644 --- a/docs/examples/1.5.x/server-rest/examples/account/update-mfa-challenge.md +++ b/docs/examples/1.5.x/server-rest/examples/account/update-mfa-challenge.md @@ -1,4 +1,4 @@ -PUT /v1/account/mfa/challenge HTTP/1.1 +PUT /v1/account/mfa/challenges HTTP/1.1 Host: <REGION>.cloud.appwrite.io Content-Type: application/json X-Appwrite-Response-Format: 1.6.0 diff --git a/docs/examples/1.6.x/client-rest/examples/account/create-mfa-challenge.md b/docs/examples/1.6.x/client-rest/examples/account/create-mfa-challenge.md index 95bf2c4926..c3007fc290 100644 --- a/docs/examples/1.6.x/client-rest/examples/account/create-mfa-challenge.md +++ b/docs/examples/1.6.x/client-rest/examples/account/create-mfa-challenge.md @@ -1,4 +1,4 @@ -POST /v1/account/mfa/challenge HTTP/1.1 +POST /v1/account/mfa/challenges HTTP/1.1 Host: cloud.appwrite.io Content-Type: application/json X-Appwrite-Response-Format: 1.6.0 diff --git a/docs/examples/1.6.x/client-rest/examples/account/update-mfa-challenge.md b/docs/examples/1.6.x/client-rest/examples/account/update-mfa-challenge.md index 5bd401cc4e..779aeb2ecd 100644 --- a/docs/examples/1.6.x/client-rest/examples/account/update-mfa-challenge.md +++ b/docs/examples/1.6.x/client-rest/examples/account/update-mfa-challenge.md @@ -1,4 +1,4 @@ -PUT /v1/account/mfa/challenge HTTP/1.1 +PUT /v1/account/mfa/challenges HTTP/1.1 Host: cloud.appwrite.io Content-Type: application/json X-Appwrite-Response-Format: 1.6.0 diff --git a/docs/examples/1.6.x/server-rest/examples/account/create-mfa-challenge.md b/docs/examples/1.6.x/server-rest/examples/account/create-mfa-challenge.md index 95bf2c4926..c3007fc290 100644 --- a/docs/examples/1.6.x/server-rest/examples/account/create-mfa-challenge.md +++ b/docs/examples/1.6.x/server-rest/examples/account/create-mfa-challenge.md @@ -1,4 +1,4 @@ -POST /v1/account/mfa/challenge HTTP/1.1 +POST /v1/account/mfa/challenges HTTP/1.1 Host: cloud.appwrite.io Content-Type: application/json X-Appwrite-Response-Format: 1.6.0 diff --git a/docs/examples/1.6.x/server-rest/examples/account/update-mfa-challenge.md b/docs/examples/1.6.x/server-rest/examples/account/update-mfa-challenge.md index 5bd401cc4e..779aeb2ecd 100644 --- a/docs/examples/1.6.x/server-rest/examples/account/update-mfa-challenge.md +++ b/docs/examples/1.6.x/server-rest/examples/account/update-mfa-challenge.md @@ -1,4 +1,4 @@ -PUT /v1/account/mfa/challenge HTTP/1.1 +PUT /v1/account/mfa/challenges HTTP/1.1 Host: cloud.appwrite.io Content-Type: application/json X-Appwrite-Response-Format: 1.6.0 diff --git a/docs/examples/1.7.x/client-rest/examples/account/create-mfa-challenge.md b/docs/examples/1.7.x/client-rest/examples/account/create-mfa-challenge.md index 9a84c0ef69..bda2de889d 100644 --- a/docs/examples/1.7.x/client-rest/examples/account/create-mfa-challenge.md +++ b/docs/examples/1.7.x/client-rest/examples/account/create-mfa-challenge.md @@ -1,4 +1,4 @@ -POST /v1/account/mfa/challenge HTTP/1.1 +POST /v1/account/mfa/challenges HTTP/1.1 Host: cloud.appwrite.io Content-Type: application/json X-Appwrite-Response-Format: 1.7.0 diff --git a/docs/examples/1.7.x/client-rest/examples/account/update-mfa-challenge.md b/docs/examples/1.7.x/client-rest/examples/account/update-mfa-challenge.md index ddc27ae334..506059dc3d 100644 --- a/docs/examples/1.7.x/client-rest/examples/account/update-mfa-challenge.md +++ b/docs/examples/1.7.x/client-rest/examples/account/update-mfa-challenge.md @@ -1,4 +1,4 @@ -PUT /v1/account/mfa/challenge HTTP/1.1 +PUT /v1/account/mfa/challenges HTTP/1.1 Host: cloud.appwrite.io Content-Type: application/json X-Appwrite-Response-Format: 1.7.0 diff --git a/docs/examples/1.7.x/server-rest/examples/account/create-mfa-challenge.md b/docs/examples/1.7.x/server-rest/examples/account/create-mfa-challenge.md index 9a84c0ef69..bda2de889d 100644 --- a/docs/examples/1.7.x/server-rest/examples/account/create-mfa-challenge.md +++ b/docs/examples/1.7.x/server-rest/examples/account/create-mfa-challenge.md @@ -1,4 +1,4 @@ -POST /v1/account/mfa/challenge HTTP/1.1 +POST /v1/account/mfa/challenges HTTP/1.1 Host: cloud.appwrite.io Content-Type: application/json X-Appwrite-Response-Format: 1.7.0 diff --git a/docs/examples/1.7.x/server-rest/examples/account/update-mfa-challenge.md b/docs/examples/1.7.x/server-rest/examples/account/update-mfa-challenge.md index ddc27ae334..506059dc3d 100644 --- a/docs/examples/1.7.x/server-rest/examples/account/update-mfa-challenge.md +++ b/docs/examples/1.7.x/server-rest/examples/account/update-mfa-challenge.md @@ -1,4 +1,4 @@ -PUT /v1/account/mfa/challenge HTTP/1.1 +PUT /v1/account/mfa/challenges HTTP/1.1 Host: cloud.appwrite.io Content-Type: application/json X-Appwrite-Response-Format: 1.7.0 diff --git a/docs/examples/1.8.x/client-rest/examples/account/create-mfa-challenge.md b/docs/examples/1.8.x/client-rest/examples/account/create-mfa-challenge.md index dd5ef4c731..e5a5b0ea05 100644 --- a/docs/examples/1.8.x/client-rest/examples/account/create-mfa-challenge.md +++ b/docs/examples/1.8.x/client-rest/examples/account/create-mfa-challenge.md @@ -1,4 +1,4 @@ -POST /v1/account/mfa/challenge HTTP/1.1 +POST /v1/account/mfa/challenges HTTP/1.1 Host: cloud.appwrite.io Content-Type: application/json X-Appwrite-Response-Format: 1.8.0 diff --git a/docs/examples/1.8.x/client-rest/examples/account/update-mfa-challenge.md b/docs/examples/1.8.x/client-rest/examples/account/update-mfa-challenge.md index b6a7e92b28..df2cd9a1e8 100644 --- a/docs/examples/1.8.x/client-rest/examples/account/update-mfa-challenge.md +++ b/docs/examples/1.8.x/client-rest/examples/account/update-mfa-challenge.md @@ -1,4 +1,4 @@ -PUT /v1/account/mfa/challenge HTTP/1.1 +PUT /v1/account/mfa/challenges HTTP/1.1 Host: cloud.appwrite.io Content-Type: application/json X-Appwrite-Response-Format: 1.8.0 diff --git a/docs/examples/1.8.x/server-rest/examples/account/create-mfa-challenge.md b/docs/examples/1.8.x/server-rest/examples/account/create-mfa-challenge.md index dd5ef4c731..e5a5b0ea05 100644 --- a/docs/examples/1.8.x/server-rest/examples/account/create-mfa-challenge.md +++ b/docs/examples/1.8.x/server-rest/examples/account/create-mfa-challenge.md @@ -1,4 +1,4 @@ -POST /v1/account/mfa/challenge HTTP/1.1 +POST /v1/account/mfa/challenges HTTP/1.1 Host: cloud.appwrite.io Content-Type: application/json X-Appwrite-Response-Format: 1.8.0 diff --git a/docs/examples/1.8.x/server-rest/examples/account/update-mfa-challenge.md b/docs/examples/1.8.x/server-rest/examples/account/update-mfa-challenge.md index b6a7e92b28..df2cd9a1e8 100644 --- a/docs/examples/1.8.x/server-rest/examples/account/update-mfa-challenge.md +++ b/docs/examples/1.8.x/server-rest/examples/account/update-mfa-challenge.md @@ -1,4 +1,4 @@ -PUT /v1/account/mfa/challenge HTTP/1.1 +PUT /v1/account/mfa/challenges HTTP/1.1 Host: cloud.appwrite.io Content-Type: application/json X-Appwrite-Response-Format: 1.8.0 From 623a8a747d923031bad85e0b83a04b682982898a Mon Sep 17 00:00:00 2001 From: Darshan <itznotabug@gmail.com> Date: Tue, 25 Nov 2025 12:39:31 +0530 Subject: [PATCH 19/22] temp: compare the screenshots. --- .github/workflows/tests.yml | 105 ++++++++++++++++++++++++++++++++++++ 1 file changed, 105 insertions(+) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index cebdc02163..07685b8e6a 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -455,6 +455,58 @@ jobs: -e _APP_E2E_RESPONSE_FORMAT="${{ github.event.inputs.response_format }}" \ appwrite test /usr/src/code/tests/e2e/Services/Sites --debug --group=screenshots + - name: Extract Screenshots for Comparison + if: failure() + run: | + mkdir -p /tmp/screenshot-artifacts/reference + mkdir -p /tmp/screenshot-artifacts/actual + + # Copy reference screenshots + cp tests/resources/sites/static-themed/screenshot-*.png /tmp/screenshot-artifacts/reference/ 2>/dev/null || true + + # Extract actual screenshots from storage + docker compose exec -T appwrite sh -c ' + cd /storage/uploads && + find . -name "*.png" -type f -mmin -10 -exec cp {} /tmp/ \; + ' || true + + docker cp appwrite:/tmp/ /tmp/screenshot-artifacts/actual/ 2>/dev/null || true + + # Create a comparison info file + cat > /tmp/screenshot-artifacts/README.txt << 'EOF' + Screenshot Test Failure Report + ================================ + + This artifact contains screenshots for comparison: + - reference/screenshot-*.png: Expected screenshots (committed to repo) + - actual/*.png: Screenshots generated during the test run + + To compare: + 1. Download this artifact + 2. Open both sets of images side-by-side + 3. Look for rendering differences (fonts, anti-aliasing, etc.) + + Common causes of differences: + - Different CPU architectures (ARM64 vs AMD64) + - Browser version changes + - Font rendering differences + EOF + + echo "" >> /tmp/screenshot-artifacts/README.txt + echo "Generated: $(date)" >> /tmp/screenshot-artifacts/README.txt + echo "Branch: ${{ github.head_ref || github.ref_name }}" >> /tmp/screenshot-artifacts/README.txt + echo "Commit: ${{ github.sha }}" >> /tmp/screenshot-artifacts/README.txt + + ls -lah /tmp/screenshot-artifacts/ + + - name: Upload Screenshot Artifacts + if: failure() + uses: actions/upload-artifact@v4 + with: + name: screenshot-comparison-dedicated-mode + path: /tmp/screenshot-artifacts/ + retention-days: 7 + - name: Failure Logs if: failure() run: | @@ -512,6 +564,59 @@ jobs: -e _APP_E2E_RESPONSE_FORMAT="${{ github.event.inputs.response_format }}" \ appwrite test /usr/src/code/tests/e2e/Services/Sites --debug --group=screenshots + - name: Extract Screenshots for Comparison + if: failure() + run: | + mkdir -p /tmp/screenshot-artifacts/reference + mkdir -p /tmp/screenshot-artifacts/actual + + # Copy reference screenshots + cp tests/resources/sites/static-themed/screenshot-*.png /tmp/screenshot-artifacts/reference/ 2>/dev/null || true + + # Extract actual screenshots from storage + docker compose exec -T appwrite sh -c ' + cd /storage/uploads && + find . -name "*.png" -type f -mmin -10 -exec cp {} /tmp/ \; + ' || true + + docker cp appwrite:/tmp/ /tmp/screenshot-artifacts/actual/ 2>/dev/null || true + + # Create a comparison info file + cat > /tmp/screenshot-artifacts/README.txt << 'EOF' + Screenshot Test Failure Report + ================================ + + This artifact contains screenshots for comparison: + - reference/screenshot-*.png: Expected screenshots (committed to repo) + - actual/*.png: Screenshots generated during the test run + + To compare: + 1. Download this artifact + 2. Open both sets of images side-by-side + 3. Look for rendering differences (fonts, anti-aliasing, etc.) + + Common causes of differences: + - Different CPU architectures (ARM64 vs AMD64) + - Browser version changes + - Font rendering differences + EOF + + echo "" >> /tmp/screenshot-artifacts/README.txt + echo "Generated: $(date)" >> /tmp/screenshot-artifacts/README.txt + echo "Branch: ${{ github.head_ref || github.ref_name }}" >> /tmp/screenshot-artifacts/README.txt + echo "Commit: ${{ github.sha }}" >> /tmp/screenshot-artifacts/README.txt + echo "Table Mode: ${{ matrix.tables-mode }}" >> /tmp/screenshot-artifacts/README.txt + + ls -lah /tmp/screenshot-artifacts/ + + - name: Upload Screenshot Artifacts + if: failure() + uses: actions/upload-artifact@v4 + with: + name: screenshot-comparison-${{ matrix.tables-mode }} + path: /tmp/screenshot-artifacts/ + retention-days: 7 + - name: Failure Logs if: failure() run: | From fe9ecb6fcb314dc9670c2912b720c8feb2233f27 Mon Sep 17 00:00:00 2001 From: Darshan <itznotabug@gmail.com> Date: Tue, 25 Nov 2025 13:22:19 +0530 Subject: [PATCH 20/22] simplify: index. --- .../resources/sites/static-themed/index.html | 113 +++--------------- .../sites/static-themed/screenshot-dark.png | Bin 40811 -> 14704 bytes .../sites/static-themed/screenshot-light.png | Bin 40460 -> 14674 bytes 3 files changed, 19 insertions(+), 94 deletions(-) diff --git a/tests/resources/sites/static-themed/index.html b/tests/resources/sites/static-themed/index.html index f6b9d42643..f1a446612f 100644 --- a/tests/resources/sites/static-themed/index.html +++ b/tests/resources/sites/static-themed/index.html @@ -13,137 +13,62 @@ /* Light theme */ :root { - --bg-primary: #ffffff; - --bg-secondary: #f5f5f5; - --text-primary: #1a1a1a; - --text-secondary: #666666; + --bg: #ffffff; + --text: #1a1a1a; --accent: #fd366e; - --border: #e0e0e0; } /* Dark theme */ @media (prefers-color-scheme: dark) { :root { - --bg-primary: #1a1a1a; - --bg-secondary: #2d2d2d; - --text-primary: #ffffff; - --text-secondary: #b0b0b0; + --bg: #1a1a1a; + --text: #ffffff; --accent: #fd366e; - --border: #404040; } } body { - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; - background-color: var(--bg-primary); - color: var(--text-primary); - line-height: 1.6; + font-family: system-ui, sans-serif; + background-color: var(--bg); + color: var(--text); min-height: 100vh; display: flex; - flex-direction: column; align-items: center; justify-content: center; padding: 2rem; } - .container { - max-width: 600px; - width: 100%; - } - - header { + .content { + max-width: 500px; text-align: center; - margin-bottom: 3rem; } h1 { - font-size: 2.5rem; - margin-bottom: 0.5rem; - color: var(--text-primary); - } - - .subtitle { - font-size: 1.125rem; - color: var(--text-secondary); - } - - .card { - background-color: var(--bg-secondary); - border: 1px solid var(--border); - border-radius: 12px; - padding: 2rem; - margin-bottom: 2rem; - } - - .card h2 { - font-size: 1.5rem; - margin-bottom: 1rem; - color: var(--text-primary); - } - - .card p { - color: var(--text-secondary); + font-size: 2rem; margin-bottom: 1rem; } - .feature-list { - list-style: none; - margin-top: 1.5rem; - } - - .feature-list li { - padding: 0.75rem; - margin-bottom: 0.5rem; - background-color: var(--bg-primary); - border-radius: 8px; - border-left: 3px solid var(--accent); - color: var(--text-primary); + p { + font-size: 1rem; + margin-bottom: 1.5rem; + opacity: 0.8; } .badge { display: inline-block; - padding: 0.25rem 0.75rem; + padding: 0.5rem 1rem; background-color: var(--accent); color: white; - border-radius: 20px; - font-size: 0.875rem; - font-weight: 600; - } - - footer { - text-align: center; - margin-top: 3rem; - color: var(--text-secondary); + border-radius: 4px; font-size: 0.875rem; } </style> </head> <body> -<div class="container"> - <header> + <div class="content"> <h1>Themed website</h1> - <p class="subtitle">Adaptive light & dark mode showcase</p> - </header> - - <div class="card"> - <h2>About This Site</h2> - <p>This is a test deployment showcasing automatic theme switching based on your system preferences.</p> - <span class="badge">Appwrite Sites</span> + <p>Adaptive light and dark mode showcase</p> + <div class="badge">Appwrite Sites</div> </div> - - <div class="card"> - <h2>Features</h2> - <ul class="feature-list"> - <li>🎨 Automatic theme detection</li> - <li>🌓 Light and dark mode support</li> - <li>📱 Responsive design</li> - <li>⚡ Fast static deployment</li> - </ul> - </div> - - <footer> - <p>Powered by Appwrite Sites • E2E Test</p> - </footer> -</div> </body> </html> diff --git a/tests/resources/sites/static-themed/screenshot-dark.png b/tests/resources/sites/static-themed/screenshot-dark.png index bcab317265dbd9f8adc3e491f440108a16952262..199a09b9029950970eb985ec107de228617d6e26 100644 GIT binary patch literal 14704 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 literal 40811 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 diff --git a/tests/resources/sites/static-themed/screenshot-light.png b/tests/resources/sites/static-themed/screenshot-light.png index 9836740a2f5390ad017d9c54a21b20fc4d9bbd34..4eae73c5b79e7237090c673dcc720be7c5f0510f 100644 GIT binary patch literal 14674 zcmeHuXH=8v+h@=b9YxUrq{Aqqh%|%J5iArH5fKHX1rwDTdT&7;=^<jFh=78EQYQ+8 z9uSfsy>|!@N+<yWgc3-yciuhk**#~^+0Xy8J6}LKkUY8W>-x3pd1CI~F*<r!>@WlZ zIePPk{yhlf0Ql#=&O`gaw=2@Ocp#ADkem8f?+1QZ8vDK4c71G{e(~Fx-%6FyXG$B# z4`GW9V)efbx4cmL8pe;(X54kjKVQ0H^D=AmG92bY{$n@ePob-BZF~4Fsg4a_ckeSw z?-zH&@yzK|O`oT;3KT|V=DN1e)7QZ@_TUZc0>=L0zW-l;Mu0cIaTT&30(tcu!V7`? zd4PuxT+rctA`nP~;BObeFUcKp0AKGNuLEB{|N963R_Wi?_;()uMU8)<^8Y)2usaOt zUK}WfZN)PBZHg!IwK#k0Rh--?so<~MMcc~Z_YD&yhDsixIJ;zF$c0n!ko~?E4N?3n zV^slNJVsJM)3a$F_H_XS$$-%=-||OMlKv@f-+vv3IGu^$iCBrQe{nF=xBF(F+=$x` zG=%r#|Dt8BXTM}KLpv^S72KKc$u;GH2wda7_E@E#>%z0{&QI5itJj(r-o89({S}4} zN6y*$b{QO@{#EHus~mB!5`uK{>=XI;BNig%%5OWAr}d<EXLi?RX0`J=zn%Zk6Mmi5 zx)x>pXtojNfUvD+x?lf8WNF_psy}3<CZPYJW6kQ%BgJ?&)3vx}bGd4T=riY5TF)yU zpI?bc#gW5z7v3IGDtCDIz#@pfGpAfh&8k|ily`5{S}ZVDs(9S0)}gb0>1iCHYHzIt zVFnAO69TgjoV1$A)nm<@hOfQxfS(d)?{uK-!x(*AV*xOQ!N1<zRsp3YJson&M#FOj zC4c-no5m-lQ|_hSKckG=G4_j;^dFpzl`nD{MN5{ns<d2MJ|?c_Mkfdtx1)xgbJ2`d zl8O4*C8y4hR$GJnu&CUO;Dw6W_wp8~M1zw<-Ytr<>Av0YYmF+T*E*Yn2)av6FvB`{ z{>$zrZ8uw*<z%~#k<e7}Uqs@@HrusV;zgjR%jWKzo2WMMU2N5$#Q$hjF<v5%*MyLQ zO*CiT_T@Hy`mhkURMK;2cj<@eX!-1W*Rr9fY1mq`j*QFs0f?cZn$6*yAU2~Il^!r2 zWU52gcSEa>2g+ES(siDRdi3R;#R3_R;;9RG3E_26AjXUOY)?jIhOYfW%bAvmX?Qwp zINm31)o#^bb)j0DudW}9;mf}hn)6^Zx;0u5V<|GwlV<DJ_aKB;I&e@r^zL|AZQqw< z`(Wy)DEHDn3+yW|nSI4jj(K5L6mC3tk+ntE>)uGSYNC#V%h7~?gPzHgvtIt-(+)n2 zd2GT%_llW3p3s$=Ewwh!x%2>w6+@2Y{&LzL{ke(xX$=JBlH{44)dqfY@M57=>0mWB znX{3rNAP+B*8!I-dr-pjibSE)?TY+9EJ>=WMX72Dd`GGystrQ_)@5%~78+4y!OInM zV&-b(&~+J%&iC!e%dCwaY?U->kG{~DY%Nr%=OT14EF6pycbt?V)V;l97a#_O7Ww07 z9xR!?iR)t)YR!Lsb4QQ0QPv}4M0wbSHhJ_=2rH<Fos1MWbQgl1eeY65D|7Aj@S|^z zc;H1mbe790c?=t&9#Z9=B&;b;xmH6euiv8qOc3RhV`f+xHNvGW67;n8d%Tx=o<ij9 z{EPY!oR2!MPHwvY@WR_0J?|{-E53!N=^rh^62)j8=FFuZ9dPLfi?{_0Y7fEZMA9)a zF5emtQSVzp8Hc_`n4~S>Zc4n2nPgF?_eHduLiXS7U1Thb0nnhncP(;uWaIZ{l#!GR z&(~K_oRFSI&AAUgzs@8t4VSw5!TByq`SqF+lAS~HT>5Hvm&FnBwASM<u;66u>7`1K z`OKt3SL@9t2pZM9%T<mUK~KAQz^4lAd3`^v?3+i!<*iYH2YN-KNnp+w#u8<Wh+o~} zA(patz&RT$U>j|H+8(5f81eA(i5!<1F{qLX-<}kAFaMM-x(wb;M8EJ(40a}vo-=gP z+Ub*S7_$|;pt<Q~KRPl7>DPJnpen6xJ~uP`+n1#G?v?X-0^l^;z^|;Ycj$p5n+E;? zfdl9v4PF+sk-h#kjMU{k{-e^mI=p}6o5Mp3<b(&nHAH7N{g5vzpNf&q81(Hi$;S|z zY(|n?UGu)>#MX~nMQ-EfC3=ZXtHt4re5ZD9zs$cKi|*3~$GK?NB+@OR>T=)jmM`T@ zvh7;0xhabx&56d+6ShVGl6(iSW0~7Xk0#Q<Wc=gI#^^G?LL)@;C0xzIN}P2x)et4N z6nC8a7#t0sM7+9K)&S!$2Oc`6m(rcwCc)h1i}2fkfuP~Y>`|W%0zM5{_OK^gHX{(O z@R+g;uI66Y1~nTKKAY+YijX7XDWwzWIhx=XUn44H9gkzPcJbqR^RCm6nqHkMw!D;( z7cX@=7;|~^Yr7^uIerO^3u?36h8~Ouj9|rRjka3yN*Ca{B0HsB2Z=W*wy()rZ2n*a z##<%=wbKH#a17iWa?BTguLW8qkAhfl*Y@b0n0{kwKb{Vcm1D0)=~ZuZ8z;U8uvG54 z8+T&fCi1Sr_39?}?h5%*1Ay@Hrj&OU*591#e5jqD`l`SvfEg`dKBteSr~vXI(5UNv z19kzCqE80{Dd@O_n9}l@Bpb1S{H~GcN$NDQvXKvqk5y~HLrwi&=raw6A2@1kbY52< zba+7=JG4T<=!L&AQFFc>^AU|4WG6pUAN6d|xO;CrL6dU)`dc5y#ymhF*UpbuT?<Ps zy56TlZ;S_?Q?pdK(sKX@4{;wqA6&alv_-KPeLgL5{XMh!!j^ITp$ZFLB*}pkZ$L>j zfV2l7>8U3iAVhdw1W#XmE43r%$gKy;7WYds4%^g-%jBc~Q6M;2r(QW~BJ&K%ppD?P z_y)_8vNdsI{v*UOFlIlTKQlr502t1mpPm$fDlOt3y%e(8?#|=fGZB?Wq&zuH#G^g3 zionI9<BU}g?|=dp>tX{|#-V?Ymy08M`-~7#hFD>aGeOAGO{~#<*a=CiUT-%sA3oNQ zWGO6fjZxuF40_%yrK=<NgZC{7U#H!|iJbLUczcsYp^!pr{Fm*&@2>#zTI#ceDMF~N zx!pD4|D6Q@0nu6DI#48ET<z_5tFgQFVuvlu(1~&WZnh!XMQeSt(ZZn09uVpl8HIZ9 zoFh8GXX3ce(x#|ndn!)d<U=@fz9nueDX*fN{*sH!X;PAl#b*@D2H_MR{{U^chWa>k z90xta{0FFpzFM^8#?K?lZ}<NUAcqBLV&{LG&son*OVDV9c*j2zhRxDR(qur$M23^w zs!M0mN&CP_k={c4-MkZZ28+>NjNx|vl2~#2+d<zrh3_}q`lLDABzlm1?M)LKnWRr4 zTgxzJfsu5WZ-VCRIS93c`{@*JeVniCG*}$8W4JSZ8pE`bw3U$fF6+LxSCA-pKu+q@ z^TP~=(&N&pTU^Q=*&%6pJ#Qp>#B#hv^LVNs=dW#QuT)?3rFM#3MT$|7OrFohWe0}w z*SvcmfpSTDhLn8syyUwHS9f!sG4K86bDzqcYXFr-l`9o<8L;5ri;5M>`YcDpz&u*< zTyZ=b!Q-R`NWLU}aKoT2uQ=3ibn{5=e4Zr|bw@2yIxVNfCu}xB`EgI_caqi<TLIbo zJrhXH?vd?%o)Vu@!ZwHU=c35uyh<h(pJX3AzYuqcCD9cqw#cum8t?ifEyq%MS7&GX z4GavkHm$^5`vnKeF%Y)fXo0cU#F?d^HB`FWjlM|s8AtPUifSGZi%58nV(+Bo6qNQ` zA5pGeP1f})$UTn1E7_f$xZq9_{yrb|Jhs0@yEL8S>ijvUek4jdJaD=x5zRC19a=6{ z2h|@fxj=X7QU4IKx%BX|x^{xHTMeV1keuq>EH@Ig0qD$G8@hm-ullaHH_4wLKsnT+ z3p5OnjRY~mYokFj5P>Gi1IM_MCAFR~plIQe5j6YF{x|u}H8Ix$`WMr9q!pwy^>+t| z*1|!(?RtQ}c1Ke{nN`<vQc`?FPmN@hhXCoiHzkaBdOx@^Vhn4$c0^I|-C((cH=kvO z|FARqYRlU6+<Z=&J5XnG_`y(sJ*r{CIA^{JCjk$$UK?cTgM-SK81)CGb=I0>;uOoQ z{HDeN$YYm=M>j)$8Z6#j{azT`e`-kBKJXNL8^~Jp0oT@bz|1Gf@fv^LJ328~CNLs< zGQ#7z`A42@u}kO~`+zY&grvAzv|B#=UOO)P#gU=1sUjc|bLit&U+}8h1vBMs02<^4 zbbxXYX+#`KT-_cZ(n7Ji5aP&08DauR$a_$Cu3(YN{cx}5<bYLr&}^!Sc{_lX9J#&| z6F>zN)8ZN(*Z>l$fPyXf(XQ1ukF_WKo#I6lP0Im4)<&fNaCX&U^qO-45$fI$c{C1j z93a~V2=9dJaYRgCl?OO3Lvf@a!>WewL09mp87}{FOMpPDijkdI{l1>35Rf1s{EFIl zfK6~KpNs?&(~-~G@J~C?W+O<P9bMI#$?HKe^2(-*>9yLhetlU;{!rylv%Y+_lKvbD zw@v?pr+TrdN-eBqtASsynobnw*O-V9vb2Q~5%`U&o@AgOfFRHYgVvR8M8faq<%_25 z>l3Z?TkOZ9IK(lawtay_sszeQmr2cDaD(!S+pK{T&AaoV(>ww30q@*wW+W+u=tT~k z_7Cgf*F}QQCHukiT0ywq)aqoz!DA94M<hrxQ-*-}<^-J}wY+8;(9i&&>D6<pnG5+q zz_IK@XsSRjytC{RQ+a&jKAcYOp}*9jeth8$9L>VJR4x_*SzW%(dnxQ_`6s4)Ju)iD zxU13xKO%89&F#B@sSY%+Huz?qv5nu#co+bp6kxbojBlD!4`j7Fs}>o7%FVeX6DK2E zZ2DYE0Z+Gr<2P{iBZ;_;7~3-z`tnnbBa3f^8RQekG)m0+w}EC3YC09Q1ge9Mni|7^ z21O)jp4H`}YheRr4C9@4M=T+}F8f3xijAflL2*YnzO<>h#l{#O6jr&WWd8_AIjI3Y zGn4#+@6Et&Som7;ts6#`)_k_e)D08EIi@~I%8Ao_mzOxY@gBcBwgTTtE9%}0&WHbO z1JFmmhZO!;SprDA%}P2}wsz3PwgVjx^J!C!``wKF+7lESV=Pfo4h|RoTa^}+7@O(w z0J3T~&8@H$Ff@Hf0XeVMAH=czvdT`=lC0n{Q#J$im!m+*Aq262eUNjQ0UaQ;&z1}g zL;J5zEI4-{SCWXTDiUhoZK><@iYc$gJ(v8#{*B!Z6ZkuTQa`<D%|JkGy*5^gNt}cK zWGs*E#$Rkt>6{~$Zvh(7W6R#j^!)idU5RoL1(erRC->AHe*;Bo1B$*9*lQ2a#$3r5 zzeJ!uiI=b49CihE=w@#(nCfxhR1c-iB*Szg&yBE)JRBc30g(l0nUxi%6zla9cx9C= zEkg@S(Emr(1|5JSybP39T=C7{JbSjM-<sbU@|7z-G_i1V%wSPC0<kj^G-Ne=u|vma zb)p_O>TO)G3px@8{NNhg42&BCNK~P#BH6`ecx1;pclolx2bOk1%6@IC16({Ru4OKO zHV~C*z%t&~`r5@h)Pl+80z)z^d<P(G430hVf<LxD&f4e>iPzTY)60GsiiM42ZCiQP zAMgOZ`)vQg@vyBiXs_PmrdJDcWF&znt~P8JFvu4mSDkhqMSnRj><s8Iw(x|wPXCz& zb+K~D$2a-tMcE~g;7?RF9{SIY>Z$i27ra7tX}AGv*Fq35IQD)uqjgk(^gz<P>6AfK z&CYBZ$R6sWPsrp6>~5`~hMpt~!ScaLdV?xUX!$w?1?(lGHmF_nMvz1J7hn=ZFP=@} z0QaF(t0++7rS&~+7)JN#Aqc~&*~~>@SU!+>w6EC-=OGi~G7&sy{kaHRW6#;>G4%kh zaLR@bnv844O?avY;c;!i)f3d`qW}wXO_8tFZ=MRAA04_j5XgOv17Z&FfF2WdMbO$Z zMhVrd%@Kq|`L^;7nZqJaiEpNn_i`YRZQ>z^h?wCXQ$5zOYq7x=Fg;N~p||H3*J_a@ zd@f1V07mH0<Lrt2L+i^Q;1zef8#cAOf=2=M$fbbmQOD8&D|5>L)Ijo&HZ_|as;J|K z6O>=tOaC;0Pqx=>tlV~~=Qfz;Wm6QhCy<&EvMguXIR=EW_(d=PH7E`n&+IGa_7{kg zjQF^Ll26Cw01EAx?)C%_c3a@@%!9VpK;Q#P&h=|t0|%~P6dtEo>ASP$lfd=LfUcH4 zzBqP%h1i@CI28l<dlb;s?wicLY2brufxSp;zf=o&f&!@rhEv4m&y#_``-rQ$^hS!i zi}16)0Iq`5fI{fnu*W`}iuehzmk!>Q)U*juVMmX%u7~~9Y($a67EXn6hkddkiqyHm zg_OE|M=oWAPrei;9u5bp)mXV^<7pbQpB?PysZRUeqQor_ki^|M4{~AIa2|8RYG`nc z>xh>Ux$15{tR+`EVAPAg%$3FQMWlny6*Eb3?_%e%kAxpSP7I_EI?e250$Bxq+YeNv z223lKkHuuj>-~WklSEqj>;(je5{oJq@=jfkynUI`i){wze9MhU%r@2CnXY@bUpiz7 zYc-M6CY8;^-UCM(I#6T>1eRz>DDX_@$Xe{h=nmpvZ%Xza`81v|;uE`cj!gq8;3%4L z8N?rm47r??)5~0<#9oDm5L>Ya;Q5>LJ+e6*ng=Rmk4{1ug>G~iVG{sn-Q3wwwwndA zPQ(5JTRZkktZ@97*r3CZv$yt%IAoptv{thcrtCchJXeAqUFO2+SItpV7{<9iYAQDY zi=9b9B<z5mpG$SjSnG27LH+v-z<9vDal|UnM!@)c0(c~lsWX6sz__gejur9?SS-YG zGr5GQ)4W{k|1|-?pz7&=-12fRW+p#;n(97QNqDZhPs4uU*+Hpj9i^2F(7B0+PqZe! zH9KHdh`j&=Bocb+Gz~b_OaNSGBVT6Q6oC-Yij)(SW%U$j$mkNMm2<BUKzIk>ckNmw zX4W}uGAHgc1}*TT4X`c}#>p%^J)QM&>fH54&%c*>4oY;UoZ7bp+_`1-oq{FMw^9$o z!Rsd<@B@K~ej;&^Lp9QiJv|KCoD?s5u}Els!aY_bE}rb1n+|t1QdBRzR_zz6lK={D z*r*nhyMpJi!Ego(hD*pr(*(3<K1E4cg9wca+fF|<o_b9@JD7Bl#2e2<BP8hdC;aHO ziH%qXWB>_}oA5|A(~yGip}a%9XnYp&mF~pm<|}i#bShvH0r9_1dVo|fHv1xWh&w$% zndwNy&eOf@KsGBgj4%f%qgu668=~-7!E`A)uxb!PcdA&Kx^c^G=eq;NJv6T+Ojt4m z1bD>CWcfeV8?}>oB&t-{Hql5`cw_XSK6Vj7X;mkO(v;V^C^q;g%7Blg8(S!OLdsNU z@^1m+Tr@v~tjoi9f&O0^)t~ZgE#>vI>~ahw0a|BWXi;MS@?2z^SX&t|i$>*Y&F6Xj z#4W93@$IP1ADITJjrpp?YuU&$0MRNUQyokZ{TgakoCTm%kW=j@@<5^X=ZW>hN_~K^ z#TsB;t!C9Tul4zic#6t~UA(mIWh*a94FkJ4wG2j99L*%@vFUSqoFIjJ(D5R`m`?1b zH17*YV7Q%Z%*|%Oq00oGw#;fi?bPxv7TPM}B{{ID+?Q7^c~iQj0(2@DY=FJsZ;rtl zc3D0NCfp4wxYPKkAW&3O+@snn-ZQBKEfmi~^r|sC7+_v)cb^OZ?IbQ+&j=%BcLzgX zJ=Z<BFEJYZY_V)=9;@7pZ4O)%5ywahXK!IE;I|6w^jAPWbfNFQIibl>M-XZ&r5eqM zJ%qvNE8MIO?^%wHC|FMTeRv+aAXW9$Hn)njk5r#*)sw{#5E(iDT2(EiOKU+2@e_Sn z%IM{kBw|k|L80-3*N4;B)cwPJy(Pq=F}|aUbMdFhYYn2Ds|H_5W*Cifb6vsAe4-5i zuW|H<*#xvCeCC}c2u3q#3O>~ioA;*P{dX3ifRwOxT58ltqA$WY!9b^QLjj(;M$+WX zh?oKJ_u>c>sG!9-TJM|8Dw~c$y$z5Knoityt%9yg6y}*?%-R5Jd91gobH!oy!Kp8& zSH&=fRZ06{U$$j*r6kqdte{e-Z@_QDtqSOd^bIgGWwv=DZ{h>oS|zZ8*S`Vidkj4V zzocwP6U%*lN9EUFed}h%X1?eVkoKYWZ0zbc2@#L@rk6q13!M-(7H5bH02*Qt`Jfw? z{T^M`8D4x-ZR+Is5oon&1EvpGwEVJ6t2_-(K#Zu>JXLU12<ml+r<R%HO-wPGfRvaw z8o!5M*Zj!=L@iYKPDDC}c=cJ9Y7@ew#WjYoY~}FmfLN8xvFG;#NWalGqWcD4lOzLd z-y&OkHk_77-@_ZYqU?#i`{pW8Ultv26J%6@h4xhN?_xemxco<|bxFb&@sR&pWOayy zGRQAP0x_qivi!(|W<yP3gur4TfNuR-qK1Eg@!r5d;G&nJPxS;4hP#Z#ffSV%g&&RQ z8|%Os=>Ix&p{`Nha2hUH1ic}|!;M-<jfe8gvJDB+Q%`)<^v5r`lL1Aewi~43`brr> zl)t-V>2o2BsJEvtHY?vwIxRazZW<;YchWwcHO0>o*+wq}f=0+n1n_)US5W2+)?wy` z!-IzpDISf{Crm8=cDEin{vsSb@}$79|0HD&Xlt&*2X(3uS&Xw}gJ_-~NQQ;YZ?%EZ zM@6oGG+5jjH#u^#s0S0*e=$lM6div*z+7%vp==!(mysJoUNN|$j5zW2WXFBUSqKW* zy9Pd>9`;u=Awm7TwJ9z^qOjK6uTa)0mvqP{KAGsb3~a;k%q*aT{TO|gKzM}gIQ2Zm zbf78%6Q{5~;z-+AbdS^0_HClW5;#gRx+so9y%}`USE488-Fef)=Q^^a7um73=!WA^ zf60UTaHB?&TqFtxc0ilS6`}QIdM*#fnjp9D|8}8H|NON@ki21vtbl3H?~xhtAN3-M zh{owH>T#{+b?MO6s}$vxbNM2SVX)c}#}GT=ezWId!M!dS4Wof2)t@`JsUx@*z}twM zAjnPn`c{N4pKB!5_}t*OntcVdCgr$Uj0r5LFx<R^!h2Lqu%QrbeDA2J%43~OtEZ1B z_ouTM1!G=qqx7-f5>ew5&OoVMUai}Y_^n)mxm@1#Q$<CbGX8{_Bpnl2QrUd!nCK;U zQ|cX9<%33-Y1g1<MS7f_4udNAs^;^u`8ZNnCOZ3ftSAtI>w)zm0^>lRA%afYxM2@X zh<rjnn_9bZbqBeL4Sxla+FGj&pkoWY4o!e<*LL?4z?-R*Ph1H$?_gq1+<$@YdH52j z5YaL_oa<d><flw&BFDu+0{zwyB*$Rsm`L19lt=UAXEEdp&`0E69+V3FDCdwBPQh#w z$GDJ33~zzUanllAS4X-92wS{wy5m`!A~kOJ&B4h6f_oIq$}Jl>=T3fpL}?oYJgQ<E z!#Q3=MsjRq%!D&)hjJi)ME01xASD-oZKC>2D(=ey?W`o(gc3Zo?VwQFdk=bO&yO`q z*I92B&@QT!{IBgoVgf_aI>1Om#!0~@R7ITpk7Dh@9ZZ;@X?$KNP=CV}u~u>x6^<Pl z*c)g$V5Osvj%>dVR&eAyM7o-)G5Sx_={Uhc#82!isHx_jO}jwmss^=llY!uY>r|WJ zp;LBt^<ZS<V@VpTpu__uQwY|Y4n$zTOdUkv8{iBmyUZR0(0SP|^xV+B7-{4lkhhOP za4mYlp$_P7Y?q<<QIO1*QxfvqMH^t;^-8>jftvpQ#_Yb%^Vawi!3*DViRM{$+@&Vh zl>jXhJrNU?^*4eGbP^OhuYhL<PC9KM8-PDQdBg{Z71BA|AavHfzv@9Y5)71H^1uX| zr}9uPch?GHFC=r>b+;dAu>7LR^Y8IGST$m5Hm-#rE^YQFJ%@|l1ruBc?EkpRG*cJ* z67dW1qcM&<Hk6%a>=--|py^#-uHNnA=8Pk$_!m{q1<jv!L0rZfL}1Xox*(`Wy!t^+ z;p#x3`{QTg8>F!b+yyVlR(=pp+PqCG8kK}jRD;Gd;E>#)*AW4?JMZX$%^;jF6vPoR zlXMZoT41uWr%7|~f8lr9JmAI|z-Y|O^MVBKQ9PB+&H7r8dq_qhvie$9&XZDBxoS;a zOkDO4uCz=@To560i)Mrt6FMSsk!yj?3h9{*z|+@){iMMtk?PK<s-;-Z{?l+wTf(!B z=LAQ-u;kvJ(TS>U;CUW`we<*l?Ibu&AaJ0u&@a*5in!75^B=mmUxWZe!sxLm{L9pi z>&G;w!O{keAI`p+bp<YngF52FS>vGEhdv*AcW4-F_2&21JBn-X>$d~%A%LQiGM5sg z=>ehS+OU==;JDv!r?IEvly$+p>w$`{1w@R1z1tqw6`i;-qZ%uIS{D96@wG~%07644 z>0<Mv=Sn<wB^hhMK)`b|4lrS!@iWBl1{r8h=}UhQ#~o?`F^~@N6^W@`$Cb$imhoS? z>*jrO<4?efU93Zx47M)W2}<P?&$*#2c0Q|_>&XBB)sOWiY+umdPl7(@?r^h;C`YS- zsWciSJ(WWp)KD{{1Vo5e$rxs|jA6k6S(^SK#YvaC-saUf3GAD^ULa_zg$@*Zo&K;| z<G4V}aTA*iKWQWNslclYn|ibA6Lm9;B9JGi5O^{Co_;L_|NJ~`B;lp+Hr=$lZThF~ z;7n;1{2QEsKsR590LF3Gsqw0>@K4gbrsWTF!v(f+R|g<QjipceE9o7pLs=>&oEC=s zC4ibCU0B=3^*8?6onO$l4>BPS-Ny%TEAk^^y0AL5Yoh)|6rbpKN`m%D2n6;HEWuB! z25(Sh5;Uf=F}mF4BKY4495=*<KtdnfdG=rTDuPM=54<PQS1mwSbM`}8tnm1GT@kxg z7_?82F#5xP=(`8@=g$KWU#U{=ZJ1Xap;Rn%fBU%<4PVWL0YbChc@XJ<pybLNbapiD z$XPM}gOKz%kXTdi+e=tnp#P%{p#naHq&G)N3*XjRItI}a`oG_;IS~-SGcnRRV>&W$ zY`9?iX>XZ*eoGze2;`%kVFXXp=|4)W;I+`*OuJF06TL7Wf2lxZXHwYMo7BaS;aKBm z+3;(HWmd^YEzi%guC~zT$OU@qF0Aqv8+ZPYVv)6AO=ILpc<BRS@gMZb1CZ5BaB-Tg zDt2A2%S-|;U>?7b!np;J_IcQKRbF{`28DC^H7hQihu;k^TzpLMJ`zTsvK$lO$U`Fh z!TR9dS61lG6x(qz`B>4>QG81H0=)67cW`o#e<44rSZ(Y+VV{p+X1G0`g=J!s3FxG# zW!)n7fVN9qhs{RHm%xz6jXpXxGhcY!tkN9Ipd%Tb-n+tjMYOdaNt2^3D=tCJLm|D= z!A0y`c~|X18=GJ@b3)dIm3;nbhJUL{6>-~#m7d7Bu0-`_d?HN0=|48Az_zU3noLpM zL<Xg+CVgdfED$6vo&-XKym^E5w|ra&r?2{OSa6vFaYadCrJura52VILmD$VNKWgQT z>Qj4Iv6-)GoitsmSJt{dhac#usYvm9Kisb8`Y_QsM<KFS_pJS1m#}q4zw5FR-u_1l zY7;)}u4wE%eTMT6>jl~F2e;nd`^vD%W6$Nssv8NK*4o;jUNW5rSi7I3xAHPk#;gb* z*P6MQaz$NMLHN=POs5z1jY&u4lh=Kb=}%lj-0z}79((N~2n`*=uF7_|)o%KF7Y>XP zKnKOOnd?IW7Ogivu*k)ui2mWK70eK;cWP`a-&o}-ZGW2SGqzW8lCU1FlKGJoC*9ut zWU(}sN4%dOUmhgM-nj~;-6#C?A)JqQRb3S3KVM5OoEM}jk=z7kbY?q`#3o1fF%^eq z29d<Qg~6$rNxMxeyzVrE>6I*$Z8%_I^;v}F%mX1iD?LAOWuLsVwQpK=_<DoaMK)T5 zjWL1+{s`~xOZ-N^%lfYE>Z&*t_&K~s&sxQ=@hfvkU5Nh}?R)!oMS+!&oRsMh!qj?c zD|S!^x@lumV$?$WGONs1%?Wh4L77J!tN0kWj85`IS|%%66vKi{<JE*4*bhf{TT{%7 z3k!#GX>JT-UIV7Trq7mA^76pUTRRt<f^n6s-I+ypHhg)AxD;Z+7a`n02B-GPBNUqe z{e#__Pl#)X4Hz$mc?EcN^c;YM=8E$0PE-{e^Gy>cTo!K<7>m*cv=Vph#vUQ8BMH-= zZN@QWrCPe0y)!pG@ghrci?h;vPm%v<SWQ{$b4A=Z=iK{>e#cs7l?<X67rxw93fn8E z>RxZrBzC<i%r(UYQpSie{jk#+Rh=J7IS*-iXWOCX^o0=)bfy7A+S~jao-}Vg?({7~ z(GliRwva+*W4}0%3i}aBA_dDT+bNtBvFk%vL)>NSkr~I*Ga>yjF$<LE4b?H83GJJZ z{i}Auln*UR_tV^7TjNpuwEpnQ+bqt$K!vkuCFcw=Pl9Q292jcj<#3js+x&<u%w>Mk z5tgr5b0vQNfOPOO`67z>5Sp=Iz)~LQ?Wpvsek!%u!Sv%79JqWp(>+hgQp(oGafm5h z?6gH#Bpl2p6_j;YNomf0pNdJ2Jup|GsAh_6=|gyGoXmzqtSIsD)dkp=xYN6274hM{ zDS!OJ{W{MOnMeDhtJc{c#{-?7X<8G*#mU38Ar`sgeQ`*OWwbHB9;~3s7TIgWC^y?& z?LJZx%Cp_O7)w8q-Du~+soiZ}wa(0-h}RwE?0uZEsWQJk&W;<%L}jb>2^wT(5MCfe zCkn}{cfA^}W0|Fw)xQ>Y8-u!x&4QaSLP@2zO$+U^u~#wbY|EUdg`93!YZn+9e^=p) zx6N&80$Lc@BGy*NdcVcun78QMd~Ol44@bR7PQj1a-6<w>+>9S9;qJ2PKPd)ChZk9S zB_^2{NQ-INw+NoPyn^ouZXHr5#tnc8nK>AaANVr!-m0odJxORN2S&rOY-&3s{4f>> z2;>lj`-}j`X2z)Wg_?4$-jf#L*J;&Ee<)=O;a6K8GGI=iDEkk2ys=8o8kttiwec7! z-eY|p4o^F2yy=$eIP6~fa#mqH6bGt&39x5qt|Al$9cX1xIGfS}dOHQ>oYZ0Kum#~U zi?`&%8O0&AVKO1bg_UHqfMSOrh|C>aZ<$ri%vP*@NN)H>9lR3()9GZYP9zo2ClNwW zi$lE~wf$?&lvCrlvJPUYi3NMg@zrv$OKU_=m0K6CY_nT2xht4PW11Jo&ph1Hv9k&J zrqs2dm-6XJ3bR6;xD+;v7ybqvL1OyVY)Xv_+Bqz~?vydpLL{eWIAO8(*m(6!+AkTs z0`kb~wo=iYB1z6j(vjeoxiHb5l6Q~^?OcoEfg=#=Uqpo(V|uB}j^xCr;sH#huLA#A zm@Vt$`hq$u+hMd>T7oy?PW}JBGyy&elKyKT{O~17?s81)#Q!NSt|jk-XeGMa9XTt- zrRG?0kEZ#~q{0CK{;K^EcMQP&45_|DaK`hP$T6NPbKrwRsBX)l-mRMILdw+-LN8QK z;Nf|4u@#QA?@!3^moLF}>io|~EXu)~|NErH|C5iGoP$7`uKfFe&A(3-{X5|QfnM$3 xOZpFc@Nbd>o<-zvWSj9(U|@K;a%VpTcd+F7KgrJn!A~GJ4esa{U2}N;KLGw(mrMWv literal 40460 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 From 8c854b7d0738972012863ca76326b671054bfb27 Mon Sep 17 00:00:00 2001 From: Darshan <itznotabug@gmail.com> Date: Tue, 25 Nov 2025 13:26:33 +0530 Subject: [PATCH 21/22] remove: screenshots uploader for comparison. --- .github/workflows/tests.yml | 105 ------------------------------------ 1 file changed, 105 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 07685b8e6a..cebdc02163 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -455,58 +455,6 @@ jobs: -e _APP_E2E_RESPONSE_FORMAT="${{ github.event.inputs.response_format }}" \ appwrite test /usr/src/code/tests/e2e/Services/Sites --debug --group=screenshots - - name: Extract Screenshots for Comparison - if: failure() - run: | - mkdir -p /tmp/screenshot-artifacts/reference - mkdir -p /tmp/screenshot-artifacts/actual - - # Copy reference screenshots - cp tests/resources/sites/static-themed/screenshot-*.png /tmp/screenshot-artifacts/reference/ 2>/dev/null || true - - # Extract actual screenshots from storage - docker compose exec -T appwrite sh -c ' - cd /storage/uploads && - find . -name "*.png" -type f -mmin -10 -exec cp {} /tmp/ \; - ' || true - - docker cp appwrite:/tmp/ /tmp/screenshot-artifacts/actual/ 2>/dev/null || true - - # Create a comparison info file - cat > /tmp/screenshot-artifacts/README.txt << 'EOF' - Screenshot Test Failure Report - ================================ - - This artifact contains screenshots for comparison: - - reference/screenshot-*.png: Expected screenshots (committed to repo) - - actual/*.png: Screenshots generated during the test run - - To compare: - 1. Download this artifact - 2. Open both sets of images side-by-side - 3. Look for rendering differences (fonts, anti-aliasing, etc.) - - Common causes of differences: - - Different CPU architectures (ARM64 vs AMD64) - - Browser version changes - - Font rendering differences - EOF - - echo "" >> /tmp/screenshot-artifacts/README.txt - echo "Generated: $(date)" >> /tmp/screenshot-artifacts/README.txt - echo "Branch: ${{ github.head_ref || github.ref_name }}" >> /tmp/screenshot-artifacts/README.txt - echo "Commit: ${{ github.sha }}" >> /tmp/screenshot-artifacts/README.txt - - ls -lah /tmp/screenshot-artifacts/ - - - name: Upload Screenshot Artifacts - if: failure() - uses: actions/upload-artifact@v4 - with: - name: screenshot-comparison-dedicated-mode - path: /tmp/screenshot-artifacts/ - retention-days: 7 - - name: Failure Logs if: failure() run: | @@ -564,59 +512,6 @@ jobs: -e _APP_E2E_RESPONSE_FORMAT="${{ github.event.inputs.response_format }}" \ appwrite test /usr/src/code/tests/e2e/Services/Sites --debug --group=screenshots - - name: Extract Screenshots for Comparison - if: failure() - run: | - mkdir -p /tmp/screenshot-artifacts/reference - mkdir -p /tmp/screenshot-artifacts/actual - - # Copy reference screenshots - cp tests/resources/sites/static-themed/screenshot-*.png /tmp/screenshot-artifacts/reference/ 2>/dev/null || true - - # Extract actual screenshots from storage - docker compose exec -T appwrite sh -c ' - cd /storage/uploads && - find . -name "*.png" -type f -mmin -10 -exec cp {} /tmp/ \; - ' || true - - docker cp appwrite:/tmp/ /tmp/screenshot-artifacts/actual/ 2>/dev/null || true - - # Create a comparison info file - cat > /tmp/screenshot-artifacts/README.txt << 'EOF' - Screenshot Test Failure Report - ================================ - - This artifact contains screenshots for comparison: - - reference/screenshot-*.png: Expected screenshots (committed to repo) - - actual/*.png: Screenshots generated during the test run - - To compare: - 1. Download this artifact - 2. Open both sets of images side-by-side - 3. Look for rendering differences (fonts, anti-aliasing, etc.) - - Common causes of differences: - - Different CPU architectures (ARM64 vs AMD64) - - Browser version changes - - Font rendering differences - EOF - - echo "" >> /tmp/screenshot-artifacts/README.txt - echo "Generated: $(date)" >> /tmp/screenshot-artifacts/README.txt - echo "Branch: ${{ github.head_ref || github.ref_name }}" >> /tmp/screenshot-artifacts/README.txt - echo "Commit: ${{ github.sha }}" >> /tmp/screenshot-artifacts/README.txt - echo "Table Mode: ${{ matrix.tables-mode }}" >> /tmp/screenshot-artifacts/README.txt - - ls -lah /tmp/screenshot-artifacts/ - - - name: Upload Screenshot Artifacts - if: failure() - uses: actions/upload-artifact@v4 - with: - name: screenshot-comparison-${{ matrix.tables-mode }} - path: /tmp/screenshot-artifacts/ - retention-days: 7 - - name: Failure Logs if: failure() run: | From 7e9d74e1417a49c91e97dbe629acf9a7cf560035 Mon Sep 17 00:00:00 2001 From: Darshan <itznotabug@gmail.com> Date: Tue, 25 Nov 2025 17:15:13 +0530 Subject: [PATCH 22/22] bump: migrations for logs --- composer.lock | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/composer.lock b/composer.lock index 96a54d09c8..6a1ad0a2d5 100644 --- a/composer.lock +++ b/composer.lock @@ -4456,16 +4456,16 @@ }, { "name": "utopia-php/migration", - "version": "1.3.3", + "version": "1.3.6", "source": { "type": "git", "url": "https://github.com/utopia-php/migration.git", - "reference": "731b3a963c58c30e0b2368695d57a7e8fcb7455c" + "reference": "4abe70cc242bbffebfa377e4126ee2a4a1c9ef7e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/migration/zipball/731b3a963c58c30e0b2368695d57a7e8fcb7455c", - "reference": "731b3a963c58c30e0b2368695d57a7e8fcb7455c", + "url": "https://api.github.com/repos/utopia-php/migration/zipball/4abe70cc242bbffebfa377e4126ee2a4a1c9ef7e", + "reference": "4abe70cc242bbffebfa377e4126ee2a4a1c9ef7e", "shasum": "" }, "require": { @@ -4505,9 +4505,9 @@ ], "support": { "issues": "https://github.com/utopia-php/migration/issues", - "source": "https://github.com/utopia-php/migration/tree/1.3.3" + "source": "https://github.com/utopia-php/migration/tree/1.3.6" }, - "time": "2025-10-28T04:02:08+00:00" + "time": "2025-11-25T11:36:57+00:00" }, { "name": "utopia-php/mongo",