Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions src/Migration/Destinations/Appwrite.php
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,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;
Expand Down Expand Up @@ -291,6 +292,7 @@ public static function getSupportedResources(): array
Resource::TYPE_PLATFORM,
Resource::TYPE_API_KEY,
Resource::TYPE_WEBHOOK,
Resource::TYPE_SMTP,

// Project
Resource::TYPE_PROJECT_VARIABLE,
Expand Down Expand Up @@ -3113,6 +3115,10 @@ public function importIntegrationsResource(Resource $resource): Resource
/** @var Webhook $resource */
$this->createWebhook($resource);
break;
case Resource::TYPE_SMTP:
/** @var SMTP $resource */
$this->createSMTP($resource);
break;
}

if ($resource->getStatus() !== Resource::STATUS_SKIPPED) {
Expand Down Expand Up @@ -3244,6 +3250,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();
Comment thread
premtsd-code marked this conversation as resolved.
$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', [
Expand Down
2 changes: 2 additions & 0 deletions src/Migration/Resource.php
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ abstract class Resource implements \JsonSerializable
public const TYPE_PLATFORM = 'platform';
public const TYPE_API_KEY = 'api-key';
public const TYPE_WEBHOOK = 'webhook';
public const TYPE_SMTP = 'smtp';

// Project (per-project singleton/settings resources)
public const TYPE_PROJECT_VARIABLE = 'project-variable';
Expand Down Expand Up @@ -129,6 +130,7 @@ abstract class Resource implements \JsonSerializable
self::TYPE_PLATFORM,
self::TYPE_API_KEY,
self::TYPE_WEBHOOK,
self::TYPE_SMTP,
self::TYPE_PROJECT_VARIABLE,
self::TYPE_PROJECT_PROTOCOLS,
self::TYPE_PROJECT_LABELS,
Expand Down
129 changes: 129 additions & 0 deletions src/Migration/Resources/Settings/SMTP.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
<?php

namespace Utopia\Migration\Resources\Settings;

use Utopia\Migration\Resource;
use Utopia\Migration\Transfer;

/**
* Singleton resource representing the project's custom SMTP configuration.
* Password is not migrated — the source API never exposes it.
*/
class SMTP extends Resource
{
public function __construct(
string $id,
private readonly bool $enabled = false,
private readonly string $senderName = '',
private readonly string $senderEmail = '',
private readonly string $replyToName = '',
private readonly string $replyToEmail = '',
private readonly string $host = '',
private readonly int $port = 0,
private readonly string $username = '',
private readonly string $secure = '',
string $createdAt = '',
string $updatedAt = '',
) {
$this->id = $id;
$this->createdAt = $createdAt;
$this->updatedAt = $updatedAt;
}

/**
* @param array<string, mixed> $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<string, mixed>
*/
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_INTEGRATIONS;
}

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;
}
}
44 changes: 44 additions & 0 deletions src/Migration/Sources/Appwrite.php
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,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;
Expand Down Expand Up @@ -226,6 +227,7 @@ public static function getSupportedResources(): array
Resource::TYPE_PLATFORM,
Resource::TYPE_API_KEY,
Resource::TYPE_WEBHOOK,
Resource::TYPE_SMTP,

// Backups
Resource::TYPE_BACKUP_POLICY,
Expand Down Expand Up @@ -1658,6 +1660,7 @@ protected function exportGroupProjects(int $batchSize, array $resources): void
previous: $e
));
}

}

private function exportServices(): void
Expand Down Expand Up @@ -1714,6 +1717,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
*/
Expand Down Expand Up @@ -2630,6 +2655,11 @@ private function reportIntegrations(array $resources, array &$report, array $res
$report[Resource::TYPE_WEBHOOK] = 0;
}
}

if (\in_array(Resource::TYPE_SMTP, $resources)) {
// Singleton — one SMTP config per project.
$report[Resource::TYPE_SMTP] = 1;
}
}

/**
Expand Down Expand Up @@ -2703,6 +2733,20 @@ protected function exportGroupIntegrations(int $batchSize, array $resources): vo
));
}
}

try {
if (\in_array(Resource::TYPE_SMTP, $resources)) {
$this->exportSMTP();
}
} catch (\Throwable $e) {
$this->addError(new Exception(
Resource::TYPE_SMTP,
Transfer::GROUP_INTEGRATIONS,
message: $e->getMessage(),
code: (int) $e->getCode() ?: Exception::CODE_INTERNAL,
previous: $e
));
}
}

/**
Expand Down
2 changes: 2 additions & 0 deletions src/Migration/Transfer.php
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ class Transfer
Resource::TYPE_PLATFORM,
Resource::TYPE_API_KEY,
Resource::TYPE_WEBHOOK,
Resource::TYPE_SMTP,
];
public const GROUP_DOCUMENTSDB_RESOURCES = [
Resource::TYPE_DATABASE_DOCUMENTSDB,
Expand Down Expand Up @@ -144,6 +145,7 @@ class Transfer
Resource::TYPE_PLATFORM,
Resource::TYPE_API_KEY,
Resource::TYPE_WEBHOOK,
Resource::TYPE_SMTP,

// Project
Resource::TYPE_PROJECT_VARIABLE,
Expand Down
Loading