Skip to content

Commit 183f0e2

Browse files
committed
Merge remote-tracking branch 'origin/main' into work-176
2 parents 7ee57f7 + 50526e1 commit 183f0e2

35 files changed

Lines changed: 5154 additions & 505 deletions

composer.json

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,11 @@
2222
"check": "./vendor/bin/phpstan analyse --level 3 src tests --memory-limit 2G"
2323
},
2424
"require": {
25-
"php": ">=8.1",
25+
"php": ">=8.2",
2626
"ext-curl": "*",
2727
"ext-openssl": "*",
28-
"appwrite/appwrite": "19.*",
28+
29+
"appwrite/appwrite": "24.*",
2930
"utopia-php/database": "5.*",
3031
"utopia-php/storage": "2.*",
3132
"utopia-php/dsn": "0.2.*",
@@ -38,9 +39,6 @@
3839
"laravel/pint": "1.*",
3940
"phpstan/phpstan": "1.*"
4041
},
41-
"platform": {
42-
"php": "8.1"
43-
},
4442
"config": {
4543
"allow-plugins": {
4644
"php-http/discovery": true,

composer.lock

Lines changed: 92 additions & 81 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: 1632 additions & 166 deletions
Large diffs are not rendered by default.

src/Migration/Destinations/OnDuplicate.php

Lines changed: 55 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,69 @@
22

33
namespace Utopia\Migration\Destinations;
44

5-
/**
6-
* Behavior when a destination row with an existing ID is encountered.
7-
*/
5+
enum SchemaAction
6+
{
7+
case Create;
8+
case Skip;
9+
case Overwrite;
10+
}
11+
812
enum OnDuplicate: string
913
{
1014
case Fail = 'fail';
1115
case Skip = 'skip';
12-
case Upsert = 'upsert';
16+
case Overwrite = 'overwrite';
1317

1418
/**
15-
* @return array<string>
19+
* @return list<string>
1620
*/
1721
public static function values(): array
1822
{
19-
return \array_map(fn (self $case) => $case->value, self::cases());
23+
return \array_values(\array_map(fn (self $case) => $case->value, self::cases()));
24+
}
25+
26+
/**
27+
* Coarse routing for re-migration. The caller follows up with a spec-match
28+
* check that overrides Overwrite to Skip when source and dest
29+
* already have identical spec — see DestinationAppwrite for the full flow.
30+
*/
31+
public function resolveSchemaAction(
32+
bool $exists,
33+
?string $sourceUpdatedAt = null,
34+
?string $destUpdatedAt = null,
35+
): SchemaAction {
36+
if (!$exists) {
37+
return SchemaAction::Create;
38+
}
39+
return match ($this) {
40+
self::Fail => SchemaAction::Create,
41+
self::Skip => SchemaAction::Skip,
42+
self::Overwrite => $this->sourceIsNewer($sourceUpdatedAt, $destUpdatedAt)
43+
? SchemaAction::Overwrite
44+
: SchemaAction::Skip,
45+
};
46+
}
47+
48+
private function sourceIsNewer(?string $source, ?string $dest): bool
49+
{
50+
$src = $this->parseTimestamp($source);
51+
$dst = $this->parseTimestamp($dest);
52+
return $src !== null && $dst !== null && $src > $dst;
53+
}
54+
55+
/**
56+
* strtotime accepts '0000-00-00' leniently (returns a large negative epoch,
57+
* not false), so non-positive epochs are rejected too.
58+
*/
59+
private function parseTimestamp(?string $value): ?int
60+
{
61+
if ($value === null || $value === '') {
62+
return null;
63+
}
64+
$epoch = \strtotime($value);
65+
if ($epoch === false || $epoch <= 0) {
66+
return null;
67+
}
68+
return $epoch;
2069
}
2170
}

src/Migration/Resource.php

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,10 +69,35 @@ abstract class Resource implements \JsonSerializable
6969

7070
public const TYPE_HASH = 'hash';
7171

72+
public const TYPE_AUTH_METHODS = 'auth-methods';
73+
74+
public const TYPE_POLICIES = 'policies';
75+
76+
// One type shared by all OAuth2 provider Resource classes (dispatch is by
77+
// `instanceof` on the destination). A per-provider type would overflow the
78+
// migration document's 3KB `statusCounters` column when OAuth is selected.
79+
public const TYPE_OAUTH2_PROVIDER = 'oauth2-provider';
80+
7281
public const TYPE_ENVIRONMENT_VARIABLE = 'environment-variable';
7382

74-
public const TYPE_SUBSCRIBER = 'subscriber';
83+
// Integrations
84+
public const TYPE_PLATFORM = 'platform';
85+
public const TYPE_API_KEY = 'api-key';
86+
public const TYPE_WEBHOOK = 'webhook';
87+
public const TYPE_SMTP = 'smtp';
7588

89+
// Project (per-project singleton/settings resources)
90+
public const TYPE_PROJECT_VARIABLE = 'project-variable';
91+
public const TYPE_PROJECT_PROTOCOLS = 'project-protocols';
92+
public const TYPE_PROJECT_LABELS = 'project-labels';
93+
public const TYPE_PROJECT_SERVICES = 'project-services';
94+
public const TYPE_PROJECT_EMAIL_TEMPLATE = 'project-email-template';
95+
96+
// Domains
97+
public const TYPE_RULE = 'rule';
98+
99+
// Messaging
100+
public const TYPE_SUBSCRIBER = 'subscriber';
76101
public const TYPE_MESSAGE = 'message';
77102

78103
// Backups
@@ -109,6 +134,19 @@ abstract class Resource implements \JsonSerializable
109134
self::TYPE_ENVIRONMENT_VARIABLE,
110135
self::TYPE_TEAM,
111136
self::TYPE_MEMBERSHIP,
137+
self::TYPE_AUTH_METHODS,
138+
self::TYPE_POLICIES,
139+
self::TYPE_OAUTH2_PROVIDER,
140+
self::TYPE_PLATFORM,
141+
self::TYPE_API_KEY,
142+
self::TYPE_WEBHOOK,
143+
self::TYPE_SMTP,
144+
self::TYPE_PROJECT_VARIABLE,
145+
self::TYPE_PROJECT_PROTOCOLS,
146+
self::TYPE_PROJECT_LABELS,
147+
self::TYPE_PROJECT_SERVICES,
148+
self::TYPE_PROJECT_EMAIL_TEMPLATE,
149+
self::TYPE_RULE,
112150
self::TYPE_PROVIDER,
113151
self::TYPE_TOPIC,
114152
self::TYPE_SUBSCRIBER,
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
<?php
2+
3+
namespace Utopia\Migration\Resources\Auth;
4+
5+
use Utopia\Migration\Resource;
6+
use Utopia\Migration\Transfer;
7+
8+
/**
9+
* Singleton resource representing the project's auth-method flags.
10+
* One per project; the destination updates the project doc, not a new row.
11+
*/
12+
class AuthMethods extends Resource
13+
{
14+
public function __construct(
15+
string $id,
16+
private readonly bool $emailPassword = true,
17+
private readonly bool $magicURL = true,
18+
private readonly bool $emailOtp = true,
19+
private readonly bool $anonymous = true,
20+
private readonly bool $invites = true,
21+
private readonly bool $jwt = true,
22+
private readonly bool $phone = true,
23+
string $createdAt = '',
24+
string $updatedAt = '',
25+
) {
26+
$this->id = $id;
27+
$this->createdAt = $createdAt;
28+
$this->updatedAt = $updatedAt;
29+
}
30+
31+
/**
32+
* @param array<string, mixed> $array
33+
*/
34+
public static function fromArray(array $array): self
35+
{
36+
return new self(
37+
$array['id'],
38+
(bool) ($array['emailPassword'] ?? true),
39+
(bool) ($array['magicURL'] ?? true),
40+
(bool) ($array['emailOtp'] ?? true),
41+
(bool) ($array['anonymous'] ?? true),
42+
(bool) ($array['invites'] ?? true),
43+
(bool) ($array['jwt'] ?? true),
44+
(bool) ($array['phone'] ?? true),
45+
createdAt: $array['createdAt'] ?? '',
46+
updatedAt: $array['updatedAt'] ?? '',
47+
);
48+
}
49+
50+
/**
51+
* @return array<string, mixed>
52+
*/
53+
public function jsonSerialize(): array
54+
{
55+
return [
56+
'id' => $this->id,
57+
'emailPassword' => $this->emailPassword,
58+
'magicURL' => $this->magicURL,
59+
'emailOtp' => $this->emailOtp,
60+
'anonymous' => $this->anonymous,
61+
'invites' => $this->invites,
62+
'jwt' => $this->jwt,
63+
'phone' => $this->phone,
64+
'createdAt' => $this->createdAt,
65+
'updatedAt' => $this->updatedAt,
66+
];
67+
}
68+
69+
public static function getName(): string
70+
{
71+
return Resource::TYPE_AUTH_METHODS;
72+
}
73+
74+
public function getGroup(): string
75+
{
76+
return Transfer::GROUP_AUTH;
77+
}
78+
79+
public function getEmailPassword(): bool
80+
{
81+
return $this->emailPassword;
82+
}
83+
84+
public function getMagicURL(): bool
85+
{
86+
return $this->magicURL;
87+
}
88+
89+
public function getEmailOtp(): bool
90+
{
91+
return $this->emailOtp;
92+
}
93+
94+
public function getAnonymous(): bool
95+
{
96+
return $this->anonymous;
97+
}
98+
99+
public function getInvites(): bool
100+
{
101+
return $this->invites;
102+
}
103+
104+
public function getJwt(): bool
105+
{
106+
return $this->jwt;
107+
}
108+
109+
public function getPhone(): bool
110+
{
111+
return $this->phone;
112+
}
113+
}

0 commit comments

Comments
 (0)