diff --git a/src/Migration/Destinations/Appwrite.php b/src/Migration/Destinations/Appwrite.php index 7af35c2d..fc49a348 100644 --- a/src/Migration/Destinations/Appwrite.php +++ b/src/Migration/Destinations/Appwrite.php @@ -65,6 +65,7 @@ use Utopia\Migration\Resources\Settings\ProjectVariable; use Utopia\Migration\Resources\Settings\Protocols; use Utopia\Migration\Resources\Settings\Services as ServicesResource; +use Utopia\Migration\Resources\Settings\SMTP; use Utopia\Migration\Resources\Settings\Webhook; use Utopia\Migration\Resources\Sites\Deployment as SiteDeployment; use Utopia\Migration\Resources\Sites\EnvVar as SiteEnvVar; @@ -298,6 +299,7 @@ public static function getSupportedResources(): array Resource::TYPE_PROTOCOLS, Resource::TYPE_LABELS, Resource::TYPE_SERVICES, + Resource::TYPE_SMTP, // Backups Resource::TYPE_BACKUP_POLICY, @@ -3142,6 +3144,10 @@ public function importSettingsResource(Resource $resource): Resource /** @var ServicesResource $resource */ $this->createServices($resource); break; + case Resource::TYPE_SMTP: + /** @var SMTP $resource */ + $this->createSMTP($resource); + break; } if ($resource->getStatus() !== Resource::STATUS_SKIPPED) { @@ -3259,6 +3265,36 @@ protected function createServices(ServicesResource $resource): bool return true; } + /** + * Password is intentionally not copied — source API never exposes it. + * Read-then-merge preserves the destination's existing password. + */ + protected function createSMTP(SMTP $resource): bool + { + $project = $this->dbForPlatform->getDocument('projects', $this->projectId); + $smtp = $project->getAttribute('smtp', []); + + $smtp['enabled'] = $resource->getEnabled(); + $smtp['senderName'] = $resource->getSenderName(); + $smtp['senderEmail'] = $resource->getSenderEmail(); + $smtp['replyToName'] = $resource->getReplyToName(); + $smtp['replyToEmail'] = $resource->getReplyToEmail(); + $smtp['host'] = $resource->getHost(); + $smtp['port'] = $resource->getPort(); + $smtp['username'] = $resource->getUsername(); + $smtp['secure'] = $resource->getSecure(); + + $this->dbForPlatform->getAuthorization()->skip(fn () => $this->dbForPlatform->updateDocument( + 'projects', + $this->projectId, + new UtopiaDocument(['smtp' => $smtp]), + )); + + $this->dbForPlatform->purgeCachedDocument('projects', $this->projectId); + + return true; + } + protected function createWebhook(Webhook $resource): bool { $existing = $this->dbForPlatform->findOne('webhooks', [ diff --git a/src/Migration/Resource.php b/src/Migration/Resource.php index f77b3d8d..53020e1a 100644 --- a/src/Migration/Resource.php +++ b/src/Migration/Resource.php @@ -85,6 +85,7 @@ abstract class Resource implements \JsonSerializable public const TYPE_PROTOCOLS = 'protocols'; public const TYPE_LABELS = 'labels'; public const TYPE_SERVICES = 'services'; + public const TYPE_SMTP = 'smtp'; // Messaging public const TYPE_SUBSCRIBER = 'subscriber'; @@ -133,6 +134,7 @@ abstract class Resource implements \JsonSerializable self::TYPE_PROTOCOLS, self::TYPE_LABELS, self::TYPE_SERVICES, + self::TYPE_SMTP, self::TYPE_PROVIDER, self::TYPE_TOPIC, self::TYPE_SUBSCRIBER, diff --git a/src/Migration/Resources/Settings/SMTP.php b/src/Migration/Resources/Settings/SMTP.php new file mode 100644 index 00000000..9233225d --- /dev/null +++ b/src/Migration/Resources/Settings/SMTP.php @@ -0,0 +1,129 @@ +id = $id; + $this->createdAt = $createdAt; + $this->updatedAt = $updatedAt; + } + + /** + * @param array $array + */ + public static function fromArray(array $array): self + { + return new self( + $array['id'], + (bool) ($array['enabled'] ?? false), + (string) ($array['senderName'] ?? ''), + (string) ($array['senderEmail'] ?? ''), + (string) ($array['replyToName'] ?? ''), + (string) ($array['replyToEmail'] ?? ''), + (string) ($array['host'] ?? ''), + (int) ($array['port'] ?? 0), + (string) ($array['username'] ?? ''), + (string) ($array['secure'] ?? ''), + createdAt: $array['createdAt'] ?? '', + updatedAt: $array['updatedAt'] ?? '', + ); + } + + /** + * @return array + */ + public function jsonSerialize(): array + { + return [ + 'id' => $this->id, + 'enabled' => $this->enabled, + 'senderName' => $this->senderName, + 'senderEmail' => $this->senderEmail, + 'replyToName' => $this->replyToName, + 'replyToEmail' => $this->replyToEmail, + 'host' => $this->host, + 'port' => $this->port, + 'username' => $this->username, + 'secure' => $this->secure, + 'createdAt' => $this->createdAt, + 'updatedAt' => $this->updatedAt, + ]; + } + + public static function getName(): string + { + return Resource::TYPE_SMTP; + } + + public function getGroup(): string + { + return Transfer::GROUP_SETTINGS; + } + + public function getEnabled(): bool + { + return $this->enabled; + } + + public function getSenderName(): string + { + return $this->senderName; + } + + public function getSenderEmail(): string + { + return $this->senderEmail; + } + + public function getReplyToName(): string + { + return $this->replyToName; + } + + public function getReplyToEmail(): string + { + return $this->replyToEmail; + } + + public function getHost(): string + { + return $this->host; + } + + public function getPort(): int + { + return $this->port; + } + + public function getUsername(): string + { + return $this->username; + } + + public function getSecure(): string + { + return $this->secure; + } +} diff --git a/src/Migration/Sources/Appwrite.php b/src/Migration/Sources/Appwrite.php index 1b9c601e..9a31eedc 100644 --- a/src/Migration/Sources/Appwrite.php +++ b/src/Migration/Sources/Appwrite.php @@ -72,6 +72,7 @@ use Utopia\Migration\Resources\Settings\ProjectVariable; use Utopia\Migration\Resources\Settings\Protocols; use Utopia\Migration\Resources\Settings\Services as ServicesResource; +use Utopia\Migration\Resources\Settings\SMTP; use Utopia\Migration\Resources\Settings\Webhook; use Utopia\Migration\Resources\Sites\Deployment as SiteDeployment; use Utopia\Migration\Resources\Sites\EnvVar as SiteEnvVar; @@ -236,6 +237,7 @@ public static function getSupportedResources(): array Resource::TYPE_PROTOCOLS, Resource::TYPE_LABELS, Resource::TYPE_SERVICES, + Resource::TYPE_SMTP, ]; } @@ -1609,6 +1611,11 @@ private function reportSettings(array $resources, array &$report, array $resourc // Singleton — one services config per project. $report[Resource::TYPE_SERVICES] = 1; } + + if (\in_array(Resource::TYPE_SMTP, $resources)) { + // Singleton — one SMTP config per project. + $report[Resource::TYPE_SMTP] = 1; + } } /** @@ -1686,6 +1693,20 @@ protected function exportGroupSettings(int $batchSize, array $resources): void previous: $e )); } + + try { + if (\in_array(Resource::TYPE_SMTP, $resources)) { + $this->exportSMTP(); + } + } catch (\Throwable $e) { + $this->addError(new Exception( + Resource::TYPE_SMTP, + Transfer::GROUP_SETTINGS, + message: $e->getMessage(), + code: (int) $e->getCode() ?: Exception::CODE_INTERNAL, + previous: $e + )); + } } private function exportServices(): void @@ -1758,6 +1779,28 @@ private function exportProtocols(): void $this->callback([$protocols]); } + private function exportSMTP(): void + { + $project = $this->project->get(); + + $smtp = new SMTP( + $this->projectId, + $project->smtpEnabled, + $project->smtpSenderName, + $project->smtpSenderEmail, + $project->smtpReplyToName, + $project->smtpReplyToEmail, + $project->smtpHost, + $project->smtpPort, + $project->smtpUsername, + $project->smtpSecure, + createdAt: $project->createdAt, + updatedAt: $project->updatedAt, + ); + + $this->callback([$smtp]); + } + /** * @throws AppwriteException */ diff --git a/src/Migration/Transfer.php b/src/Migration/Transfer.php index c22d1c26..0ed5cb78 100644 --- a/src/Migration/Transfer.php +++ b/src/Migration/Transfer.php @@ -102,6 +102,7 @@ class Transfer Resource::TYPE_PROTOCOLS, Resource::TYPE_LABELS, Resource::TYPE_SERVICES, + Resource::TYPE_SMTP, ]; public const GROUP_BACKUPS_RESOURCES = [ @@ -150,6 +151,7 @@ class Transfer Resource::TYPE_PROTOCOLS, Resource::TYPE_LABELS, Resource::TYPE_SERVICES, + Resource::TYPE_SMTP, // legacy Resource::TYPE_DOCUMENT,