Skip to content

Commit 2a8a3e2

Browse files
committed
fix: handle empty string provider fields in messaging migration
1 parent 123a830 commit 2a8a3e2

9 files changed

Lines changed: 233 additions & 402 deletions

File tree

src/Migration/Destinations/Appwrite.php

Lines changed: 129 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1593,7 +1593,7 @@ public function importMessagingResource(Resource $resource): Resource
15931593
* @throws AppwriteException
15941594
* @throws \Exception
15951595
*/
1596-
protected function createProvider(Provider $resource): void
1596+
protected function createProvider(Provider $resource): bool
15971597
{
15981598
$credentials = $resource->getCredentials();
15991599
$options = $resource->getOptions();
@@ -1608,50 +1608,50 @@ protected function createProvider(Provider $resource): void
16081608
$credentials['apiKey'] ?? null,
16091609
$credentials['domain'] ?? null,
16101610
$credentials['isEuRegion'] ?? null,
1611-
$options['fromName'] ?? null,
1612-
$options['fromEmail'] ?? null,
1613-
$options['replyToName'] ?? null,
1614-
$options['replyToEmail'] ?? null,
1611+
$options['fromName'] ?: null,
1612+
$options['fromEmail'] ?: null,
1613+
$options['replyToName'] ?: null,
1614+
$options['replyToEmail'] ?: null,
16151615
$enabled,
16161616
),
16171617
'sendgrid' => $this->messaging->createSendgridProvider(
16181618
$id,
16191619
$name,
16201620
$credentials['apiKey'] ?? null,
1621-
$options['fromName'] ?? null,
1622-
$options['fromEmail'] ?? null,
1623-
$options['replyToName'] ?? null,
1624-
$options['replyToEmail'] ?? null,
1621+
$options['fromName'] ?: null,
1622+
$options['fromEmail'] ?: null,
1623+
$options['replyToName'] ?: null,
1624+
$options['replyToEmail'] ?: null,
16251625
$enabled,
16261626
),
16271627
'resend' => $this->messaging->createResendProvider(
16281628
$id,
16291629
$name,
16301630
$credentials['apiKey'] ?? null,
1631-
$options['fromName'] ?? null,
1632-
$options['fromEmail'] ?? null,
1633-
$options['replyToName'] ?? null,
1634-
$options['replyToEmail'] ?? null,
1631+
$options['fromName'] ?: null,
1632+
$options['fromEmail'] ?: null,
1633+
$options['replyToName'] ?: null,
1634+
$options['replyToEmail'] ?: null,
16351635
$enabled,
16361636
),
16371637
'smtp' => $this->messaging->createSMTPProvider(
16381638
$id,
16391639
$name,
16401640
$credentials['host'] ?? '',
16411641
$credentials['port'] ?? null,
1642-
$credentials['username'] ?? null,
1643-
$credentials['password'] ?? null,
1642+
$credentials['username'] ?: null,
1643+
$credentials['password'] ?: null,
16441644
match ($options['encryption'] ?? '') {
16451645
'ssl' => SmtpEncryption::SSL(),
16461646
'tls' => SmtpEncryption::TLS(),
16471647
default => SmtpEncryption::NONE(),
16481648
},
16491649
$options['autoTLS'] ?? null,
16501650
$options['mailer'] ?: null,
1651-
$options['fromName'] ?? null,
1652-
$options['fromEmail'] ?? null,
1653-
$options['replyToName'] ?? null,
1654-
$options['replyToEmail'] ?? null,
1651+
$options['fromName'] ?: null,
1652+
$options['fromEmail'] ?: null,
1653+
$options['replyToName'] ?: null,
1654+
$options['replyToEmail'] ?: null,
16551655
$enabled,
16561656
),
16571657
'msg91' => $this->messaging->createMsg91Provider(
@@ -1665,31 +1665,31 @@ protected function createProvider(Provider $resource): void
16651665
'telesign' => $this->messaging->createTelesignProvider(
16661666
$id,
16671667
$name,
1668-
$options['from'] ?? null,
1668+
$options['from'] ?: null,
16691669
$credentials['customerId'] ?? null,
16701670
$credentials['apiKey'] ?? null,
16711671
$enabled,
16721672
),
16731673
'textmagic' => $this->messaging->createTextmagicProvider(
16741674
$id,
16751675
$name,
1676-
$options['from'] ?? null,
1676+
$options['from'] ?: null,
16771677
$credentials['username'] ?? null,
16781678
$credentials['apiKey'] ?? null,
16791679
$enabled,
16801680
),
16811681
'twilio' => $this->messaging->createTwilioProvider(
16821682
$id,
16831683
$name,
1684-
$options['from'] ?? null,
1684+
$options['from'] ?: null,
16851685
$credentials['accountSid'] ?? null,
16861686
$credentials['authToken'] ?? null,
16871687
$enabled,
16881688
),
16891689
'vonage' => $this->messaging->createVonageProvider(
16901690
$id,
16911691
$name,
1692-
$options['from'] ?? null,
1692+
$options['from'] ?: null,
16931693
$credentials['apiKey'] ?? null,
16941694
$credentials['apiSecret'] ?? null,
16951695
$enabled,
@@ -1712,50 +1712,105 @@ protected function createProvider(Provider $resource): void
17121712
),
17131713
default => throw new \Exception('Unknown provider: ' . $resource->getProvider()),
17141714
};
1715+
1716+
return true;
17151717
}
17161718

17171719
/**
17181720
* @throws AppwriteException
17191721
* @throws \Exception
17201722
*/
1721-
protected function createMessage(Message $resource): void
1723+
protected function createMessage(Message $resource): bool
1724+
{
1725+
$resolvedTargets = $this->resolveMessageTargets($resource);
1726+
$status = $resource->getMessageStatus();
1727+
1728+
// Use SDK for scheduled messages so the platform schedule document is created.
1729+
// Fall back to draft if scheduledAt is missing or in the past.
1730+
if ($status === 'scheduled') {
1731+
$scheduledAt = $resource->getScheduledAt();
1732+
1733+
if (!empty($scheduledAt) && new \DateTime($scheduledAt) > new \DateTime()) {
1734+
return $this->createScheduledMessage($resource, $resolvedTargets);
1735+
}
1736+
1737+
$status = 'draft';
1738+
}
1739+
1740+
// Processing messages have no worker on the destination, import as draft.
1741+
if ($status === 'processing') {
1742+
$status = 'draft';
1743+
}
1744+
1745+
$createdAt = $this->normalizeDateTime($resource->getCreatedAt());
1746+
$updatedAt = $this->normalizeDateTime($resource->getUpdatedAt(), $createdAt);
1747+
1748+
$this->database->createDocument('messages', new UtopiaDocument([
1749+
'$id' => $resource->getId(),
1750+
'$createdAt' => $createdAt,
1751+
'$updatedAt' => $updatedAt,
1752+
'providerType' => $resource->getProviderType(),
1753+
'topics' => $resource->getTopics(),
1754+
'users' => $resource->getUsers(),
1755+
'targets' => $resolvedTargets,
1756+
'scheduledAt' => null,
1757+
'deliveredAt' => $resource->getDeliveredAt() ?: null,
1758+
'deliveryErrors' => $resource->getDeliveryErrors(),
1759+
'deliveredTotal' => $resource->getDeliveredTotal(),
1760+
'data' => $resource->getData(),
1761+
'status' => $status,
1762+
]));
1763+
1764+
return true;
1765+
}
1766+
1767+
/**
1768+
* Create a scheduled message via SDK so the platform schedule document is created.
1769+
*
1770+
* @param array<string> $resolvedTargets
1771+
* @throws AppwriteException
1772+
* @throws \Exception
1773+
*/
1774+
protected function createScheduledMessage(Message $resource, array $resolvedTargets): bool
17221775
{
1723-
$id = $resource->getId();
17241776
$data = $resource->getData();
17251777
$topics = $resource->getTopics() ?: null;
17261778
$users = $resource->getUsers() ?: null;
1727-
$targets = $resource->getTargets() ?: null;
1779+
$targets = $resolvedTargets ?: null;
1780+
$scheduledAt = $resource->getScheduledAt();
17281781

17291782
match ($resource->getProviderType()) {
17301783
'email' => $this->messaging->createEmail(
1731-
$id,
1784+
$resource->getId(),
17321785
$data['subject'] ?? '',
17331786
$data['content'] ?? '',
17341787
$topics,
17351788
$users,
17361789
$targets,
1737-
!empty($data['cc']) ? $data['cc'] : null,
1738-
!empty($data['bcc']) ? $data['bcc'] : null,
1790+
$data['cc'] ?? null,
1791+
$data['bcc'] ?? null,
17391792
null,
17401793
false,
17411794
$data['html'] ?? null,
1795+
$scheduledAt,
17421796
),
17431797
'sms' => $this->messaging->createSMS(
1744-
$id,
1798+
$resource->getId(),
17451799
$data['content'] ?? '',
17461800
$topics,
17471801
$users,
17481802
$targets,
17491803
false,
1804+
$scheduledAt,
17501805
),
17511806
'push' => $this->messaging->createPush(
1752-
$id,
1807+
$resource->getId(),
17531808
$data['title'] ?? null,
17541809
$data['body'] ?? null,
17551810
$topics,
17561811
$users,
17571812
$targets,
1758-
!empty($data['data']) ? $data['data'] : null,
1813+
$data['data'] ?? null,
17591814
$data['action'] ?? null,
17601815
$data['image'] ?? null,
17611816
$data['icon'] ?? null,
@@ -1764,9 +1819,49 @@ protected function createMessage(Message $resource): void
17641819
$data['tag'] ?? null,
17651820
$data['badge'] ?? null,
17661821
false,
1822+
$scheduledAt,
1823+
$data['contentAvailable'] ?? null,
1824+
$data['critical'] ?? null,
1825+
null,
17671826
),
1768-
default => throw new \Exception('Unknown message provider type: ' . $resource->getProviderType()),
1827+
default => throw new \Exception('Unknown provider type: ' . $resource->getProviderType()),
17691828
};
1829+
1830+
return true;
1831+
}
1832+
1833+
/**
1834+
* Resolve source target IDs to destination target IDs for a message.
1835+
*
1836+
* @return array<string>
1837+
*/
1838+
private function resolveMessageTargets(Message $resource): array
1839+
{
1840+
$targetUserMap = $resource->getTargetUserMap();
1841+
$providerType = $resource->getProviderType();
1842+
$resolvedTargets = [];
1843+
$targetCache = [];
1844+
1845+
foreach ($resource->getTargets() as $sourceTargetId) {
1846+
$userId = $targetUserMap[$sourceTargetId] ?? null;
1847+
1848+
if ($userId === null) {
1849+
continue;
1850+
}
1851+
1852+
if (!isset($targetCache[$userId])) {
1853+
$targetCache[$userId] = $this->users->listTargets($userId);
1854+
}
1855+
1856+
foreach ($targetCache[$userId]['targets'] as $target) {
1857+
if ($target['providerType'] === $providerType) {
1858+
$resolvedTargets[] = $target['$id'];
1859+
break;
1860+
}
1861+
}
1862+
}
1863+
1864+
return $resolvedTargets;
17701865
}
17711866

17721867
/**
@@ -1775,7 +1870,7 @@ protected function createMessage(Message $resource): void
17751870
* User targets are auto-generated on the destination with new IDs,
17761871
* so we look up the matching target by userId and providerType.
17771872
*/
1778-
protected function resolveTargetId(Subscriber $resource): string
1873+
private function resolveTargetId(Subscriber $resource): string
17791874
{
17801875
$response = $this->users->listTargets($resource->getUserId());
17811876

src/Migration/Resources/Messaging/Message.php

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@ class Message extends Resource
1616
* @param array<string, mixed> $data
1717
* @param string $messageStatus
1818
* @param string $scheduledAt
19+
* @param string $deliveredAt
20+
* @param array<string> $deliveryErrors
21+
* @param int $deliveredTotal
22+
* @param array<string, string> $targetUserMap Source target ID => source user ID mapping for ID resolution
1923
*/
2024
public function __construct(
2125
string $id,
@@ -26,6 +30,10 @@ public function __construct(
2630
private readonly array $data = [],
2731
private readonly string $messageStatus = '',
2832
private readonly string $scheduledAt = '',
33+
private readonly string $deliveredAt = '',
34+
private readonly array $deliveryErrors = [],
35+
private readonly int $deliveredTotal = 0,
36+
private readonly array $targetUserMap = [],
2937
protected string $createdAt = '',
3038
protected string $updatedAt = '',
3139
) {
@@ -47,6 +55,10 @@ public static function fromArray(array $array): self
4755
$array['data'] ?? [],
4856
$array['messageStatus'] ?? $array['status'] ?? '',
4957
$array['scheduledAt'] ?? '',
58+
$array['deliveredAt'] ?? '',
59+
$array['deliveryErrors'] ?? [],
60+
$array['deliveredTotal'] ?? 0,
61+
$array['targetUserMap'] ?? [],
5062
$array['createdAt'] ?? '',
5163
$array['updatedAt'] ?? '',
5264
);
@@ -66,6 +78,10 @@ public function jsonSerialize(): array
6678
'data' => $this->data,
6779
'messageStatus' => $this->messageStatus,
6880
'scheduledAt' => $this->scheduledAt,
81+
'deliveredAt' => $this->deliveredAt,
82+
'deliveryErrors' => $this->deliveryErrors,
83+
'deliveredTotal' => $this->deliveredTotal,
84+
'targetUserMap' => $this->targetUserMap,
6985
'createdAt' => $this->createdAt,
7086
'updatedAt' => $this->updatedAt,
7187
];
@@ -127,4 +143,30 @@ public function getScheduledAt(): string
127143
{
128144
return $this->scheduledAt;
129145
}
146+
147+
public function getDeliveredAt(): string
148+
{
149+
return $this->deliveredAt;
150+
}
151+
152+
/**
153+
* @return array<string>
154+
*/
155+
public function getDeliveryErrors(): array
156+
{
157+
return $this->deliveryErrors;
158+
}
159+
160+
public function getDeliveredTotal(): int
161+
{
162+
return $this->deliveredTotal;
163+
}
164+
165+
/**
166+
* @return array<string, string>
167+
*/
168+
public function getTargetUserMap(): array
169+
{
170+
return $this->targetUserMap;
171+
}
130172
}

src/Migration/Source.php

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,5 @@ abstract protected function exportGroupFunctions(int $batchSize, array $resource
168168
* @param int $batchSize
169169
* @param array<string> $resources Resources to export
170170
*/
171-
protected function exportGroupMessaging(int $batchSize, array $resources): void
172-
{
173-
}
171+
abstract protected function exportGroupMessaging(int $batchSize, array $resources): void;
174172
}

0 commit comments

Comments
 (0)