Skip to content

Commit 79d9a10

Browse files
authored
Merge pull request #183 from utopia-php/add-api-key-migration
Add API key migration support
2 parents 3ee6e12 + 0bfdd53 commit 79d9a10

7 files changed

Lines changed: 244 additions & 0 deletions

File tree

src/Migration/Destinations/Appwrite.php

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
use Utopia\Migration\Resources\Functions\Deployment;
5151
use Utopia\Migration\Resources\Functions\EnvVar;
5252
use Utopia\Migration\Resources\Functions\Func;
53+
use Utopia\Migration\Resources\Integrations\ApiKey;
5354
use Utopia\Migration\Resources\Integrations\Platform;
5455
use Utopia\Migration\Resources\Messaging\Message;
5556
use Utopia\Migration\Resources\Messaging\Provider;
@@ -275,6 +276,7 @@ public static function getSupportedResources(): array
275276

276277
// Integrations
277278
Resource::TYPE_PLATFORM,
279+
Resource::TYPE_API_KEY,
278280

279281
// Backups
280282
Resource::TYPE_BACKUP_POLICY,
@@ -3074,6 +3076,10 @@ public function importIntegrationsResource(Resource $resource): Resource
30743076
/** @var Platform $resource */
30753077
$this->createPlatform($resource);
30763078
break;
3079+
case Resource::TYPE_API_KEY:
3080+
/** @var ApiKey $resource */
3081+
$this->createApiKey($resource);
3082+
break;
30773083
}
30783084

30793085
if ($resource->getStatus() !== Resource::STATUS_SKIPPED) {
@@ -3126,6 +3132,49 @@ protected function createPlatform(Platform $resource): bool
31263132
return true;
31273133
}
31283134

3135+
protected function createApiKey(ApiKey $resource): bool
3136+
{
3137+
$existing = $this->dbForPlatform->findOne('keys', [
3138+
Query::equal('resourceType', ['projects']),
3139+
Query::equal('resourceInternalId', [$this->projectInternalId]),
3140+
Query::equal('name', [$resource->getApiKeyName()]),
3141+
]);
3142+
3143+
if ($existing !== false && !$existing->isEmpty()) {
3144+
$resource->setStatus(Resource::STATUS_SKIPPED, 'API key already exists');
3145+
return false;
3146+
}
3147+
3148+
$createdAt = $this->normalizeDateTime($resource->getCreatedAt());
3149+
$updatedAt = $this->normalizeDateTime($resource->getUpdatedAt(), $createdAt);
3150+
$expire = $resource->getExpire();
3151+
3152+
try {
3153+
$this->dbForPlatform->createDocument('keys', new UtopiaDocument([
3154+
'$id' => ID::unique(),
3155+
'$permissions' => $resource->getPermissions(),
3156+
'resourceInternalId' => $this->projectInternalId,
3157+
'resourceId' => $this->project,
3158+
'resourceType' => 'projects',
3159+
'name' => $resource->getApiKeyName(),
3160+
'scopes' => $resource->getScopes(),
3161+
'expire' => empty($expire) ? null : $expire,
3162+
'sdks' => $resource->getSdks(),
3163+
'accessedAt' => null,
3164+
'secret' => 'standard_' . \bin2hex(\random_bytes(128)),
3165+
'$createdAt' => $createdAt,
3166+
'$updatedAt' => $updatedAt,
3167+
]));
3168+
} catch (DuplicateException) {
3169+
$resource->setStatus(Resource::STATUS_SKIPPED, 'API key already exists');
3170+
return false;
3171+
}
3172+
3173+
$this->dbForPlatform->purgeCachedDocument('projects', $this->project);
3174+
3175+
return true;
3176+
}
3177+
31293178
private function validateFieldsForIndexes(Index $resource, UtopiaDocument $table, array &$lengths)
31303179
{
31313180
/**

src/Migration/Resource.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ abstract class Resource implements \JsonSerializable
7373

7474
// Integrations
7575
public const TYPE_PLATFORM = 'platform';
76+
public const TYPE_API_KEY = 'api-key';
7677
public const TYPE_SUBSCRIBER = 'subscriber';
7778

7879
public const TYPE_MESSAGE = 'message';
@@ -112,6 +113,7 @@ abstract class Resource implements \JsonSerializable
112113
self::TYPE_TEAM,
113114
self::TYPE_MEMBERSHIP,
114115
self::TYPE_PLATFORM,
116+
self::TYPE_API_KEY,
115117
self::TYPE_PROVIDER,
116118
self::TYPE_TOPIC,
117119
self::TYPE_SUBSCRIBER,
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
<?php
2+
3+
namespace Utopia\Migration\Resources\Integrations;
4+
5+
use Utopia\Migration\Resource;
6+
use Utopia\Migration\Transfer;
7+
8+
class ApiKey extends Resource
9+
{
10+
/**
11+
* @param string $id
12+
* @param string $name
13+
* @param array<string> $scopes
14+
* @param string $expire
15+
* @param string $accessedAt
16+
* @param array<string> $sdks
17+
* @param string $createdAt
18+
* @param string $updatedAt
19+
*/
20+
public function __construct(
21+
string $id,
22+
private readonly string $name,
23+
private readonly array $scopes = [],
24+
private readonly string $expire = '',
25+
private readonly string $accessedAt = '',
26+
private readonly array $sdks = [],
27+
string $createdAt = '',
28+
string $updatedAt = '',
29+
) {
30+
$this->id = $id;
31+
$this->createdAt = $createdAt;
32+
$this->updatedAt = $updatedAt;
33+
}
34+
35+
/**
36+
* @param array<string, mixed> $array
37+
* @return self
38+
*/
39+
public static function fromArray(array $array): self
40+
{
41+
return new self(
42+
$array['id'],
43+
$array['name'],
44+
$array['scopes'] ?? [],
45+
$array['expire'] ?? '',
46+
$array['accessedAt'] ?? '',
47+
$array['sdks'] ?? [],
48+
createdAt: $array['createdAt'] ?? '',
49+
updatedAt: $array['updatedAt'] ?? '',
50+
);
51+
}
52+
53+
/**
54+
* @return array<string, mixed>
55+
*/
56+
public function jsonSerialize(): array
57+
{
58+
return [
59+
'id' => $this->id,
60+
'name' => $this->name,
61+
'scopes' => $this->scopes,
62+
'expire' => $this->expire,
63+
'accessedAt' => $this->accessedAt,
64+
'sdks' => $this->sdks,
65+
'createdAt' => $this->createdAt,
66+
'updatedAt' => $this->updatedAt,
67+
];
68+
}
69+
70+
public static function getName(): string
71+
{
72+
return Resource::TYPE_API_KEY;
73+
}
74+
75+
public function getGroup(): string
76+
{
77+
return Transfer::GROUP_INTEGRATIONS;
78+
}
79+
80+
public function getApiKeyName(): string
81+
{
82+
return $this->name;
83+
}
84+
85+
/**
86+
* @return array<string>
87+
*/
88+
public function getScopes(): array
89+
{
90+
return $this->scopes;
91+
}
92+
93+
public function getExpire(): string
94+
{
95+
return $this->expire;
96+
}
97+
98+
public function getAccessedAt(): string
99+
{
100+
return $this->accessedAt;
101+
}
102+
103+
/**
104+
* @return array<string>
105+
*/
106+
public function getSdks(): array
107+
{
108+
return $this->sdks;
109+
}
110+
}

src/Migration/Sources/Appwrite.php

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@
5555
use Utopia\Migration\Resources\Functions\Deployment;
5656
use Utopia\Migration\Resources\Functions\EnvVar;
5757
use Utopia\Migration\Resources\Functions\Func;
58+
use Utopia\Migration\Resources\Integrations\ApiKey;
5859
use Utopia\Migration\Resources\Integrations\Platform;
5960
use Utopia\Migration\Resources\Messaging\Message;
6061
use Utopia\Migration\Resources\Messaging\Provider;
@@ -207,6 +208,7 @@ public static function getSupportedResources(): array
207208

208209
// Integrations
209210
Resource::TYPE_PLATFORM,
211+
Resource::TYPE_API_KEY,
210212

211213
// Backups
212214
Resource::TYPE_BACKUP_POLICY,
@@ -2239,6 +2241,19 @@ private function reportIntegrations(array $resources, array &$report, array $res
22392241
$report[Resource::TYPE_PLATFORM] = 0;
22402242
}
22412243
}
2244+
2245+
if (\in_array(Resource::TYPE_API_KEY, $resources)) {
2246+
$keyQueries = $this->buildQueries(
2247+
resourceType: Resource::TYPE_API_KEY,
2248+
resourceIds: $resourceIds,
2249+
limit: 1
2250+
);
2251+
try {
2252+
$report[Resource::TYPE_API_KEY] = $this->project->listKeys($keyQueries)->total;
2253+
} catch (\Throwable) {
2254+
$report[Resource::TYPE_API_KEY] = 0;
2255+
}
2256+
}
22422257
}
22432258

22442259
/**
@@ -2284,6 +2299,20 @@ protected function exportGroupIntegrations(int $batchSize, array $resources): vo
22842299
));
22852300
}
22862301
}
2302+
2303+
if (\in_array(Resource::TYPE_API_KEY, $resources)) {
2304+
try {
2305+
$this->exportApiKeys($batchSize);
2306+
} catch (\Throwable $e) {
2307+
$this->addError(new Exception(
2308+
Resource::TYPE_API_KEY,
2309+
Transfer::GROUP_INTEGRATIONS,
2310+
message: $e->getMessage(),
2311+
code: $e->getCode(),
2312+
previous: $e
2313+
));
2314+
}
2315+
}
22872316
}
22882317

22892318
/**
@@ -2367,6 +2396,55 @@ private function exportPlatforms(int $batchSize): void
23672396
}
23682397
}
23692398

2399+
/**
2400+
* @throws AppwriteException
2401+
*/
2402+
private function exportApiKeys(int $batchSize): void
2403+
{
2404+
$lastId = null;
2405+
2406+
while (true) {
2407+
$queries = [Query::limit($batchSize)];
2408+
2409+
if ($this->rootResourceId !== '' && $this->rootResourceType === Resource::TYPE_API_KEY) {
2410+
$queries[] = Query::equal('$id', $this->rootResourceId);
2411+
$queries[] = Query::limit(1);
2412+
}
2413+
2414+
if ($lastId !== null) {
2415+
$queries[] = Query::cursorAfter($lastId);
2416+
}
2417+
2418+
$response = $this->project->listKeys($queries);
2419+
if ($response->total === 0) {
2420+
break;
2421+
}
2422+
2423+
$apiKeys = [];
2424+
2425+
foreach ($response->keys as $key) {
2426+
$apiKeys[] = new ApiKey(
2427+
$key->id,
2428+
$key->name,
2429+
$key->scopes,
2430+
$key->expire,
2431+
$key->accessedAt,
2432+
$key->sdks,
2433+
createdAt: $key->createdAt,
2434+
updatedAt: $key->updatedAt,
2435+
);
2436+
2437+
$lastId = $key->id;
2438+
}
2439+
2440+
$this->callback($apiKeys);
2441+
2442+
if (\count($response->keys) < $batchSize) {
2443+
break;
2444+
}
2445+
}
2446+
}
2447+
23702448
/**
23712449
* eg.,documents/attributes
23722450
* @param string $databaseType

src/Migration/Transfer.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ class Transfer
6262

6363
public const GROUP_INTEGRATIONS_RESOURCES = [
6464
Resource::TYPE_PLATFORM,
65+
Resource::TYPE_API_KEY,
6566
];
6667
public const GROUP_DOCUMENTSDB_RESOURCES = [
6768
Resource::TYPE_DATABASE_DOCUMENTSDB,
@@ -129,6 +130,7 @@ class Transfer
129130

130131
// Integrations
131132
Resource::TYPE_PLATFORM,
133+
Resource::TYPE_API_KEY,
132134

133135
// legacy
134136
Resource::TYPE_DOCUMENT,
@@ -146,6 +148,7 @@ class Transfer
146148
Resource::TYPE_USER,
147149
Resource::TYPE_TEAM,
148150
Resource::TYPE_PLATFORM,
151+
Resource::TYPE_API_KEY,
149152
Resource::TYPE_PROVIDER,
150153
Resource::TYPE_TOPIC,
151154
Resource::TYPE_MESSAGE,

tests/Migration/Unit/Adapters/MockDestination.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ public static function getSupportedResources(): array
5252
Resource::TYPE_TEAM,
5353
Resource::TYPE_MEMBERSHIP,
5454
Resource::TYPE_PLATFORM,
55+
Resource::TYPE_API_KEY,
5556
Resource::TYPE_PROVIDER,
5657
Resource::TYPE_TOPIC,
5758
Resource::TYPE_SUBSCRIBER,

tests/Migration/Unit/Adapters/MockSource.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ public static function getSupportedResources(): array
8181
Resource::TYPE_TEAM,
8282
Resource::TYPE_MEMBERSHIP,
8383
Resource::TYPE_PLATFORM,
84+
Resource::TYPE_API_KEY,
8485
Resource::TYPE_PROVIDER,
8586
Resource::TYPE_TOPIC,
8687
Resource::TYPE_SUBSCRIBER,

0 commit comments

Comments
 (0)