diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d8256ddc7a..48c8ea901b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -701,9 +701,8 @@ jobs: - name: Installing latest version run: | - rm docker-compose.yml + # TODO: Minify docker-compose; remove development tooling rm .env - curl https://appwrite.io/install/compose -o docker-compose.yml curl https://appwrite.io/install/env -o .env sed -i 's/_APP_OPTIONS_ABUSE=enabled/_APP_OPTIONS_ABUSE=disabled/g' .env docker compose up -d diff --git a/CHANGES.md b/CHANGES.md index 548c0d72b0..d717de4668 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,11 @@ +# Version 1.9.2 + +### Notable changes + +### Fixes + +### Miscellaneous + # Version 1.9.0 ## What's Changed diff --git a/README-CN.md b/README-CN.md index 2c7402f1ef..7f758bc247 100644 --- a/README-CN.md +++ b/README-CN.md @@ -72,7 +72,7 @@ docker run -it --rm \ --volume /var/run/docker.sock:/var/run/docker.sock \ --volume "$(pwd)"/appwrite:/usr/src/code/appwrite:rw \ --entrypoint="install" \ - appwrite/appwrite:1.9.1 + appwrite/appwrite:1.9.2 ``` ### Windows @@ -84,7 +84,7 @@ docker run -it --rm ^ --volume //var/run/docker.sock:/var/run/docker.sock ^ --volume "%cd%"/appwrite:/usr/src/code/appwrite:rw ^ --entrypoint="install" ^ - appwrite/appwrite:1.9.1 + appwrite/appwrite:1.9.2 ``` #### PowerShell @@ -94,7 +94,7 @@ docker run -it --rm ` --volume /var/run/docker.sock:/var/run/docker.sock ` --volume ${pwd}/appwrite:/usr/src/code/appwrite:rw ` --entrypoint="install" ` - appwrite/appwrite:1.9.1 + appwrite/appwrite:1.9.2 ``` 运行后,可以在浏览器上访问 http://localhost 找到 Appwrite 控制台。在非 Linux 的本机主机上完成安装后,服务器可能需要几分钟才能启动。 diff --git a/README.md b/README.md index 31076ffa31..b0926bbeeb 100644 --- a/README.md +++ b/README.md @@ -75,7 +75,7 @@ docker run -it --rm \ --volume /var/run/docker.sock:/var/run/docker.sock \ --volume "$(pwd)"/appwrite:/usr/src/code/appwrite:rw \ --entrypoint="install" \ - appwrite/appwrite:1.9.1 + appwrite/appwrite:1.9.2 ``` ### Windows @@ -88,7 +88,7 @@ docker run -it --rm ^ --volume //var/run/docker.sock:/var/run/docker.sock ^ --volume "%cd%"/appwrite:/usr/src/code/appwrite:rw ^ --entrypoint="install" ^ - appwrite/appwrite:1.9.1 + appwrite/appwrite:1.9.2 ``` #### PowerShell @@ -99,7 +99,7 @@ docker run -it --rm ` --volume /var/run/docker.sock:/var/run/docker.sock ` --volume ${pwd}/appwrite:/usr/src/code/appwrite:rw ` --entrypoint="install" ` - appwrite/appwrite:1.9.1 + appwrite/appwrite:1.9.2 ``` Once the Docker installation is complete, go to http://localhost to access the Appwrite console from your browser. Please note that on non-Linux native hosts, the server might take a few minutes to start after completing the installation. diff --git a/app/controllers/general.php b/app/controllers/general.php index b4f4a5c1d1..596fbd0926 100644 --- a/app/controllers/general.php +++ b/app/controllers/general.php @@ -26,6 +26,7 @@ use Appwrite\Utopia\Request\Filters\V19 as RequestV19; use Appwrite\Utopia\Request\Filters\V20 as RequestV20; use Appwrite\Utopia\Request\Filters\V21 as RequestV21; use Appwrite\Utopia\Request\Filters\V22 as RequestV22; +use Appwrite\Utopia\Request\Filters\V23 as RequestV23; use Appwrite\Utopia\Response; use Appwrite\Utopia\Response\Filters\V16 as ResponseV16; use Appwrite\Utopia\Response\Filters\V17 as ResponseV17; @@ -34,6 +35,7 @@ use Appwrite\Utopia\Response\Filters\V19 as ResponseV19; use Appwrite\Utopia\Response\Filters\V20 as ResponseV20; use Appwrite\Utopia\Response\Filters\V21 as ResponseV21; use Appwrite\Utopia\Response\Filters\V22 as ResponseV22; +use Appwrite\Utopia\Response\Filters\V23 as ResponseV23; use Appwrite\Utopia\View; use Executor\Executor; use MaxMind\Db\Reader; @@ -897,6 +899,9 @@ Http::init() if (version_compare($requestFormat, '1.9.1', '<')) { $request->addFilter(new RequestV22()); } + if (version_compare($requestFormat, '1.9.2', '<')) { + $request->addFilter(new RequestV23()); + } } $localeParam = (string) $request->getParam('locale', $request->getHeader('x-appwrite-locale', '')); @@ -921,6 +926,9 @@ Http::init() */ $responseFormat = $request->getHeader('x-appwrite-response-format', System::getEnv('_APP_SYSTEM_RESPONSE_FORMAT', '')); if ($responseFormat) { + if (version_compare($responseFormat, '1.9.2', '<')) { + $response->addFilter(new ResponseV23()); + } if (version_compare($responseFormat, '1.9.1', '<')) { $response->addFilter(new ResponseV22()); } diff --git a/app/init/constants.php b/app/init/constants.php index f2127cd666..54a94652fb 100644 --- a/app/init/constants.php +++ b/app/init/constants.php @@ -46,8 +46,8 @@ const APP_PROJECT_ACCESS = 24 * 60 * 60; // 24 hours const APP_RESOURCE_TOKEN_ACCESS = 24 * 60 * 60; // 24 hours const APP_FILE_ACCESS = 24 * 60 * 60; // 24 hours const APP_CACHE_UPDATE = 24 * 60 * 60; // 24 hours -const APP_CACHE_BUSTER = 4322; -const APP_VERSION_STABLE = '1.9.1'; +const APP_CACHE_BUSTER = 4323; +const APP_VERSION_STABLE = '1.9.2'; const APP_DATABASE_ATTRIBUTE_EMAIL = 'email'; const APP_DATABASE_ATTRIBUTE_ENUM = 'enum'; const APP_DATABASE_ATTRIBUTE_IP = 'ip'; diff --git a/src/Appwrite/Migration/Migration.php b/src/Appwrite/Migration/Migration.php index a01031de9b..ef0dd9f8b5 100644 --- a/src/Appwrite/Migration/Migration.php +++ b/src/Appwrite/Migration/Migration.php @@ -94,6 +94,7 @@ abstract class Migration '1.8.1' => 'V23', '1.9.0' => 'V24', '1.9.1' => 'V24', + '1.9.2' => 'V24', ]; /** diff --git a/src/Appwrite/Platform/Modules/Project/Http/Project/Templates/Email/Delete.php b/src/Appwrite/Platform/Modules/Project/Http/Project/Templates/Email/Delete.php index 9133971c40..cf02704d47 100644 --- a/src/Appwrite/Platform/Modules/Project/Http/Project/Templates/Email/Delete.php +++ b/src/Appwrite/Platform/Modules/Project/Http/Project/Templates/Email/Delete.php @@ -33,13 +33,13 @@ class Delete extends Action $this->setHttpMethod(Action::HTTP_REQUEST_METHOD_DELETE) ->setHttpPath('/v1/project/templates/email') ->httpAlias('/v1/projects/:projectId/templates/email') - ->httpAlias('/v1/projects/:projectId/templates/email/:type/:locale') + ->httpAlias('/v1/projects/:projectId/templates/email/:templateId/:locale') ->desc('Delete project email template') ->groups(['api', 'project']) ->label('scope', 'templates.write') ->label('event', 'templates.[templateType].delete') ->label('audits.event', 'project.template.delete') - ->label('audits.resource', 'project.template/{response.type}') + ->label('audits.resource', 'project.template/{response.templateId}') ->label('sdk', new Method( namespace: 'project', group: 'templates', @@ -56,8 +56,8 @@ class Delete extends Action ], contentType: ContentType::NONE )) - ->param('type', '', new WhiteList(Config::getParam('locale-templates')['email'] ?? [], true), 'Custom email template type. Can be one of: '.\implode(', ', Config::getParam('locale-templates')['email'] ?? [])) - ->param('locale', '', fn ($localeCodes) => new WhiteList($localeCodes), 'Custom email template locale.', optional: true, injections: ['localeCodes']) + ->param('templateId', '', new WhiteList(Config::getParam('locale-templates')['email'] ?? [], true), 'Custom email template type. Can be one of: '.\implode(', ', Config::getParam('locale-templates')['email'] ?? [])) + ->param('locale', '', fn ($localeCodes) => new WhiteList($localeCodes), 'Custom email template locale. If left empty, the fallback locale (en) will be used.', optional: true, injections: ['localeCodes']) ->inject('response') ->inject('queueForEvents') ->inject('dbForPlatform') @@ -68,7 +68,7 @@ class Delete extends Action } public function action( - string $type, + string $templateId, string $locale, Response $response, QueueEvent $queueForEvents, @@ -77,16 +77,16 @@ class Delete extends Action Document $project, Locale $localeObject, ) { - $locale = $locale ?: $localeObject->default ?: $localeObject->fallback ?: System::getEnv('_APP_LOCALE', 'en'); + $locale = $locale ?: System::getEnv('_APP_LOCALE', 'en'); $templates = $project->getAttribute('templates', []); - $template = $templates['email.' . $type . '-' . $locale] ?? null; + $template = $templates['email.' . $templateId . '-' . $locale] ?? null; if (is_null($template)) { throw new Exception(Exception::PROJECT_TEMPLATE_DEFAULT_DELETION); } - unset($templates['email.' . $type . '-' . $locale]); + unset($templates['email.' . $templateId . '-' . $locale]); $updates = new Document([ 'templates' => $templates, @@ -94,7 +94,7 @@ class Delete extends Action $project = $authorization->skip(fn () => $dbForPlatform->updateDocument('projects', $project->getId(), $updates)); - $queueForEvents->setParam('templateType', $type); + $queueForEvents->setParam('templateType', $templateId); $response->noContent(); } diff --git a/src/Appwrite/Platform/Modules/Project/Http/Project/Templates/Email/Get.php b/src/Appwrite/Platform/Modules/Project/Http/Project/Templates/Email/Get.php index 9725215613..115b10f7dd 100644 --- a/src/Appwrite/Platform/Modules/Project/Http/Project/Templates/Email/Get.php +++ b/src/Appwrite/Platform/Modules/Project/Http/Project/Templates/Email/Get.php @@ -27,8 +27,8 @@ class Get extends Action public function __construct() { $this->setHttpMethod(Action::HTTP_REQUEST_METHOD_GET) - ->setHttpPath('/v1/project/templates/email/:type') - ->httpAlias('/v1/projects/:projectId/templates/email/:type/:locale') + ->setHttpPath('/v1/project/templates/email/:templateId') + ->httpAlias('/v1/projects/:projectId/templates/email/:templateId/:locale') ->desc('Get project email template') ->groups(['api', 'project']) ->label('scope', 'templates.read') @@ -47,8 +47,8 @@ class Get extends Action ) ] )) - ->param('type', '', new WhiteList(Config::getParam('locale-templates')['email'] ?? [], true), 'Custom email template type. Can be one of: '.\implode(', ', Config::getParam('locale-templates')['email'] ?? [])) - ->param('locale', '', fn ($localeCodes) => new WhiteList($localeCodes), 'Custom email template locale.', optional: true, injections: ['localeCodes']) + ->param('templateId', '', new WhiteList(Config::getParam('locale-templates')['email'] ?? [], true), 'Custom email template type. Can be one of: '.\implode(', ', Config::getParam('locale-templates')['email'] ?? [])) + ->param('locale', '', fn ($localeCodes) => new WhiteList($localeCodes), 'Custom email template locale. If left empty, the fallback locale (en) will be used.', optional: true, injections: ['localeCodes']) ->inject('response') ->inject('project') ->inject('locale') @@ -56,16 +56,16 @@ class Get extends Action } public function action( - string $type, + string $templateId, string $locale, Response $response, Document $project, Locale $localeObject, ) { - $locale = $locale ?: $localeObject->default ?: $localeObject->fallback ?: System::getEnv('_APP_LOCALE', 'en'); + $locale = $locale ?: System::getEnv('_APP_LOCALE', 'en'); $templates = $project->getAttribute('templates', []); - $template = $templates['email.' . $type . '-' . $locale] ?? null; + $template = $templates['email.' . $templateId . '-' . $locale] ?? null; $localeObj = new Locale($locale); $localeObj->setFallback(System::getEnv('_APP_LOCALE', 'en')); @@ -94,7 +94,7 @@ class Get extends Action ]; // fallback to the base template. - $config = $templateConfigs[$type] ?? [ + $config = $templateConfigs[$templateId] ?? [ 'file' => 'email-inner-base.tpl', 'placeholders' => ['buttonText', 'body', 'footer'] ]; @@ -107,21 +107,21 @@ class Get extends Action // Set type-specific parameters foreach ($config['placeholders'] as $param) { $escapeHtml = !in_array($param, ['clientInfo', 'body', 'footer', 'description']); - $message->setParam("{{{$param}}}", $localeObj->getText("emails.{$type}.{$param}"), escapeHtml: $escapeHtml); + $message->setParam("{{{$param}}}", $localeObj->getText("emails.{$templateId}.{$param}"), escapeHtml: $escapeHtml); } $message // common placeholders on all the templates - ->setParam('{{hello}}', $localeObj->getText("emails.{$type}.hello")) - ->setParam('{{thanks}}', $localeObj->getText("emails.{$type}.thanks")) - ->setParam('{{signature}}', $localeObj->getText("emails.{$type}.signature")); + ->setParam('{{hello}}', $localeObj->getText("emails.{$templateId}.hello")) + ->setParam('{{thanks}}', $localeObj->getText("emails.{$templateId}.thanks")) + ->setParam('{{signature}}', $localeObj->getText("emails.{$templateId}.signature")); // `useContent: false` will strip new lines! $message = $message->render(useContent: true); $template = [ 'message' => $message, - 'subject' => $localeObj->getText('emails.' . $type . '.subject'), + 'subject' => $localeObj->getText('emails.' . $templateId . '.subject'), 'senderEmail' => '', 'senderName' => '', 'custom' => false, @@ -130,7 +130,7 @@ class Get extends Action $template['custom'] = true; } - $template['type'] = $type; + $template['templateId'] = $templateId; $template['locale'] = $locale; $response->dynamic(new Document($template), Response::MODEL_EMAIL_TEMPLATE); diff --git a/src/Appwrite/Platform/Modules/Project/Http/Project/Templates/Email/Update.php b/src/Appwrite/Platform/Modules/Project/Http/Project/Templates/Email/Update.php index ff739f9fe8..f17be381f6 100644 --- a/src/Appwrite/Platform/Modules/Project/Http/Project/Templates/Email/Update.php +++ b/src/Appwrite/Platform/Modules/Project/Http/Project/Templates/Email/Update.php @@ -33,13 +33,13 @@ class Update extends Action $this->setHttpMethod(Action::HTTP_REQUEST_METHOD_PATCH) ->setHttpPath('/v1/project/templates/email') ->httpAlias('/v1/projects/:projectId/templates/email') - ->httpAlias('/v1/projects/:projectId/templates/email/:type/:locale') + ->httpAlias('/v1/projects/:projectId/templates/email/:templateId/:locale') ->desc('Update project email template') ->groups(['api', 'project']) ->label('scope', 'templates.write') ->label('event', 'templates.[templateType].update') ->label('audits.event', 'project.template.update') - ->label('audits.resource', 'project.template/{response.type}') + ->label('audits.resource', 'project.template/{response.templateId}') ->label('sdk', new Method( namespace: 'project', group: 'templates', @@ -55,8 +55,8 @@ class Update extends Action ) ] )) - ->param('type', '', new WhiteList(Config::getParam('locale-templates')['email'] ?? [], true), 'Custom email template type. Can be one of: '.\implode(', ', Config::getParam('locale-templates')['email'] ?? [])) - ->param('locale', '', fn ($localeCodes) => new WhiteList($localeCodes), 'Custom email template locale.', optional: true, injections: ['localeCodes']) + ->param('templateId', '', new WhiteList(Config::getParam('locale-templates')['email'] ?? [], true), 'Custom email template type. Can be one of: '.\implode(', ', Config::getParam('locale-templates')['email'] ?? [])) + ->param('locale', '', fn ($localeCodes) => new WhiteList($localeCodes), 'Custom email template locale. If left empty, the fallback locale (en) will be used.', optional: true, injections: ['localeCodes']) ->param('subject', '', new Text(255), 'Subject of the email template. Can be up to 255 characters.') ->param('message', '', new Text(10485760), 'Plain or HTML body of the email template message. Can be up to 10MB of content.') ->param('senderName', '', new Text(255, 0), 'Name of the email sender.', true) @@ -72,7 +72,7 @@ class Update extends Action } public function action( - string $type, + string $templateId, string $locale, string $subject, string $message, @@ -86,7 +86,7 @@ class Update extends Action Document $project, Locale $localeObject, ) { - $locale = $locale ?: $localeObject->default ?: $localeObject->fallback ?: System::getEnv('_APP_LOCALE', 'en'); + $locale = $locale ?: System::getEnv('_APP_LOCALE', 'en'); $template = [ 'senderName' => $senderName, @@ -97,7 +97,7 @@ class Update extends Action ]; $templates = $project->getAttribute('templates', []); - $templates['email.' . $type . '-' . $locale] = $template; + $templates['email.' . $templateId . '-' . $locale] = $template; $updates = new Document([ 'templates' => $templates, @@ -105,10 +105,10 @@ class Update extends Action $project = $authorization->skip(fn () => $dbForPlatform->updateDocument('projects', $project->getId(), $updates)); - $queueForEvents->setParam('templateType', $type); + $queueForEvents->setParam('templateType', $templateId); $response->dynamic(new Document([ - 'type' => $type, + 'templateId' => $templateId, 'locale' => $locale, 'senderName' => $template['senderName'], 'senderEmail' => $template['senderEmail'], diff --git a/src/Appwrite/Utopia/Request/Filters/V23.php b/src/Appwrite/Utopia/Request/Filters/V23.php new file mode 100644 index 0000000000..adb5d69aea --- /dev/null +++ b/src/Appwrite/Utopia/Request/Filters/V23.php @@ -0,0 +1,31 @@ +parseEmailTemplate($content); + break; + } + return $content; + } +} diff --git a/src/Appwrite/Utopia/Response/Filters/V23.php b/src/Appwrite/Utopia/Response/Filters/V23.php new file mode 100644 index 0000000000..54fcf8459f --- /dev/null +++ b/src/Appwrite/Utopia/Response/Filters/V23.php @@ -0,0 +1,28 @@ + $this->parseEmailTemplate($content), + default => $content, + }; + } + + private function parseEmailTemplate(array $content): array + { + if (isset($content['templateId'])) { + $content['type'] = $content['templateId']; + unset($content['templateId']); + } + + return $content; + } +} diff --git a/src/Appwrite/Utopia/Response/Model/Template.php b/src/Appwrite/Utopia/Response/Model/Template.php deleted file mode 100644 index 3ce9cacdb3..0000000000 --- a/src/Appwrite/Utopia/Response/Model/Template.php +++ /dev/null @@ -1,32 +0,0 @@ -addRule('type', [ - 'type' => self::TYPE_STRING, - 'description' => 'Template type', - 'default' => '', - 'example' => 'verification', - ]) - ->addRule('locale', [ - 'type' => self::TYPE_STRING, - 'description' => 'Template locale', - 'default' => '', - 'example' => 'en_us', - ]) - ->addRule('message', [ - 'type' => self::TYPE_STRING, - 'description' => 'Template message', - 'default' => '', - 'example' => 'Click on the link to verify your account.', - ]) - ; - } -} diff --git a/src/Appwrite/Utopia/Response/Model/TemplateEmail.php b/src/Appwrite/Utopia/Response/Model/TemplateEmail.php index 95cd57a584..9b77617b8c 100644 --- a/src/Appwrite/Utopia/Response/Model/TemplateEmail.php +++ b/src/Appwrite/Utopia/Response/Model/TemplateEmail.php @@ -3,13 +3,31 @@ namespace Appwrite\Utopia\Response\Model; use Appwrite\Utopia\Response; +use Appwrite\Utopia\Response\Model; -class TemplateEmail extends Template +class TemplateEmail extends Model { public function __construct() { - parent::__construct(); $this + ->addRule('templateId', [ + 'type' => self::TYPE_STRING, + 'description' => 'Template type', + 'default' => '', + 'example' => 'verification', + ]) + ->addRule('locale', [ + 'type' => self::TYPE_STRING, + 'description' => 'Template locale', + 'default' => '', + 'example' => 'en_us', + ]) + ->addRule('message', [ + 'type' => self::TYPE_STRING, + 'description' => 'Template message', + 'default' => '', + 'example' => 'Click on the link to verify your account.', + ]) ->addRule('senderName', [ 'type' => self::TYPE_STRING, 'description' => 'Name of the sender', diff --git a/tests/e2e/Services/Project/TemplatesBase.php b/tests/e2e/Services/Project/TemplatesBase.php index 45e09779b9..b61f85aeef 100644 --- a/tests/e2e/Services/Project/TemplatesBase.php +++ b/tests/e2e/Services/Project/TemplatesBase.php @@ -15,7 +15,7 @@ trait TemplatesBase $template = $this->getEmailTemplate('verification', 'en'); $this->assertSame(200, $template['headers']['status-code']); - $this->assertSame('verification', $template['body']['type']); + $this->assertSame('verification', $template['body']['templateId']); $this->assertSame('en', $template['body']['locale']); $this->assertFalse($template['body']['custom']); $this->assertNotEmpty($template['body']['subject']); @@ -27,7 +27,7 @@ trait TemplatesBase $template = $this->getEmailTemplate('verification'); $this->assertSame(200, $template['headers']['status-code']); - $this->assertSame('verification', $template['body']['type']); + $this->assertSame('verification', $template['body']['templateId']); $this->assertSame('en', $template['body']['locale']); $this->assertFalse($template['body']['custom']); } @@ -40,7 +40,7 @@ trait TemplatesBase $get = $this->getEmailTemplate('magicSession', 'en'); $this->assertSame(200, $get['headers']['status-code']); - $this->assertSame('magicSession', $get['body']['type']); + $this->assertSame('magicSession', $get['body']['templateId']); $this->assertSame('en', $get['body']['locale']); $this->assertTrue($get['body']['custom']); $this->assertSame('Magic Subject', $get['body']['subject']); @@ -85,7 +85,7 @@ trait TemplatesBase ); $this->assertSame(200, $update['headers']['status-code']); - $this->assertSame('verification', $update['body']['type']); + $this->assertSame('verification', $update['body']['templateId']); $this->assertSame('en', $update['body']['locale']); $this->assertSame('Please verify your email', $update['body']['subject']); $this->assertSame('Click here to verify: {{url}}', $update['body']['message']); @@ -135,7 +135,7 @@ trait TemplatesBase ); $this->assertSame(200, $update['headers']['status-code']); - $this->assertSame('sessionAlert', $update['body']['type']); + $this->assertSame('sessionAlert', $update['body']['templateId']); $this->assertSame('en', $update['body']['locale']); // Cleanup @@ -281,6 +281,105 @@ trait TemplatesBase $this->deleteEmailTemplate('recovery', 'en'); } + // ========================================================================= + // Legacy response format tests (request + response filters) + // ========================================================================= + + public function testGetEmailTemplateLegacyResponseFormat(): void + { + $headers = \array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-response-format' => '1.9.1', + ], $this->getHeaders()); + + $template = $this->client->call( + Client::METHOD_GET, + '/project/templates/email/verification', + $headers, + ); + + $this->assertSame(200, $template['headers']['status-code']); + // Response filter should rename templateId -> type for < 1.9.2 clients. + $this->assertArrayHasKey('type', $template['body']); + $this->assertArrayNotHasKey('templateId', $template['body']); + $this->assertSame('verification', $template['body']['type']); + $this->assertSame('en', $template['body']['locale']); + } + + public function testUpdateEmailTemplateLegacyResponseFormat(): void + { + $headers = \array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-response-format' => '1.9.1', + ], $this->getHeaders()); + + // Request filter should accept legacy `type` and map it to `templateId`. + $update = $this->client->call( + Client::METHOD_PATCH, + '/project/templates/email', + $headers, + [ + 'type' => 'magicSession', + 'locale' => 'en', + 'subject' => 'Legacy Subject', + 'message' => 'Legacy Body', + ], + ); + + $this->assertSame(200, $update['headers']['status-code']); + // Response filter should rename templateId -> type for < 1.9.2 clients. + $this->assertArrayHasKey('type', $update['body']); + $this->assertArrayNotHasKey('templateId', $update['body']); + $this->assertSame('magicSession', $update['body']['type']); + $this->assertSame('Legacy Subject', $update['body']['subject']); + $this->assertSame('Legacy Body', $update['body']['message']); + $this->assertTrue($update['body']['custom']); + + // Verify persisted, then cleanup via legacy DELETE with `type`. + $get = $this->getEmailTemplate('magicSession', 'en'); + $this->assertSame(200, $get['headers']['status-code']); + $this->assertTrue($get['body']['custom']); + + $delete = $this->client->call( + Client::METHOD_DELETE, + '/project/templates/email', + $headers, + [ + 'type' => 'magicSession', + 'locale' => 'en', + ], + ); + $this->assertSame(204, $delete['headers']['status-code']); + + $after = $this->getEmailTemplate('magicSession', 'en'); + $this->assertFalse($after['body']['custom']); + } + + public function testUpdateEmailTemplateLegacyInvalidType(): void + { + $headers = \array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-response-format' => '1.9.1', + ], $this->getHeaders()); + + $update = $this->client->call( + Client::METHOD_PATCH, + '/project/templates/email', + $headers, + [ + 'type' => 'notATemplate', + 'locale' => 'en', + 'subject' => 'Subject', + 'message' => 'Message', + ], + ); + + $this->assertSame(400, $update['headers']['status-code']); + } + // ========================================================================= // Helpers // ========================================================================= @@ -315,7 +414,7 @@ trait TemplatesBase bool $authenticated = true, ): mixed { $params = [ - 'type' => $type, + 'templateId' => $type, ]; if ($locale !== null) { @@ -352,7 +451,7 @@ trait TemplatesBase protected function deleteEmailTemplate(string $type, ?string $locale = null, bool $authenticated = true): mixed { $params = [ - 'type' => $type, + 'templateId' => $type, ]; if ($locale !== null) {