Skip to content

Commit c040472

Browse files
committed
Add project variable migration support
Source: Appwrite SDK Project::listVariables() with cursor pagination, counting via reportIntegrations. Destination: dbForProject->createDocument('variables', ...) matching the upstream Project::Variables::Create payload exactly — resourceType 'project' (singular), resourceInternalId/resourceId empty, plus the search index field.
1 parent 0bfdd53 commit c040472

5 files changed

Lines changed: 204 additions & 0 deletions

File tree

src/Migration/Destinations/Appwrite.php

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
use Utopia\Migration\Resources\Functions\EnvVar;
5252
use Utopia\Migration\Resources\Functions\Func;
5353
use Utopia\Migration\Resources\Integrations\ApiKey;
54+
use Utopia\Migration\Resources\Integrations\ProjectVariable;
5455
use Utopia\Migration\Resources\Integrations\Platform;
5556
use Utopia\Migration\Resources\Messaging\Message;
5657
use Utopia\Migration\Resources\Messaging\Provider;
@@ -277,6 +278,7 @@ public static function getSupportedResources(): array
277278
// Integrations
278279
Resource::TYPE_PLATFORM,
279280
Resource::TYPE_API_KEY,
281+
Resource::TYPE_PROJECT_VARIABLE,
280282

281283
// Backups
282284
Resource::TYPE_BACKUP_POLICY,
@@ -3080,6 +3082,10 @@ public function importIntegrationsResource(Resource $resource): Resource
30803082
/** @var ApiKey $resource */
30813083
$this->createApiKey($resource);
30823084
break;
3085+
case Resource::TYPE_PROJECT_VARIABLE:
3086+
/** @var ProjectVariable $resource */
3087+
$this->createProjectVariable($resource);
3088+
break;
30833089
}
30843090

30853091
if ($resource->getStatus() !== Resource::STATUS_SKIPPED) {
@@ -3175,6 +3181,47 @@ protected function createApiKey(ApiKey $resource): bool
31753181
return true;
31763182
}
31773183

3184+
protected function createProjectVariable(ProjectVariable $resource): bool
3185+
{
3186+
// Variables live in dbForProject (not dbForPlatform). resourceType is 'project' (singular)
3187+
// for project-scope variables; function/site-scope variables use 'function'/'site'.
3188+
$existing = $this->dbForProject->findOne('variables', [
3189+
Query::equal('resourceType', ['project']),
3190+
Query::equal('key', [$resource->getKey()]),
3191+
]);
3192+
3193+
if ($existing !== false && !$existing->isEmpty()) {
3194+
$resource->setStatus(Resource::STATUS_SKIPPED, 'Project variable already exists');
3195+
return false;
3196+
}
3197+
3198+
$createdAt = $this->normalizeDateTime($resource->getCreatedAt());
3199+
$updatedAt = $this->normalizeDateTime($resource->getUpdatedAt(), $createdAt);
3200+
$variableId = ID::unique();
3201+
$key = $resource->getKey();
3202+
3203+
try {
3204+
$this->dbForProject->createDocument('variables', new UtopiaDocument([
3205+
'$id' => $variableId,
3206+
'$permissions' => $resource->getPermissions(),
3207+
'resourceInternalId' => '',
3208+
'resourceId' => '',
3209+
'resourceType' => 'project',
3210+
'key' => $key,
3211+
'value' => $resource->getValue(),
3212+
'secret' => $resource->isSecret(),
3213+
'search' => \implode(' ', [$variableId, $key, 'project']),
3214+
'$createdAt' => $createdAt,
3215+
'$updatedAt' => $updatedAt,
3216+
]));
3217+
} catch (DuplicateException) {
3218+
$resource->setStatus(Resource::STATUS_SKIPPED, 'Project variable already exists');
3219+
return false;
3220+
}
3221+
3222+
return true;
3223+
}
3224+
31783225
private function validateFieldsForIndexes(Index $resource, UtopiaDocument $table, array &$lengths)
31793226
{
31803227
/**

src/Migration/Resource.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ abstract class Resource implements \JsonSerializable
7474
// Integrations
7575
public const TYPE_PLATFORM = 'platform';
7676
public const TYPE_API_KEY = 'api-key';
77+
public const TYPE_PROJECT_VARIABLE = 'project-variable';
7778
public const TYPE_SUBSCRIBER = 'subscriber';
7879

7980
public const TYPE_MESSAGE = 'message';
@@ -114,6 +115,7 @@ abstract class Resource implements \JsonSerializable
114115
self::TYPE_MEMBERSHIP,
115116
self::TYPE_PLATFORM,
116117
self::TYPE_API_KEY,
118+
self::TYPE_PROJECT_VARIABLE,
117119
self::TYPE_PROVIDER,
118120
self::TYPE_TOPIC,
119121
self::TYPE_SUBSCRIBER,
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
<?php
2+
3+
namespace Utopia\Migration\Resources\Integrations;
4+
5+
use Utopia\Migration\Resource;
6+
use Utopia\Migration\Transfer;
7+
8+
class ProjectVariable extends Resource
9+
{
10+
public function __construct(
11+
string $id,
12+
private readonly string $key,
13+
private readonly string $value = '',
14+
private readonly bool $secret = false,
15+
string $createdAt = '',
16+
string $updatedAt = '',
17+
) {
18+
$this->id = $id;
19+
$this->createdAt = $createdAt;
20+
$this->updatedAt = $updatedAt;
21+
}
22+
23+
/**
24+
* @param array<string, mixed> $array
25+
* @return self
26+
*/
27+
public static function fromArray(array $array): self
28+
{
29+
return new self(
30+
$array['id'],
31+
$array['key'],
32+
$array['value'] ?? '',
33+
(bool) ($array['secret'] ?? false),
34+
createdAt: $array['createdAt'] ?? '',
35+
updatedAt: $array['updatedAt'] ?? '',
36+
);
37+
}
38+
39+
/**
40+
* @return array<string, mixed>
41+
*/
42+
public function jsonSerialize(): array
43+
{
44+
return [
45+
'id' => $this->id,
46+
'key' => $this->key,
47+
'value' => $this->value,
48+
'secret' => $this->secret,
49+
'createdAt' => $this->createdAt,
50+
'updatedAt' => $this->updatedAt,
51+
];
52+
}
53+
54+
public static function getName(): string
55+
{
56+
return Resource::TYPE_PROJECT_VARIABLE;
57+
}
58+
59+
public function getGroup(): string
60+
{
61+
return Transfer::GROUP_INTEGRATIONS;
62+
}
63+
64+
public function getKey(): string
65+
{
66+
return $this->key;
67+
}
68+
69+
public function getValue(): string
70+
{
71+
return $this->value;
72+
}
73+
74+
public function isSecret(): bool
75+
{
76+
return $this->secret;
77+
}
78+
}

src/Migration/Sources/Appwrite.php

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@
5656
use Utopia\Migration\Resources\Functions\EnvVar;
5757
use Utopia\Migration\Resources\Functions\Func;
5858
use Utopia\Migration\Resources\Integrations\ApiKey;
59+
use Utopia\Migration\Resources\Integrations\ProjectVariable;
5960
use Utopia\Migration\Resources\Integrations\Platform;
6061
use Utopia\Migration\Resources\Messaging\Message;
6162
use Utopia\Migration\Resources\Messaging\Provider;
@@ -209,6 +210,7 @@ public static function getSupportedResources(): array
209210
// Integrations
210211
Resource::TYPE_PLATFORM,
211212
Resource::TYPE_API_KEY,
213+
Resource::TYPE_PROJECT_VARIABLE,
212214

213215
// Backups
214216
Resource::TYPE_BACKUP_POLICY,
@@ -2254,6 +2256,19 @@ private function reportIntegrations(array $resources, array &$report, array $res
22542256
$report[Resource::TYPE_API_KEY] = 0;
22552257
}
22562258
}
2259+
2260+
if (\in_array(Resource::TYPE_PROJECT_VARIABLE, $resources)) {
2261+
$variableQueries = $this->buildQueries(
2262+
resourceType: Resource::TYPE_PROJECT_VARIABLE,
2263+
resourceIds: $resourceIds,
2264+
limit: 1
2265+
);
2266+
try {
2267+
$report[Resource::TYPE_PROJECT_VARIABLE] = $this->project->listVariables($variableQueries)->total;
2268+
} catch (\Throwable) {
2269+
$report[Resource::TYPE_PROJECT_VARIABLE] = 0;
2270+
}
2271+
}
22572272
}
22582273

22592274
/**
@@ -2313,6 +2328,20 @@ protected function exportGroupIntegrations(int $batchSize, array $resources): vo
23132328
));
23142329
}
23152330
}
2331+
2332+
if (\in_array(Resource::TYPE_PROJECT_VARIABLE, $resources)) {
2333+
try {
2334+
$this->exportProjectVariables($batchSize);
2335+
} catch (\Throwable $e) {
2336+
$this->addError(new Exception(
2337+
Resource::TYPE_PROJECT_VARIABLE,
2338+
Transfer::GROUP_INTEGRATIONS,
2339+
message: $e->getMessage(),
2340+
code: $e->getCode(),
2341+
previous: $e
2342+
));
2343+
}
2344+
}
23162345
}
23172346

23182347
/**
@@ -2445,6 +2474,53 @@ private function exportApiKeys(int $batchSize): void
24452474
}
24462475
}
24472476

2477+
/**
2478+
* @throws AppwriteException
2479+
*/
2480+
private function exportProjectVariables(int $batchSize): void
2481+
{
2482+
$lastId = null;
2483+
2484+
while (true) {
2485+
$queries = [Query::limit($batchSize)];
2486+
2487+
if ($this->rootResourceId !== '' && $this->rootResourceType === Resource::TYPE_PROJECT_VARIABLE) {
2488+
$queries[] = Query::equal('$id', $this->rootResourceId);
2489+
$queries[] = Query::limit(1);
2490+
}
2491+
2492+
if ($lastId !== null) {
2493+
$queries[] = Query::cursorAfter($lastId);
2494+
}
2495+
2496+
$response = $this->project->listVariables($queries);
2497+
if ($response->total === 0) {
2498+
break;
2499+
}
2500+
2501+
$variables = [];
2502+
2503+
foreach ($response->variables as $variable) {
2504+
$variables[] = new ProjectVariable(
2505+
$variable->id,
2506+
$variable->key,
2507+
$variable->value,
2508+
$variable->secret,
2509+
createdAt: $variable->createdAt,
2510+
updatedAt: $variable->updatedAt,
2511+
);
2512+
2513+
$lastId = $variable->id;
2514+
}
2515+
2516+
$this->callback($variables);
2517+
2518+
if (\count($response->variables) < $batchSize) {
2519+
break;
2520+
}
2521+
}
2522+
}
2523+
24482524
/**
24492525
* eg.,documents/attributes
24502526
* @param string $databaseType

src/Migration/Transfer.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ class Transfer
6363
public const GROUP_INTEGRATIONS_RESOURCES = [
6464
Resource::TYPE_PLATFORM,
6565
Resource::TYPE_API_KEY,
66+
Resource::TYPE_PROJECT_VARIABLE,
6667
];
6768
public const GROUP_DOCUMENTSDB_RESOURCES = [
6869
Resource::TYPE_DATABASE_DOCUMENTSDB,

0 commit comments

Comments
 (0)