Skip to content

Commit e99d8b9

Browse files
authored
Merge pull request #186 from utopia-php/add-policies-migration
feat(migration): add project-level resource migrations
2 parents 539ae39 + 24de598 commit e99d8b9

19 files changed

Lines changed: 955 additions & 48 deletions

File tree

composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
"ext-curl": "*",
2727
"ext-openssl": "*",
2828

29-
"appwrite/appwrite": "23.*",
29+
"appwrite/appwrite": "24.*",
3030
"utopia-php/database": "5.*",
3131
"utopia-php/storage": "2.*",
3232
"utopia-php/dsn": "0.2.*",

composer.lock

Lines changed: 7 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Migration/Destinations/Appwrite.php

Lines changed: 166 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,13 @@
99
use Appwrite\Enums\Compression;
1010
use Appwrite\Enums\Framework;
1111
use Appwrite\Enums\PasswordHash;
12+
use Appwrite\Enums\ProjectProtocolId;
1213
use Appwrite\Enums\Runtime;
1314
use Appwrite\Enums\SmtpEncryption;
1415
use Appwrite\InputFile;
1516
use Appwrite\Services\Functions;
1617
use Appwrite\Services\Messaging;
18+
use Appwrite\Services\Project;
1719
use Appwrite\Services\Sites;
1820
use Appwrite\Services\Storage;
1921
use Appwrite\Services\Teams;
@@ -37,8 +39,10 @@
3739
use Utopia\Migration\Destination;
3840
use Utopia\Migration\Exception;
3941
use Utopia\Migration\Resource;
42+
use Utopia\Migration\Resources\Auth\AuthMethods;
4043
use Utopia\Migration\Resources\Auth\Hash;
4144
use Utopia\Migration\Resources\Auth\Membership;
45+
use Utopia\Migration\Resources\Auth\Policies;
4246
use Utopia\Migration\Resources\Auth\Team;
4347
use Utopia\Migration\Resources\Auth\User;
4448
use Utopia\Migration\Resources\Database\Attribute;
@@ -56,7 +60,10 @@
5660
use Utopia\Migration\Resources\Messaging\Provider;
5761
use Utopia\Migration\Resources\Messaging\Subscriber;
5862
use Utopia\Migration\Resources\Messaging\Topic;
63+
use Utopia\Migration\Resources\Settings\Labels;
5964
use Utopia\Migration\Resources\Settings\ProjectVariable;
65+
use Utopia\Migration\Resources\Settings\Protocols;
66+
use Utopia\Migration\Resources\Settings\Services as ServicesResource;
6067
use Utopia\Migration\Resources\Settings\Webhook;
6168
use Utopia\Migration\Resources\Sites\Deployment as SiteDeployment;
6269
use Utopia\Migration\Resources\Sites\EnvVar as SiteEnvVar;
@@ -91,12 +98,13 @@ class Appwrite extends Destination
9198
];
9299

93100
protected Client $client;
94-
protected string $project;
101+
protected string $projectId;
95102

96103
protected string $key;
97104

98105
private Functions $functions;
99106
private Messaging $messaging;
107+
private Project $project;
100108
private Sites $sites;
101109
private Storage $storage;
102110
private Teams $teams;
@@ -170,7 +178,7 @@ public function __construct(
170178
protected OnDuplicate $onDuplicate = OnDuplicate::Fail,
171179
?callable $getDatabaseDSN = null,
172180
) {
173-
$this->project = $project;
181+
$this->projectId = $project;
174182
$this->endpoint = $endpoint;
175183
$this->key = $key;
176184

@@ -181,6 +189,7 @@ public function __construct(
181189

182190
$this->functions = new Functions($this->client);
183191
$this->messaging = new Messaging($this->client);
192+
$this->project = new Project($this->client);
184193
$this->sites = new Sites($this->client);
185194
$this->storage = new Storage($this->client);
186195
$this->teams = new Teams($this->client);
@@ -241,6 +250,8 @@ public static function getSupportedResources(): array
241250
Resource::TYPE_USER,
242251
Resource::TYPE_TEAM,
243252
Resource::TYPE_MEMBERSHIP,
253+
Resource::TYPE_AUTH_METHODS,
254+
Resource::TYPE_POLICIES,
244255

245256
// Database
246257
Resource::TYPE_DATABASE,
@@ -281,8 +292,11 @@ public static function getSupportedResources(): array
281292
Resource::TYPE_API_KEY,
282293
Resource::TYPE_WEBHOOK,
283294

284-
// Settings
295+
// Project
285296
Resource::TYPE_PROJECT_VARIABLE,
297+
Resource::TYPE_PROJECT_PROTOCOLS,
298+
Resource::TYPE_PROJECT_LABELS,
299+
Resource::TYPE_PROJECT_SERVICES,
286300

287301
// Backups
288302
Resource::TYPE_BACKUP_POLICY,
@@ -444,7 +458,7 @@ protected function import(array $resources, callable $callback): void
444458
Transfer::GROUP_SITES => $this->importSiteResource($resource),
445459
Transfer::GROUP_INTEGRATIONS => $this->importIntegrationsResource($resource),
446460
Transfer::GROUP_BACKUPS => $this->importBackupResource($resource),
447-
Transfer::GROUP_SETTINGS => $this->importSettingsResource($resource),
461+
Transfer::GROUP_PROJECTS => $this->importProjectsResource($resource),
448462
default => throw new \Exception('Invalid resource group', Exception::CODE_VALIDATION),
449463
};
450464
} catch (\Throwable $e) {
@@ -2160,6 +2174,14 @@ public function importAuthResource(Resource $resource): Resource
21602174
userId: $user->getId(),
21612175
);
21622176
break;
2177+
case Resource::TYPE_AUTH_METHODS:
2178+
/** @var AuthMethods $resource */
2179+
$this->createAuthMethods($resource);
2180+
break;
2181+
case Resource::TYPE_POLICIES:
2182+
/** @var Policies $resource */
2183+
$this->createPolicies($resource);
2184+
break;
21632185
}
21642186

21652187
$resource->setStatus(Resource::STATUS_SUCCESS);
@@ -3100,13 +3122,25 @@ public function importIntegrationsResource(Resource $resource): Resource
31003122
return $resource;
31013123
}
31023124

3103-
public function importSettingsResource(Resource $resource): Resource
3125+
public function importProjectsResource(Resource $resource): Resource
31043126
{
31053127
switch ($resource->getName()) {
31063128
case Resource::TYPE_PROJECT_VARIABLE:
31073129
/** @var ProjectVariable $resource */
31083130
$this->createProjectVariable($resource);
31093131
break;
3132+
case Resource::TYPE_PROJECT_PROTOCOLS:
3133+
/** @var Protocols $resource */
3134+
$this->createProtocols($resource);
3135+
break;
3136+
case Resource::TYPE_PROJECT_LABELS:
3137+
/** @var Labels $resource */
3138+
$this->createLabels($resource);
3139+
break;
3140+
case Resource::TYPE_PROJECT_SERVICES:
3141+
/** @var ServicesResource $resource */
3142+
$this->createServices($resource);
3143+
break;
31103144
}
31113145

31123146
if ($resource->getStatus() !== Resource::STATUS_SKIPPED) {
@@ -3155,6 +3189,61 @@ protected function createProjectVariable(ProjectVariable $resource): bool
31553189
return true;
31563190
}
31573191

3192+
protected function createProtocols(Protocols $resource): bool
3193+
{
3194+
$project = $this->dbForPlatform->getDocument('projects', $this->projectId);
3195+
$apis = $project->getAttribute('apis', []);
3196+
3197+
$apis[(string) ProjectProtocolId::REST()] = $resource->getRest();
3198+
$apis[(string) ProjectProtocolId::GRAPHQL()] = $resource->getGraphql();
3199+
$apis[(string) ProjectProtocolId::WEBSOCKET()] = $resource->getWebsocket();
3200+
3201+
$this->dbForPlatform->getAuthorization()->skip(fn () => $this->dbForPlatform->updateDocument(
3202+
'projects',
3203+
$this->projectId,
3204+
new UtopiaDocument(['apis' => $apis]),
3205+
));
3206+
3207+
$this->dbForPlatform->purgeCachedDocument('projects', $this->projectId);
3208+
3209+
return true;
3210+
}
3211+
3212+
protected function createLabels(Labels $resource): bool
3213+
{
3214+
$labels = \array_values(\array_unique($resource->getLabels()));
3215+
3216+
$this->dbForPlatform->getAuthorization()->skip(fn () => $this->dbForPlatform->updateDocument(
3217+
'projects',
3218+
$this->projectId,
3219+
new UtopiaDocument(['labels' => $labels]),
3220+
));
3221+
3222+
$this->dbForPlatform->purgeCachedDocument('projects', $this->projectId);
3223+
3224+
return true;
3225+
}
3226+
3227+
protected function createServices(ServicesResource $resource): bool
3228+
{
3229+
$project = $this->dbForPlatform->getDocument('projects', $this->projectId);
3230+
$services = $project->getAttribute('services', []);
3231+
3232+
foreach ($resource->getServices() as $serviceId => $enabled) {
3233+
$services[$serviceId] = (bool) $enabled;
3234+
}
3235+
3236+
$this->dbForPlatform->getAuthorization()->skip(fn () => $this->dbForPlatform->updateDocument(
3237+
'projects',
3238+
$this->projectId,
3239+
new UtopiaDocument(['services' => $services]),
3240+
));
3241+
3242+
$this->dbForPlatform->purgeCachedDocument('projects', $this->projectId);
3243+
3244+
return true;
3245+
}
3246+
31583247
protected function createWebhook(Webhook $resource): bool
31593248
{
31603249
$existing = $this->dbForPlatform->findOne('webhooks', [
@@ -3175,7 +3264,7 @@ protected function createWebhook(Webhook $resource): bool
31753264
'$id' => ID::unique(),
31763265
'$permissions' => $resource->getPermissions(),
31773266
'projectInternalId' => $this->projectInternalId,
3178-
'projectId' => $this->project,
3267+
'projectId' => $this->projectId,
31793268
'name' => $resource->getWebhookName(),
31803269
'events' => $resource->getEvents(),
31813270
'url' => $resource->getUrl(),
@@ -3194,7 +3283,7 @@ protected function createWebhook(Webhook $resource): bool
31943283
return false;
31953284
}
31963285

3197-
$this->dbForPlatform->purgeCachedDocument('projects', $this->project);
3286+
$this->dbForPlatform->purgeCachedDocument('projects', $this->projectId);
31983287

31993288
return true;
32003289
}
@@ -3205,7 +3294,7 @@ protected function createWebhook(Webhook $resource): bool
32053294
protected function createPlatform(Platform $resource): bool
32063295
{
32073296
$existing = $this->dbForPlatform->findOne('platforms', [
3208-
Query::equal('projectId', [$this->project]),
3297+
Query::equal('projectId', [$this->projectId]),
32093298
Query::equal('type', [$resource->getType()]),
32103299
Query::equal('name', [$resource->getPlatformName()]),
32113300
]);
@@ -3223,7 +3312,7 @@ protected function createPlatform(Platform $resource): bool
32233312
'$id' => ID::unique(),
32243313
'$permissions' => $resource->getPermissions(),
32253314
'projectInternalId' => $this->projectInternalId,
3226-
'projectId' => $this->project,
3315+
'projectId' => $this->projectId,
32273316
'type' => $resource->getType(),
32283317
'name' => $resource->getPlatformName(),
32293318
'key' => $resource->getKey(),
@@ -3237,7 +3326,72 @@ protected function createPlatform(Platform $resource): bool
32373326
return false;
32383327
}
32393328

3240-
$this->dbForPlatform->purgeCachedDocument('projects', $this->project);
3329+
$this->dbForPlatform->purgeCachedDocument('projects', $this->projectId);
3330+
3331+
return true;
3332+
}
3333+
3334+
/**
3335+
* Storage keys mirror app/config/auth.php, not the SDK enum values.
3336+
* Shares the `auths` map with createPolicies — read-then-merge.
3337+
*/
3338+
protected function createAuthMethods(AuthMethods $resource): bool
3339+
{
3340+
$project = $this->dbForPlatform->getDocument('projects', $this->projectId);
3341+
$auths = $project->getAttribute('auths', []);
3342+
3343+
$auths['emailPassword'] = $resource->getEmailPassword();
3344+
$auths['usersAuthMagicURL'] = $resource->getMagicURL();
3345+
$auths['emailOtp'] = $resource->getEmailOtp();
3346+
$auths['anonymous'] = $resource->getAnonymous();
3347+
$auths['invites'] = $resource->getInvites();
3348+
$auths['JWT'] = $resource->getJwt();
3349+
$auths['phone'] = $resource->getPhone();
3350+
3351+
$this->dbForPlatform->getAuthorization()->skip(fn () => $this->dbForPlatform->updateDocument(
3352+
'projects',
3353+
$this->projectId,
3354+
new UtopiaDocument(['auths' => $auths]),
3355+
));
3356+
3357+
$this->dbForPlatform->purgeCachedDocument('projects', $this->projectId);
3358+
3359+
return true;
3360+
}
3361+
3362+
/**
3363+
* Direct DB write — SDK policy setters reject `total: 0` but `0` is the
3364+
* storage value for "disabled". Shares the `auths` map with
3365+
* createAuthMethods — read-then-merge.
3366+
*/
3367+
protected function createPolicies(Policies $resource): bool
3368+
{
3369+
$project = $this->dbForPlatform->getDocument('projects', $this->projectId);
3370+
$auths = $project->getAttribute('auths', []);
3371+
3372+
$auths['passwordHistory'] = $resource->getPasswordHistory();
3373+
$auths['duration'] = $resource->getSessionDuration();
3374+
$auths['maxSessions'] = $resource->getSessionsLimit();
3375+
$auths['limit'] = $resource->getUserLimit();
3376+
3377+
$auths['passwordDictionary'] = $resource->getPasswordDictionary();
3378+
$auths['personalDataCheck'] = $resource->getPersonalDataCheck();
3379+
$auths['sessionAlerts'] = $resource->getSessionAlerts();
3380+
$auths['invalidateSessions'] = $resource->getSessionInvalidation();
3381+
3382+
$auths['membershipsUserId'] = $resource->getMembershipsUserId();
3383+
$auths['membershipsUserEmail'] = $resource->getMembershipsUserEmail();
3384+
$auths['membershipsUserName'] = $resource->getMembershipsUserName();
3385+
$auths['membershipsMfa'] = $resource->getMembershipsUserMfa();
3386+
$auths['membershipsUserPhone'] = $resource->getMembershipsUserPhone();
3387+
3388+
$this->dbForPlatform->getAuthorization()->skip(fn () => $this->dbForPlatform->updateDocument(
3389+
'projects',
3390+
$this->projectId,
3391+
new UtopiaDocument(['auths' => $auths]),
3392+
));
3393+
3394+
$this->dbForPlatform->purgeCachedDocument('projects', $this->projectId);
32413395

32423396
return true;
32433397
}
@@ -3264,7 +3418,7 @@ protected function createApiKey(ApiKey $resource): bool
32643418
'$id' => ID::unique(),
32653419
'$permissions' => $resource->getPermissions(),
32663420
'resourceInternalId' => $this->projectInternalId,
3267-
'resourceId' => $this->project,
3421+
'resourceId' => $this->projectId,
32683422
'resourceType' => 'projects',
32693423
'name' => $resource->getApiKeyName(),
32703424
'scopes' => $resource->getScopes(),
@@ -3280,7 +3434,7 @@ protected function createApiKey(ApiKey $resource): bool
32803434
return false;
32813435
}
32823436

3283-
$this->dbForPlatform->purgeCachedDocument('projects', $this->project);
3437+
$this->dbForPlatform->purgeCachedDocument('projects', $this->projectId);
32843438

32853439
return true;
32863440
}

0 commit comments

Comments
 (0)