Skip to content

Commit b07a09f

Browse files
authored
Merge pull request #187 from utopia-php/add-smtp-migration
Add SMTP migration
2 parents e99d8b9 + ad10a57 commit b07a09f

7 files changed

Lines changed: 215 additions & 0 deletions

File tree

src/Migration/Destinations/Appwrite.php

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@
6464
use Utopia\Migration\Resources\Settings\ProjectVariable;
6565
use Utopia\Migration\Resources\Settings\Protocols;
6666
use Utopia\Migration\Resources\Settings\Services as ServicesResource;
67+
use Utopia\Migration\Resources\Settings\SMTP;
6768
use Utopia\Migration\Resources\Settings\Webhook;
6869
use Utopia\Migration\Resources\Sites\Deployment as SiteDeployment;
6970
use Utopia\Migration\Resources\Sites\EnvVar as SiteEnvVar;
@@ -291,6 +292,7 @@ public static function getSupportedResources(): array
291292
Resource::TYPE_PLATFORM,
292293
Resource::TYPE_API_KEY,
293294
Resource::TYPE_WEBHOOK,
295+
Resource::TYPE_SMTP,
294296

295297
// Project
296298
Resource::TYPE_PROJECT_VARIABLE,
@@ -3113,6 +3115,10 @@ public function importIntegrationsResource(Resource $resource): Resource
31133115
/** @var Webhook $resource */
31143116
$this->createWebhook($resource);
31153117
break;
3118+
case Resource::TYPE_SMTP:
3119+
/** @var SMTP $resource */
3120+
$this->createSMTP($resource);
3121+
break;
31163122
}
31173123

31183124
if ($resource->getStatus() !== Resource::STATUS_SKIPPED) {
@@ -3244,6 +3250,36 @@ protected function createServices(ServicesResource $resource): bool
32443250
return true;
32453251
}
32463252

3253+
/**
3254+
* Password is intentionally not copied — source API never exposes it.
3255+
* Read-then-merge preserves the destination's existing password.
3256+
*/
3257+
protected function createSMTP(SMTP $resource): bool
3258+
{
3259+
$project = $this->dbForPlatform->getDocument('projects', $this->projectId);
3260+
$smtp = $project->getAttribute('smtp', []);
3261+
3262+
$smtp['enabled'] = $resource->getEnabled();
3263+
$smtp['senderName'] = $resource->getSenderName();
3264+
$smtp['senderEmail'] = $resource->getSenderEmail();
3265+
$smtp['replyToName'] = $resource->getReplyToName();
3266+
$smtp['replyToEmail'] = $resource->getReplyToEmail();
3267+
$smtp['host'] = $resource->getHost();
3268+
$smtp['port'] = $resource->getPort();
3269+
$smtp['username'] = $resource->getUsername();
3270+
$smtp['secure'] = $resource->getSecure();
3271+
3272+
$this->dbForPlatform->getAuthorization()->skip(fn () => $this->dbForPlatform->updateDocument(
3273+
'projects',
3274+
$this->projectId,
3275+
new UtopiaDocument(['smtp' => $smtp]),
3276+
));
3277+
3278+
$this->dbForPlatform->purgeCachedDocument('projects', $this->projectId);
3279+
3280+
return true;
3281+
}
3282+
32473283
protected function createWebhook(Webhook $resource): bool
32483284
{
32493285
$existing = $this->dbForPlatform->findOne('webhooks', [

src/Migration/Resource.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ abstract class Resource implements \JsonSerializable
7979
public const TYPE_PLATFORM = 'platform';
8080
public const TYPE_API_KEY = 'api-key';
8181
public const TYPE_WEBHOOK = 'webhook';
82+
public const TYPE_SMTP = 'smtp';
8283

8384
// Project (per-project singleton/settings resources)
8485
public const TYPE_PROJECT_VARIABLE = 'project-variable';
@@ -129,6 +130,7 @@ abstract class Resource implements \JsonSerializable
129130
self::TYPE_PLATFORM,
130131
self::TYPE_API_KEY,
131132
self::TYPE_WEBHOOK,
133+
self::TYPE_SMTP,
132134
self::TYPE_PROJECT_VARIABLE,
133135
self::TYPE_PROJECT_PROTOCOLS,
134136
self::TYPE_PROJECT_LABELS,
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
<?php
2+
3+
namespace Utopia\Migration\Resources\Settings;
4+
5+
use Utopia\Migration\Resource;
6+
use Utopia\Migration\Transfer;
7+
8+
/**
9+
* Singleton resource representing the project's custom SMTP configuration.
10+
* Password is not migrated — the source API never exposes it.
11+
*/
12+
class SMTP extends Resource
13+
{
14+
public function __construct(
15+
string $id,
16+
private readonly bool $enabled = false,
17+
private readonly string $senderName = '',
18+
private readonly string $senderEmail = '',
19+
private readonly string $replyToName = '',
20+
private readonly string $replyToEmail = '',
21+
private readonly string $host = '',
22+
private readonly int $port = 0,
23+
private readonly string $username = '',
24+
private readonly string $secure = '',
25+
string $createdAt = '',
26+
string $updatedAt = '',
27+
) {
28+
$this->id = $id;
29+
$this->createdAt = $createdAt;
30+
$this->updatedAt = $updatedAt;
31+
}
32+
33+
/**
34+
* @param array<string, mixed> $array
35+
*/
36+
public static function fromArray(array $array): self
37+
{
38+
return new self(
39+
$array['id'],
40+
(bool) ($array['enabled'] ?? false),
41+
(string) ($array['senderName'] ?? ''),
42+
(string) ($array['senderEmail'] ?? ''),
43+
(string) ($array['replyToName'] ?? ''),
44+
(string) ($array['replyToEmail'] ?? ''),
45+
(string) ($array['host'] ?? ''),
46+
(int) ($array['port'] ?? 0),
47+
(string) ($array['username'] ?? ''),
48+
(string) ($array['secure'] ?? ''),
49+
createdAt: $array['createdAt'] ?? '',
50+
updatedAt: $array['updatedAt'] ?? '',
51+
);
52+
}
53+
54+
/**
55+
* @return array<string, mixed>
56+
*/
57+
public function jsonSerialize(): array
58+
{
59+
return [
60+
'id' => $this->id,
61+
'enabled' => $this->enabled,
62+
'senderName' => $this->senderName,
63+
'senderEmail' => $this->senderEmail,
64+
'replyToName' => $this->replyToName,
65+
'replyToEmail' => $this->replyToEmail,
66+
'host' => $this->host,
67+
'port' => $this->port,
68+
'username' => $this->username,
69+
'secure' => $this->secure,
70+
'createdAt' => $this->createdAt,
71+
'updatedAt' => $this->updatedAt,
72+
];
73+
}
74+
75+
public static function getName(): string
76+
{
77+
return Resource::TYPE_SMTP;
78+
}
79+
80+
public function getGroup(): string
81+
{
82+
return Transfer::GROUP_INTEGRATIONS;
83+
}
84+
85+
public function getEnabled(): bool
86+
{
87+
return $this->enabled;
88+
}
89+
90+
public function getSenderName(): string
91+
{
92+
return $this->senderName;
93+
}
94+
95+
public function getSenderEmail(): string
96+
{
97+
return $this->senderEmail;
98+
}
99+
100+
public function getReplyToName(): string
101+
{
102+
return $this->replyToName;
103+
}
104+
105+
public function getReplyToEmail(): string
106+
{
107+
return $this->replyToEmail;
108+
}
109+
110+
public function getHost(): string
111+
{
112+
return $this->host;
113+
}
114+
115+
public function getPort(): int
116+
{
117+
return $this->port;
118+
}
119+
120+
public function getUsername(): string
121+
{
122+
return $this->username;
123+
}
124+
125+
public function getSecure(): string
126+
{
127+
return $this->secure;
128+
}
129+
}

src/Migration/Sources/Appwrite.php

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@
7171
use Utopia\Migration\Resources\Settings\ProjectVariable;
7272
use Utopia\Migration\Resources\Settings\Protocols;
7373
use Utopia\Migration\Resources\Settings\Services as ServicesResource;
74+
use Utopia\Migration\Resources\Settings\SMTP;
7475
use Utopia\Migration\Resources\Settings\Webhook;
7576
use Utopia\Migration\Resources\Sites\Deployment as SiteDeployment;
7677
use Utopia\Migration\Resources\Sites\EnvVar as SiteEnvVar;
@@ -226,6 +227,7 @@ public static function getSupportedResources(): array
226227
Resource::TYPE_PLATFORM,
227228
Resource::TYPE_API_KEY,
228229
Resource::TYPE_WEBHOOK,
230+
Resource::TYPE_SMTP,
229231

230232
// Backups
231233
Resource::TYPE_BACKUP_POLICY,
@@ -1658,6 +1660,7 @@ protected function exportGroupProjects(int $batchSize, array $resources): void
16581660
previous: $e
16591661
));
16601662
}
1663+
16611664
}
16621665

16631666
private function exportServices(): void
@@ -1714,6 +1717,28 @@ private function exportProtocols(): void
17141717
$this->callback([$protocols]);
17151718
}
17161719

1720+
private function exportSMTP(): void
1721+
{
1722+
$project = $this->project->get();
1723+
1724+
$smtp = new SMTP(
1725+
$this->projectId,
1726+
$project->smtpEnabled,
1727+
$project->smtpSenderName,
1728+
$project->smtpSenderEmail,
1729+
$project->smtpReplyToName,
1730+
$project->smtpReplyToEmail,
1731+
$project->smtpHost,
1732+
$project->smtpPort,
1733+
$project->smtpUsername,
1734+
$project->smtpSecure,
1735+
createdAt: $project->createdAt,
1736+
updatedAt: $project->updatedAt,
1737+
);
1738+
1739+
$this->callback([$smtp]);
1740+
}
1741+
17171742
/**
17181743
* @throws AppwriteException
17191744
*/
@@ -2630,6 +2655,11 @@ private function reportIntegrations(array $resources, array &$report, array $res
26302655
$report[Resource::TYPE_WEBHOOK] = 0;
26312656
}
26322657
}
2658+
2659+
if (\in_array(Resource::TYPE_SMTP, $resources)) {
2660+
// Singleton — one SMTP config per project.
2661+
$report[Resource::TYPE_SMTP] = 1;
2662+
}
26332663
}
26342664

26352665
/**
@@ -2703,6 +2733,20 @@ protected function exportGroupIntegrations(int $batchSize, array $resources): vo
27032733
));
27042734
}
27052735
}
2736+
2737+
try {
2738+
if (\in_array(Resource::TYPE_SMTP, $resources)) {
2739+
$this->exportSMTP();
2740+
}
2741+
} catch (\Throwable $e) {
2742+
$this->addError(new Exception(
2743+
Resource::TYPE_SMTP,
2744+
Transfer::GROUP_INTEGRATIONS,
2745+
message: $e->getMessage(),
2746+
code: (int) $e->getCode() ?: Exception::CODE_INTERNAL,
2747+
previous: $e
2748+
));
2749+
}
27062750
}
27072751

27082752
/**

src/Migration/Transfer.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ class Transfer
6868
Resource::TYPE_PLATFORM,
6969
Resource::TYPE_API_KEY,
7070
Resource::TYPE_WEBHOOK,
71+
Resource::TYPE_SMTP,
7172
];
7273
public const GROUP_DOCUMENTSDB_RESOURCES = [
7374
Resource::TYPE_DATABASE_DOCUMENTSDB,
@@ -144,6 +145,7 @@ class Transfer
144145
Resource::TYPE_PLATFORM,
145146
Resource::TYPE_API_KEY,
146147
Resource::TYPE_WEBHOOK,
148+
Resource::TYPE_SMTP,
147149

148150
// Project
149151
Resource::TYPE_PROJECT_VARIABLE,

tests/Migration/Unit/Adapters/MockDestination.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ public static function getSupportedResources(): array
5353
Resource::TYPE_MEMBERSHIP,
5454
Resource::TYPE_PLATFORM,
5555
Resource::TYPE_API_KEY,
56+
Resource::TYPE_SMTP,
5657
Resource::TYPE_PROJECT_VARIABLE,
5758
Resource::TYPE_WEBHOOK,
5859
Resource::TYPE_AUTH_METHODS,

tests/Migration/Unit/Adapters/MockSource.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ public static function getSupportedResources(): array
8282
Resource::TYPE_MEMBERSHIP,
8383
Resource::TYPE_PLATFORM,
8484
Resource::TYPE_API_KEY,
85+
Resource::TYPE_SMTP,
8586
Resource::TYPE_PROJECT_VARIABLE,
8687
Resource::TYPE_WEBHOOK,
8788
Resource::TYPE_AUTH_METHODS,

0 commit comments

Comments
 (0)