mirror of
https://github.com/appwrite/appwrite.git
synced 2026-05-26 13:51:13 +00:00
Compare commits
49
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a0167d6c6c | ||
|
|
74ab41f2b5 | ||
|
|
d8954f424c | ||
|
|
38493094db | ||
|
|
58e5e69fec | ||
|
|
895ec7fd20 | ||
|
|
d3442d86c1 | ||
|
|
c20dfc7063 | ||
|
|
591cc70e76 | ||
|
|
2de28cfcb7 | ||
|
|
c6d476b978 | ||
|
|
5dca3ad875 | ||
|
|
b6793dc0b5 | ||
|
|
a2ad25a00a | ||
|
|
16ad05792d | ||
|
|
e5f0bc2df6 | ||
|
|
0d3d14d9e1 | ||
|
|
f2826189c6 | ||
|
|
8b026d3459 | ||
|
|
2306072dda | ||
|
|
91edf82060 | ||
|
|
3fc50e4d8d | ||
|
|
71dff441ed | ||
|
|
65780d75f9 | ||
|
|
ee107d30b3 | ||
|
|
499ca71c28 | ||
|
|
cfe60dd2a2 | ||
|
|
ab85942276 | ||
|
|
0214f22d20 | ||
|
|
e0269e268f | ||
|
|
a4844599c6 | ||
|
|
ce876adb3a | ||
|
|
59edcf1934 | ||
|
|
5821832da6 | ||
|
|
d4df2c51de | ||
|
|
dbbc57bc37 | ||
|
|
d772220414 | ||
|
|
5510231b8e | ||
|
|
152de6c584 | ||
|
|
161c5af66f | ||
|
|
c497a68613 | ||
|
|
50f9c67862 | ||
|
|
198f9a64a3 | ||
|
|
248b3c8b32 | ||
|
|
300aaeb251 | ||
|
|
f989ccde57 | ||
|
|
fc88d4b4ab | ||
|
|
df604cb2ef | ||
|
|
a6d58a847d |
@@ -47,6 +47,36 @@ Examples:
|
||||
'resourceType' => 'deployments'
|
||||
```
|
||||
|
||||
## Performance Patterns
|
||||
|
||||
### Document Update Optimization
|
||||
|
||||
When updating documents, always pass only the changed attributes as a sparse `Document` rather than the full document. This is more efficient because `updateDocument()` internally performs `array_merge($old, $new)`.
|
||||
|
||||
**Correct Pattern:**
|
||||
```php
|
||||
// Good: Pass only changed attributes directly
|
||||
$user = $dbForProject->updateDocument('users', $user->getId(), new Document([
|
||||
'name' => $name,
|
||||
'email' => $email,
|
||||
]));
|
||||
```
|
||||
|
||||
**Incorrect Pattern:**
|
||||
```php
|
||||
$user->setAttribute('name', $name);
|
||||
$user->setAttribute('email', $email);
|
||||
|
||||
// Bad: Passing full document is inefficient
|
||||
$user = $dbForProject->updateDocument('users', $user->getId(), $user);
|
||||
```
|
||||
|
||||
**Exceptions:**
|
||||
- Migration files (need full document updates by design)
|
||||
- Cases already using `array_merge()` with `getArrayCopy()`
|
||||
- Updates where almost all attributes of the document change at once (sparse update provides little benefit compared to passing the full document)
|
||||
- Complex nested relationship logic where full document state is required
|
||||
|
||||
## Security Considerations
|
||||
|
||||
### Critical Security Practices
|
||||
|
||||
+7
-7
@@ -111,7 +111,7 @@ $ git push origin [name_of_your_new_branch]
|
||||
|
||||
## Setup From Source
|
||||
|
||||
To set up a working **development environment**, just fork the project git repository and install the backend and frontend dependencies using the proper package manager and create run the docker-compose stack.
|
||||
To set up a working **development environment**, just fork the project git repository and install the backend and frontend dependencies using the proper package manager and run the docker-compose stack.
|
||||
|
||||
> If you just want to install Appwrite for day-to-day use and not as a contributor, you can reference the [installation guide](https://github.com/appwrite/appwrite#installation), the [getting started guide](https://appwrite.io/docs/quick-starts), or the main [README](README.md) file.
|
||||
|
||||
@@ -173,12 +173,12 @@ Learn more at our [Technology Stack](#technology-stack) section.
|
||||
- [MVVM](https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93viewmodel) - Appwrite console architecture
|
||||
|
||||
##### Container Namespace Conventions
|
||||
To keep our services easy to understand within Docker we follow a naming convention for all our containers depending on it's intended use.
|
||||
To keep our services easy to understand within Docker we follow a naming convention for all our containers depending on its intended use.
|
||||
|
||||
`appwrite-worker-X` - Workers (`src/Appwrite/Platform/Workers/*`)
|
||||
`appwrite-task-X` - Tasks (`src/Appwrite/Platform/Tasks/*`)
|
||||
|
||||
Other containes should be named the same as their service, for example `redis` should just be called `redis`.
|
||||
Other containers should be named the same as their service, for example `redis` should just be called `redis`.
|
||||
|
||||
##### Security
|
||||
|
||||
@@ -189,7 +189,7 @@ Other containes should be named the same as their service, for example `redis` s
|
||||
|
||||
## Modules
|
||||
|
||||
As Appwrite grows, we noticed approach of having all service endpoints in `app/controllers/api/[service].php` is not maintainable. Not only it creates massive files, it also doesnt contain all product's features such as workers or tasks. While there might still be some occurances of those controller files, we avoid it in all new development, and gradually migrate existing controllers to **HTTP modules**.
|
||||
As Appwrite grows, we noticed approach of having all service endpoints in `app/controllers/api/[service].php` is not maintainable. Not only it creates massive files, it also doesn't contain all product's features such as workers or tasks. While there might still be some occurrences of those controller files, we avoid it in all new development, and gradually migrate existing controllers to **HTTP modules**.
|
||||
|
||||
### HTTP Endpoints
|
||||
|
||||
@@ -204,7 +204,7 @@ Tips and tricks:
|
||||
1. If endpoint doesn't have resource, use service name as resource name too
|
||||
> Example: `Modules/Sites/Http/Sites/Get.php`
|
||||
|
||||
2. If there are multiple resources, use then all in folder structure
|
||||
2. If there are multiple resources, use them all in folder structure
|
||||
> Example: `Modules/Sites/Http/Deployments/Builds/Create.php`
|
||||
|
||||
3. Action can only be `Get`, `Create`, `Update`, `Delete` or `XList`
|
||||
@@ -395,7 +395,7 @@ These are the current metrics we collect usage stats for:
|
||||
|
||||
> Note: The curly brackets in the metric name represents a template and is replaced with a value when the metric is processed.
|
||||
|
||||
Metrics are collected within 3 scopes Daily, monthly, an infinity. Adding new usage metric in order to aggregate usage stats is very simple, but very much dependent on where do you want to collect
|
||||
Metrics are collected within 3 scopes Daily, monthly, and infinity. Adding new usage metric in order to aggregate usage stats is very simple, but very much dependent on where do you want to collect
|
||||
statistics ,via API or via background worker. For both cases you will need to add a `const` variable in `app/init.php` under the usage metrics list using the naming convention `METRIC_<RESOURCE_NAME>` as shown below.
|
||||
|
||||
```php
|
||||
@@ -661,7 +661,7 @@ docker compose exec redis redis-cli FLUSHALL
|
||||
|
||||
## Using preview domains locally
|
||||
|
||||
Appwrite Functions are automatically given a domain you can visit to execute the function. This domain has format `[SOMETHING].functions.localhost` unless you changed `_APP_DOMAIN_FUNCTIONS` environment variable. This default value works great when running Appwrite locally, but it can be impossible to use preview domains with Cloud woekspaces such as Gitpod or GitHub Codespaces.
|
||||
Appwrite Functions are automatically given a domain you can visit to execute the function. This domain has format `[SOMETHING].functions.localhost` unless you changed `_APP_DOMAIN_FUNCTIONS` environment variable. This default value works great when running Appwrite locally, but it can be impossible to use preview domains with Cloud workspaces such as Gitpod or GitHub Codespaces.
|
||||
|
||||
To use preview domains on Cloud workspaces, you can visit hostname provided by them, and supply function's preview domain as URL parameter:
|
||||
|
||||
|
||||
@@ -70,6 +70,7 @@ RUN chmod +x /usr/local/bin/doctor && \
|
||||
chmod +x /usr/local/bin/sdks && \
|
||||
chmod +x /usr/local/bin/specs && \
|
||||
chmod +x /usr/local/bin/ssl && \
|
||||
chmod +x /usr/local/bin/time-travel && \
|
||||
chmod +x /usr/local/bin/screenshot && \
|
||||
chmod +x /usr/local/bin/test && \
|
||||
chmod +x /usr/local/bin/upgrade && \
|
||||
|
||||
@@ -777,6 +777,7 @@ return [
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
// At the moment, always empty (no runtime supports it yet)
|
||||
'array' => false,
|
||||
'$id' => ID::custom('startCommand'),
|
||||
'type' => Database::VAR_STRING,
|
||||
@@ -787,17 +788,6 @@ return [
|
||||
'default' => null,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'array' => false,
|
||||
'$id' => ID::custom('specification'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 128,
|
||||
'signed' => false,
|
||||
'required' => false,
|
||||
'default' => APP_COMPUTE_SPECIFICATION_DEFAULT,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'array' => false,
|
||||
'$id' => ID::custom('buildSpecification'),
|
||||
@@ -1255,17 +1245,6 @@ return [
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'array' => false,
|
||||
'$id' => ID::custom('specification'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 128,
|
||||
'signed' => false,
|
||||
'required' => false,
|
||||
'default' => APP_COMPUTE_SPECIFICATION_DEFAULT,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'array' => false,
|
||||
'$id' => ID::custom('buildSpecification'),
|
||||
|
||||
@@ -27,7 +27,7 @@ return [
|
||||
Exception::GENERAL_RESOURCE_BLOCKED => [
|
||||
'name' => Exception::GENERAL_RESOURCE_BLOCKED,
|
||||
'description' => 'Access to this resource is blocked.',
|
||||
'code' => 401,
|
||||
'code' => 403,
|
||||
],
|
||||
Exception::GENERAL_UNKNOWN_ORIGIN => [
|
||||
'name' => Exception::GENERAL_UNKNOWN_ORIGIN,
|
||||
@@ -168,8 +168,8 @@ return [
|
||||
],
|
||||
Exception::USER_BLOCKED => [
|
||||
'name' => Exception::USER_BLOCKED,
|
||||
'description' => 'The current user has been blocked. You can unblock the user by making a request to the User API\'s "Update User Status" endpoint or in the Appwrite Console\'s Auth section.',
|
||||
'code' => 401,
|
||||
'description' => 'The current user has been blocked.',
|
||||
'code' => 403,
|
||||
],
|
||||
Exception::USER_INVALID_TOKEN => [
|
||||
'name' => Exception::USER_INVALID_TOKEN,
|
||||
|
||||
+1
-1
@@ -231,7 +231,7 @@ return [
|
||||
'url' => 'https://github.com/appwrite/sdk-for-cli',
|
||||
'package' => 'https://www.npmjs.com/package/appwrite-cli',
|
||||
'enabled' => true,
|
||||
'beta' => true,
|
||||
'beta' => false,
|
||||
'dev' => false,
|
||||
'hidden' => false,
|
||||
'family' => APP_SDK_PLATFORM_CONSOLE,
|
||||
|
||||
@@ -288,7 +288,10 @@ $createSession = function (string $userId, string $secret, Request $request, Res
|
||||
}
|
||||
|
||||
try {
|
||||
$dbForProject->updateDocument('users', $user->getId(), $user);
|
||||
$dbForProject->updateDocument('users', $user->getId(), new Document([
|
||||
'emailVerification' => $user->getAttribute('emailVerification'),
|
||||
'phoneVerification' => $user->getAttribute('phoneVerification'),
|
||||
]));
|
||||
} catch (\Throwable $th) {
|
||||
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Failed saving user to DB');
|
||||
}
|
||||
@@ -1032,7 +1035,11 @@ Http::post('/v1/account/sessions/email')
|
||||
->setAttribute('password', $proofForPasswordUpdated->hash($password))
|
||||
->setAttribute('hash', $proofForPasswordUpdated->getHash()->getName())
|
||||
->setAttribute('hashOptions', $proofForPasswordUpdated->getHash()->getOptions());
|
||||
$dbForProject->updateDocument('users', $user->getId(), $user);
|
||||
$dbForProject->updateDocument('users', $user->getId(), new Document([
|
||||
'password' => $user->getAttribute('password'),
|
||||
'hash' => $user->getAttribute('hash'),
|
||||
'hashOptions' => $user->getAttribute('hashOptions'),
|
||||
]));
|
||||
}
|
||||
|
||||
$dbForProject->purgeCachedDocument('users', $user->getId());
|
||||
@@ -1822,7 +1829,11 @@ Http::get('/v1/account/sessions/oauth2/:provider/redirect')
|
||||
->setAttribute('providerAccessToken', $accessToken)
|
||||
->setAttribute('providerRefreshToken', $refreshToken)
|
||||
->setAttribute('providerAccessTokenExpiry', DateTime::addSeconds(new \DateTime(), (int) $accessTokenExpiry));
|
||||
$dbForProject->updateDocument('identities', $identity->getId(), $identity);
|
||||
$dbForProject->updateDocument('identities', $identity->getId(), new Document([
|
||||
'providerAccessToken' => $identity->getAttribute('providerAccessToken'),
|
||||
'providerRefreshToken' => $identity->getAttribute('providerRefreshToken'),
|
||||
'providerAccessTokenExpiry' => $identity->getAttribute('providerAccessTokenExpiry'),
|
||||
]));
|
||||
}
|
||||
|
||||
if (empty($user->getAttribute('email'))) {
|
||||
@@ -1960,7 +1971,10 @@ Http::get('/v1/account/sessions/oauth2/:provider/redirect')
|
||||
->setAttribute('sessionId', $session->getId())
|
||||
->setAttribute('sessionInternalId', $session->getSequence());
|
||||
|
||||
$dbForProject->updateDocument('targets', $target->getId(), $target);
|
||||
$dbForProject->updateDocument('targets', $target->getId(), new Document([
|
||||
'sessionId' => $target->getAttribute('sessionId'),
|
||||
'sessionInternalId' => $target->getAttribute('sessionInternalId'),
|
||||
]));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3145,7 +3159,9 @@ Http::patch('/v1/account/name')
|
||||
|
||||
$user->setAttribute('name', $name);
|
||||
|
||||
$user = $dbForProject->updateDocument('users', $user->getId(), $user);
|
||||
$user = $dbForProject->updateDocument('users', $user->getId(), new Document([
|
||||
'name' => $user->getAttribute('name'),
|
||||
]));
|
||||
|
||||
$queueForEvents->setParam('userId', $user->getId());
|
||||
|
||||
@@ -3798,13 +3814,15 @@ Http::put('/v1/account/recovery')
|
||||
|
||||
$hooks->trigger('passwordValidator', [$dbForProject, $project, $password, &$user, true]);
|
||||
|
||||
$profile = $dbForProject->updateDocument('users', $profile->getId(), $profile
|
||||
->setAttribute('password', $newPassword)
|
||||
->setAttribute('passwordHistory', $history)
|
||||
->setAttribute('passwordUpdate', DateTime::now())
|
||||
->setAttribute('hash', $proofForPassword->getHash()->getName())
|
||||
->setAttribute('hashOptions', $proofForPassword->getHash()->getOptions())
|
||||
->setAttribute('emailVerification', true));
|
||||
$profile = $dbForProject->updateDocument('users', $profile->getId(), new Document(
|
||||
[
|
||||
'password' => $newPassword,
|
||||
'passwordHistory' => $history,
|
||||
'passwordUpdate' => DateTime::now(),
|
||||
'hash' => $proofForPassword->getHash()->getName(),
|
||||
'hashOptions' => $proofForPassword->getHash()->getOptions(),
|
||||
'emailVerification' => true]
|
||||
));
|
||||
|
||||
$user->setAttributes($profile->getArrayCopy());
|
||||
|
||||
@@ -4126,7 +4144,7 @@ Http::put('/v1/account/verifications/email')
|
||||
|
||||
$authorization->addRole(Role::user($profile->getId())->toString());
|
||||
|
||||
$profile = $dbForProject->updateDocument('users', $profile->getId(), $profile->setAttribute('emailVerification', true));
|
||||
$profile = $dbForProject->updateDocument('users', $profile->getId(), new Document(['emailVerification' => true]));
|
||||
|
||||
$user->setAttributes($profile->getArrayCopy());
|
||||
|
||||
@@ -4342,7 +4360,7 @@ Http::put('/v1/account/verifications/phone')
|
||||
|
||||
$authorization->addRole(Role::user($profile->getId())->toString());
|
||||
|
||||
$profile = $dbForProject->updateDocument('users', $profile->getId(), $profile->setAttribute('phoneVerification', true));
|
||||
$profile = $dbForProject->updateDocument('users', $profile->getId(), new Document(['phoneVerification' => true]));
|
||||
|
||||
$user->setAttributes($profile->getArrayCopy());
|
||||
|
||||
@@ -4500,7 +4518,11 @@ Http::put('/v1/account/targets/:targetId/push')
|
||||
|
||||
$target->setAttribute('name', "{$device['deviceBrand']} {$device['deviceModel']}");
|
||||
|
||||
$target = $dbForProject->updateDocument('targets', $target->getId(), $target);
|
||||
$target = $dbForProject->updateDocument('targets', $target->getId(), new Document([
|
||||
'identifier' => $target->getAttribute('identifier'),
|
||||
'expired' => $target->getAttribute('expired'),
|
||||
'name' => $target->getAttribute('name'),
|
||||
]));
|
||||
|
||||
$dbForProject->purgeCachedDocument('users', $user->getId());
|
||||
|
||||
|
||||
@@ -1161,7 +1161,7 @@ Http::patch('/v1/users/:userId/status')
|
||||
throw new Exception(Exception::USER_NOT_FOUND);
|
||||
}
|
||||
|
||||
$user = $dbForProject->updateDocument('users', $user->getId(), $user->setAttribute('status', (bool) $status));
|
||||
$user = $dbForProject->updateDocument('users', $user->getId(), new Document(['status' => (bool) $status]));
|
||||
|
||||
$queueForEvents
|
||||
->setParam('userId', $user->getId());
|
||||
@@ -1204,7 +1204,7 @@ Http::put('/v1/users/:userId/labels')
|
||||
|
||||
$user->setAttribute('labels', (array) \array_values(\array_unique($labels)));
|
||||
|
||||
$user = $dbForProject->updateDocument('users', $user->getId(), $user);
|
||||
$user = $dbForProject->updateDocument('users', $user->getId(), new Document(['labels' => $user->getAttribute('labels')]));
|
||||
|
||||
$queueForEvents
|
||||
->setParam('userId', $user->getId());
|
||||
@@ -1245,7 +1245,7 @@ Http::patch('/v1/users/:userId/verification/phone')
|
||||
throw new Exception(Exception::USER_NOT_FOUND);
|
||||
}
|
||||
|
||||
$user = $dbForProject->updateDocument('users', $user->getId(), $user->setAttribute('phoneVerification', $phoneVerification));
|
||||
$user = $dbForProject->updateDocument('users', $user->getId(), new Document(['phoneVerification' => $phoneVerification]));
|
||||
|
||||
$queueForEvents
|
||||
->setParam('userId', $user->getId());
|
||||
@@ -1289,7 +1289,7 @@ Http::patch('/v1/users/:userId/name')
|
||||
|
||||
$user->setAttribute('name', $name);
|
||||
|
||||
$user = $dbForProject->updateDocument('users', $user->getId(), $user);
|
||||
$user = $dbForProject->updateDocument('users', $user->getId(), new Document(['name' => $user->getAttribute('name')]));
|
||||
|
||||
$queueForEvents->setParam('userId', $user->getId());
|
||||
|
||||
@@ -1344,7 +1344,10 @@ Http::patch('/v1/users/:userId/password')
|
||||
->setAttribute('password', '')
|
||||
->setAttribute('passwordUpdate', DateTime::now());
|
||||
|
||||
$user = $dbForProject->updateDocument('users', $user->getId(), $user);
|
||||
$user = $dbForProject->updateDocument('users', $user->getId(), new Document([
|
||||
'password' => $user->getAttribute('password'),
|
||||
'passwordUpdate' => $user->getAttribute('passwordUpdate'),
|
||||
]));
|
||||
$queueForEvents->setParam('userId', $user->getId());
|
||||
$response->dynamic($user, Response::MODEL_USER);
|
||||
}
|
||||
@@ -1377,7 +1380,13 @@ Http::patch('/v1/users/:userId/password')
|
||||
->setAttribute('hash', $hasher->getName())
|
||||
->setAttribute('hashOptions', $hasher->getOptions());
|
||||
|
||||
$user = $dbForProject->updateDocument('users', $user->getId(), $user);
|
||||
$user = $dbForProject->updateDocument('users', $user->getId(), new Document([
|
||||
'password' => $user->getAttribute('password'),
|
||||
'passwordHistory' => $user->getAttribute('passwordHistory'),
|
||||
'passwordUpdate' => $user->getAttribute('passwordUpdate'),
|
||||
'hash' => $user->getAttribute('hash'),
|
||||
'hashOptions' => $user->getAttribute('hashOptions'),
|
||||
]));
|
||||
|
||||
$sessions = $user->getAttribute('sessions', []);
|
||||
$invalidate = $project->getAttribute('auths', default: [])['invalidateSessions'] ?? false;
|
||||
@@ -1469,7 +1478,15 @@ Http::patch('/v1/users/:userId/email')
|
||||
;
|
||||
|
||||
try {
|
||||
$user = $dbForProject->updateDocument('users', $user->getId(), $user);
|
||||
$user = $dbForProject->updateDocument('users', $user->getId(), new Document([
|
||||
'email' => $user->getAttribute('email'),
|
||||
'emailVerification' => $user->getAttribute('emailVerification'),
|
||||
'emailCanonical' => $user->getAttribute('emailCanonical'),
|
||||
'emailIsCanonical' => $user->getAttribute('emailIsCanonical'),
|
||||
'emailIsCorporate' => $user->getAttribute('emailIsCorporate'),
|
||||
'emailIsDisposable' => $user->getAttribute('emailIsDisposable'),
|
||||
'emailIsFree' => $user->getAttribute('emailIsFree'),
|
||||
]));
|
||||
/**
|
||||
* @var Document $oldTarget
|
||||
*/
|
||||
@@ -1477,7 +1494,8 @@ Http::patch('/v1/users/:userId/email')
|
||||
|
||||
if ($oldTarget instanceof Document && !$oldTarget->isEmpty()) {
|
||||
if (\strlen($email) !== 0) {
|
||||
$dbForProject->updateDocument('targets', $oldTarget->getId(), $oldTarget->setAttribute('identifier', $email));
|
||||
$dbForProject->updateDocument('targets', $oldTarget->getId(), new Document(['identifier' => $email]));
|
||||
$oldTarget->setAttribute('identifier', $email);
|
||||
} else {
|
||||
$dbForProject->deleteDocument('targets', $oldTarget->getId());
|
||||
}
|
||||
@@ -1558,7 +1576,10 @@ Http::patch('/v1/users/:userId/phone')
|
||||
}
|
||||
|
||||
try {
|
||||
$user = $dbForProject->updateDocument('users', $user->getId(), $user);
|
||||
$user = $dbForProject->updateDocument('users', $user->getId(), new Document([
|
||||
'phone' => $user->getAttribute('phone'),
|
||||
'phoneVerification' => $user->getAttribute('phoneVerification'),
|
||||
]));
|
||||
/**
|
||||
* @var Document $oldTarget
|
||||
*/
|
||||
@@ -1566,7 +1587,8 @@ Http::patch('/v1/users/:userId/phone')
|
||||
|
||||
if ($oldTarget instanceof Document && !$oldTarget->isEmpty()) {
|
||||
if (\strlen($number) !== 0) {
|
||||
$dbForProject->updateDocument('targets', $oldTarget->getId(), $oldTarget->setAttribute('identifier', $number));
|
||||
$dbForProject->updateDocument('targets', $oldTarget->getId(), new Document(['identifier' => $number]));
|
||||
$oldTarget->setAttribute('identifier', $number);
|
||||
} else {
|
||||
$dbForProject->deleteDocument('targets', $oldTarget->getId());
|
||||
}
|
||||
@@ -1630,7 +1652,7 @@ Http::patch('/v1/users/:userId/verification')
|
||||
throw new Exception(Exception::USER_NOT_FOUND);
|
||||
}
|
||||
|
||||
$user = $dbForProject->updateDocument('users', $user->getId(), $user->setAttribute('emailVerification', $emailVerification));
|
||||
$user = $dbForProject->updateDocument('users', $user->getId(), new Document(['emailVerification' => $emailVerification]));
|
||||
|
||||
$queueForEvents->setParam('userId', $user->getId());
|
||||
|
||||
@@ -1668,7 +1690,7 @@ Http::patch('/v1/users/:userId/prefs')
|
||||
throw new Exception(Exception::USER_NOT_FOUND);
|
||||
}
|
||||
|
||||
$user = $dbForProject->updateDocument('users', $user->getId(), $user->setAttribute('prefs', $prefs));
|
||||
$user = $dbForProject->updateDocument('users', $user->getId(), new Document(['prefs' => $prefs]));
|
||||
|
||||
$queueForEvents
|
||||
->setParam('userId', $user->getId());
|
||||
@@ -1768,7 +1790,13 @@ Http::patch('/v1/users/:userId/targets/:targetId')
|
||||
$target->setAttribute('name', $name);
|
||||
}
|
||||
|
||||
$target = $dbForProject->updateDocument('targets', $target->getId(), $target);
|
||||
$target = $dbForProject->updateDocument('targets', $target->getId(), new Document([
|
||||
'identifier' => $target->getAttribute('identifier'),
|
||||
'expired' => $target->getAttribute('expired'),
|
||||
'providerId' => $target->getAttribute('providerId'),
|
||||
'providerInternalId' => $target->getAttribute('providerInternalId'),
|
||||
'name' => $target->getAttribute('name'),
|
||||
]));
|
||||
$dbForProject->purgeCachedDocument('users', $user->getId());
|
||||
|
||||
$queueForEvents
|
||||
@@ -1836,7 +1864,7 @@ Http::patch('/v1/users/:userId/mfa')
|
||||
|
||||
$user->setAttribute('mfa', $mfa);
|
||||
|
||||
$user = $dbForProject->updateDocument('users', $user->getId(), $user);
|
||||
$user = $dbForProject->updateDocument('users', $user->getId(), new Document(['mfa' => $user->getAttribute('mfa')]));
|
||||
|
||||
$queueForEvents->setParam('userId', $user->getId());
|
||||
|
||||
@@ -2024,7 +2052,7 @@ Http::patch('/v1/users/:userId/mfa/recovery-codes')
|
||||
|
||||
$mfaRecoveryCodes = Type::generateBackupCodes();
|
||||
$user->setAttribute('mfaRecoveryCodes', $mfaRecoveryCodes);
|
||||
$dbForProject->updateDocument('users', $user->getId(), $user);
|
||||
$dbForProject->updateDocument('users', $user->getId(), new Document(['mfaRecoveryCodes' => $mfaRecoveryCodes]));
|
||||
|
||||
$queueForEvents->setParam('userId', $user->getId());
|
||||
|
||||
@@ -2096,7 +2124,7 @@ Http::put('/v1/users/:userId/mfa/recovery-codes')
|
||||
|
||||
$mfaRecoveryCodes = Type::generateBackupCodes();
|
||||
$user->setAttribute('mfaRecoveryCodes', $mfaRecoveryCodes);
|
||||
$dbForProject->updateDocument('users', $user->getId(), $user);
|
||||
$dbForProject->updateDocument('users', $user->getId(), new Document(['mfaRecoveryCodes' => $mfaRecoveryCodes]));
|
||||
|
||||
$queueForEvents->setParam('userId', $user->getId());
|
||||
|
||||
|
||||
+26
-10
@@ -30,6 +30,8 @@ use Appwrite\Utopia\Response\Filters\V16 as ResponseV16;
|
||||
use Appwrite\Utopia\Response\Filters\V17 as ResponseV17;
|
||||
use Appwrite\Utopia\Response\Filters\V18 as ResponseV18;
|
||||
use Appwrite\Utopia\Response\Filters\V19 as ResponseV19;
|
||||
use Appwrite\Utopia\Response\Filters\V20 as ResponseV20;
|
||||
use Appwrite\Utopia\Response\Filters\V21 as ResponseV21;
|
||||
use Appwrite\Utopia\View;
|
||||
use Executor\Executor;
|
||||
use MaxMind\Db\Reader;
|
||||
@@ -132,8 +134,9 @@ function router(Http $utopia, Database $dbForPlatform, callable $getProjectDB, S
|
||||
if (!$project->isEmpty() && $project->getId() !== 'console') {
|
||||
$accessedAt = $project->getAttribute('accessedAt', 0);
|
||||
if (DateTime::formatTz(DateTime::addSeconds(new \DateTime(), -APP_PROJECT_ACCESS)) > $accessedAt) {
|
||||
$project->setAttribute('accessedAt', DateTime::now());
|
||||
$authorization->skip(fn () => $dbForPlatform->updateDocument('projects', $project->getId(), $project));
|
||||
$authorization->skip(fn () => $dbForPlatform->updateDocument('projects', $project->getId(), new Document([
|
||||
'accessedAt' => DateTime::now()
|
||||
])));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -321,7 +324,7 @@ function router(Http $utopia, Database $dbForPlatform, callable $getProjectDB, S
|
||||
};
|
||||
|
||||
$runtimes = Config::getParam($version === 'v2' ? 'runtimes-v2' : 'runtimes', []);
|
||||
$spec = Config::getParam('specifications')[$resource->getAttribute('specification', APP_COMPUTE_SPECIFICATION_DEFAULT)];
|
||||
$spec = Config::getParam('specifications')[$resource->getAttribute('runtimeSpecification', APP_COMPUTE_SPECIFICATION_DEFAULT)];
|
||||
|
||||
$runtime = match ($type) {
|
||||
'function' => $runtimes[$resource->getAttribute('runtime')] ?? null,
|
||||
@@ -551,6 +554,10 @@ function router(Http $utopia, Database $dbForPlatform, callable $getProjectDB, S
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($deployment->getAttribute('startCommand', ''))) {
|
||||
$startCommand = 'cd /usr/local/server/src/function/ && ' . $deployment->getAttribute('startCommand', '');
|
||||
}
|
||||
|
||||
$runtimeEntrypoint = match ($version) {
|
||||
'v2' => '',
|
||||
default => "cp /tmp/code.$extension /mnt/code/code.$extension && nohup helpers/start.sh \"$startCommand\"",
|
||||
@@ -932,17 +939,23 @@ Http::init()
|
||||
*/
|
||||
$responseFormat = $request->getHeader('x-appwrite-response-format', System::getEnv('_APP_SYSTEM_RESPONSE_FORMAT', ''));
|
||||
if ($responseFormat) {
|
||||
if (version_compare($responseFormat, '1.4.0', '<')) {
|
||||
$response->addFilter(new ResponseV16());
|
||||
if (version_compare($responseFormat, '1.9.0', '<')) {
|
||||
$response->addFilter(new ResponseV21());
|
||||
}
|
||||
if (version_compare($responseFormat, '1.5.0', '<')) {
|
||||
$response->addFilter(new ResponseV17());
|
||||
if (version_compare($responseFormat, '1.8.0', '<')) {
|
||||
$response->addFilter(new ResponseV20());
|
||||
}
|
||||
if (version_compare($responseFormat, '1.7.0', '<')) {
|
||||
$response->addFilter(new ResponseV19());
|
||||
}
|
||||
if (version_compare($responseFormat, '1.6.0', '<')) {
|
||||
$response->addFilter(new ResponseV18());
|
||||
}
|
||||
if (version_compare($responseFormat, '1.7.0', '<')) {
|
||||
$response->addFilter(new ResponseV19());
|
||||
if (version_compare($responseFormat, '1.5.0', '<')) {
|
||||
$response->addFilter(new ResponseV17());
|
||||
}
|
||||
if (version_compare($responseFormat, '1.4.0', '<')) {
|
||||
$response->addFilter(new ResponseV16());
|
||||
}
|
||||
if (version_compare($responseFormat, APP_VERSION_STABLE, '>')) {
|
||||
$warnings[] = "The current SDK is built for Appwrite " . $responseFormat . ". However, the current Appwrite server version is " . APP_VERSION_STABLE . ". Please downgrade your SDK to match the Appwrite version: https://appwrite.io/docs/sdks";
|
||||
@@ -1649,7 +1662,10 @@ Http::get('/v1/ping')
|
||||
->setAttribute('pingedAt', $pingedAt);
|
||||
|
||||
$authorization->skip(function () use ($dbForPlatform, $project) {
|
||||
$dbForPlatform->updateDocument('projects', $project->getId(), $project);
|
||||
$dbForPlatform->updateDocument('projects', $project->getId(), new Document([
|
||||
'pingCount' => $project->getAttribute('pingCount'),
|
||||
'pingedAt' => $project->getAttribute('pingedAt')
|
||||
]));
|
||||
});
|
||||
|
||||
$queueForEvents
|
||||
|
||||
@@ -214,6 +214,8 @@ Http::init()
|
||||
}
|
||||
|
||||
if (!$dbKey) {
|
||||
\var_dump($apiKey);
|
||||
\var_dump($request->getHeader('x-appwrite-key', ''));
|
||||
throw new Exception(Exception::USER_UNAUTHORIZED);
|
||||
}
|
||||
|
||||
@@ -360,8 +362,9 @@ Http::init()
|
||||
if (!$project->isEmpty() && $project->getId() !== 'console') {
|
||||
$accessedAt = $project->getAttribute('accessedAt', 0);
|
||||
if (DateTime::formatTz(DateTime::addSeconds(new \DateTime(), -APP_PROJECT_ACCESS)) > $accessedAt) {
|
||||
$project->setAttribute('accessedAt', DateTime::now());
|
||||
$authorization->skip(fn () => $dbForPlatform->updateDocument('projects', $project->getId(), $project));
|
||||
$authorization->skip(fn () => $dbForPlatform->updateDocument('projects', $project->getId(), new Document([
|
||||
'accessedAt' => DateTime::now()
|
||||
])));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -371,9 +374,13 @@ Http::init()
|
||||
$user->setAttribute('accessedAt', DateTime::now());
|
||||
|
||||
if ($project->getId() !== 'console' && APP_MODE_ADMIN !== $mode) {
|
||||
$dbForProject->updateDocument('users', $user->getId(), $user);
|
||||
$dbForProject->updateDocument('users', $user->getId(), new Document([
|
||||
'accessedAt' => $user->getAttribute('accessedAt')
|
||||
]));
|
||||
} else {
|
||||
$authorization->skip(fn () => $dbForPlatform->updateDocument('users', $user->getId(), $user));
|
||||
$authorization->skip(fn () => $dbForPlatform->updateDocument('users', $user->getId(), new Document([
|
||||
'accessedAt' => $user->getAttribute('accessedAt')
|
||||
])));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -649,7 +656,9 @@ Http::init()
|
||||
$transformedAt = $file->getAttribute('transformedAt', '');
|
||||
if (DateTime::formatTz(DateTime::addSeconds(new \DateTime(), -APP_PROJECT_ACCESS)) > $transformedAt) {
|
||||
$file->setAttribute('transformedAt', DateTime::now());
|
||||
$authorization->skip(fn () => $dbForProject->updateDocument('bucket_' . $file->getAttribute('bucketInternalId'), $file->getId(), $file));
|
||||
$authorization->skip(fn () => $dbForProject->updateDocument('bucket_' . $file->getAttribute('bucketInternalId'), $file->getId(), new Document([
|
||||
'transformedAt' => $file->getAttribute('transformedAt')
|
||||
])));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -948,7 +957,9 @@ Http::shutdown()
|
||||
}
|
||||
} elseif (DateTime::formatTz(DateTime::addSeconds(new \DateTime(), -APP_CACHE_UPDATE)) > $accessedAt) {
|
||||
$cacheLog->setAttribute('accessedAt', $now);
|
||||
$authorization->skip(fn () => $dbForProject->updateDocument('cache', $cacheLog->getId(), $cacheLog));
|
||||
$authorization->skip(fn () => $dbForProject->updateDocument('cache', $cacheLog->getId(), new Document([
|
||||
'accessedAt' => $cacheLog->getAttribute('accessedAt')
|
||||
])));
|
||||
// Overwrite the file every APP_CACHE_UPDATE seconds to update the file modified time that is used in the TTL checks in cache->load()
|
||||
$cache->save($key, $data['payload']);
|
||||
}
|
||||
|
||||
@@ -93,6 +93,7 @@ const APP_SOCIAL_YOUTUBE = 'https://www.youtube.com/c/appwrite?sub_confirmation=
|
||||
const APP_COMPUTE_CPUS_DEFAULT = 0.5;
|
||||
const APP_COMPUTE_MEMORY_DEFAULT = 512;
|
||||
const APP_COMPUTE_SPECIFICATION_DEFAULT = Specification::S_1VCPU_512MB;
|
||||
const APP_COMPUTE_DEPLOYMENT_MAX_RETENTION = 100 * 365; // 100 years
|
||||
const APP_SDK_PLATFORM_SERVER = 'server';
|
||||
const APP_SDK_PLATFORM_CLIENT = 'client';
|
||||
const APP_SDK_PLATFORM_CONSOLE = 'console';
|
||||
|
||||
+10
-3
@@ -1235,7 +1235,9 @@ Http::setResource('devKey', function (Request $request, Document $project, array
|
||||
$accessedAt = $key->getAttribute('accessedAt', 0);
|
||||
if (empty($accessedAt) || DatabaseDateTime::formatTz(DatabaseDateTime::addSeconds(new \DateTime(), -APP_KEY_ACCESS)) > $accessedAt) {
|
||||
$key->setAttribute('accessedAt', DatabaseDateTime::now());
|
||||
$authorization->skip(fn () => $dbForPlatform->updateDocument('devKeys', $key->getId(), $key));
|
||||
$authorization->skip(fn () => $dbForPlatform->updateDocument('devKeys', $key->getId(), new Document([
|
||||
'accessedAt' => $key->getAttribute('accessedAt')
|
||||
])));
|
||||
$dbForPlatform->purgeCachedDocument('projects', $project->getId());
|
||||
}
|
||||
|
||||
@@ -1252,7 +1254,10 @@ Http::setResource('devKey', function (Request $request, Document $project, array
|
||||
|
||||
/** Update access time as well */
|
||||
$key->setAttribute('accessedAt', DatabaseDateTime::now());
|
||||
$key = $authorization->skip(fn () => $dbForPlatform->updateDocument('devKeys', $key->getId(), $key));
|
||||
$key = $authorization->skip(fn () => $dbForPlatform->updateDocument('devKeys', $key->getId(), new Document([
|
||||
'sdks' => $key->getAttribute('sdks'),
|
||||
'accessedAt' => $key->getAttribute('accessedAt')
|
||||
])));
|
||||
$dbForPlatform->purgeCachedDocument('projects', $project->getId());
|
||||
}
|
||||
}
|
||||
@@ -1409,7 +1414,9 @@ Http::setResource('resourceToken', function ($project, $dbForProject, $request,
|
||||
$accessedAt = $token->getAttribute('accessedAt', 0);
|
||||
if (empty($accessedAt) || DatabaseDateTime::formatTz(DatabaseDateTime::addSeconds(new \DateTime(), -APP_RESOURCE_TOKEN_ACCESS)) > $accessedAt) {
|
||||
$token->setAttribute('accessedAt', DatabaseDateTime::now());
|
||||
$authorization->skip(fn () => $dbForProject->updateDocument('resourceTokens', $token->getId(), $token));
|
||||
$authorization->skip(fn () => $dbForProject->updateDocument('resourceTokens', $token->getId(), new Document([
|
||||
'accessedAt' => $token->getAttribute('accessedAt')
|
||||
])));
|
||||
}
|
||||
|
||||
return new Document([
|
||||
|
||||
+4
-1
@@ -356,7 +356,10 @@ $server->onStart(function () use ($stats, $register, $containerId, &$statsDocume
|
||||
->setAttribute('timestamp', DateTime::now())
|
||||
->setAttribute('value', json_encode($payload));
|
||||
|
||||
$database->getAuthorization()->skip(fn () => $database->updateDocument('realtime', $statsDocument->getId(), $statsDocument));
|
||||
$database->getAuthorization()->skip(fn () => $database->updateDocument('realtime', $statsDocument->getId(), new Document([
|
||||
'timestamp' => $statsDocument->getAttribute('timestamp'),
|
||||
'value' => $statsDocument->getAttribute('value')
|
||||
])));
|
||||
} catch (Throwable $th) {
|
||||
logError($th, "updateWorkerDocument");
|
||||
}
|
||||
|
||||
Executable
+3
@@ -0,0 +1,3 @@
|
||||
#!/bin/sh
|
||||
|
||||
exec php /usr/src/code/app/cli.php time-travel "$@"
|
||||
@@ -100,6 +100,7 @@
|
||||
"swoole/ide-helper": "6.*",
|
||||
"phpstan/phpstan": "1.12.*",
|
||||
"textalk/websocket": "1.5.*",
|
||||
"czproject/git-php": "4.*",
|
||||
"laravel/pint": "1.*",
|
||||
"phpbench/phpbench": "1.*"
|
||||
},
|
||||
|
||||
Generated
+134
-69
@@ -4,7 +4,7 @@
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "1fb043a556550f62ab27a0aad0b9332a",
|
||||
"content-hash": "1cc64e07484256225f56bd525674c3b8",
|
||||
"packages": [
|
||||
{
|
||||
"name": "adhocore/jwt",
|
||||
@@ -2708,16 +2708,16 @@
|
||||
},
|
||||
{
|
||||
"name": "symfony/http-client",
|
||||
"version": "v7.4.5",
|
||||
"version": "v7.4.7",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/http-client.git",
|
||||
"reference": "84bb634857a893cc146cceb467e31b3f02c5fe9f"
|
||||
"reference": "1010624285470eb60e88ed10035102c75b4ea6af"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/http-client/zipball/84bb634857a893cc146cceb467e31b3f02c5fe9f",
|
||||
"reference": "84bb634857a893cc146cceb467e31b3f02c5fe9f",
|
||||
"url": "https://api.github.com/repos/symfony/http-client/zipball/1010624285470eb60e88ed10035102c75b4ea6af",
|
||||
"reference": "1010624285470eb60e88ed10035102c75b4ea6af",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -2785,7 +2785,7 @@
|
||||
"http"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/http-client/tree/v7.4.5"
|
||||
"source": "https://github.com/symfony/http-client/tree/v7.4.7"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -2805,7 +2805,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2026-01-27T16:16:02+00:00"
|
||||
"time": "2026-03-05T11:16:58+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/http-client-contracts",
|
||||
@@ -3850,16 +3850,16 @@
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/database",
|
||||
"version": "5.3.4",
|
||||
"version": "5.3.7",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/utopia-php/database.git",
|
||||
"reference": "ba1ee9cb2c7624d0fada782b285bd9958a07bbe5"
|
||||
"reference": "438cc82af2981cd41ad200dd9b0df5bf00f3046a"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/utopia-php/database/zipball/ba1ee9cb2c7624d0fada782b285bd9958a07bbe5",
|
||||
"reference": "ba1ee9cb2c7624d0fada782b285bd9958a07bbe5",
|
||||
"url": "https://api.github.com/repos/utopia-php/database/zipball/438cc82af2981cd41ad200dd9b0df5bf00f3046a",
|
||||
"reference": "438cc82af2981cd41ad200dd9b0df5bf00f3046a",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -3902,9 +3902,9 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/utopia-php/database/issues",
|
||||
"source": "https://github.com/utopia-php/database/tree/5.3.4"
|
||||
"source": "https://github.com/utopia-php/database/tree/5.3.7"
|
||||
},
|
||||
"time": "2026-02-24T00:37:36+00:00"
|
||||
"time": "2026-03-09T04:28:56+00:00"
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/detector",
|
||||
@@ -4058,16 +4058,16 @@
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/domains",
|
||||
"version": "1.0.2",
|
||||
"version": "1.0.5",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/utopia-php/domains.git",
|
||||
"reference": "b4896a6746f0fbe29dfd5e32f7790bd94c1af1e6"
|
||||
"reference": "0edf6bb2b07f30db849a267027077bf5abb994c6"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/utopia-php/domains/zipball/b4896a6746f0fbe29dfd5e32f7790bd94c1af1e6",
|
||||
"reference": "b4896a6746f0fbe29dfd5e32f7790bd94c1af1e6",
|
||||
"url": "https://api.github.com/repos/utopia-php/domains/zipball/0edf6bb2b07f30db849a267027077bf5abb994c6",
|
||||
"reference": "0edf6bb2b07f30db849a267027077bf5abb994c6",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -4114,9 +4114,9 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/utopia-php/domains/issues",
|
||||
"source": "https://github.com/utopia-php/domains/tree/1.0.2"
|
||||
"source": "https://github.com/utopia-php/domains/tree/1.0.5"
|
||||
},
|
||||
"time": "2026-02-25T08:18:25+00:00"
|
||||
"time": "2026-03-03T09:20:50+00:00"
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/dsn",
|
||||
@@ -4267,16 +4267,16 @@
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/framework",
|
||||
"version": "0.33.40",
|
||||
"version": "0.33.41",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/utopia-php/http.git",
|
||||
"reference": "0ba25e1282c6a2f849053f7ccf28d567c2c321b1"
|
||||
"reference": "0f3bf2377c867e547c929c3733b8224afee6ef06"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/utopia-php/http/zipball/0ba25e1282c6a2f849053f7ccf28d567c2c321b1",
|
||||
"reference": "0ba25e1282c6a2f849053f7ccf28d567c2c321b1",
|
||||
"url": "https://api.github.com/repos/utopia-php/http/zipball/0f3bf2377c867e547c929c3733b8224afee6ef06",
|
||||
"reference": "0f3bf2377c867e547c929c3733b8224afee6ef06",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -4310,9 +4310,9 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/utopia-php/http/issues",
|
||||
"source": "https://github.com/utopia-php/http/tree/0.33.40"
|
||||
"source": "https://github.com/utopia-php/http/tree/0.33.41"
|
||||
},
|
||||
"time": "2026-02-19T13:00:08+00:00"
|
||||
"time": "2026-02-24T12:01:28+00:00"
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/image",
|
||||
@@ -5438,16 +5438,16 @@
|
||||
"packages-dev": [
|
||||
{
|
||||
"name": "appwrite/sdk-generator",
|
||||
"version": "1.11.3",
|
||||
"version": "1.11.6",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/appwrite/sdk-generator.git",
|
||||
"reference": "45d22c0107a53bb9a0a4e39db0e738d461631d11"
|
||||
"reference": "f80e302d000cdc2f98b4bb5ff2fc3bd0bdff7b38"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/45d22c0107a53bb9a0a4e39db0e738d461631d11",
|
||||
"reference": "45d22c0107a53bb9a0a4e39db0e738d461631d11",
|
||||
"url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/f80e302d000cdc2f98b4bb5ff2fc3bd0bdff7b38",
|
||||
"reference": "f80e302d000cdc2f98b4bb5ff2fc3bd0bdff7b38",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -5483,9 +5483,9 @@
|
||||
"description": "Appwrite PHP library for generating API SDKs for multiple programming languages and platforms",
|
||||
"support": {
|
||||
"issues": "https://github.com/appwrite/sdk-generator/issues",
|
||||
"source": "https://github.com/appwrite/sdk-generator/tree/1.11.3"
|
||||
"source": "https://github.com/appwrite/sdk-generator/tree/1.11.6"
|
||||
},
|
||||
"time": "2026-02-27T06:54:59+00:00"
|
||||
"time": "2026-03-09T07:12:51+00:00"
|
||||
},
|
||||
{
|
||||
"name": "brianium/paratest",
|
||||
@@ -5580,6 +5580,70 @@
|
||||
],
|
||||
"time": "2026-02-25T14:53:45+00:00"
|
||||
},
|
||||
{
|
||||
"name": "czproject/git-php",
|
||||
"version": "v4.6.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/czproject/git-php.git",
|
||||
"reference": "1f1ecc92aea9ee31120f4f5b759f5aa947420b0a"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/czproject/git-php/zipball/1f1ecc92aea9ee31120f4f5b759f5aa947420b0a",
|
||||
"reference": "1f1ecc92aea9ee31120f4f5b759f5aa947420b0a",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "8.0 - 8.5"
|
||||
},
|
||||
"require-dev": {
|
||||
"nette/tester": "^2.5"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"classmap": [
|
||||
"src/"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"BSD-3-Clause"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Jan Pecha",
|
||||
"email": "janpecha@email.cz"
|
||||
}
|
||||
],
|
||||
"description": "Library for work with Git repository in PHP.",
|
||||
"keywords": [
|
||||
"git"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/czproject/git-php/issues",
|
||||
"source": "https://github.com/czproject/git-php/tree/v4.6.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://github.com/sponsors/janpecha",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://www.janpecha.cz/donate/git-php/",
|
||||
"type": "other"
|
||||
},
|
||||
{
|
||||
"url": "https://donate.stripe.com/7sIcO2a9maTSg2A9AA",
|
||||
"type": "stripe"
|
||||
},
|
||||
{
|
||||
"url": "https://thanks.dev/u/gh/czproject",
|
||||
"type": "thanks.dev"
|
||||
}
|
||||
],
|
||||
"time": "2025-11-10T07:24:07+00:00"
|
||||
},
|
||||
{
|
||||
"name": "doctrine/annotations",
|
||||
"version": "2.0.2",
|
||||
@@ -6334,16 +6398,16 @@
|
||||
},
|
||||
{
|
||||
"name": "phpbench/phpbench",
|
||||
"version": "1.4.3",
|
||||
"version": "1.5.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/phpbench/phpbench.git",
|
||||
"reference": "b641dde59d969ea42eed70a39f9b51950bc96878"
|
||||
"reference": "9a28fd0833f11171b949843c6fd663eb69b6d14c"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/phpbench/phpbench/zipball/b641dde59d969ea42eed70a39f9b51950bc96878",
|
||||
"reference": "b641dde59d969ea42eed70a39f9b51950bc96878",
|
||||
"url": "https://api.github.com/repos/phpbench/phpbench/zipball/9a28fd0833f11171b949843c6fd663eb69b6d14c",
|
||||
"reference": "9a28fd0833f11171b949843c6fd663eb69b6d14c",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -6354,7 +6418,7 @@
|
||||
"ext-reflection": "*",
|
||||
"ext-spl": "*",
|
||||
"ext-tokenizer": "*",
|
||||
"php": "^8.1",
|
||||
"php": "^8.2",
|
||||
"phpbench/container": "^2.2",
|
||||
"psr/log": "^1.1 || ^2.0 || ^3.0",
|
||||
"seld/jsonlint": "^1.1",
|
||||
@@ -6374,8 +6438,9 @@
|
||||
"phpstan/extension-installer": "^1.1",
|
||||
"phpstan/phpstan": "^1.0",
|
||||
"phpstan/phpstan-phpunit": "^1.0",
|
||||
"phpunit/phpunit": "^10.4 || ^11.0",
|
||||
"phpunit/phpunit": "^11.5",
|
||||
"rector/rector": "^1.2",
|
||||
"sebastian/exporter": "^6.3.2",
|
||||
"symfony/error-handler": "^6.1 || ^7.0 || ^8.0",
|
||||
"symfony/var-dumper": "^6.1 || ^7.0 || ^8.0"
|
||||
},
|
||||
@@ -6420,7 +6485,7 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/phpbench/phpbench/issues",
|
||||
"source": "https://github.com/phpbench/phpbench/tree/1.4.3"
|
||||
"source": "https://github.com/phpbench/phpbench/tree/1.5.1"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -6428,15 +6493,15 @@
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2025-11-06T19:07:31+00:00"
|
||||
"time": "2026-03-05T08:18:58+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phpstan/phpstan",
|
||||
"version": "1.12.32",
|
||||
"version": "1.12.33",
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/2770dcdf5078d0b0d53f94317e06affe88419aa8",
|
||||
"reference": "2770dcdf5078d0b0d53f94317e06affe88419aa8",
|
||||
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/37982d6fc7cbb746dda7773530cda557cdf119e1",
|
||||
"reference": "37982d6fc7cbb746dda7773530cda557cdf119e1",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -6481,7 +6546,7 @@
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2025-09-30T10:16:31+00:00"
|
||||
"time": "2026-02-28T20:30:03+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phpunit/php-code-coverage",
|
||||
@@ -8031,16 +8096,16 @@
|
||||
},
|
||||
{
|
||||
"name": "symfony/console",
|
||||
"version": "v8.0.4",
|
||||
"version": "v8.0.7",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/console.git",
|
||||
"reference": "ace03c4cf9805080ff40cbeec69fca180c339a3b"
|
||||
"reference": "15ed9008a4ebe2d6a78e4937f74e0c13ef2e618a"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/console/zipball/ace03c4cf9805080ff40cbeec69fca180c339a3b",
|
||||
"reference": "ace03c4cf9805080ff40cbeec69fca180c339a3b",
|
||||
"url": "https://api.github.com/repos/symfony/console/zipball/15ed9008a4ebe2d6a78e4937f74e0c13ef2e618a",
|
||||
"reference": "15ed9008a4ebe2d6a78e4937f74e0c13ef2e618a",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -8097,7 +8162,7 @@
|
||||
"terminal"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/console/tree/v8.0.4"
|
||||
"source": "https://github.com/symfony/console/tree/v8.0.7"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -8117,20 +8182,20 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2026-01-13T13:06:50+00:00"
|
||||
"time": "2026-03-06T14:06:22+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/filesystem",
|
||||
"version": "v8.0.1",
|
||||
"version": "v8.0.6",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/filesystem.git",
|
||||
"reference": "d937d400b980523dc9ee946bb69972b5e619058d"
|
||||
"reference": "7bf9162d7a0dff98d079b72948508fa48018a770"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/filesystem/zipball/d937d400b980523dc9ee946bb69972b5e619058d",
|
||||
"reference": "d937d400b980523dc9ee946bb69972b5e619058d",
|
||||
"url": "https://api.github.com/repos/symfony/filesystem/zipball/7bf9162d7a0dff98d079b72948508fa48018a770",
|
||||
"reference": "7bf9162d7a0dff98d079b72948508fa48018a770",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -8167,7 +8232,7 @@
|
||||
"description": "Provides basic utilities for the filesystem",
|
||||
"homepage": "https://symfony.com",
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/filesystem/tree/v8.0.1"
|
||||
"source": "https://github.com/symfony/filesystem/tree/v8.0.6"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -8187,20 +8252,20 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2025-12-01T09:13:36+00:00"
|
||||
"time": "2026-02-25T16:59:43+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/finder",
|
||||
"version": "v8.0.5",
|
||||
"version": "v8.0.6",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/finder.git",
|
||||
"reference": "8bd576e97c67d45941365bf824e18dc8538e6eb0"
|
||||
"reference": "441404f09a54de6d1bd6ad219e088cdf4c91f97c"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/finder/zipball/8bd576e97c67d45941365bf824e18dc8538e6eb0",
|
||||
"reference": "8bd576e97c67d45941365bf824e18dc8538e6eb0",
|
||||
"url": "https://api.github.com/repos/symfony/finder/zipball/441404f09a54de6d1bd6ad219e088cdf4c91f97c",
|
||||
"reference": "441404f09a54de6d1bd6ad219e088cdf4c91f97c",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -8235,7 +8300,7 @@
|
||||
"description": "Finds files and directories via an intuitive fluent interface",
|
||||
"homepage": "https://symfony.com",
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/finder/tree/v8.0.5"
|
||||
"source": "https://github.com/symfony/finder/tree/v8.0.6"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -8255,7 +8320,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2026-01-26T15:08:38+00:00"
|
||||
"time": "2026-01-29T09:41:02+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/options-resolver",
|
||||
@@ -8725,16 +8790,16 @@
|
||||
},
|
||||
{
|
||||
"name": "symfony/string",
|
||||
"version": "v8.0.4",
|
||||
"version": "v8.0.6",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/string.git",
|
||||
"reference": "758b372d6882506821ed666032e43020c4f57194"
|
||||
"reference": "6c9e1108041b5dce21a9a4984b531c4923aa9ec4"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/string/zipball/758b372d6882506821ed666032e43020c4f57194",
|
||||
"reference": "758b372d6882506821ed666032e43020c4f57194",
|
||||
"url": "https://api.github.com/repos/symfony/string/zipball/6c9e1108041b5dce21a9a4984b531c4923aa9ec4",
|
||||
"reference": "6c9e1108041b5dce21a9a4984b531c4923aa9ec4",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -8791,7 +8856,7 @@
|
||||
"utf8"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/string/tree/v8.0.4"
|
||||
"source": "https://github.com/symfony/string/tree/v8.0.6"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -8811,7 +8876,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2026-01-12T12:37:40+00:00"
|
||||
"time": "2026-02-09T10:14:57+00:00"
|
||||
},
|
||||
{
|
||||
"name": "textalk/websocket",
|
||||
@@ -9043,7 +9108,7 @@
|
||||
],
|
||||
"aliases": [],
|
||||
"minimum-stability": "dev",
|
||||
"stability-flags": [],
|
||||
"stability-flags": {},
|
||||
"prefer-stable": true,
|
||||
"prefer-lowest": false,
|
||||
"platform": {
|
||||
@@ -9067,5 +9132,5 @@
|
||||
"platform-overrides": {
|
||||
"php": "8.3"
|
||||
},
|
||||
"plugin-api-version": "2.6.0"
|
||||
"plugin-api-version": "2.9.0"
|
||||
}
|
||||
|
||||
@@ -152,7 +152,6 @@ services:
|
||||
- _APP_DB_SCHEMA
|
||||
- _APP_DB_USER
|
||||
- _APP_DB_PASS
|
||||
- _APP_DB_ADAPTER
|
||||
- _APP_SMTP_HOST
|
||||
- _APP_SMTP_PORT
|
||||
- _APP_SMTP_SECURE
|
||||
@@ -305,7 +304,6 @@ services:
|
||||
- _APP_DB_SCHEMA
|
||||
- _APP_DB_USER
|
||||
- _APP_DB_PASS
|
||||
- _APP_DB_ADAPTER
|
||||
- _APP_USAGE_STATS
|
||||
- _APP_LOGGING_CONFIG
|
||||
- _APP_LOGGING_CONFIG_REALTIME
|
||||
@@ -340,7 +338,6 @@ services:
|
||||
- _APP_DB_SCHEMA
|
||||
- _APP_DB_USER
|
||||
- _APP_DB_PASS
|
||||
- _APP_DB_ADAPTER
|
||||
- _APP_LOGGING_CONFIG
|
||||
- _APP_DATABASE_SHARED_TABLES
|
||||
|
||||
@@ -371,7 +368,6 @@ services:
|
||||
- _APP_DB_SCHEMA
|
||||
- _APP_DB_USER
|
||||
- _APP_DB_PASS
|
||||
- _APP_DB_ADAPTER
|
||||
- _APP_REDIS_HOST
|
||||
- _APP_REDIS_PORT
|
||||
- _APP_REDIS_USER
|
||||
@@ -414,7 +410,6 @@ services:
|
||||
- _APP_DB_SCHEMA
|
||||
- _APP_DB_USER
|
||||
- _APP_DB_PASS
|
||||
- _APP_DB_ADAPTER
|
||||
- _APP_STORAGE_DEVICE
|
||||
- _APP_STORAGE_S3_ACCESS_KEY
|
||||
- _APP_STORAGE_S3_SECRET
|
||||
@@ -474,7 +469,6 @@ services:
|
||||
- _APP_DB_SCHEMA
|
||||
- _APP_DB_USER
|
||||
- _APP_DB_PASS
|
||||
- _APP_DB_ADAPTER
|
||||
- _APP_LOGGING_CONFIG
|
||||
- _APP_WORKERS_NUM
|
||||
- _APP_QUEUE_NAME
|
||||
@@ -513,7 +507,6 @@ services:
|
||||
- _APP_DB_SCHEMA
|
||||
- _APP_DB_USER
|
||||
- _APP_DB_PASS
|
||||
- _APP_DB_ADAPTER
|
||||
- _APP_LOGGING_CONFIG
|
||||
- _APP_VCS_GITHUB_APP_NAME
|
||||
- _APP_VCS_GITHUB_PRIVATE_KEY
|
||||
@@ -658,7 +651,6 @@ services:
|
||||
- _APP_DB_SCHEMA
|
||||
- _APP_DB_USER
|
||||
- _APP_DB_PASS
|
||||
- _APP_DB_ADAPTER
|
||||
- _APP_LOGGING_CONFIG
|
||||
- _APP_DATABASE_SHARED_TABLES
|
||||
|
||||
@@ -722,7 +714,6 @@ services:
|
||||
- _APP_DB_SCHEMA
|
||||
- _APP_DB_USER
|
||||
- _APP_DB_PASS
|
||||
- _APP_DB_ADAPTER
|
||||
- _APP_FUNCTIONS_TIMEOUT
|
||||
- _APP_SITES_TIMEOUT
|
||||
- _APP_COMPUTE_BUILD_TIMEOUT
|
||||
@@ -808,7 +799,6 @@ services:
|
||||
- _APP_DB_SCHEMA
|
||||
- _APP_DB_USER
|
||||
- _APP_DB_PASS
|
||||
- _APP_DB_ADAPTER
|
||||
- _APP_LOGGING_CONFIG
|
||||
- _APP_SMS_FROM
|
||||
- _APP_SMS_PROVIDER
|
||||
@@ -875,7 +865,6 @@ services:
|
||||
- _APP_DB_SCHEMA
|
||||
- _APP_DB_USER
|
||||
- _APP_DB_PASS
|
||||
- _APP_DB_ADAPTER
|
||||
- _APP_LOGGING_CONFIG
|
||||
- _APP_MIGRATIONS_FIREBASE_CLIENT_ID
|
||||
- _APP_MIGRATIONS_FIREBASE_CLIENT_SECRET
|
||||
@@ -918,7 +907,6 @@ services:
|
||||
- _APP_DB_SCHEMA
|
||||
- _APP_DB_USER
|
||||
- _APP_DB_PASS
|
||||
- _APP_DB_ADAPTER
|
||||
- _APP_MAINTENANCE_INTERVAL
|
||||
- _APP_MAINTENANCE_RETENTION_EXECUTION
|
||||
- _APP_MAINTENANCE_RETENTION_CACHE
|
||||
@@ -994,7 +982,6 @@ services:
|
||||
- _APP_DB_SCHEMA
|
||||
- _APP_DB_USER
|
||||
- _APP_DB_PASS
|
||||
- _APP_DB_ADAPTER
|
||||
- _APP_REDIS_HOST
|
||||
- _APP_REDIS_PORT
|
||||
- _APP_REDIS_USER
|
||||
@@ -1028,7 +1015,6 @@ services:
|
||||
- _APP_DB_SCHEMA
|
||||
- _APP_DB_USER
|
||||
- _APP_DB_PASS
|
||||
- _APP_DB_ADAPTER
|
||||
- _APP_REDIS_HOST
|
||||
- _APP_REDIS_PORT
|
||||
- _APP_REDIS_USER
|
||||
@@ -1062,7 +1048,6 @@ services:
|
||||
- _APP_DB_SCHEMA
|
||||
- _APP_DB_USER
|
||||
- _APP_DB_PASS
|
||||
- _APP_DB_ADAPTER
|
||||
- _APP_REDIS_HOST
|
||||
- _APP_REDIS_PORT
|
||||
- _APP_REDIS_USER
|
||||
@@ -1100,7 +1085,6 @@ services:
|
||||
- _APP_DB_SCHEMA
|
||||
- _APP_DB_USER
|
||||
- _APP_DB_PASS
|
||||
- _APP_DB_ADAPTER
|
||||
- _APP_DATABASE_SHARED_TABLES
|
||||
|
||||
appwrite-task-scheduler-executions:
|
||||
@@ -1131,7 +1115,6 @@ services:
|
||||
- _APP_DB_SCHEMA
|
||||
- _APP_DB_USER
|
||||
- _APP_DB_PASS
|
||||
- _APP_DB_ADAPTER
|
||||
|
||||
appwrite-task-scheduler-messages:
|
||||
entrypoint: schedule-messages
|
||||
@@ -1161,7 +1144,6 @@ services:
|
||||
- _APP_DB_SCHEMA
|
||||
- _APP_DB_USER
|
||||
- _APP_DB_PASS
|
||||
- _APP_DB_ADAPTER
|
||||
- _APP_DATABASE_SHARED_TABLES
|
||||
|
||||
appwrite-assistant:
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
colors="true"
|
||||
processIsolation="false"
|
||||
stopOnFailure="false"
|
||||
stopOnError="false"
|
||||
cacheDirectory=".phpunit.cache"
|
||||
>
|
||||
<extensions>
|
||||
|
||||
@@ -119,7 +119,7 @@ class Update extends Action
|
||||
|
||||
$authenticator->setAttribute('verified', true);
|
||||
|
||||
$dbForProject->updateDocument('authenticators', $authenticator->getId(), $authenticator);
|
||||
$dbForProject->updateDocument('authenticators', $authenticator->getId(), new Document(['verified' => true]));
|
||||
$dbForProject->purgeCachedDocument('users', $user->getId());
|
||||
|
||||
$factors = $session->getAttribute('factors', []);
|
||||
@@ -127,7 +127,7 @@ class Update extends Action
|
||||
$factors = \array_values(\array_unique($factors));
|
||||
|
||||
$session->setAttribute('factors', $factors);
|
||||
$dbForProject->updateDocument('sessions', $session->getId(), $session);
|
||||
$dbForProject->updateDocument('sessions', $session->getId(), new Document(['factors' => $factors]));
|
||||
|
||||
$queueForEvents->setParam('userId', $user->getId());
|
||||
|
||||
|
||||
@@ -117,7 +117,7 @@ class Update extends Action
|
||||
$mfaRecoveryCodes = \array_diff($mfaRecoveryCodes, [$otp]);
|
||||
$mfaRecoveryCodes = \array_values($mfaRecoveryCodes);
|
||||
$user->setAttribute('mfaRecoveryCodes', $mfaRecoveryCodes);
|
||||
$dbForProject->updateDocument('users', $user->getId(), $user);
|
||||
$dbForProject->updateDocument('users', $user->getId(), new Document(['mfaRecoveryCodes' => $mfaRecoveryCodes]));
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -147,11 +147,13 @@ class Update extends Action
|
||||
$factors[] = $type;
|
||||
$factors = \array_values(\array_unique($factors));
|
||||
|
||||
$mfaUpdatedAt = DateTime::now();
|
||||
|
||||
$session
|
||||
->setAttribute('factors', $factors)
|
||||
->setAttribute('mfaUpdatedAt', DateTime::now());
|
||||
->setAttribute('mfaUpdatedAt', $mfaUpdatedAt);
|
||||
|
||||
$dbForProject->updateDocument('sessions', $session->getId(), $session);
|
||||
$dbForProject->updateDocument('sessions', $session->getId(), new Document(['factors' => $factors, 'mfaUpdatedAt' => $mfaUpdatedAt]));
|
||||
|
||||
$queueForEvents
|
||||
->setParam('userId', $user->getId())
|
||||
|
||||
@@ -93,7 +93,7 @@ class Create extends Action
|
||||
|
||||
$mfaRecoveryCodes = Type::generateBackupCodes();
|
||||
$user->setAttribute('mfaRecoveryCodes', $mfaRecoveryCodes);
|
||||
$dbForProject->updateDocument('users', $user->getId(), $user);
|
||||
$dbForProject->updateDocument('users', $user->getId(), new Document(['mfaRecoveryCodes' => $mfaRecoveryCodes]));
|
||||
|
||||
$queueForEvents->setParam('userId', $user->getId());
|
||||
|
||||
|
||||
@@ -91,7 +91,7 @@ class Update extends Action
|
||||
|
||||
$mfaRecoveryCodes = Type::generateBackupCodes();
|
||||
$user->setAttribute('mfaRecoveryCodes', $mfaRecoveryCodes);
|
||||
$dbForProject->updateDocument('users', $user->getId(), $user);
|
||||
$dbForProject->updateDocument('users', $user->getId(), new Document(['mfaRecoveryCodes' => $mfaRecoveryCodes]));
|
||||
|
||||
$queueForEvents->setParam('userId', $user->getId());
|
||||
|
||||
|
||||
@@ -72,7 +72,7 @@ class Update extends Action
|
||||
): void {
|
||||
$user->setAttribute('mfa', $mfa);
|
||||
|
||||
$user = $dbForProject->updateDocument('users', $user->getId(), $user);
|
||||
$user = $dbForProject->updateDocument('users', $user->getId(), new Document(['mfa' => $mfa]));
|
||||
|
||||
if ($mfa) {
|
||||
$factors = $session->getAttribute('factors', []);
|
||||
@@ -89,7 +89,7 @@ class Update extends Action
|
||||
$factors = \array_values(\array_unique($factors));
|
||||
|
||||
$session->setAttribute('factors', $factors);
|
||||
$dbForProject->updateDocument('sessions', $session->getId(), $session);
|
||||
$dbForProject->updateDocument('sessions', $session->getId(), new Document(['factors' => $factors]));
|
||||
}
|
||||
|
||||
$queueForEvents->setParam('userId', $user->getId());
|
||||
|
||||
@@ -143,7 +143,12 @@ class Base extends Action
|
||||
->setAttribute('latestDeploymentInternalId', $deployment->getSequence())
|
||||
->setAttribute('latestDeploymentCreatedAt', $deployment->getCreatedAt())
|
||||
->setAttribute('latestDeploymentStatus', $deployment->getAttribute('status', ''));
|
||||
$dbForProject->updateDocument('functions', $function->getId(), $function);
|
||||
$dbForProject->updateDocument('functions', $function->getId(), new Document([
|
||||
'latestDeploymentId' => $deployment->getId(),
|
||||
'latestDeploymentInternalId' => $deployment->getSequence(),
|
||||
'latestDeploymentCreatedAt' => $deployment->getCreatedAt(),
|
||||
'latestDeploymentStatus' => $deployment->getAttribute('status', ''),
|
||||
]));
|
||||
|
||||
$queueForBuilds
|
||||
->setType(BUILD_TYPE_DEPLOYMENT)
|
||||
@@ -249,7 +254,12 @@ class Base extends Action
|
||||
->setAttribute('latestDeploymentInternalId', $deployment->getSequence())
|
||||
->setAttribute('latestDeploymentCreatedAt', $deployment->getCreatedAt())
|
||||
->setAttribute('latestDeploymentStatus', $deployment->getAttribute('status', ''));
|
||||
$dbForProject->updateDocument('sites', $site->getId(), $site);
|
||||
$dbForProject->updateDocument('sites', $site->getId(), new Document([
|
||||
'latestDeploymentId' => $deployment->getId(),
|
||||
'latestDeploymentInternalId' => $deployment->getSequence(),
|
||||
'latestDeploymentCreatedAt' => $deployment->getCreatedAt(),
|
||||
'latestDeploymentStatus' => $deployment->getAttribute('status', ''),
|
||||
]));
|
||||
|
||||
$sitesDomain = $platform['sitesDomain'];
|
||||
$domain = ID::unique() . "." . $sitesDomain;
|
||||
|
||||
+5
-2
@@ -12,6 +12,7 @@ use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Response as UtopiaResponse;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Database\Validator\IndexDependency as IndexDependencyValidator;
|
||||
use Utopia\Database\Validator\Key;
|
||||
@@ -98,7 +99,8 @@ class Delete extends Action
|
||||
}
|
||||
|
||||
if ($attribute->getAttribute('status') === 'available') {
|
||||
$attribute = $dbForProject->updateDocument('attributes', $attribute->getId(), $attribute->setAttribute('status', 'deleting'));
|
||||
$attribute->setAttribute('status', 'deleting');
|
||||
$attribute = $dbForProject->updateDocument('attributes', $attribute->getId(), new Document(['status' => 'deleting']));
|
||||
}
|
||||
|
||||
$dbForProject->purgeCachedDocument('database_' . $db->getSequence(), $collectionId);
|
||||
@@ -118,7 +120,8 @@ class Delete extends Action
|
||||
}
|
||||
|
||||
if ($relatedAttribute->getAttribute('status') === 'available') {
|
||||
$dbForProject->updateDocument('attributes', $relatedAttribute->getId(), $relatedAttribute->setAttribute('status', 'deleting'));
|
||||
$relatedAttribute->setAttribute('status', 'deleting');
|
||||
$dbForProject->updateDocument('attributes', $relatedAttribute->getId(), new Document(['status' => 'deleting']));
|
||||
}
|
||||
|
||||
$dbForProject->purgeCachedDocument('database_' . $db->getSequence(), $options['relatedCollection']);
|
||||
|
||||
+3
-1
@@ -12,6 +12,7 @@ use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Response as UtopiaResponse;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Database\Validator\Key;
|
||||
use Utopia\Database\Validator\UID;
|
||||
@@ -96,7 +97,8 @@ class Delete extends Action
|
||||
|
||||
// Only update status if removing available index
|
||||
if ($index->getAttribute('status') === 'available') {
|
||||
$index = $dbForProject->updateDocument('indexes', $index->getId(), $index->setAttribute('status', 'deleting'));
|
||||
$index->setAttribute('status', 'deleting');
|
||||
$index = $dbForProject->updateDocument('indexes', $index->getId(), new Document(['status' => 'deleting']));
|
||||
}
|
||||
|
||||
$dbForProject->purgeCachedDocument('database_' . $db->getSequence(), $collectionId);
|
||||
|
||||
@@ -322,13 +322,19 @@ class Databases extends Action
|
||||
$dbForProject->updateDocument(
|
||||
'attributes',
|
||||
$attribute->getId(),
|
||||
$attribute->setAttribute('status', 'stuck')
|
||||
new Document([
|
||||
'error' => $attribute->getAttribute('error'),
|
||||
'status' => 'stuck',
|
||||
])
|
||||
);
|
||||
if (!$relatedAttribute->isEmpty()) {
|
||||
$dbForProject->updateDocument(
|
||||
'attributes',
|
||||
$relatedAttribute->getId(),
|
||||
$relatedAttribute->setAttribute('status', 'stuck')
|
||||
new Document([
|
||||
'error' => $relatedAttribute->getAttribute('error'),
|
||||
'status' => 'stuck',
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
@@ -382,7 +388,11 @@ class Databases extends Action
|
||||
if ($exists) { // Delete the duplicate if created, else update in db
|
||||
$this->deleteIndex($database, $collection, $index, $project, $dbForPlatform, $dbForProject, $queueForRealtime);
|
||||
} else {
|
||||
$dbForProject->updateDocument('indexes', $index->getId(), $index);
|
||||
$dbForProject->updateDocument('indexes', $index->getId(), new Document([
|
||||
'attributes' => $index->getAttribute('attributes'),
|
||||
'lengths' => $index->getAttribute('lengths'),
|
||||
'orders' => $index->getAttribute('orders'),
|
||||
]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -227,7 +227,9 @@ class Create extends Action
|
||||
|
||||
foreach ($activeDeployments as $activeDeployment) {
|
||||
$activeDeployment->setAttribute('activate', false);
|
||||
$dbForProject->updateDocument('deployments', $activeDeployment->getId(), $activeDeployment);
|
||||
$dbForProject->updateDocument('deployments', $activeDeployment->getId(), new Document([
|
||||
'activate' => false,
|
||||
]));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -255,14 +257,17 @@ class Create extends Action
|
||||
'type' => $type
|
||||
]));
|
||||
|
||||
$function = $function
|
||||
->setAttribute('latestDeploymentId', $deployment->getId())
|
||||
->setAttribute('latestDeploymentInternalId', $deployment->getSequence())
|
||||
->setAttribute('latestDeploymentCreatedAt', $deployment->getCreatedAt())
|
||||
->setAttribute('latestDeploymentStatus', $deployment->getAttribute('status', ''));
|
||||
$dbForProject->updateDocument('functions', $function->getId(), $function);
|
||||
$function = $dbForProject->updateDocument('functions', $function->getId(), new Document([
|
||||
'latestDeploymentId' => $deployment->getId(),
|
||||
'latestDeploymentInternalId' => $deployment->getSequence(),
|
||||
'latestDeploymentCreatedAt' => $deployment->getCreatedAt(),
|
||||
'latestDeploymentStatus' => $deployment->getAttribute('status', ''),
|
||||
]));
|
||||
} else {
|
||||
$deployment = $dbForProject->updateDocument('deployments', $deploymentId, $deployment->setAttribute('sourceSize', $fileSize)->setAttribute('sourceMetadata', $metadata));
|
||||
$deployment = $dbForProject->updateDocument('deployments', $deploymentId, new Document([
|
||||
'sourceSize' => $fileSize,
|
||||
'sourceMetadata' => $metadata,
|
||||
]));
|
||||
}
|
||||
|
||||
// Start the build
|
||||
@@ -295,14 +300,17 @@ class Create extends Action
|
||||
'type' => $type
|
||||
]));
|
||||
|
||||
$function = $function
|
||||
->setAttribute('latestDeploymentId', $deployment->getId())
|
||||
->setAttribute('latestDeploymentInternalId', $deployment->getSequence())
|
||||
->setAttribute('latestDeploymentCreatedAt', $deployment->getCreatedAt())
|
||||
->setAttribute('latestDeploymentStatus', $deployment->getAttribute('status', ''));
|
||||
$dbForProject->updateDocument('functions', $function->getId(), $function);
|
||||
$function = $dbForProject->updateDocument('functions', $function->getId(), new Document([
|
||||
'latestDeploymentId' => $deployment->getId(),
|
||||
'latestDeploymentInternalId' => $deployment->getSequence(),
|
||||
'latestDeploymentCreatedAt' => $deployment->getCreatedAt(),
|
||||
'latestDeploymentStatus' => $deployment->getAttribute('status', ''),
|
||||
]));
|
||||
} else {
|
||||
$deployment = $dbForProject->updateDocument('deployments', $deploymentId, $deployment->setAttribute('sourceChunksUploaded', $chunksUploaded)->setAttribute('sourceMetadata', $metadata));
|
||||
$deployment = $dbForProject->updateDocument('deployments', $deploymentId, new Document([
|
||||
'sourceChunksUploaded' => $chunksUploaded,
|
||||
'sourceMetadata' => $metadata,
|
||||
]));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -107,11 +107,12 @@ class Delete extends Action
|
||||
$function = $dbForProject->updateDocument(
|
||||
'functions',
|
||||
$function->getId(),
|
||||
$function
|
||||
->setAttribute('latestDeploymentCreatedAt', $latestDeployment->isEmpty() ? '' : $latestDeployment->getCreatedAt())
|
||||
->setAttribute('latestDeploymentInternalId', $latestDeployment->isEmpty() ? '' : $latestDeployment->getSequence())
|
||||
->setAttribute('latestDeploymentId', $latestDeployment->isEmpty() ? '' : $latestDeployment->getId())
|
||||
->setAttribute('latestDeploymentStatus', $latestDeployment->isEmpty() ? '' : $latestDeployment->getAttribute('status', ''))
|
||||
new Document([
|
||||
'latestDeploymentCreatedAt' => $latestDeployment->isEmpty() ? '' : $latestDeployment->getCreatedAt(),
|
||||
'latestDeploymentInternalId' => $latestDeployment->isEmpty() ? '' : $latestDeployment->getSequence(),
|
||||
'latestDeploymentId' => $latestDeployment->isEmpty() ? '' : $latestDeployment->getId(),
|
||||
'latestDeploymentStatus' => $latestDeployment->isEmpty() ? '' : $latestDeployment->getAttribute('status', ''),
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Helpers\ID;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Platform\Action;
|
||||
@@ -119,7 +120,12 @@ class Create extends Action
|
||||
->setAttribute('latestDeploymentInternalId', $deployment->getSequence())
|
||||
->setAttribute('latestDeploymentCreatedAt', $deployment->getCreatedAt())
|
||||
->setAttribute('latestDeploymentStatus', $deployment->getAttribute('status', ''));
|
||||
$dbForProject->updateDocument('functions', $function->getId(), $function);
|
||||
$dbForProject->updateDocument('functions', $function->getId(), new Document([
|
||||
'latestDeploymentId' => $function->getAttribute('latestDeploymentId'),
|
||||
'latestDeploymentInternalId' => $function->getAttribute('latestDeploymentInternalId'),
|
||||
'latestDeploymentCreatedAt' => $function->getAttribute('latestDeploymentCreatedAt'),
|
||||
'latestDeploymentStatus' => $function->getAttribute('latestDeploymentStatus'),
|
||||
]));
|
||||
|
||||
$queueForBuilds
|
||||
->setType(BUILD_TYPE_DEPLOYMENT)
|
||||
|
||||
@@ -91,7 +91,7 @@ class Update extends Action
|
||||
$endTime = new \DateTime('now');
|
||||
$duration = $endTime->getTimestamp() - $startTime->getTimestamp();
|
||||
|
||||
$deployment = $dbForProject->updateDocument('deployments', $deployment->getId(), $deployment->setAttributes([
|
||||
$deployment = $dbForProject->updateDocument('deployments', $deployment->getId(), new Document([
|
||||
'buildEndedAt' => DateTime::now(),
|
||||
'buildDuration' => $duration,
|
||||
'status' => 'canceled'
|
||||
@@ -99,7 +99,9 @@ class Update extends Action
|
||||
|
||||
if ($deployment->getSequence() === $function->getAttribute('latestDeploymentInternalId', '')) {
|
||||
$function = $function->setAttribute('latestDeploymentStatus', $deployment->getAttribute('status', ''));
|
||||
$dbForProject->updateDocument('functions', $function->getId(), $function);
|
||||
$dbForProject->updateDocument('functions', $function->getId(), new Document([
|
||||
'latestDeploymentStatus' => $function->getAttribute('latestDeploymentStatus'),
|
||||
]));
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
@@ -174,7 +174,12 @@ class Create extends Base
|
||||
->setAttribute('latestDeploymentInternalId', $deployment->getSequence())
|
||||
->setAttribute('latestDeploymentCreatedAt', $deployment->getCreatedAt())
|
||||
->setAttribute('latestDeploymentStatus', $deployment->getAttribute('status', ''));
|
||||
$dbForProject->updateDocument('functions', $function->getId(), $function);
|
||||
$dbForProject->updateDocument('functions', $function->getId(), new Document([
|
||||
'latestDeploymentId' => $function->getAttribute('latestDeploymentId'),
|
||||
'latestDeploymentInternalId' => $function->getAttribute('latestDeploymentInternalId'),
|
||||
'latestDeploymentCreatedAt' => $function->getAttribute('latestDeploymentCreatedAt'),
|
||||
'latestDeploymentStatus' => $function->getAttribute('latestDeploymentStatus'),
|
||||
]));
|
||||
|
||||
|
||||
$this->updateEmptyManualRule($project, $function, $deployment, $dbForPlatform, $authorization);
|
||||
|
||||
@@ -180,7 +180,8 @@ class Create extends Base
|
||||
|
||||
$version = $function->getAttribute('version', 'v2');
|
||||
$runtimes = Config::getParam($version === 'v2' ? 'runtimes-v2' : 'runtimes', []);
|
||||
$spec = Config::getParam('specifications')[$function->getAttribute('specification', APP_COMPUTE_SPECIFICATION_DEFAULT)];
|
||||
|
||||
$spec = Config::getParam('specifications')[$function->getAttribute('runtimeSpecification', APP_COMPUTE_SPECIFICATION_DEFAULT)];
|
||||
|
||||
$runtime = (isset($runtimes[$function->getAttribute('runtime', '')])) ? $runtimes[$function->getAttribute('runtime', '')] : null;
|
||||
|
||||
@@ -420,6 +421,11 @@ class Create extends Base
|
||||
try {
|
||||
$version = $function->getAttribute('version', 'v2');
|
||||
$command = $runtime['startCommand'];
|
||||
|
||||
if (!empty($deployment->getAttribute('startCommand', ''))) {
|
||||
$command = 'cd /usr/local/server/src/function/ && ' . $deployment->getAttribute('startCommand', '');
|
||||
}
|
||||
|
||||
$source = $deployment->getAttribute('buildPath', '');
|
||||
$extension = str_ends_with($source, '.tar') ? 'tar' : 'tar.gz';
|
||||
$command = $version === 'v2' ? '' : "cp /tmp/code.$extension /mnt/code/code.$extension && nohup helpers/start.sh \"$command\"";
|
||||
|
||||
@@ -12,6 +12,7 @@ use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\DateTime;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Database\Validator\UID;
|
||||
@@ -119,7 +120,10 @@ class Delete extends Base
|
||||
->setAttribute('resourceUpdatedAt', DateTime::now())
|
||||
->setAttribute('active', false);
|
||||
|
||||
$authorization->skip(fn () => $dbForPlatform->updateDocument('schedules', $schedule->getId(), $schedule));
|
||||
$authorization->skip(fn () => $dbForPlatform->updateDocument('schedules', $schedule->getId(), new Document([
|
||||
'resourceUpdatedAt' => $schedule->getAttribute('resourceUpdatedAt'),
|
||||
'active' => $schedule->getAttribute('active'),
|
||||
])));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -93,16 +93,23 @@ class Create extends Base
|
||||
->param('providerBranch', '', new Text(128, 0), 'Production branch for the repo linked to the function.', true)
|
||||
->param('providerSilentMode', false, new Boolean(), 'Is the VCS (Version Control System) connection in silent mode for the repo linked to the function? In silent mode, comments will not be made on commits and pull requests.', true)
|
||||
->param('providerRootDirectory', '', new Text(128, 0), 'Path to function code in the linked repo.', true)
|
||||
->param('specification', fn (array $plan) => $this->getDefaultSpecification($plan), fn (array $plan) => new Specification(
|
||||
->param('buildSpecification', fn (array $plan) => $this->getDefaultSpecification($plan), fn (array $plan) => new Specification(
|
||||
$plan,
|
||||
Config::getParam('specifications', []),
|
||||
System::getEnv('_APP_COMPUTE_CPUS', 0),
|
||||
System::getEnv('_APP_COMPUTE_MEMORY', 0)
|
||||
), 'Runtime specification for the function and builds.', true, ['plan'])
|
||||
), 'Build specification for the function deployments.', true, ['plan'])
|
||||
->param('runtimeSpecification', fn (array $plan) => $this->getDefaultSpecification($plan), fn (array $plan) => new Specification(
|
||||
$plan,
|
||||
Config::getParam('specifications', []),
|
||||
System::getEnv('_APP_COMPUTE_CPUS', 0),
|
||||
System::getEnv('_APP_COMPUTE_MEMORY', 0)
|
||||
), 'Runtime specification for the function executions.', true, ['plan'])
|
||||
->param('templateRepository', '', new Text(128, 0), 'Repository name of the template.', true, deprecated: true)
|
||||
->param('templateOwner', '', new Text(128, 0), 'The name of the owner of the template.', true, deprecated: true)
|
||||
->param('templateRootDirectory', '', new Text(128, 0), 'Path to function code in the template repo.', true, deprecated: true)
|
||||
->param('templateVersion', '', new Text(128, 0), 'Version (tag) for the repo linked to the function template.', true, deprecated: true)
|
||||
->param('deploymentRetention', 0, new Range(0, APP_COMPUTE_DEPLOYMENT_MAX_RETENTION), 'Days to keep non-active deployments before deletion. Value 0 means all deployments will be kept.', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('timelimit')
|
||||
@@ -138,11 +145,13 @@ class Create extends Base
|
||||
string $providerBranch,
|
||||
bool $providerSilentMode,
|
||||
string $providerRootDirectory,
|
||||
string $specification,
|
||||
string $buildSpecification,
|
||||
string $runtimeSpecification,
|
||||
string $templateRepository,
|
||||
string $templateOwner,
|
||||
string $templateRootDirectory,
|
||||
string $templateVersion,
|
||||
int $deploymentRetention,
|
||||
Response $response,
|
||||
Database $dbForProject,
|
||||
callable $timelimit,
|
||||
@@ -215,6 +224,7 @@ class Create extends Base
|
||||
'logging' => $logging,
|
||||
'name' => $name,
|
||||
'runtime' => $runtime,
|
||||
'deploymentRetention' => $deploymentRetention,
|
||||
'deploymentInternalId' => '',
|
||||
'deploymentId' => '',
|
||||
'events' => $events,
|
||||
@@ -225,7 +235,6 @@ class Create extends Base
|
||||
'entrypoint' => $entrypoint,
|
||||
'commands' => $commands,
|
||||
'scopes' => $scopes,
|
||||
'deploymentRetention' => 0,
|
||||
'startCommand' => '',
|
||||
'search' => implode(' ', [$functionId, $name, $runtime]),
|
||||
'version' => 'v5',
|
||||
@@ -237,9 +246,8 @@ class Create extends Base
|
||||
'providerBranch' => $providerBranch,
|
||||
'providerRootDirectory' => $providerRootDirectory,
|
||||
'providerSilentMode' => $providerSilentMode,
|
||||
'specification' => $specification,
|
||||
'buildSpecification' => $specification,
|
||||
'runtimeSpecification' => $specification,
|
||||
'buildSpecification' => $buildSpecification,
|
||||
'runtimeSpecification' => $runtimeSpecification,
|
||||
]));
|
||||
} catch (DuplicateException) {
|
||||
throw new Exception(Exception::FUNCTION_ALREADY_EXISTS);
|
||||
@@ -283,7 +291,12 @@ class Create extends Base
|
||||
$function->setAttribute('repositoryInternalId', $repository->getSequence());
|
||||
}
|
||||
|
||||
$function = $dbForProject->updateDocument('functions', $function->getId(), $function);
|
||||
$function = $dbForProject->updateDocument('functions', $function->getId(), new Document([
|
||||
'scheduleId' => $function->getAttribute('scheduleId'),
|
||||
'scheduleInternalId' => $function->getAttribute('scheduleInternalId'),
|
||||
'repositoryId' => $function->getAttribute('repositoryId'),
|
||||
'repositoryInternalId' => $function->getAttribute('repositoryInternalId'),
|
||||
]));
|
||||
|
||||
// Backwards compatibility with 1.6 behaviour
|
||||
$requestFormat = $request->getHeader('x-appwrite-response-format', System::getEnv('_APP_SYSTEM_RESPONSE_FORMAT', ''));
|
||||
@@ -321,12 +334,12 @@ class Create extends Base
|
||||
referenceType: 'branch'
|
||||
);
|
||||
|
||||
$function = $function
|
||||
->setAttribute('latestDeploymentId', $deployment->getId())
|
||||
->setAttribute('latestDeploymentInternalId', $deployment->getSequence())
|
||||
->setAttribute('latestDeploymentCreatedAt', $deployment->getCreatedAt())
|
||||
->setAttribute('latestDeploymentStatus', $deployment->getAttribute('status', ''));
|
||||
$dbForProject->updateDocument('functions', $function->getId(), $function);
|
||||
$function = $dbForProject->updateDocument('functions', $function->getId(), new Document([
|
||||
'latestDeploymentId' => $deployment->getId(),
|
||||
'latestDeploymentInternalId' => $deployment->getSequence(),
|
||||
'latestDeploymentCreatedAt' => $deployment->getCreatedAt(),
|
||||
'latestDeploymentStatus' => $deployment->getAttribute('status', ''),
|
||||
]));
|
||||
} elseif (!$template->isEmpty()) {
|
||||
// Deploy non-VCS from template
|
||||
$deploymentId = ID::unique();
|
||||
@@ -347,12 +360,12 @@ class Create extends Base
|
||||
'activate' => true,
|
||||
]));
|
||||
|
||||
$function = $function
|
||||
->setAttribute('latestDeploymentId', $deployment->getId())
|
||||
->setAttribute('latestDeploymentInternalId', $deployment->getSequence())
|
||||
->setAttribute('latestDeploymentCreatedAt', $deployment->getCreatedAt())
|
||||
->setAttribute('latestDeploymentStatus', $deployment->getAttribute('status', ''));
|
||||
$dbForProject->updateDocument('functions', $function->getId(), $function);
|
||||
$function = $dbForProject->updateDocument('functions', $function->getId(), new Document([
|
||||
'latestDeploymentId' => $deployment->getId(),
|
||||
'latestDeploymentInternalId' => $deployment->getSequence(),
|
||||
'latestDeploymentCreatedAt' => $deployment->getCreatedAt(),
|
||||
'latestDeploymentStatus' => $deployment->getAttribute('status', ''),
|
||||
]));
|
||||
|
||||
$queueForBuilds
|
||||
->setType(BUILD_TYPE_DEPLOYMENT)
|
||||
|
||||
@@ -13,6 +13,7 @@ use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\DateTime;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Platform\Action;
|
||||
@@ -90,7 +91,10 @@ class Delete extends Base
|
||||
$schedule
|
||||
->setAttribute('resourceUpdatedAt', DateTime::now())
|
||||
->setAttribute('active', false);
|
||||
$authorization->skip(fn () => $dbForPlatform->updateDocument('schedules', $schedule->getId(), $schedule));
|
||||
$authorization->skip(fn () => $dbForPlatform->updateDocument('schedules', $schedule->getId(), new Document([
|
||||
'resourceUpdatedAt' => $schedule->getAttribute('resourceUpdatedAt'),
|
||||
'active' => $schedule->getAttribute('active'),
|
||||
])));
|
||||
}
|
||||
|
||||
$queueForDeletes
|
||||
|
||||
@@ -103,7 +103,11 @@ class Update extends Base
|
||||
->setAttribute('resourceUpdatedAt', DateTime::now())
|
||||
->setAttribute('schedule', $function->getAttribute('schedule'))
|
||||
->setAttribute('active', !empty($function->getAttribute('schedule')) && !empty($function->getAttribute('deploymentId')));
|
||||
$authorization->skip(fn () => $dbForPlatform->updateDocument('schedules', $schedule->getId(), $schedule));
|
||||
$authorization->skip(fn () => $dbForPlatform->updateDocument('schedules', $schedule->getId(), new Document([
|
||||
'resourceUpdatedAt' => $schedule->getAttribute('resourceUpdatedAt'),
|
||||
'schedule' => $schedule->getAttribute('schedule'),
|
||||
'active' => $schedule->getAttribute('active'),
|
||||
])));
|
||||
|
||||
$queries = [
|
||||
Query::equal('trigger', ['manual']),
|
||||
@@ -119,7 +123,10 @@ class Update extends Base
|
||||
->setAttribute('deploymentId', $deployment->getId())
|
||||
->setAttribute('deploymentInternalId', $deployment->getSequence());
|
||||
|
||||
$authorization->skip(fn () => $dbForPlatform->updateDocument('rules', $rule->getId(), $rule));
|
||||
$authorization->skip(fn () => $dbForPlatform->updateDocument('rules', $rule->getId(), new Document([
|
||||
'deploymentId' => $rule->getAttribute('deploymentId'),
|
||||
'deploymentInternalId' => $rule->getAttribute('deploymentInternalId'),
|
||||
])));
|
||||
}, $queries));
|
||||
|
||||
$queueForEvents
|
||||
|
||||
@@ -87,12 +87,19 @@ class Update extends Base
|
||||
->param('providerBranch', '', new Text(128, 0), 'Production branch for the repo linked to the function', true)
|
||||
->param('providerSilentMode', false, new Boolean(), 'Is the VCS (Version Control System) connection in silent mode for the repo linked to the function? In silent mode, comments will not be made on commits and pull requests.', true)
|
||||
->param('providerRootDirectory', '', new Text(128, 0), 'Path to function code in the linked repo.', true)
|
||||
->param('specification', fn (array $plan) => $this->getDefaultSpecification($plan), fn (array $plan) => new Specification(
|
||||
->param('buildSpecification', fn (array $plan) => $this->getDefaultSpecification($plan), fn (array $plan) => new Specification(
|
||||
$plan,
|
||||
Config::getParam('specifications', []),
|
||||
System::getEnv('_APP_COMPUTE_CPUS', 0),
|
||||
System::getEnv('_APP_COMPUTE_MEMORY', 0)
|
||||
), 'Runtime specification for the function and builds.', true, ['plan'])
|
||||
), 'Build specification for the function deployments.', true, ['plan'])
|
||||
->param('runtimeSpecification', fn (array $plan) => $this->getDefaultSpecification($plan), fn (array $plan) => new Specification(
|
||||
$plan,
|
||||
Config::getParam('specifications', []),
|
||||
System::getEnv('_APP_COMPUTE_CPUS', 0),
|
||||
System::getEnv('_APP_COMPUTE_MEMORY', 0)
|
||||
), 'Runtime specification for the function executions.', true, ['plan'])
|
||||
->param('deploymentRetention', 0, new Range(0, APP_COMPUTE_DEPLOYMENT_MAX_RETENTION), 'Days to keep non-active deployments before deletion. Value 0 means all deployments will be kept.', true)
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
@@ -124,7 +131,9 @@ class Update extends Base
|
||||
string $providerBranch,
|
||||
bool $providerSilentMode,
|
||||
string $providerRootDirectory,
|
||||
string $specification,
|
||||
string $buildSpecification,
|
||||
string $runtimeSpecification,
|
||||
int $deploymentRetention,
|
||||
Request $request,
|
||||
Response $response,
|
||||
Database $dbForProject,
|
||||
@@ -209,7 +218,7 @@ class Update extends Base
|
||||
'resourceId' => $function->getId(),
|
||||
'resourceInternalId' => $function->getSequence(),
|
||||
'resourceType' => 'function',
|
||||
'providerPullRequestIds' => []
|
||||
'providerPullRequestIds' => [],
|
||||
]));
|
||||
|
||||
$repositoryId = $repository->getId();
|
||||
@@ -229,13 +238,22 @@ class Update extends Base
|
||||
}
|
||||
|
||||
// Enforce Cold Start if spec limits change.
|
||||
if ($function->getAttribute('specification') !== $specification && !empty($function->getAttribute('deploymentId'))) {
|
||||
try {
|
||||
$executor->deleteRuntime($project->getId(), $function->getAttribute('deploymentId'));
|
||||
} catch (\Throwable $th) {
|
||||
// Don't throw if the deployment doesn't exist
|
||||
if ($th->getCode() !== 404) {
|
||||
throw $th;
|
||||
if (!empty($function->getAttribute('deploymentId'))) {
|
||||
$specsChanged = false;
|
||||
if ($function->getAttribute('runtimeSpecification', '') !== $runtimeSpecification) {
|
||||
$specsChanged = true;
|
||||
} elseif ($function->getAttribute('buildSpecification', '') !== $buildSpecification) {
|
||||
$specsChanged = true;
|
||||
}
|
||||
|
||||
if ($specsChanged) {
|
||||
try {
|
||||
$executor->deleteRuntime($project->getId(), $function->getAttribute('deploymentId'));
|
||||
} catch (\Throwable $th) {
|
||||
// Don't throw if the deployment doesn't exist
|
||||
if ($th->getCode() !== 404) {
|
||||
throw $th;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -253,8 +271,7 @@ class Update extends Base
|
||||
'entrypoint' => $entrypoint,
|
||||
'commands' => $commands,
|
||||
'scopes' => $scopes,
|
||||
'deploymentRetention' => 0,
|
||||
'startCommand' => '',
|
||||
'deploymentRetention' => $deploymentRetention,
|
||||
'installationId' => $installation->getId(),
|
||||
'installationInternalId' => $installation->getSequence(),
|
||||
'providerRepositoryId' => $providerRepositoryId,
|
||||
@@ -263,9 +280,8 @@ class Update extends Base
|
||||
'providerBranch' => $providerBranch,
|
||||
'providerRootDirectory' => $providerRootDirectory,
|
||||
'providerSilentMode' => $providerSilentMode,
|
||||
'specification' => $specification,
|
||||
'buildSpecification' => $specification,
|
||||
'runtimeSpecification' => $specification,
|
||||
'buildSpecification' => $buildSpecification,
|
||||
'runtimeSpecification' => $runtimeSpecification,
|
||||
'search' => implode(' ', [$functionId, $name, $runtime]),
|
||||
])));
|
||||
|
||||
|
||||
@@ -105,7 +105,8 @@ class Create extends Base
|
||||
throw new Exception(Exception::VARIABLE_ALREADY_EXISTS);
|
||||
}
|
||||
|
||||
$dbForProject->updateDocument('functions', $function->getId(), $function->setAttribute('live', false));
|
||||
$function->setAttribute('live', false);
|
||||
$dbForProject->updateDocument('functions', $function->getId(), new Document(['live' => false]));
|
||||
|
||||
// Inform scheduler to pull the latest changes
|
||||
$schedule = $dbForPlatform->getDocument('schedules', $function->getAttribute('scheduleId'));
|
||||
@@ -113,7 +114,11 @@ class Create extends Base
|
||||
->setAttribute('resourceUpdatedAt', DateTime::now())
|
||||
->setAttribute('schedule', $function->getAttribute('schedule'))
|
||||
->setAttribute('active', !empty($function->getAttribute('schedule')) && !empty($function->getAttribute('deploymentId')));
|
||||
$authorization->skip(fn () => $dbForPlatform->updateDocument('schedules', $schedule->getId(), $schedule));
|
||||
$authorization->skip(fn () => $dbForPlatform->updateDocument('schedules', $schedule->getId(), new Document([
|
||||
'resourceUpdatedAt' => $schedule->getAttribute('resourceUpdatedAt'),
|
||||
'schedule' => $schedule->getAttribute('schedule'),
|
||||
'active' => $schedule->getAttribute('active'),
|
||||
])));
|
||||
|
||||
$response
|
||||
->setStatusCode(Response::STATUS_CODE_CREATED)
|
||||
|
||||
@@ -11,6 +11,7 @@ use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\DateTime;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Platform\Action;
|
||||
@@ -86,7 +87,8 @@ class Delete extends Base
|
||||
|
||||
$dbForProject->deleteDocument('variables', $variable->getId());
|
||||
|
||||
$dbForProject->updateDocument('functions', $function->getId(), $function->setAttribute('live', false));
|
||||
$function->setAttribute('live', false);
|
||||
$dbForProject->updateDocument('functions', $function->getId(), new Document(['live' => false]));
|
||||
|
||||
// Inform scheduler to pull the latest changes
|
||||
$schedule = $dbForPlatform->getDocument('schedules', $function->getAttribute('scheduleId'));
|
||||
@@ -94,7 +96,11 @@ class Delete extends Base
|
||||
->setAttribute('resourceUpdatedAt', DateTime::now())
|
||||
->setAttribute('schedule', $function->getAttribute('schedule'))
|
||||
->setAttribute('active', !empty($function->getAttribute('schedule')) && !empty($function->getAttribute('deploymentId')));
|
||||
$authorization->skip(fn () => $dbForPlatform->updateDocument('schedules', $schedule->getId(), $schedule));
|
||||
$authorization->skip(fn () => $dbForPlatform->updateDocument('schedules', $schedule->getId(), new Document([
|
||||
'resourceUpdatedAt' => $schedule->getAttribute('resourceUpdatedAt'),
|
||||
'schedule' => $schedule->getAttribute('schedule'),
|
||||
'active' => $schedule->getAttribute('active'),
|
||||
])));
|
||||
|
||||
$response->noContent();
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\DateTime;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Exception\Duplicate as DuplicateException;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Database\Validator\UID;
|
||||
@@ -99,12 +100,18 @@ class Update extends Base
|
||||
->setAttribute('search', implode(' ', [$variableId, $function->getId(), $key, 'function']));
|
||||
|
||||
try {
|
||||
$dbForProject->updateDocument('variables', $variable->getId(), $variable);
|
||||
$dbForProject->updateDocument('variables', $variable->getId(), new Document([
|
||||
'key' => $key,
|
||||
'value' => $value ?? $variable->getAttribute('value'),
|
||||
'secret' => $secret ?? $variable->getAttribute('secret'),
|
||||
'search' => implode(' ', [$variableId, $function->getId(), $key, 'function']),
|
||||
]));
|
||||
} catch (DuplicateException $th) {
|
||||
throw new Exception(Exception::VARIABLE_ALREADY_EXISTS);
|
||||
}
|
||||
|
||||
$dbForProject->updateDocument('functions', $function->getId(), $function->setAttribute('live', false));
|
||||
$function->setAttribute('live', false);
|
||||
$dbForProject->updateDocument('functions', $function->getId(), new Document(['live' => false]));
|
||||
|
||||
// Inform scheduler to pull the latest changes
|
||||
$schedule = $dbForPlatform->getDocument('schedules', $function->getAttribute('scheduleId'));
|
||||
@@ -112,7 +119,11 @@ class Update extends Base
|
||||
->setAttribute('resourceUpdatedAt', DateTime::now())
|
||||
->setAttribute('schedule', $function->getAttribute('schedule'))
|
||||
->setAttribute('active', !empty($function->getAttribute('schedule')) && !empty($function->getAttribute('deploymentId')));
|
||||
$authorization->skip(fn () => $dbForPlatform->updateDocument('schedules', $schedule->getId(), $schedule));
|
||||
$authorization->skip(fn () => $dbForPlatform->updateDocument('schedules', $schedule->getId(), new Document([
|
||||
'resourceUpdatedAt' => $schedule->getAttribute('resourceUpdatedAt'),
|
||||
'schedule' => $schedule->getAttribute('schedule'),
|
||||
'active' => $schedule->getAttribute('active'),
|
||||
])));
|
||||
|
||||
$response->dynamic($variable, Response::MODEL_VARIABLE);
|
||||
}
|
||||
|
||||
@@ -255,7 +255,7 @@ class Builds extends Action
|
||||
$version = $this->getVersion($resource);
|
||||
$runtime = $this->getRuntime($resource, $version);
|
||||
|
||||
$spec = Config::getParam('specifications')[$resource->getAttribute('specification', APP_COMPUTE_SPECIFICATION_DEFAULT)];
|
||||
$spec = Config::getParam('specifications')[$resource->getAttribute('buildSpecification', APP_COMPUTE_SPECIFICATION_DEFAULT)];
|
||||
|
||||
if ($resource->getCollection() === 'functions' && \is_null($runtime)) {
|
||||
throw new \Exception('Runtime "' . $resource->getAttribute('runtime', '') . '" is not supported');
|
||||
@@ -279,7 +279,10 @@ class Builds extends Action
|
||||
|
||||
$deployment->setAttribute('buildStartedAt', $startTime);
|
||||
$deployment->setAttribute('status', 'processing');
|
||||
$deployment = $dbForProject->updateDocument('deployments', $deployment->getId(), $deployment);
|
||||
$deployment = $dbForProject->updateDocument('deployments', $deployment->getId(), new Document([
|
||||
'buildStartedAt' => $startTime,
|
||||
'status' => 'processing',
|
||||
]));
|
||||
|
||||
if ($deployment->getSequence() === $resource->getAttribute('latestDeploymentInternalId', '')) {
|
||||
$resource = $dbForProject->updateDocument($resource->getCollection(), $resource->getId(), new Document(['latestDeploymentStatus' => $deployment->getAttribute('status', '')]));
|
||||
@@ -366,7 +369,11 @@ class Builds extends Action
|
||||
->setAttribute('sourcePath', $source)
|
||||
->setAttribute('sourceSize', $directorySize)
|
||||
->setAttribute('totalSize', $directorySize);
|
||||
$deployment = $dbForProject->updateDocument('deployments', $deployment->getId(), $deployment);
|
||||
$deployment = $dbForProject->updateDocument('deployments', $deployment->getId(), new Document([
|
||||
'sourcePath' => $deployment->getAttribute('sourcePath'),
|
||||
'sourceSize' => $deployment->getAttribute('sourceSize'),
|
||||
'totalSize' => $deployment->getAttribute('totalSize'),
|
||||
]));
|
||||
|
||||
$queueForRealtime
|
||||
->setPayload($deployment->getArrayCopy())
|
||||
@@ -480,7 +487,13 @@ class Builds extends Action
|
||||
$deployment->setAttribute('providerCommitAuthor', APP_VCS_GITHUB_USERNAME);
|
||||
$deployment->setAttribute('providerCommitMessage', "Create '" . $resource->getAttribute('name', '') . "' function");
|
||||
$deployment->setAttribute('providerCommitUrl', "https://github.com/$cloneOwner/$cloneRepository/commit/$providerCommitHash");
|
||||
$deployment = $dbForProject->updateDocument('deployments', $deployment->getId(), $deployment);
|
||||
$deployment = $dbForProject->updateDocument('deployments', $deployment->getId(), new Document([
|
||||
'providerCommitHash' => $deployment->getAttribute('providerCommitHash'),
|
||||
'providerCommitAuthorUrl' => $deployment->getAttribute('providerCommitAuthorUrl'),
|
||||
'providerCommitAuthor' => $deployment->getAttribute('providerCommitAuthor'),
|
||||
'providerCommitMessage' => $deployment->getAttribute('providerCommitMessage'),
|
||||
'providerCommitUrl' => $deployment->getAttribute('providerCommitUrl'),
|
||||
]));
|
||||
|
||||
$queueForRealtime
|
||||
->setPayload($deployment->getArrayCopy())
|
||||
@@ -528,7 +541,11 @@ class Builds extends Action
|
||||
->setAttribute('sourcePath', $source)
|
||||
->setAttribute('sourceSize', $directorySize)
|
||||
->setAttribute('totalSize', $directorySize);
|
||||
$deployment = $dbForProject->updateDocument('deployments', $deployment->getId(), $deployment);
|
||||
$deployment = $dbForProject->updateDocument('deployments', $deployment->getId(), new Document([
|
||||
'sourcePath' => $deployment->getAttribute('sourcePath'),
|
||||
'sourceSize' => $deployment->getAttribute('sourceSize'),
|
||||
'totalSize' => $deployment->getAttribute('totalSize'),
|
||||
]));
|
||||
|
||||
$queueForRealtime
|
||||
->setPayload($deployment->getArrayCopy())
|
||||
@@ -543,7 +560,9 @@ class Builds extends Action
|
||||
|
||||
/** Request the executor to build the code... */
|
||||
$deployment->setAttribute('status', 'building');
|
||||
$deployment = $dbForProject->updateDocument('deployments', $deployment->getId(), $deployment);
|
||||
$deployment = $dbForProject->updateDocument('deployments', $deployment->getId(), new Document([
|
||||
'status' => 'building',
|
||||
]));
|
||||
|
||||
if ($deployment->getSequence() === $resource->getAttribute('latestDeploymentInternalId', '')) {
|
||||
$resource = $dbForProject->updateDocument($resource->getCollection(), $resource->getId(), new Document(['latestDeploymentStatus' => $deployment->getAttribute('status', '')]));
|
||||
@@ -819,7 +838,9 @@ class Builds extends Action
|
||||
|
||||
if ($affected) {
|
||||
$deployment = $deployment->setAttribute('buildLogs', $currentLogs);
|
||||
$deployment = $dbForProject->updateDocument('deployments', $deployment->getId(), $deployment);
|
||||
$deployment = $dbForProject->updateDocument('deployments', $deployment->getId(), new Document([
|
||||
'buildLogs' => $currentLogs,
|
||||
]));
|
||||
|
||||
$queueForRealtime
|
||||
->setPayload($deployment->getArrayCopy())
|
||||
@@ -905,7 +926,14 @@ class Builds extends Action
|
||||
}
|
||||
}
|
||||
|
||||
$deployment = $dbForProject->updateDocument('deployments', $deployment->getId(), $deployment);
|
||||
$deployment = $dbForProject->updateDocument('deployments', $deployment->getId(), new Document([
|
||||
'buildPath' => $deployment->getAttribute('buildPath'),
|
||||
'buildSize' => $deployment->getAttribute('buildSize'),
|
||||
'totalSize' => $deployment->getAttribute('totalSize'),
|
||||
'buildLogs' => $deployment->getAttribute('buildLogs'),
|
||||
'adapter' => $deployment->getAttribute('adapter'),
|
||||
'fallbackFile' => $deployment->getAttribute('fallbackFile'),
|
||||
]));
|
||||
$queueForRealtime
|
||||
->setPayload($deployment->getArrayCopy())
|
||||
->trigger();
|
||||
@@ -916,12 +944,15 @@ class Builds extends Action
|
||||
|
||||
$logs = $deployment->getAttribute('buildLogs', '');
|
||||
$date = \date('H:i:s');
|
||||
$logs .= "[90m[$date] [90m[[0mappwrite[90m][32m Deployment finished. [0m\n";
|
||||
$logs .= "\033[90m[$date] \033[90m[\033[0mappwrite\033[90m]\033[32m Deployment finished. \033[0m\n";
|
||||
$deployment->setAttribute('buildLogs', $logs);
|
||||
|
||||
/** Update the status */
|
||||
$deployment->setAttribute('status', 'ready');
|
||||
$deployment = $dbForProject->updateDocument('deployments', $deploymentId, $deployment);
|
||||
$deployment = $dbForProject->updateDocument('deployments', $deploymentId, new Document([
|
||||
'buildLogs' => $deployment->getAttribute('buildLogs'),
|
||||
'status' => 'ready',
|
||||
]));
|
||||
|
||||
Console::log('Status marked as ready');
|
||||
|
||||
@@ -1109,7 +1140,11 @@ class Builds extends Action
|
||||
->setAttribute('resourceUpdatedAt', DateTime::now())
|
||||
->setAttribute('schedule', $resource->getAttribute('schedule'))
|
||||
->setAttribute('active', !empty($resource->getAttribute('schedule')) && !empty($resource->getAttribute('deploymentId')));
|
||||
$dbForPlatform->updateDocument('schedules', $schedule->getId(), $schedule);
|
||||
$dbForPlatform->updateDocument('schedules', $schedule->getId(), new Document([
|
||||
'resourceUpdatedAt' => $schedule->getAttribute('resourceUpdatedAt'),
|
||||
'schedule' => $schedule->getAttribute('schedule'),
|
||||
'active' => $schedule->getAttribute('active'),
|
||||
]));
|
||||
}
|
||||
|
||||
/** Screenshot site */
|
||||
@@ -1160,7 +1195,12 @@ class Builds extends Action
|
||||
$deployment->setAttribute('status', 'failed');
|
||||
|
||||
$deployment->setAttribute('buildLogs', $message);
|
||||
$deployment = $dbForProject->updateDocument('deployments', $deploymentId, $deployment);
|
||||
$deployment = $dbForProject->updateDocument('deployments', $deploymentId, new Document([
|
||||
'buildEndedAt' => $deployment->getAttribute('buildEndedAt'),
|
||||
'buildDuration' => $deployment->getAttribute('buildDuration'),
|
||||
'status' => 'failed',
|
||||
'buildLogs' => $message,
|
||||
]));
|
||||
|
||||
if ($deployment->getSequence() === $resource->getAttribute('latestDeploymentInternalId', '')) {
|
||||
$resource = $dbForProject->updateDocument($resource->getCollection(), $resource->getId(), new Document(['latestDeploymentStatus' => $deployment->getAttribute('status', '')]));
|
||||
@@ -1189,7 +1229,7 @@ class Builds extends Action
|
||||
|
||||
protected function sendUsage(Document $resource, Document $deployment, Document $project, StatsUsage $queue): void
|
||||
{
|
||||
$spec = Config::getParam('specifications')[$resource->getAttribute('specification', APP_COMPUTE_SPECIFICATION_DEFAULT)];
|
||||
$spec = Config::getParam('specifications')[$resource->getAttribute('buildSpecification', APP_COMPUTE_SPECIFICATION_DEFAULT)];
|
||||
|
||||
switch ($deployment->getAttribute('status')) {
|
||||
case 'ready':
|
||||
@@ -1467,7 +1507,9 @@ class Builds extends Action
|
||||
$logs .= "[90m[$date] [90m[[0mappwrite[90m][33m Git action failed. Deployment will continue. [0m\n";
|
||||
|
||||
$deployment->setAttribute('buildLogs', $logs);
|
||||
$deployment = $dbForProject->updateDocument('deployments', $deployment->getId(), $deployment);
|
||||
$deployment = $dbForProject->updateDocument('deployments', $deployment->getId(), new Document([
|
||||
'buildLogs' => $deployment->getAttribute('buildLogs'),
|
||||
]));
|
||||
|
||||
$queueForRealtime
|
||||
->setPayload($deployment->getArrayCopy())
|
||||
@@ -1483,10 +1525,12 @@ class Builds extends Action
|
||||
|
||||
$logs = $deployment->getAttribute('buildLogs', '');
|
||||
$date = \date('H:i:s');
|
||||
$logs .= "[90m[$date] [90m[[0mappwrite[90m][33m Build has been canceled. [0m\n";
|
||||
$logs .= "\033[90m[$date] \033[90m[\033[0mappwrite\033[90m]\033[33m Build has been canceled. \033[0m\n";
|
||||
|
||||
$deployment->setAttribute('buildLogs', $logs);
|
||||
$deployment = $dbForProject->updateDocument('deployments', $deployment->getId(), $deployment);
|
||||
$deployment = $dbForProject->updateDocument('deployments', $deployment->getId(), new Document([
|
||||
'buildLogs' => $deployment->getAttribute('buildLogs'),
|
||||
]));
|
||||
|
||||
$queueForRealtime
|
||||
->setPayload($deployment->getArrayCopy())
|
||||
|
||||
@@ -9,6 +9,7 @@ use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Validator\Datetime as DatetimeValidator;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Platform\Action;
|
||||
@@ -73,7 +74,7 @@ class Update extends Action
|
||||
->setAttribute('name', $name)
|
||||
->setAttribute('expire', $expire);
|
||||
|
||||
$dbForPlatform->updateDocument('devKeys', $key->getId(), $key);
|
||||
$dbForPlatform->updateDocument('devKeys', $key->getId(), new Document(['name' => $name, 'expire' => $expire]));
|
||||
|
||||
$dbForPlatform->purgeCachedDocument('projects', $project->getId());
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Database\Validator\Queries\Projects;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Platform\Scope\HTTP;
|
||||
use Utopia\Validator;
|
||||
@@ -77,9 +78,9 @@ class Update extends Action
|
||||
throw new Exception(Exception::PROJECT_NOT_FOUND);
|
||||
}
|
||||
|
||||
$project->setAttribute('labels', (array) \array_values(\array_unique($labels)));
|
||||
$labels = (array) \array_values(\array_unique($labels));
|
||||
|
||||
$project = $dbForPlatform->updateDocument('projects', $project->getId(), $project);
|
||||
$project = $dbForPlatform->updateDocument('projects', $project->getId(), new Document(['labels' => $labels]));
|
||||
|
||||
$response->dynamic($project, Response::MODEL_PROJECT);
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Database\Validator\Queries\Projects;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Platform\Scope\HTTP;
|
||||
@@ -72,34 +73,31 @@ class Update extends Action
|
||||
|
||||
$permissions = $this->getPermissions($teamId, $projectId);
|
||||
|
||||
$project
|
||||
->setAttribute('teamId', $teamId)
|
||||
->setAttribute('teamInternalId', $team->getSequence())
|
||||
->setAttribute('$permissions', $permissions);
|
||||
$project = $dbForPlatform->updateDocument('projects', $project->getId(), $project);
|
||||
$project = $dbForPlatform->updateDocument('projects', $project->getId(), new Document([
|
||||
'teamId' => $teamId,
|
||||
'teamInternalId' => $team->getSequence(),
|
||||
'$permissions' => $permissions,
|
||||
]));
|
||||
|
||||
$installations = $dbForPlatform->find('installations', [
|
||||
Query::equal('projectInternalId', [$project->getSequence()]),
|
||||
]);
|
||||
foreach ($installations as $installation) {
|
||||
$installation->setAttribute('$permissions', $permissions);
|
||||
$dbForPlatform->updateDocument('installations', $installation->getId(), $installation);
|
||||
$dbForPlatform->updateDocument('installations', $installation->getId(), new Document(['$permissions' => $permissions]));
|
||||
}
|
||||
|
||||
$repositories = $dbForPlatform->find('repositories', [
|
||||
Query::equal('projectInternalId', [$project->getSequence()]),
|
||||
]);
|
||||
foreach ($repositories as $repository) {
|
||||
$repository->setAttribute('$permissions', $permissions);
|
||||
$dbForPlatform->updateDocument('repositories', $repository->getId(), $repository);
|
||||
$dbForPlatform->updateDocument('repositories', $repository->getId(), new Document(['$permissions' => $permissions]));
|
||||
}
|
||||
|
||||
$vcsComments = $dbForPlatform->find('vcsComments', [
|
||||
Query::equal('projectInternalId', [$project->getSequence()]),
|
||||
]);
|
||||
foreach ($vcsComments as $vcsComment) {
|
||||
$vcsComment->setAttribute('$permissions', $permissions);
|
||||
$dbForPlatform->updateDocument('vcsComments', $vcsComment->getId(), $vcsComment);
|
||||
$dbForPlatform->updateDocument('vcsComments', $vcsComment->getId(), new Document(['$permissions' => $permissions]));
|
||||
}
|
||||
|
||||
$response->dynamic($project, Response::MODEL_PROJECT);
|
||||
|
||||
@@ -237,7 +237,7 @@ class Create extends Action
|
||||
|
||||
foreach ($activeDeployments as $activeDeployment) {
|
||||
$activeDeployment->setAttribute('activate', false);
|
||||
$dbForProject->updateDocument('deployments', $activeDeployment->getId(), $activeDeployment);
|
||||
$dbForProject->updateDocument('deployments', $activeDeployment->getId(), new Document(['activate' => false]));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -272,7 +272,12 @@ class Create extends Action
|
||||
->setAttribute('latestDeploymentInternalId', $deployment->getSequence())
|
||||
->setAttribute('latestDeploymentCreatedAt', $deployment->getCreatedAt())
|
||||
->setAttribute('latestDeploymentStatus', $deployment->getAttribute('status', ''));
|
||||
$dbForProject->updateDocument('sites', $site->getId(), $site);
|
||||
$dbForProject->updateDocument('sites', $site->getId(), new Document([
|
||||
'latestDeploymentId' => $deployment->getId(),
|
||||
'latestDeploymentInternalId' => $deployment->getSequence(),
|
||||
'latestDeploymentCreatedAt' => $deployment->getCreatedAt(),
|
||||
'latestDeploymentStatus' => $deployment->getAttribute('status', ''),
|
||||
]));
|
||||
|
||||
$sitesDomain = $platform['sitesDomain'];
|
||||
$domain = ID::unique() . "." . $sitesDomain;
|
||||
@@ -302,7 +307,10 @@ class Create extends Action
|
||||
]))
|
||||
);
|
||||
} else {
|
||||
$deployment = $dbForProject->updateDocument('deployments', $deploymentId, $deployment->setAttribute('sourceSize', $fileSize)->setAttribute('sourceMetadata', $metadata));
|
||||
$deployment = $dbForProject->updateDocument('deployments', $deploymentId, new Document([
|
||||
'sourceSize' => $fileSize,
|
||||
'sourceMetadata' => $metadata,
|
||||
]));
|
||||
}
|
||||
|
||||
// Start the build
|
||||
@@ -342,7 +350,12 @@ class Create extends Action
|
||||
->setAttribute('latestDeploymentInternalId', $deployment->getSequence())
|
||||
->setAttribute('latestDeploymentCreatedAt', $deployment->getCreatedAt())
|
||||
->setAttribute('latestDeploymentStatus', $deployment->getAttribute('status', ''));
|
||||
$dbForProject->updateDocument('sites', $site->getId(), $site);
|
||||
$dbForProject->updateDocument('sites', $site->getId(), new Document([
|
||||
'latestDeploymentId' => $site->getAttribute('latestDeploymentId'),
|
||||
'latestDeploymentInternalId' => $site->getAttribute('latestDeploymentInternalId'),
|
||||
'latestDeploymentCreatedAt' => $site->getAttribute('latestDeploymentCreatedAt'),
|
||||
'latestDeploymentStatus' => $site->getAttribute('latestDeploymentStatus'),
|
||||
]));
|
||||
|
||||
$sitesDomain = $platform['sitesDomain'];
|
||||
$domain = ID::unique() . "." . $sitesDomain;
|
||||
@@ -368,7 +381,10 @@ class Create extends Action
|
||||
]))
|
||||
);
|
||||
} else {
|
||||
$deployment = $dbForProject->updateDocument('deployments', $deploymentId, $deployment->setAttribute('sourceChunksUploaded', $chunksUploaded)->setAttribute('sourceMetadata', $metadata));
|
||||
$deployment = $dbForProject->updateDocument('deployments', $deploymentId, new Document([
|
||||
'sourceChunksUploaded' => $chunksUploaded,
|
||||
'sourceMetadata' => $metadata,
|
||||
]));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -107,11 +107,12 @@ class Delete extends Action
|
||||
$site = $dbForProject->updateDocument(
|
||||
'sites',
|
||||
$site->getId(),
|
||||
$site
|
||||
->setAttribute('latestDeploymentCreatedAt', $latestDeployment->isEmpty() ? '' : $latestDeployment->getCreatedAt())
|
||||
->setAttribute('latestDeploymentInternalId', $latestDeployment->isEmpty() ? '' : $latestDeployment->getSequence())
|
||||
->setAttribute('latestDeploymentId', $latestDeployment->isEmpty() ? '' : $latestDeployment->getId())
|
||||
->setAttribute('latestDeploymentStatus', $latestDeployment->isEmpty() ? '' : $latestDeployment->getAttribute('status', ''))
|
||||
new Document([
|
||||
'latestDeploymentCreatedAt' => $latestDeployment->isEmpty() ? '' : $latestDeployment->getCreatedAt(),
|
||||
'latestDeploymentInternalId' => $latestDeployment->isEmpty() ? '' : $latestDeployment->getSequence(),
|
||||
'latestDeploymentId' => $latestDeployment->isEmpty() ? '' : $latestDeployment->getId(),
|
||||
'latestDeploymentStatus' => $latestDeployment->isEmpty() ? '' : $latestDeployment->getAttribute('status', ''),
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -142,7 +142,12 @@ class Create extends Action
|
||||
->setAttribute('latestDeploymentInternalId', $deployment->getSequence())
|
||||
->setAttribute('latestDeploymentCreatedAt', $deployment->getCreatedAt())
|
||||
->setAttribute('latestDeploymentStatus', $deployment->getAttribute('status', ''));
|
||||
$dbForProject->updateDocument('sites', $site->getId(), $site);
|
||||
$dbForProject->updateDocument('sites', $site->getId(), new Document([
|
||||
'latestDeploymentId' => $site->getAttribute('latestDeploymentId'),
|
||||
'latestDeploymentInternalId' => $site->getAttribute('latestDeploymentInternalId'),
|
||||
'latestDeploymentCreatedAt' => $site->getAttribute('latestDeploymentCreatedAt'),
|
||||
'latestDeploymentStatus' => $site->getAttribute('latestDeploymentStatus'),
|
||||
]));
|
||||
|
||||
// Preview deployments for sites
|
||||
$sitesDomain = $platform['sitesDomain'];
|
||||
|
||||
@@ -89,7 +89,7 @@ class Update extends Action
|
||||
$endTime = new \DateTime('now');
|
||||
$duration = $endTime->getTimestamp() - $startTime->getTimestamp();
|
||||
|
||||
$deployment = $dbForProject->updateDocument('deployments', $deployment->getId(), $deployment->setAttributes([
|
||||
$deployment = $dbForProject->updateDocument('deployments', $deployment->getId(), new Document([
|
||||
'buildEndedAt' => DateTime::now(),
|
||||
'buildDuration' => $duration,
|
||||
'status' => 'canceled'
|
||||
@@ -97,7 +97,9 @@ class Update extends Action
|
||||
|
||||
if ($deployment->getSequence() === $site->getAttribute('latestDeploymentInternalId', '')) {
|
||||
$site = $site->setAttribute('latestDeploymentStatus', $deployment->getAttribute('status', ''));
|
||||
$dbForProject->updateDocument('sites', $site->getId(), $site);
|
||||
$dbForProject->updateDocument('sites', $site->getId(), new Document([
|
||||
'latestDeploymentStatus' => $site->getAttribute('latestDeploymentStatus'),
|
||||
]));
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
@@ -187,7 +187,12 @@ class Create extends Base
|
||||
->setAttribute('latestDeploymentInternalId', $deployment->getSequence())
|
||||
->setAttribute('latestDeploymentCreatedAt', $deployment->getCreatedAt())
|
||||
->setAttribute('latestDeploymentStatus', $deployment->getAttribute('status', ''));
|
||||
$dbForProject->updateDocument('sites', $site->getId(), $site);
|
||||
$dbForProject->updateDocument('sites', $site->getId(), new Document([
|
||||
'latestDeploymentId' => $site->getAttribute('latestDeploymentId'),
|
||||
'latestDeploymentInternalId' => $site->getAttribute('latestDeploymentInternalId'),
|
||||
'latestDeploymentCreatedAt' => $site->getAttribute('latestDeploymentCreatedAt'),
|
||||
'latestDeploymentStatus' => $site->getAttribute('latestDeploymentStatus'),
|
||||
]));
|
||||
|
||||
$sitesDomain = $platform['sitesDomain'];
|
||||
$domain = ID::unique() . "." . $sitesDomain;
|
||||
|
||||
@@ -68,6 +68,7 @@ class Create extends Base
|
||||
->param('timeout', 30, new Range(1, (int) System::getEnv('_APP_SITES_TIMEOUT', 30)), 'Maximum request time in seconds.', true)
|
||||
->param('installCommand', '', new Text(8192, 0), 'Install Command.', true)
|
||||
->param('buildCommand', '', new Text(8192, 0), 'Build Command.', true)
|
||||
->param('startCommand', '', new Text(8192, 0), 'Custom start command. Leave empty to use default.', true)
|
||||
->param('outputDirectory', '', new Text(8192, 0), 'Output Directory for site.', true)
|
||||
->param('buildRuntime', '', new WhiteList(array_keys(Config::getParam('runtimes')), true), 'Runtime to use during build step.')
|
||||
->param('adapter', '', new WhiteList(['static', 'ssr']), 'Framework adapter defining rendering strategy. Allowed values are: static, ssr', true)
|
||||
@@ -77,12 +78,19 @@ class Create extends Base
|
||||
->param('providerBranch', '', new Text(128, 0), 'Production branch for the repo linked to the site.', true)
|
||||
->param('providerSilentMode', false, new Boolean(), 'Is the VCS (Version Control System) connection in silent mode for the repo linked to the site? In silent mode, comments will not be made on commits and pull requests.', true)
|
||||
->param('providerRootDirectory', '', new Text(128, 0), 'Path to site code in the linked repo.', true)
|
||||
->param('specification', fn (array $plan) => $this->getDefaultSpecification($plan), fn (array $plan) => new Specification(
|
||||
->param('buildSpecification', fn (array $plan) => $this->getDefaultSpecification($plan), fn (array $plan) => new Specification(
|
||||
$plan,
|
||||
Config::getParam('specifications', []),
|
||||
System::getEnv('_APP_COMPUTE_CPUS', 0),
|
||||
System::getEnv('_APP_COMPUTE_MEMORY', 0)
|
||||
), 'Framework specification for the site and builds.', true, ['plan'])
|
||||
), 'Build specification for the site deployments.', true, ['plan'])
|
||||
->param('runtimeSpecification', fn (array $plan) => $this->getDefaultSpecification($plan), fn (array $plan) => new Specification(
|
||||
$plan,
|
||||
Config::getParam('specifications', []),
|
||||
System::getEnv('_APP_COMPUTE_CPUS', 0),
|
||||
System::getEnv('_APP_COMPUTE_MEMORY', 0)
|
||||
), 'Runtime specification for the SSR executions.', true, ['plan'])
|
||||
->param('deploymentRetention', 0, new Range(0, APP_COMPUTE_DEPLOYMENT_MAX_RETENTION), 'Days to keep non-active deployments before deletion. Value 0 means all deployments will be kept.', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('project')
|
||||
@@ -100,6 +108,7 @@ class Create extends Base
|
||||
int $timeout,
|
||||
string $installCommand,
|
||||
string $buildCommand,
|
||||
string $startCommand,
|
||||
string $outputDirectory,
|
||||
string $buildRuntime,
|
||||
string $adapter,
|
||||
@@ -109,7 +118,9 @@ class Create extends Base
|
||||
string $providerBranch,
|
||||
bool $providerSilentMode,
|
||||
string $providerRootDirectory,
|
||||
string $specification,
|
||||
string $buildSpecification,
|
||||
string $runtimeSpecification,
|
||||
int $deploymentRetention,
|
||||
Response $response,
|
||||
Database $dbForProject,
|
||||
Document $project,
|
||||
@@ -144,13 +155,13 @@ class Create extends Base
|
||||
'logging' => $logging,
|
||||
'name' => $name,
|
||||
'framework' => $framework,
|
||||
'deploymentRetention' => $deploymentRetention,
|
||||
'deploymentInternalId' => '',
|
||||
'deploymentId' => '',
|
||||
'timeout' => $timeout,
|
||||
'installCommand' => $installCommand,
|
||||
'buildCommand' => $buildCommand,
|
||||
'deploymentRetention' => 0,
|
||||
'startCommand' => '',
|
||||
'startCommand' => $startCommand,
|
||||
'outputDirectory' => $outputDirectory,
|
||||
'search' => implode(' ', [$siteId, $name, $framework]),
|
||||
'fallbackFile' => $fallbackFile,
|
||||
@@ -162,9 +173,8 @@ class Create extends Base
|
||||
'providerBranch' => $providerBranch,
|
||||
'providerRootDirectory' => $providerRootDirectory,
|
||||
'providerSilentMode' => $providerSilentMode,
|
||||
'specification' => $specification,
|
||||
'buildSpecification' => $specification,
|
||||
'runtimeSpecification' => $specification,
|
||||
'buildSpecification' => $buildSpecification,
|
||||
'runtimeSpecification' => $runtimeSpecification,
|
||||
'buildRuntime' => $buildRuntime,
|
||||
'adapter' => $adapter,
|
||||
]);
|
||||
@@ -193,9 +203,12 @@ class Create extends Base
|
||||
$repository = $dbForPlatform->createDocument('repositories', $repository);
|
||||
$site->setAttribute('repositoryId', $repository->getId());
|
||||
$site->setAttribute('repositoryInternalId', $repository->getSequence());
|
||||
}
|
||||
|
||||
$site = $dbForProject->updateDocument('sites', $site->getId(), $site);
|
||||
$site = $dbForProject->updateDocument('sites', $site->getId(), new Document([
|
||||
'repositoryId' => $repository->getId(),
|
||||
'repositoryInternalId' => $repository->getSequence(),
|
||||
]));
|
||||
}
|
||||
|
||||
$queueForEvents->setParam('siteId', $site->getId());
|
||||
|
||||
|
||||
@@ -111,7 +111,10 @@ class Update extends Base
|
||||
->setAttribute('deploymentId', $deployment->getId())
|
||||
->setAttribute('deploymentInternalId', $deployment->getSequence());
|
||||
|
||||
$authorization->skip(fn () => $dbForPlatform->updateDocument('rules', $rule->getId(), $rule));
|
||||
$authorization->skip(fn () => $dbForPlatform->updateDocument('rules', $rule->getId(), new Document([
|
||||
'deploymentId' => $rule->getAttribute('deploymentId'),
|
||||
'deploymentInternalId' => $rule->getAttribute('deploymentInternalId'),
|
||||
])));
|
||||
}, $queries));
|
||||
|
||||
$queueForEvents
|
||||
|
||||
@@ -71,6 +71,7 @@ class Update extends Base
|
||||
->param('timeout', 30, new Range(1, (int) System::getEnv('_APP_SITES_TIMEOUT', 30)), 'Maximum request time in seconds.', true)
|
||||
->param('installCommand', '', new Text(8192, 0), 'Install Command.', true)
|
||||
->param('buildCommand', '', new Text(8192, 0), 'Build Command.', true)
|
||||
->param('startCommand', '', new Text(8192, 0), 'Custom start command. Leave empty to use default.', true)
|
||||
->param('outputDirectory', '', new Text(8192, 0), 'Output Directory for site.', true)
|
||||
->param('buildRuntime', '', new WhiteList(array_keys(Config::getParam('runtimes')), true), 'Runtime to use during build step.', true)
|
||||
->param('adapter', '', new WhiteList(['static', 'ssr']), 'Framework adapter defining rendering strategy. Allowed values are: static, ssr', true)
|
||||
@@ -80,12 +81,19 @@ class Update extends Base
|
||||
->param('providerBranch', '', new Text(128, 0), 'Production branch for the repo linked to the site.', true)
|
||||
->param('providerSilentMode', false, new Boolean(), 'Is the VCS (Version Control System) connection in silent mode for the repo linked to the site? In silent mode, comments will not be made on commits and pull requests.', true)
|
||||
->param('providerRootDirectory', '', new Text(128, 0), 'Path to site code in the linked repo.', true)
|
||||
->param('specification', fn (array $plan) => $this->getDefaultSpecification($plan), fn (array $plan) => new Specification(
|
||||
->param('buildSpecification', fn (array $plan) => $this->getDefaultSpecification($plan), fn (array $plan) => new Specification(
|
||||
$plan,
|
||||
Config::getParam('specifications', []),
|
||||
System::getEnv('_APP_COMPUTE_CPUS', 0),
|
||||
System::getEnv('_APP_COMPUTE_MEMORY', 0)
|
||||
), 'Framework specification for the site and builds.', true, ['plan'])
|
||||
), 'Build specification for the site deployments.', true, ['plan'])
|
||||
->param('runtimeSpecification', fn (array $plan) => $this->getDefaultSpecification($plan), fn (array $plan) => new Specification(
|
||||
$plan,
|
||||
Config::getParam('specifications', []),
|
||||
System::getEnv('_APP_COMPUTE_CPUS', 0),
|
||||
System::getEnv('_APP_COMPUTE_MEMORY', 0)
|
||||
), 'Runtime specification for the SSR executions.', true, ['plan'])
|
||||
->param('deploymentRetention', 0, new Range(0, APP_COMPUTE_DEPLOYMENT_MAX_RETENTION), 'Days to keep non-active deployments before deletion. Value 0 means all deployments will be kept.', true)
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
@@ -107,6 +115,7 @@ class Update extends Base
|
||||
int $timeout,
|
||||
string $installCommand,
|
||||
string $buildCommand,
|
||||
string $startCommand,
|
||||
string $outputDirectory,
|
||||
string $buildRuntime,
|
||||
string $adapter,
|
||||
@@ -116,7 +125,9 @@ class Update extends Base
|
||||
string $providerBranch,
|
||||
bool $providerSilentMode,
|
||||
string $providerRootDirectory,
|
||||
string $specification,
|
||||
string $buildSpecification,
|
||||
string $runtimeSpecification,
|
||||
int $deploymentRetention,
|
||||
Request $request,
|
||||
Response $response,
|
||||
Database $dbForProject,
|
||||
@@ -216,6 +227,7 @@ class Update extends Base
|
||||
$site->getAttribute('name') !== $name ||
|
||||
$site->getAttribute('buildCommand') !== $buildCommand ||
|
||||
$site->getAttribute('installCommand') !== $installCommand ||
|
||||
$site->getAttribute('startCommand') !== $startCommand ||
|
||||
$site->getAttribute('outputDirectory') !== $outputDirectory ||
|
||||
$site->getAttribute('providerRootDirectory') !== $providerRootDirectory ||
|
||||
$site->getAttribute('framework') !== $framework
|
||||
@@ -223,14 +235,22 @@ class Update extends Base
|
||||
$live = false;
|
||||
}
|
||||
|
||||
// Enforce Cold Start if spec limits change.
|
||||
if ($site->getAttribute('specification') !== $specification && !empty($site->getAttribute('deploymentId'))) {
|
||||
try {
|
||||
$executor->deleteRuntime($project->getId(), $site->getAttribute('deploymentId'));
|
||||
} catch (\Throwable $th) {
|
||||
// Don't throw if the deployment doesn't exist
|
||||
if ($th->getCode() !== 404) {
|
||||
throw $th;
|
||||
if (!empty($site->getAttribute('deploymentId'))) {
|
||||
$specsChanged = false;
|
||||
if ($site->getAttribute('runtimeSpecification', '') !== $runtimeSpecification) {
|
||||
$specsChanged = true;
|
||||
} elseif ($site->getAttribute('buildSpecification', '') !== $buildSpecification) {
|
||||
$specsChanged = true;
|
||||
}
|
||||
|
||||
if ($specsChanged) {
|
||||
try {
|
||||
$executor->deleteRuntime($project->getId(), $site->getAttribute('deploymentId'));
|
||||
} catch (\Throwable $th) {
|
||||
// Don't throw if the deployment doesn't exist
|
||||
if ($th->getCode() !== 404) {
|
||||
throw $th;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -242,10 +262,10 @@ class Update extends Base
|
||||
'logging' => $logging,
|
||||
'live' => $live,
|
||||
'timeout' => $timeout,
|
||||
'deploymentRetention' => $deploymentRetention,
|
||||
'installCommand' => $installCommand,
|
||||
'buildCommand' => $buildCommand,
|
||||
'deploymentRetention' => 0,
|
||||
'startCommand' => '',
|
||||
'startCommand' => $startCommand,
|
||||
'outputDirectory' => $outputDirectory,
|
||||
'installationId' => $installation->getId(),
|
||||
'installationInternalId' => $installation->getSequence(),
|
||||
@@ -255,9 +275,8 @@ class Update extends Base
|
||||
'providerBranch' => $providerBranch,
|
||||
'providerRootDirectory' => $providerRootDirectory,
|
||||
'providerSilentMode' => $providerSilentMode,
|
||||
'specification' => $specification,
|
||||
'buildSpecification' => $specification,
|
||||
'runtimeSpecification' => $specification,
|
||||
'buildSpecification' => $buildSpecification,
|
||||
'runtimeSpecification' => $runtimeSpecification,
|
||||
'search' => implode(' ', [$siteId, $name, $framework]),
|
||||
'buildRuntime' => $buildRuntime,
|
||||
'adapter' => $adapter,
|
||||
|
||||
@@ -92,7 +92,9 @@ class Create extends Base
|
||||
throw new Exception(Exception::VARIABLE_ALREADY_EXISTS);
|
||||
}
|
||||
|
||||
$dbForProject->updateDocument('sites', $site->getId(), $site->setAttribute('live', false));
|
||||
$dbForProject->updateDocument('sites', $site->getId(), new Document([
|
||||
'live' => false,
|
||||
]));
|
||||
|
||||
$response
|
||||
->setStatusCode(Response::STATUS_CODE_CREATED)
|
||||
|
||||
@@ -10,6 +10,7 @@ use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Platform\Action;
|
||||
use Utopia\Platform\Scope\HTTP;
|
||||
@@ -76,7 +77,9 @@ class Delete extends Base
|
||||
|
||||
$dbForProject->deleteDocument('variables', $variable->getId());
|
||||
|
||||
$dbForProject->updateDocument('sites', $site->getId(), $site->setAttribute('live', false));
|
||||
$dbForProject->updateDocument('sites', $site->getId(), new Document([
|
||||
'live' => false,
|
||||
]));
|
||||
|
||||
$response->noContent();
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Exception\Duplicate as DuplicateException;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Platform\Action;
|
||||
@@ -93,12 +94,19 @@ class Update extends Base
|
||||
->setAttribute('search', implode(' ', [$variableId, $site->getId(), $key, 'site']));
|
||||
|
||||
try {
|
||||
$dbForProject->updateDocument('variables', $variable->getId(), $variable);
|
||||
$dbForProject->updateDocument('variables', $variable->getId(), new Document([
|
||||
'key' => $variable->getAttribute('key'),
|
||||
'value' => $variable->getAttribute('value'),
|
||||
'secret' => $variable->getAttribute('secret'),
|
||||
'search' => $variable->getAttribute('search'),
|
||||
]));
|
||||
} catch (DuplicateException $th) {
|
||||
throw new Exception(Exception::VARIABLE_ALREADY_EXISTS);
|
||||
}
|
||||
|
||||
$dbForProject->updateDocument('sites', $site->getId(), $site->setAttribute('live', false));
|
||||
$dbForProject->updateDocument('sites', $site->getId(), new Document([
|
||||
'live' => false,
|
||||
]));
|
||||
|
||||
$response->dynamic($variable, Response::MODEL_VARIABLE);
|
||||
}
|
||||
|
||||
@@ -266,17 +266,22 @@ class Create extends Action
|
||||
$authorization->skip(fn () => $dbForProject->increaseDocumentAttribute('teams', $team->getId(), 'total', 1));
|
||||
}
|
||||
} elseif ($membership->getAttribute('confirm') === false) {
|
||||
$membership->setAttribute('secret', $proofForToken->hash($secret));
|
||||
$membership->setAttribute('invited', DateTime::now());
|
||||
$secretHash = $proofForToken->hash($secret);
|
||||
$invitedTime = DateTime::now();
|
||||
|
||||
if ($isPrivilegedUser || $isAppUser) {
|
||||
$membership->setAttribute('joined', DateTime::now());
|
||||
$membership->setAttribute('confirm', true);
|
||||
$membership = $authorization->skip(fn () => $dbForProject->updateDocument('memberships', $membership->getId(), new Document([
|
||||
'secret' => $secretHash,
|
||||
'invited' => $invitedTime,
|
||||
'joined' => DateTime::now(),
|
||||
'confirm' => true
|
||||
])));
|
||||
} else {
|
||||
$membership = $dbForProject->updateDocument('memberships', $membership->getId(), new Document([
|
||||
'secret' => $secretHash,
|
||||
'invited' => $invitedTime
|
||||
]));
|
||||
}
|
||||
|
||||
$membership = ($isPrivilegedUser || $isAppUser) ?
|
||||
$authorization->skip(fn () => $dbForProject->updateDocument('memberships', $membership->getId(), $membership)) :
|
||||
$dbForProject->updateDocument('memberships', $membership->getId(), $membership);
|
||||
} else {
|
||||
throw new Exception(Exception::MEMBERSHIP_ALREADY_CONFIRMED);
|
||||
}
|
||||
|
||||
@@ -131,7 +131,10 @@ class Delete extends Action
|
||||
if (!$membership->isEmpty()) {
|
||||
$team->setAttribute('userId', $membership->getAttribute('userId'));
|
||||
$team->setAttribute('userInternalId', $membership->getAttribute('userInternalId'));
|
||||
$dbForProject->updateDocument('teams', $team->getId(), $team);
|
||||
$dbForProject->updateDocument('teams', $team->getId(), new Document([
|
||||
'userId' => $membership->getAttribute('userId'),
|
||||
'userInternalId' => $membership->getAttribute('userInternalId'),
|
||||
]));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -189,7 +189,7 @@ class Update extends Action
|
||||
;
|
||||
}
|
||||
|
||||
$membership = $dbForProject->updateDocument('memberships', $membership->getId(), $membership);
|
||||
$membership = $dbForProject->updateDocument('memberships', $membership->getId(), new Document(['joined' => $membership->getAttribute('joined'), 'confirm' => true]));
|
||||
|
||||
$dbForProject->purgeCachedDocument('users', $user->getId());
|
||||
|
||||
|
||||
@@ -116,7 +116,7 @@ class Update extends Action
|
||||
* Update the roles
|
||||
*/
|
||||
$membership->setAttribute('roles', $roles);
|
||||
$membership = $dbForProject->updateDocument('memberships', $membership->getId(), $membership);
|
||||
$membership = $dbForProject->updateDocument('memberships', $membership->getId(), new Document(['roles' => $roles]));
|
||||
|
||||
/**
|
||||
* Replace membership on profile
|
||||
|
||||
@@ -10,6 +10,7 @@ use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Platform\Scope\HTTP;
|
||||
use Utopia\Validator\Text;
|
||||
@@ -67,7 +68,7 @@ class Update extends Action
|
||||
->setAttribute('name', $name)
|
||||
->setAttribute('search', implode(' ', [$teamId, $name]));
|
||||
|
||||
$team = $dbForProject->updateDocument('teams', $team->getId(), $team);
|
||||
$team = $dbForProject->updateDocument('teams', $team->getId(), new Document(['name' => $name, 'search' => implode(' ', [$teamId, $name])]));
|
||||
|
||||
$queueForEvents->setParam('teamId', $team->getId());
|
||||
|
||||
|
||||
+1
-2
@@ -98,9 +98,8 @@ class Update extends Action
|
||||
}
|
||||
|
||||
$providerPullRequestIds = \array_unique(\array_merge($repository->getAttribute('providerPullRequestIds', []), [$providerPullRequestId]));
|
||||
$repository = $repository->setAttribute('providerPullRequestIds', $providerPullRequestIds);
|
||||
|
||||
$repository = $authorization->skip(fn () => $dbForPlatform->updateDocument('repositories', $repository->getId(), $repository));
|
||||
$repository = $authorization->skip(fn () => $dbForPlatform->updateDocument('repositories', $repository->getId(), new Document(['providerPullRequestIds' => $providerPullRequestIds])));
|
||||
|
||||
$privateKey = System::getEnv('_APP_VCS_GITHUB_PRIVATE_KEY');
|
||||
$githubAppId = System::getEnv('_APP_VCS_GITHUB_APP_ID');
|
||||
|
||||
@@ -152,7 +152,13 @@ class Get extends Action
|
||||
->setAttribute('personalRefreshToken', $refreshToken)
|
||||
->setAttribute('personalAccessToken', $accessToken)
|
||||
->setAttribute('personalAccessTokenExpiry', $accessTokenExpiry);
|
||||
$installation = $dbForPlatform->updateDocument('installations', $installation->getId(), $installation);
|
||||
$installation = $dbForPlatform->updateDocument('installations', $installation->getId(), new Document([
|
||||
'organization' => $installation->getAttribute('organization'),
|
||||
'personal' => $installation->getAttribute('personal'),
|
||||
'personalRefreshToken' => $installation->getAttribute('personalRefreshToken'),
|
||||
'personalAccessToken' => $installation->getAttribute('personalAccessToken'),
|
||||
'personalAccessTokenExpiry' => $installation->getAttribute('personalAccessTokenExpiry'),
|
||||
]));
|
||||
}
|
||||
} else {
|
||||
$error = 'Installation of the Appwrite GitHub App on organization accounts is restricted to organization owners. As a member of the organization, you do not have the necessary permissions to install this GitHub App. Please contact the organization owner to create the installation from the Appwrite console.';
|
||||
|
||||
@@ -326,7 +326,12 @@ trait Deployment
|
||||
->setAttribute('latestDeploymentInternalId', $deployment->getSequence())
|
||||
->setAttribute('latestDeploymentCreatedAt', $deployment->getCreatedAt())
|
||||
->setAttribute('latestDeploymentStatus', $deployment->getAttribute('status', ''));
|
||||
$authorization->skip(fn () => $dbForProject->updateDocument($resource->getCollection(), $resource->getId(), $resource));
|
||||
$authorization->skip(fn () => $dbForProject->updateDocument($resource->getCollection(), $resource->getId(), new Document([
|
||||
'latestDeploymentId' => $resource->getAttribute('latestDeploymentId'),
|
||||
'latestDeploymentInternalId' => $resource->getAttribute('latestDeploymentInternalId'),
|
||||
'latestDeploymentCreatedAt' => $resource->getAttribute('latestDeploymentCreatedAt'),
|
||||
'latestDeploymentStatus' => $resource->getAttribute('latestDeploymentStatus'),
|
||||
])));
|
||||
|
||||
if ($resource->getCollection() === 'sites') {
|
||||
$projectId = $project->getId();
|
||||
|
||||
@@ -9,6 +9,7 @@ use Appwrite\Platform\Modules\VCS\Http\GitHub\Deployment;
|
||||
use Appwrite\Utopia\Request;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Platform\Scope\HTTP;
|
||||
@@ -235,7 +236,7 @@ class Create extends Action
|
||||
if (\in_array($providerPullRequestId, $providerPullRequestIds)) {
|
||||
$providerPullRequestIds = \array_diff($providerPullRequestIds, [$providerPullRequestId]);
|
||||
$repository = $repository->setAttribute('providerPullRequestIds', $providerPullRequestIds);
|
||||
$repository = $authorization->skip(fn () => $dbForPlatform->updateDocument('repositories', $repository->getId(), $repository));
|
||||
$repository = $authorization->skip(fn () => $dbForPlatform->updateDocument('repositories', $repository->getId(), new Document(['providerPullRequestIds' => $providerPullRequestIds])));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,7 +114,11 @@ class Create extends Action
|
||||
->setAttribute('personalRefreshToken', $refreshToken)
|
||||
->setAttribute('personalAccessTokenExpiry', DateTime::addSeconds(new \DateTime(), (int)$oauth2->getAccessTokenExpiry('')));
|
||||
|
||||
$dbForPlatform->updateDocument('installations', $installation->getId(), $installation);
|
||||
$dbForPlatform->updateDocument('installations', $installation->getId(), new Document([
|
||||
'personalAccessToken' => $installation->getAttribute('personalAccessToken'),
|
||||
'personalRefreshToken' => $installation->getAttribute('personalRefreshToken'),
|
||||
'personalAccessTokenExpiry' => $installation->getAttribute('personalAccessTokenExpiry'),
|
||||
]));
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
@@ -16,6 +16,7 @@ use Appwrite\Platform\Tasks\SDKs;
|
||||
use Appwrite\Platform\Tasks\Specs;
|
||||
use Appwrite\Platform\Tasks\SSL;
|
||||
use Appwrite\Platform\Tasks\StatsResources;
|
||||
use Appwrite\Platform\Tasks\TimeTravel;
|
||||
use Appwrite\Platform\Tasks\Upgrade;
|
||||
use Appwrite\Platform\Tasks\Vars;
|
||||
use Appwrite\Platform\Tasks\Version;
|
||||
@@ -44,6 +45,7 @@ class Tasks extends Service
|
||||
->addAction(Vars::getName(), new Vars())
|
||||
->addAction(Version::getName(), new Version())
|
||||
->addAction(StatsResources::getName(), new StatsResources())
|
||||
->addAction(TimeTravel::getName(), new TimeTravel())
|
||||
;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -159,9 +159,7 @@ class Interval extends Action
|
||||
}
|
||||
|
||||
foreach ($staleExecutions as $execution) {
|
||||
$execution->setAttribute('status', 'failed');
|
||||
$execution->setAttribute('errors', 'Execution timed out');
|
||||
$dbForProject->updateDocument('executions', $execution->getId(), $execution);
|
||||
$dbForProject->updateDocument('executions', $execution->getId(), new Document(['status' => 'failed', 'errors' => 'Execution timed out']));
|
||||
}
|
||||
|
||||
$processed++;
|
||||
|
||||
@@ -13,6 +13,7 @@ use Utopia\Database\Document;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\Platform\Action;
|
||||
use Utopia\System\System;
|
||||
use Utopia\Validator\WhiteList;
|
||||
|
||||
class Maintenance extends Action
|
||||
{
|
||||
@@ -25,6 +26,7 @@ class Maintenance extends Action
|
||||
{
|
||||
$this
|
||||
->desc('Schedules maintenance tasks and publishes them to our queues')
|
||||
->param('type', 'loop', new WhiteList(['loop', 'trigger']), 'How to run task. "loop" is meant for container entrypoint, and "trigger" for manual execution.')
|
||||
->inject('dbForPlatform')
|
||||
->inject('console')
|
||||
->inject('queueForCertificates')
|
||||
@@ -32,7 +34,7 @@ class Maintenance extends Action
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(Database $dbForPlatform, Document $console, Certificate $queueForCertificates, Delete $queueForDeletes): void
|
||||
public function action(string $type, Database $dbForPlatform, Document $console, Certificate $queueForCertificates, Delete $queueForDeletes): void
|
||||
{
|
||||
Console::title('Maintenance V1');
|
||||
Console::success(APP_NAME . ' maintenance process v1 has started');
|
||||
@@ -57,9 +59,7 @@ class Maintenance extends Action
|
||||
$delay = $next->getTimestamp() - $now->getTimestamp();
|
||||
}
|
||||
|
||||
Console::info('Setting loop start time to ' . $next->format("Y-m-d H:i:s.v") . '. Delaying for ' . $delay . ' seconds.');
|
||||
|
||||
Console::loop(function () use ($interval, $cacheRetention, $schedulesDeletionRetention, $usageStatsRetentionHourly, $dbForPlatform, $console, $queueForDeletes, $queueForCertificates) {
|
||||
$action = function () use ($interval, $cacheRetention, $schedulesDeletionRetention, $usageStatsRetentionHourly, $dbForPlatform, $console, $queueForDeletes, $queueForCertificates) {
|
||||
$time = DatabaseDateTime::now();
|
||||
|
||||
Console::info("[{$time}] Notifying workers with maintenance tasks every {$interval} seconds");
|
||||
@@ -96,7 +96,17 @@ class Maintenance extends Action
|
||||
$this->notifyDeleteCache($cacheRetention, $queueForDeletes);
|
||||
$this->notifyDeleteSchedules($schedulesDeletionRetention, $queueForDeletes);
|
||||
$this->notifyDeleteCSVExports($queueForDeletes);
|
||||
}, $interval, $delay);
|
||||
};
|
||||
|
||||
if ($type === 'loop') {
|
||||
Console::info('Setting loop start time to ' . $next->format("Y-m-d H:i:s.v") . '. Delaying for ' . $delay . ' seconds.');
|
||||
|
||||
Console::loop(function () use ($action) {
|
||||
$action();
|
||||
}, $interval, $delay);
|
||||
} elseif ($type === 'trigger') {
|
||||
$action();
|
||||
}
|
||||
}
|
||||
|
||||
private function notifyDeleteConnections(Delete $queueForDeletes): void
|
||||
|
||||
@@ -25,6 +25,7 @@ use Appwrite\SDK\Language\Swift;
|
||||
use Appwrite\SDK\Language\Web;
|
||||
use Appwrite\SDK\SDK;
|
||||
use Appwrite\Spec\Swagger2;
|
||||
use CzProject\GitPhp\Git;
|
||||
use Utopia\Agents\Adapters\OpenAI;
|
||||
use Utopia\Agents\DiffCheck\DiffCheck;
|
||||
use Utopia\Agents\DiffCheck\Options as DiffCheckOptions;
|
||||
@@ -41,29 +42,6 @@ use Utopia\Validator\WhiteList;
|
||||
|
||||
class SDKs extends Action
|
||||
{
|
||||
protected array $supportedSDKS = [
|
||||
'web',
|
||||
'cli',
|
||||
'php',
|
||||
'nodejs',
|
||||
'deno',
|
||||
'python',
|
||||
'ruby',
|
||||
'flutter',
|
||||
'react-native',
|
||||
'dart',
|
||||
'go',
|
||||
'swift',
|
||||
'apple',
|
||||
'dotnet',
|
||||
'android',
|
||||
'graphql',
|
||||
'rest',
|
||||
'markdown',
|
||||
'agent-skills',
|
||||
'cursor-plugin'
|
||||
];
|
||||
|
||||
public static function getName(): string
|
||||
{
|
||||
return 'sdks';
|
||||
@@ -74,6 +52,19 @@ class SDKs extends Action
|
||||
return Specs::getPlatforms();
|
||||
}
|
||||
|
||||
protected function getSdkConfigPath(): string
|
||||
{
|
||||
return __DIR__ . '/../../../../app/config/sdks.php';
|
||||
}
|
||||
|
||||
protected function getSupportedSDKs(): array
|
||||
{
|
||||
return \array_unique(\array_merge(...\array_values(\array_map(
|
||||
fn ($platform) => \array_column($platform['sdks'], 'key'),
|
||||
Config::getParam('sdks')
|
||||
))));
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
@@ -100,8 +91,9 @@ class SDKs extends Action
|
||||
if (! $sdks) {
|
||||
$selectedPlatform ??= Console::confirm('Choose Platform ("' . implode('", "', static::getPlatforms()) . '" or "*" for all):');
|
||||
$selectedSDK ??= \strtolower(Console::confirm('Choose SDK ("*" for all):'));
|
||||
if ($selectedSDK !== '*' && ! \in_array($selectedSDK, $this->supportedSDKS)) {
|
||||
throw new \Exception('Unknown SDK "' . $selectedSDK . '" given. Options are: ' . implode(', ', $this->supportedSDKS));
|
||||
$supportedSDKs = $this->getSupportedSDKs();
|
||||
if ($selectedSDK !== '*' && ! \in_array($selectedSDK, $supportedSDKs)) {
|
||||
throw new \Exception('Unknown SDK "' . $selectedSDK . '" given. Options are: ' . implode(', ', $supportedSDKs));
|
||||
}
|
||||
} else {
|
||||
$sdks = explode(',', $sdks);
|
||||
@@ -117,9 +109,6 @@ class SDKs extends Action
|
||||
|
||||
$prUrls = [];
|
||||
|
||||
if ($git) {
|
||||
$message ??= Console::confirm('Please enter your commit message:');
|
||||
}
|
||||
} elseif ($examplesOnly) {
|
||||
$git = false;
|
||||
$prUrls = [];
|
||||
@@ -337,7 +326,7 @@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
}
|
||||
|
||||
// Check if release already exists
|
||||
$checkReleaseCommand = 'gh release view "' . $releaseVersion . '" --repo "' . $repoName . '" --json url --jq ".url" 2>/dev/null';
|
||||
$checkReleaseCommand = 'gh release view ' . \escapeshellarg($releaseVersion) . ' --repo ' . \escapeshellarg($repoName) . ' --json url --jq ".url" 2>/dev/null';
|
||||
$existingReleaseUrl = trim(\shell_exec($checkReleaseCommand) ?? '');
|
||||
|
||||
if (! empty($existingReleaseUrl)) {
|
||||
@@ -368,7 +357,7 @@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
}
|
||||
|
||||
$previousVersion = '';
|
||||
$tagListCommand = 'gh release list --repo "' . $repoName . '" --limit 1 --json tagName --jq ".[0].tagName" 2>&1';
|
||||
$tagListCommand = 'gh release list --repo ' . \escapeshellarg($repoName) . ' --limit 1 --json tagName --jq ".[0].tagName" 2>&1';
|
||||
$previousVersion = trim(\shell_exec($tagListCommand) ?? '');
|
||||
|
||||
$formattedNotes = "## What's Changed\n\n";
|
||||
@@ -396,11 +385,11 @@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
$tempNotesFile = \tempnam(\sys_get_temp_dir(), 'release_notes_');
|
||||
\file_put_contents($tempNotesFile, $formattedNotes);
|
||||
|
||||
$releaseCommand = 'gh release create "' . $releaseVersion . '" \
|
||||
--repo "' . $repoName . '" \
|
||||
--title "' . $releaseTitle . '" \
|
||||
--notes-file "' . $tempNotesFile . '" \
|
||||
--target "' . $releaseTarget . '" \
|
||||
$releaseCommand = 'gh release create ' . \escapeshellarg($releaseVersion) . ' \
|
||||
--repo ' . \escapeshellarg($repoName) . ' \
|
||||
--title ' . \escapeshellarg($releaseTitle) . ' \
|
||||
--notes-file ' . \escapeshellarg($tempNotesFile) . ' \
|
||||
--target ' . \escapeshellarg($releaseTarget) . ' \
|
||||
2>&1';
|
||||
|
||||
$releaseOutput = [];
|
||||
@@ -491,7 +480,10 @@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
Console::info("Analyzing SDK changes with AI...");
|
||||
$aiResult = $this->generateVersionAndChangelog($language, $result);
|
||||
|
||||
if ($aiResult !== null) {
|
||||
if (!empty($aiResult['skip'])) {
|
||||
Console::warning("Skipping {$language['name']} SDK generation");
|
||||
continue;
|
||||
} elseif ($aiResult !== null) {
|
||||
$newVersion = $aiResult['version'];
|
||||
$newChangelog = $aiResult['changelog'];
|
||||
$aiChangelog = $newChangelog; // Store for PR description
|
||||
@@ -499,19 +491,17 @@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
// Update the version in the config
|
||||
$this->updateSdkVersion($key, $language['key'], $newVersion);
|
||||
|
||||
// Update the changelog file
|
||||
// Update the source changelog file
|
||||
$this->updateChangelogFile($language['changelog'], $newVersion, $newChangelog);
|
||||
|
||||
// Also update CHANGELOG.md in the generated SDK directory
|
||||
$sdkChangelogPath = $result . '/CHANGELOG.md';
|
||||
if (file_exists($sdkChangelogPath)) {
|
||||
$this->updateChangelogFile($sdkChangelogPath, $newVersion, $newChangelog);
|
||||
}
|
||||
// Re-read updated changelog so regeneration includes the new entry
|
||||
$updatedChangelog = \file_get_contents($language['changelog']);
|
||||
$sdk->setChangelog($updatedChangelog);
|
||||
|
||||
// Reload the language config with updated values
|
||||
$language['version'] = $newVersion;
|
||||
|
||||
// Regenerate SDK with new version
|
||||
// Regenerate SDK with new version and updated changelog
|
||||
$sdk->setVersion($newVersion);
|
||||
try {
|
||||
$sdk->generate($result);
|
||||
@@ -527,159 +517,26 @@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
$gitBranch = $language['gitBranch'];
|
||||
|
||||
$repoBranch = $language['repoBranch'] ?? 'main';
|
||||
if ($git && ! empty($gitUrl)) {
|
||||
Console::info("Preparing {$language['name']} SDK repository...");
|
||||
|
||||
\exec('rm -rf ' . $target . ' && \
|
||||
mkdir -p ' . $target . ' && \
|
||||
cd ' . $target . ' && \
|
||||
git init --quiet && \
|
||||
git config core.ignorecase false && \
|
||||
git config pull.rebase false && \
|
||||
git config advice.defaultBranchName false && \
|
||||
git remote add origin ' . $gitUrl . ' && \
|
||||
git fetch origin --quiet --no-tags --depth 1 ' . $repoBranch . ' 2>&1 | grep -v "^remote:" | grep -v "^From " | grep -v "^ \* " || true && \
|
||||
(git checkout -f ' . $repoBranch . ' 2>/dev/null || git checkout -b ' . $repoBranch . ') && \
|
||||
git pull origin ' . $repoBranch . ' --quiet --no-tags 2>&1 | grep -v "^From " | grep -v "^ \* " || true && \
|
||||
(git checkout -f ' . $gitBranch . ' 2>/dev/null || git checkout -b ' . $gitBranch . ') && \
|
||||
(git fetch origin ' . $gitBranch . ' --quiet --no-tags --depth 1 2>/dev/null || git push -u origin ' . $gitBranch . ' --quiet 2>&1 | grep -v "^remote:" || true) && \
|
||||
git reset --hard origin/' . $gitBranch . ' 2>/dev/null || true && \
|
||||
(if [ -d .github ]; then cp -r .github /tmp/.github-backup-$$ 2>/dev/null; fi) && \
|
||||
git rm -rf --cached . 2>/dev/null && \
|
||||
git clean -fdx -e .git -e .github 2>/dev/null && \
|
||||
cp -r ' . $result . '/. ' . $target . '/ && \
|
||||
(if [ -d /tmp/.github-backup-$$/.github ]; then cp -rn /tmp/.github-backup-$$/.github . 2>/dev/null && rm -rf /tmp/.github-backup-$$; fi) && \
|
||||
git add -A && \
|
||||
git commit -m "' . $message . '" --quiet && \
|
||||
git push -u origin ' . $gitBranch . ' --quiet 2>&1 | grep -E "^(To | |[0-9a-f]+\\.\\.[0-9a-f]+)" || true
|
||||
', $gitOutput, $gitReturnCode);
|
||||
|
||||
if ($gitReturnCode !== 0) {
|
||||
Console::warning("Git operations completed with warnings (exit code: {$gitReturnCode})");
|
||||
if ($git && !empty($gitUrl)) {
|
||||
// Generate commit message: use provided message, AI changelog, or fallback
|
||||
if (! empty($message)) {
|
||||
$commitMessage = $message;
|
||||
} elseif (! empty($aiChangelog) && $aiChangelog !== '* No user-facing SDK changes.') {
|
||||
$commitMessage = "feat: update {$language['name']} SDK to {$language['version']}\n\n{$aiChangelog}";
|
||||
} else {
|
||||
$commitMessage = "chore: update {$language['name']} SDK to {$language['version']}";
|
||||
}
|
||||
|
||||
Console::success("Pushed {$language['name']} SDK to {$gitUrl}");
|
||||
if ($git) {
|
||||
$prTitle = "feat: {$language['name']} SDK update for version {$language['version']}";
|
||||
$pushSuccess = $this->pushToGit($language, $target, $result, $gitUrl, $gitBranch, $repoBranch, $commitMessage);
|
||||
|
||||
// Build PR body with AI changelog if available
|
||||
$prBody = "This PR contains updates to the {$language['name']} SDK for version {$language['version']}.";
|
||||
if (!empty($aiChangelog) && $aiChangelog !== '* No user-facing SDK changes.') {
|
||||
$prBody .= "\n\n## Changes\n\n{$aiChangelog}";
|
||||
}
|
||||
$repoName = $language['gitUserName'] . '/' . $language['gitRepoName'];
|
||||
|
||||
Console::info("Creating pull request for {$language['name']} SDK...");
|
||||
|
||||
$prCommand = 'cd ' . $target . ' && \
|
||||
gh pr create \
|
||||
--repo "' . $repoName . '" \
|
||||
--title "' . $prTitle . '" \
|
||||
--body "' . $prBody . '" \
|
||||
--base "' . $repoBranch . '" \
|
||||
--head "' . $gitBranch . '" \
|
||||
2>&1';
|
||||
|
||||
$prOutput = [];
|
||||
$prReturnCode = 0;
|
||||
\exec($prCommand, $prOutput, $prReturnCode);
|
||||
|
||||
if ($prReturnCode === 0) {
|
||||
Console::success("Successfully created pull request for {$language['name']} SDK");
|
||||
if (! empty($prOutput)) {
|
||||
$prUrls[$language['name']] = end($prOutput);
|
||||
}
|
||||
} else {
|
||||
$errorMessage = implode("\n", $prOutput);
|
||||
if (strpos($errorMessage, 'already exists') !== false) {
|
||||
Console::warning("Pull request already exists for {$language['name']} SDK, updating title and body...");
|
||||
$prNumberCommand = 'cd ' . $target . ' && \
|
||||
gh pr list \
|
||||
--repo "' . $repoName . '" \
|
||||
--head "' . $gitBranch . '" \
|
||||
--json number \
|
||||
--jq ".[0].number" \
|
||||
2>&1';
|
||||
|
||||
$prNumberOutput = [];
|
||||
$prNumberReturnCode = 0;
|
||||
\exec($prNumberCommand, $prNumberOutput, $prNumberReturnCode);
|
||||
|
||||
if ($prNumberReturnCode === 0 && ! empty($prNumberOutput[0])) {
|
||||
$prNumber = trim($prNumberOutput[0]);
|
||||
|
||||
// Use API directly to update PR to avoid deprecated projectCards field
|
||||
$updateCommand = 'cd ' . $target . ' && \
|
||||
gh api \
|
||||
--method PATCH \
|
||||
-H "Accept: application/vnd.github+json" \
|
||||
-H "X-GitHub-Api-Version: 2022-11-28" \
|
||||
/repos/' . $repoName . '/pulls/' . $prNumber . ' \
|
||||
-f title="' . $prTitle . '" \
|
||||
-f body="' . $prBody . '" \
|
||||
2>&1';
|
||||
|
||||
$updateOutput = [];
|
||||
$updateReturnCode = 0;
|
||||
\exec($updateCommand, $updateOutput, $updateReturnCode);
|
||||
|
||||
if ($updateReturnCode === 0) {
|
||||
Console::success("Successfully updated pull request for {$language['name']} SDK");
|
||||
|
||||
$prUrlCommand = 'cd ' . $target . ' && \
|
||||
gh pr list \
|
||||
--repo "' . $repoName . '" \
|
||||
--head "' . $gitBranch . '" \
|
||||
--json url \
|
||||
--jq ".[0].url" \
|
||||
2>&1';
|
||||
|
||||
$prUrlOutput = [];
|
||||
$prUrlReturnCode = 0;
|
||||
\exec($prUrlCommand, $prUrlOutput, $prUrlReturnCode);
|
||||
|
||||
if ($prUrlReturnCode === 0 && ! empty($prUrlOutput)) {
|
||||
$prUrls[$language['name']] = trim($prUrlOutput[0]);
|
||||
}
|
||||
} else {
|
||||
$updateErrorMessage = implode("\n", $updateOutput);
|
||||
Console::error("Failed to update pull request for {$language['name']} SDK: " . $updateErrorMessage);
|
||||
}
|
||||
} else {
|
||||
Console::error("Failed to get PR number for {$language['name']} SDK");
|
||||
}
|
||||
} else {
|
||||
Console::error("Failed to create pull request for {$language['name']} SDK: " . $errorMessage);
|
||||
}
|
||||
}
|
||||
if ($pushSuccess) {
|
||||
$this->createPullRequest($language, $target, $gitBranch, $repoBranch, $aiChangelog, $prUrls);
|
||||
}
|
||||
|
||||
\exec('chmod -R u+w ' . $target . ' && rm -rf ' . $target);
|
||||
Console::success("Remove temp directory '{$target}' for {$language['name']} SDK");
|
||||
$this->cleanupTarget($target, $language['name']);
|
||||
}
|
||||
|
||||
$docDirectories = $language['docDirectories'] ?? [''];
|
||||
|
||||
if ($version === 'latest') {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($docDirectories as $languageTitle => $path) {
|
||||
$languagePath = strtolower($languageTitle !== 0 ? '/' . $languageTitle : '');
|
||||
$examplesSource = $result . '/docs/examples' . $languagePath;
|
||||
|
||||
if (! \is_dir($examplesSource)) {
|
||||
Console::warning("No code examples found for {$language['name']} SDK at: {$examplesSource}. Skipping copy.");
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
\exec(
|
||||
'mkdir -p ' . $resultExamples . $languagePath . ' && \
|
||||
cp -r ' . $examplesSource . ' ' . $resultExamples
|
||||
);
|
||||
Console::success("Copied code examples for {$language['name']} SDK to: {$resultExamples}");
|
||||
}
|
||||
$this->copyExamples($language, $version, $result, $resultExamples);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -693,6 +550,176 @@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
}
|
||||
}
|
||||
|
||||
private function pushToGit(array $language, string $target, string $result, string $gitUrl, string $gitBranch, string $repoBranch, string $commitMessage): bool
|
||||
{
|
||||
Console::info("Preparing {$language['name']} SDK repository...");
|
||||
|
||||
try {
|
||||
// Init fresh repo
|
||||
\exec('rm -rf ' . \escapeshellarg($target));
|
||||
\mkdir($target, 0755, true);
|
||||
|
||||
$gitClient = new Git();
|
||||
$repo = $gitClient->init($target);
|
||||
|
||||
$repo->execute('config', 'core.ignorecase', 'false');
|
||||
$repo->execute('config', 'pull.rebase', 'false');
|
||||
$repo->execute('config', 'advice.defaultBranchName', 'false');
|
||||
$repo->addRemote('origin', $gitUrl);
|
||||
|
||||
// Fetch and checkout base branch (or create if new repo)
|
||||
try {
|
||||
$repo->execute('fetch', 'origin', '--quiet', '--no-tags', '--depth', '1', $repoBranch);
|
||||
try {
|
||||
$repo->execute('checkout', '-f', $repoBranch);
|
||||
} catch (\Throwable) {
|
||||
$repo->execute('checkout', '-b', $repoBranch);
|
||||
}
|
||||
} catch (\Throwable) {
|
||||
$repo->execute('checkout', '-b', $repoBranch);
|
||||
}
|
||||
|
||||
try {
|
||||
$repo->execute('pull', 'origin', $repoBranch, '--quiet', '--no-tags');
|
||||
} catch (\Throwable) {
|
||||
}
|
||||
|
||||
// Checkout dev branch (or create if it doesn't exist)
|
||||
try {
|
||||
$repo->execute('checkout', '-f', $gitBranch);
|
||||
} catch (\Throwable) {
|
||||
$repo->execute('checkout', '-b', $gitBranch);
|
||||
}
|
||||
|
||||
// Fetch dev branch, or push to create it on remote
|
||||
try {
|
||||
$repo->execute('fetch', 'origin', $gitBranch, '--quiet', '--no-tags', '--depth', '1');
|
||||
} catch (\Throwable) {
|
||||
try {
|
||||
$repo->execute('push', '-u', 'origin', $gitBranch, '--quiet');
|
||||
} catch (\Throwable) {
|
||||
}
|
||||
}
|
||||
|
||||
// Sync with remote dev branch
|
||||
try {
|
||||
$repo->execute('reset', '--hard', "origin/{$gitBranch}");
|
||||
} catch (\Throwable) {
|
||||
}
|
||||
|
||||
// Backup .github before cleaning working tree
|
||||
$githubDir = $target . '/.github';
|
||||
$githubBackup = \sys_get_temp_dir() . '/.github-backup-' . \getmypid();
|
||||
$hasGithubDir = \is_dir($githubDir);
|
||||
if ($hasGithubDir) {
|
||||
\exec('cp -r ' . \escapeshellarg($githubDir) . ' ' . \escapeshellarg($githubBackup));
|
||||
}
|
||||
|
||||
// Clean working tree
|
||||
try {
|
||||
$repo->execute('rm', '-rf', '--cached', '.');
|
||||
} catch (\Throwable) {
|
||||
}
|
||||
try {
|
||||
$repo->execute('clean', '-fdx', '-e', '.git', '-e', '.github');
|
||||
} catch (\Throwable) {
|
||||
}
|
||||
|
||||
// Copy generated SDK and restore .github
|
||||
\exec('cp -r ' . \escapeshellarg($result . '/.') . ' ' . \escapeshellarg($target . '/'));
|
||||
|
||||
if ($hasGithubDir && \is_dir($githubBackup)) {
|
||||
\exec('cp -rn ' . \escapeshellarg($githubBackup . '/.github') . ' ' . \escapeshellarg($target . '/') . ' 2>/dev/null');
|
||||
\exec('rm -rf ' . \escapeshellarg($githubBackup));
|
||||
}
|
||||
|
||||
// Stage, commit, push
|
||||
$repo->addAllChanges();
|
||||
$repo->commit($commitMessage);
|
||||
$repo->execute('push', '-u', 'origin', $gitBranch, '--quiet');
|
||||
} catch (\Throwable $e) {
|
||||
Console::warning("Git operations failed for {$language['name']} SDK: " . $e->getMessage());
|
||||
return false;
|
||||
}
|
||||
|
||||
Console::success("Pushed {$language['name']} SDK to {$gitUrl}");
|
||||
return true;
|
||||
}
|
||||
|
||||
private function createPullRequest(array $language, string $target, string $gitBranch, string $repoBranch, string $aiChangelog, array &$prUrls): void
|
||||
{
|
||||
$prTitle = "feat: {$language['name']} SDK update for version {$language['version']}";
|
||||
$prBody = "This PR contains updates to the {$language['name']} SDK for version {$language['version']}.";
|
||||
if (!empty($aiChangelog) && $aiChangelog !== '* No user-facing SDK changes.') {
|
||||
$prBody .= "\n\n## Changes\n\n{$aiChangelog}";
|
||||
}
|
||||
$repoName = $language['gitUserName'] . '/' . $language['gitRepoName'];
|
||||
|
||||
Console::info("Creating pull request for {$language['name']} SDK...");
|
||||
|
||||
$prCommand = 'cd ' . $target . ' && \
|
||||
gh pr create \
|
||||
--repo ' . \escapeshellarg($repoName) . ' \
|
||||
--title ' . \escapeshellarg($prTitle) . ' \
|
||||
--body ' . \escapeshellarg($prBody) . ' \
|
||||
--base ' . \escapeshellarg($repoBranch) . ' \
|
||||
--head ' . \escapeshellarg($gitBranch) . ' \
|
||||
2>&1';
|
||||
|
||||
$prOutput = [];
|
||||
$prReturnCode = 0;
|
||||
\exec($prCommand, $prOutput, $prReturnCode);
|
||||
|
||||
if ($prReturnCode === 0) {
|
||||
Console::success("Successfully created pull request for {$language['name']} SDK");
|
||||
foreach ($prOutput as $line) {
|
||||
if (\str_starts_with(trim($line), 'https://')) {
|
||||
$prUrls[$language['name']] = trim($line);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$errorMessage = implode("\n", $prOutput);
|
||||
if (strpos($errorMessage, 'already exists') === false) {
|
||||
Console::error("Failed to create pull request for {$language['name']} SDK: " . $errorMessage);
|
||||
} else {
|
||||
$this->updateExistingPr($target, $repoName, $gitBranch, $prTitle, $prBody, $language['name'], $prUrls);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function cleanupTarget(string $target, string $languageName): void
|
||||
{
|
||||
\exec('chmod -R u+w ' . $target . ' && rm -rf ' . $target);
|
||||
Console::success("Remove temp directory '{$target}' for {$languageName} SDK");
|
||||
}
|
||||
|
||||
private function copyExamples(array $language, string $version, string $result, string $resultExamples): void
|
||||
{
|
||||
$docDirectories = $language['docDirectories'] ?? [''];
|
||||
|
||||
if ($version === 'latest') {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($docDirectories as $languageTitle => $path) {
|
||||
$languagePath = strtolower($languageTitle !== 0 ? '/' . $languageTitle : '');
|
||||
$examplesSource = $result . '/docs/examples' . $languagePath;
|
||||
|
||||
if (! \is_dir($examplesSource)) {
|
||||
Console::warning("No code examples found for {$language['name']} SDK at: {$examplesSource}. Skipping copy.");
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
\exec(
|
||||
'mkdir -p ' . $resultExamples . $languagePath . ' && \
|
||||
cp -r ' . $examplesSource . ' ' . $resultExamples
|
||||
);
|
||||
Console::success("Copied code examples for {$language['name']} SDK to: {$resultExamples}");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract release notes from changelog for a specific version
|
||||
*/
|
||||
@@ -780,20 +807,25 @@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
required: $object->getNames()
|
||||
);
|
||||
|
||||
$isBeta = !empty($language['beta']);
|
||||
$betaNote = $isBeta
|
||||
? "\n Note: This SDK is in beta (version < 1.0.0). Do NOT bump to 1.0.0. Use `minor` for both breaking changes and new features, `patch` for bug fixes only."
|
||||
: '';
|
||||
|
||||
$prompt = <<<PROMPT
|
||||
You are a technical writer generating a changelog for the {$language['name']} SDK release.
|
||||
|
||||
|
||||
Analyze the git diff below and return a JSON response with the version bump type, new version number, and changelog.
|
||||
|
||||
|
||||
## Versioning
|
||||
|
||||
|
||||
Current version: {$language['version']}
|
||||
|
||||
|
||||
Determine the semantic version bump:
|
||||
- `major`: Breaking changes (removed/renamed public APIs, changed method signatures, dropped support)
|
||||
- `minor`: New features that are backward-compatible (new methods, new optional parameters, new classes)
|
||||
- `patch`: Bug fixes, documentation updates, refactors with no API surface change
|
||||
|
||||
{$betaNote}
|
||||
When multiple change types are present, use the highest severity bump.
|
||||
|
||||
## Changelog guidelines
|
||||
@@ -810,6 +842,7 @@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
- Only include changes visible to SDK users (public API, behavior, docs, examples, CLI)
|
||||
- Ignore: CI/CD pipelines (.github/), internal tooling, code formatting, test infrastructure
|
||||
- Consolidate related changes into one entry (e.g., "Added `timeout`, `retries`, and `baseUrl` options" not three separate lines)
|
||||
- Wrap all method names, parameter names, class names, and code identifiers in backticks (e.g., `listDocuments`, `ttl`)
|
||||
- If the diff contains zero user-facing changes, return a single entry: "No user-facing SDK changes"
|
||||
- Do not speculate — only document what the diff explicitly shows
|
||||
|
||||
@@ -856,7 +889,6 @@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
|
||||
if (empty(trim($responseContent))) {
|
||||
Console::warning('AI returned empty response');
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -866,7 +898,6 @@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
Console::warning('Failed to parse AI response as JSON: ' . json_last_error_msg());
|
||||
Console::log('Raw response:');
|
||||
Console::log($responseContent);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -875,6 +906,12 @@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
return null;
|
||||
}
|
||||
|
||||
// Guard: beta SDKs must not be bumped to >= 1.0.0
|
||||
if ($isBeta && ($parsed['versionBump'] === 'major' || \version_compare($parsed['version'], '1.0.0', '>='))) {
|
||||
Console::warning("Beta SDK {$language['name']} cannot have a major bump or version >= 1.0.0 (AI suggested {$parsed['version']}), skipping");
|
||||
return ['skip' => true];
|
||||
}
|
||||
|
||||
Console::success("✓ Analysis complete");
|
||||
Console::log(" Version: {$language['version']} → {$parsed['version']} ({$parsed['versionBump']} bump)");
|
||||
Console::log(" Changelog:");
|
||||
@@ -891,21 +928,10 @@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
];
|
||||
} catch (\Throwable $e) {
|
||||
Console::error('Error generating version and changelog: ' . $e->getMessage());
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the SDK config file path
|
||||
*
|
||||
* @return string Path to the SDK config file
|
||||
*/
|
||||
protected function getSdkConfigPath(): string
|
||||
{
|
||||
return __DIR__ . '/../../../../app/config/sdks.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* Update SDK version in the config file
|
||||
*
|
||||
@@ -920,7 +946,6 @@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
|
||||
if (! file_exists($configPath)) {
|
||||
Console::error("Config file not found: {$configPath}");
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -936,11 +961,9 @@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
|
||||
if (file_put_contents($configPath, $newContent) !== false) {
|
||||
Console::success("Updated {$sdkKey} version from {$oldVersion} to {$newVersion} in config");
|
||||
|
||||
return true;
|
||||
} else {
|
||||
Console::error('Failed to write config file');
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -956,11 +979,9 @@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
|
||||
if (file_put_contents($configPath, $newContent) !== false) {
|
||||
Console::success("Updated {$sdkKey} version from {$oldVersion} to {$newVersion} in config");
|
||||
|
||||
return true;
|
||||
} else {
|
||||
Console::error('Failed to write config file');
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -1019,12 +1040,71 @@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
|
||||
if (file_put_contents($changelogPath, $newContent) !== false) {
|
||||
Console::success("Updated changelog at {$changelogPath} with version {$version}");
|
||||
|
||||
return true;
|
||||
} else {
|
||||
Console::error('Failed to write changelog file');
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private function updateExistingPr(string $target, string $repoName, string $gitBranch, string $prTitle, string $prBody, string $sdkName, array &$prUrls): void
|
||||
{
|
||||
Console::warning("Pull request already exists for {$sdkName} SDK, updating title and body...");
|
||||
|
||||
$prNumberCommand = 'cd ' . $target . ' && \
|
||||
gh pr list \
|
||||
--repo ' . \escapeshellarg($repoName) . ' \
|
||||
--head ' . \escapeshellarg($gitBranch) . ' \
|
||||
--json number \
|
||||
--jq ".[0].number" \
|
||||
2>&1';
|
||||
|
||||
$prNumberOutput = [];
|
||||
$prNumberReturnCode = 0;
|
||||
\exec($prNumberCommand, $prNumberOutput, $prNumberReturnCode);
|
||||
|
||||
if ($prNumberReturnCode !== 0 || empty($prNumberOutput[0])) {
|
||||
Console::error("Failed to get PR number for {$sdkName} SDK");
|
||||
return;
|
||||
}
|
||||
|
||||
$prNumber = trim($prNumberOutput[0]);
|
||||
$apiPath = "/repos/{$repoName}/pulls/{$prNumber}";
|
||||
$updateCommand = 'cd ' . $target . ' && \
|
||||
gh api \
|
||||
--method PATCH \
|
||||
-H "Accept: application/vnd.github+json" \
|
||||
-H "X-GitHub-Api-Version: 2022-11-28" \
|
||||
' . \escapeshellarg($apiPath) . ' \
|
||||
-f title=' . \escapeshellarg($prTitle) . ' \
|
||||
-f body=' . \escapeshellarg($prBody) . ' \
|
||||
2>&1';
|
||||
|
||||
$updateOutput = [];
|
||||
$updateReturnCode = 0;
|
||||
\exec($updateCommand, $updateOutput, $updateReturnCode);
|
||||
|
||||
if ($updateReturnCode !== 0) {
|
||||
Console::error("Failed to update pull request for {$sdkName} SDK: " . implode("\n", $updateOutput));
|
||||
return;
|
||||
}
|
||||
|
||||
Console::success("Successfully updated pull request for {$sdkName} SDK");
|
||||
|
||||
$prUrlCommand = 'cd ' . $target . ' && \
|
||||
gh pr list \
|
||||
--repo ' . \escapeshellarg($repoName) . ' \
|
||||
--head ' . \escapeshellarg($gitBranch) . ' \
|
||||
--json url \
|
||||
--jq ".[0].url" \
|
||||
2>&1';
|
||||
|
||||
$prUrlOutput = [];
|
||||
$prUrlReturnCode = 0;
|
||||
\exec($prUrlCommand, $prUrlOutput, $prUrlReturnCode);
|
||||
|
||||
if ($prUrlReturnCode === 0 && ! empty($prUrlOutput)) {
|
||||
$prUrls[$sdkName] = trim($prUrlOutput[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,8 +59,11 @@ abstract class ScheduleBase extends Action
|
||||
if (!$project->isEmpty() && $project->getId() !== 'console') {
|
||||
$accessedAt = $project->getAttribute('accessedAt', 0);
|
||||
if (DateTime::formatTz(DateTime::addSeconds(new \DateTime(), -APP_PROJECT_ACCESS)) > $accessedAt) {
|
||||
$project->setAttribute('accessedAt', DateTime::now());
|
||||
$dbForPlatform->updateDocument('projects', $project->getId(), $project);
|
||||
$now = DateTime::now();
|
||||
$dbForPlatform->updateDocument('projects', $project->getId(), new Document([
|
||||
'accessedAt' => $now
|
||||
]));
|
||||
$project->setAttribute('accessedAt', $now);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Tasks;
|
||||
|
||||
use Utopia\Console;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Validator\Datetime as DatetimeValidator;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Platform\Action;
|
||||
use Utopia\System\System;
|
||||
use Utopia\Validator\WhiteList;
|
||||
|
||||
class TimeTravel extends Action
|
||||
{
|
||||
public static function getName(): string
|
||||
{
|
||||
return 'time-travel';
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->desc('Create a time-travel to change $createdAt')
|
||||
->param('projectId', '', new UID(), 'Project ID.')
|
||||
->param('resourceType', '', new WhiteList(['deployment']), 'Type of resource.')
|
||||
->param('resourceId', '', new UID(), 'ID of resource.')
|
||||
->param('createdAt', '', new DatetimeValidator(), 'New value for $createdAt')
|
||||
->inject('getProjectDB')
|
||||
->inject('dbForPlatform')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(string $projectId, string $resourceType, string $resourceId, string $createdAt, callable $getProjectDB, Database $dbForPlatform): void
|
||||
{
|
||||
$isDevelopment = System::getEnv('_APP_ENV', 'development') === 'development';
|
||||
|
||||
if (!$isDevelopment) {
|
||||
Console::error('This task is only available in development mode.');
|
||||
return;
|
||||
}
|
||||
|
||||
$project = $dbForPlatform->getDocument('projects', $projectId);
|
||||
|
||||
if ($project->isEmpty()) {
|
||||
Console::error('Project not found.');
|
||||
return;
|
||||
}
|
||||
|
||||
$collection = match ($resourceType) {
|
||||
'deployment' => 'deployments',
|
||||
default => throw new \Exception('Resource type not implemented')
|
||||
};
|
||||
|
||||
/** @var Database $dbForProject */
|
||||
$dbForProject = $getProjectDB($project);
|
||||
|
||||
$resource = $dbForProject->getDocument($collection, $resourceId);
|
||||
if ($resource->isEmpty()) {
|
||||
Console::error('Resource not found.');
|
||||
return;
|
||||
}
|
||||
|
||||
$update = new Document([
|
||||
'$createdAt' => $createdAt,
|
||||
]);
|
||||
|
||||
$dbForProject->withPreserveDates(fn () => $dbForProject->updateDocument($collection, $resourceId, $update));
|
||||
|
||||
Console::success('Time-travel successful. Updated $createdAt for ' . $resourceType . ' ' . $resourceId . ' to ' . $createdAt);
|
||||
}
|
||||
}
|
||||
@@ -423,7 +423,11 @@ class Certificates extends Action
|
||||
Func $queueForFunctions,
|
||||
Realtime $queueForRealtime
|
||||
): void {
|
||||
$rule = $dbForPlatform->updateDocument('rules', $rule->getId(), $rule);
|
||||
$rule = $dbForPlatform->updateDocument('rules', $rule->getId(), new Document([
|
||||
'status' => $rule->getAttribute('status'),
|
||||
'certificateId' => $rule->getAttribute('certificateId'),
|
||||
'logs' => $rule->getAttribute('logs'),
|
||||
]));
|
||||
$projectId = $rule->getAttribute('projectId');
|
||||
|
||||
// Skip events for console project (triggered by auto-ssl generation for 1 click setups)
|
||||
|
||||
@@ -5,6 +5,7 @@ namespace Appwrite\Platform\Workers;
|
||||
use Appwrite\Certificates\Adapter as CertificatesAdapter;
|
||||
use Appwrite\Deletes\Identities;
|
||||
use Appwrite\Deletes\Targets;
|
||||
use Appwrite\Event\Delete as DeleteEvent;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Executor\Executor;
|
||||
use Throwable;
|
||||
@@ -65,6 +66,7 @@ class Deletes extends Action
|
||||
->inject('executionsRetentionCount')
|
||||
->inject('auditRetention')
|
||||
->inject('log')
|
||||
->inject('queueForDeletes')
|
||||
->inject('getAudit')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
@@ -90,6 +92,7 @@ class Deletes extends Action
|
||||
int $executionsRetentionCount,
|
||||
string $auditRetention,
|
||||
Log $log,
|
||||
DeleteEvent $queueForDeletes,
|
||||
callable $getAudit,
|
||||
): void {
|
||||
$payload = $message->getPayload() ?? [];
|
||||
@@ -210,6 +213,7 @@ class Deletes extends Action
|
||||
$this->deleteUsageStats($project, $getProjectDB, $getLogsDB, $hourlyUsageRetentionDatetime);
|
||||
$this->deleteExpiredSessions($project, $getProjectDB);
|
||||
$this->deleteExpiredTransactions($project, $getProjectDB);
|
||||
$this->deleteOldDeployments($queueForDeletes, $project, $getProjectDB);
|
||||
break;
|
||||
default:
|
||||
throw new \Exception('No delete operation for type: ' . \strval($type));
|
||||
@@ -327,6 +331,61 @@ class Deletes extends Action
|
||||
Targets::delete($getProjectDB($project), Query::equal('sessionInternalId', [$session->getSequence()]));
|
||||
}
|
||||
|
||||
private function deleteOldDeployments(DeleteEvent $queueForDeletes, Document $project, callable $getProjectDB): void
|
||||
{
|
||||
/** @var Database $dbForProject */
|
||||
$dbForProject = $getProjectDB($project);
|
||||
|
||||
$removalCallback = function (Document $resource) use ($dbForProject, $queueForDeletes, $project) {
|
||||
$retention = $resource->getAttribute('deploymentRetention', 0);
|
||||
|
||||
// 0 means unlimited - never delete
|
||||
if ($retention === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
$activeDeploymentId = $resource->getAttribute('deploymentId', '');
|
||||
|
||||
$queries = [
|
||||
Query::createdBefore(DateTime::addSeconds(new \DateTime(), -1 * $retention * 24 * 60 * 60)),
|
||||
Query::equal('resourceInternalId', [$resource->getSequence()]),
|
||||
Query::equal('resourceType', [$resource->getCollection()]),
|
||||
Query::orderDesc('$createdAt'),
|
||||
];
|
||||
|
||||
if (!empty($activeDeploymentId)) {
|
||||
$queries[] = Query::notEqual('$id', $activeDeploymentId);
|
||||
}
|
||||
|
||||
$this->deleteByGroup(
|
||||
'deployments',
|
||||
$queries,
|
||||
$dbForProject,
|
||||
function (Document $deployment) use ($queueForDeletes, $project) {
|
||||
$queueForDeletes
|
||||
->setType(DELETE_TYPE_DOCUMENT)
|
||||
->setDocument($deployment)
|
||||
->setProject($project)
|
||||
->trigger();
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
$this->listByGroup(
|
||||
'functions',
|
||||
[],
|
||||
$dbForProject,
|
||||
$removalCallback
|
||||
);
|
||||
|
||||
$this->listByGroup(
|
||||
'sites',
|
||||
[],
|
||||
$dbForProject,
|
||||
$removalCallback
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Document $project
|
||||
* @param callable $getProjectDB
|
||||
|
||||
@@ -357,7 +357,7 @@ class Functions extends Action
|
||||
$user ??= new Document();
|
||||
$functionId = $function->getId();
|
||||
$deploymentId = $function->getAttribute('deploymentId', '');
|
||||
$spec = Config::getParam('specifications')[$function->getAttribute('specification', APP_COMPUTE_SPECIFICATION_DEFAULT)];
|
||||
$spec = Config::getParam('specifications')[$function->getAttribute('runtimeSpecification', APP_COMPUTE_SPECIFICATION_DEFAULT)];
|
||||
|
||||
$log->addTag('deploymentId', $deploymentId);
|
||||
|
||||
@@ -511,6 +511,11 @@ class Functions extends Action
|
||||
try {
|
||||
$version = $function->getAttribute('version', 'v2');
|
||||
$command = $runtime['startCommand'];
|
||||
|
||||
if (!empty($deployment->getAttribute('startCommand', ''))) {
|
||||
$command = 'cd /usr/local/server/src/function/ && ' . $deployment->getAttribute('startCommand', '');
|
||||
}
|
||||
|
||||
$source = $deployment->getAttribute('buildPath', '');
|
||||
$extension = str_ends_with($source, '.tar') ? 'tar' : 'tar.gz';
|
||||
$command = $version === 'v2' ? '' : "cp /tmp/code.$extension /mnt/code/code.$extension && nohup helpers/start.sh \"$command\"";
|
||||
|
||||
@@ -365,7 +365,13 @@ class Messaging extends Action
|
||||
$message->setAttribute('deliveredTotal', $deliveredTotal);
|
||||
$message->setAttribute('deliveredAt', DateTime::now());
|
||||
|
||||
$dbForProject->updateDocument('messages', $message->getId(), $message);
|
||||
$dbForProject->updateDocument('messages', $message->getId(), new Document([
|
||||
'deliveryErrors' => $message->getAttribute('deliveryErrors'),
|
||||
'status' => $message->getAttribute('status'),
|
||||
'search' => $message->getAttribute('search'),
|
||||
'deliveredTotal' => $message->getAttribute('deliveredTotal'),
|
||||
'deliveredAt' => $message->getAttribute('deliveredAt'),
|
||||
]));
|
||||
|
||||
// Delete any attachments that were downloaded to local storage
|
||||
if ($provider->getAttribute('type') === MESSAGE_TYPE_EMAIL) {
|
||||
|
||||
@@ -571,11 +571,10 @@ class Migrations extends Action
|
||||
} finally {
|
||||
$message = "Export file size {$sizeMB}MB exceeds your plan limit.";
|
||||
|
||||
$this->dbForProject->updateDocument('migrations', $migration->getId(), $migration->setAttribute(
|
||||
'errors',
|
||||
json_encode(['code' => 0, 'message' => $message]),
|
||||
Document::SET_TYPE_APPEND,
|
||||
));
|
||||
$errors = $migration->getAttribute('errors', []);
|
||||
$errors[] = json_encode(['code' => 0, 'message' => $message]);
|
||||
$migration->setAttribute('errors', $errors);
|
||||
$migration = $this->updateMigrationDocument($migration, $project, $queueForRealtime);
|
||||
|
||||
$this->sendCSVEmail(
|
||||
success: false,
|
||||
|
||||
@@ -175,7 +175,7 @@ class StatsResources extends Action
|
||||
try {
|
||||
$this->countImageTransformations($dbForProject, $dbForLogs, $region);
|
||||
} catch (Throwable $th) {
|
||||
call_user_func_array($this->logError, [$th, "StatsResources", "count_for_buckets_{$project->getId()}"]);
|
||||
call_user_func_array($this->logError, [$th, "StatsResources", "count_for_image_transformations_{$project->getId()}"]);
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -202,12 +202,23 @@ class StatsResources extends Action
|
||||
$totalFiles = 0;
|
||||
$totalStorage = 0;
|
||||
$this->foreachDocument($dbForProject, 'buckets', [], function ($bucket) use ($dbForProject, $dbForLogs, $region, &$totalFiles, &$totalStorage) {
|
||||
$files = $dbForProject->count('bucket_' . $bucket->getSequence());
|
||||
try {
|
||||
$files = $dbForProject->count('bucket_' . $bucket->getSequence());
|
||||
} catch (Throwable $th) {
|
||||
call_user_func_array($this->logError, [$th, "StatsResources", "count_for_bucket_{$bucket->getSequence()}"]);
|
||||
return;
|
||||
}
|
||||
|
||||
$metric = str_replace('{bucketInternalId}', $bucket->getSequence(), METRIC_BUCKET_ID_FILES);
|
||||
$this->createStatsDocuments($region, $metric, $files);
|
||||
|
||||
$storage = $dbForProject->sum('bucket_' . $bucket->getSequence(), 'sizeActual');
|
||||
try {
|
||||
$storage = $dbForProject->sum('bucket_' . $bucket->getSequence(), 'sizeActual');
|
||||
} catch (Throwable $th) {
|
||||
call_user_func_array($this->logError, [$th, "StatsResources", "sum_for_bucket_{$bucket->getSequence()}"]);
|
||||
return;
|
||||
}
|
||||
|
||||
$metric = str_replace('{bucketInternalId}', $bucket->getSequence(), METRIC_BUCKET_ID_FILES_STORAGE);
|
||||
$this->createStatsDocuments($region, $metric, $storage);
|
||||
|
||||
@@ -227,9 +238,15 @@ class StatsResources extends Action
|
||||
$totalImageTransformations = 0;
|
||||
$last30Days = (new \DateTime())->sub(\DateInterval::createFromDateString('30 days'))->format('Y-m-d 00:00:00');
|
||||
$this->foreachDocument($dbForProject, 'buckets', [], function ($bucket) use ($dbForProject, $last30Days, $region, &$totalImageTransformations) {
|
||||
$imageTransformations = $dbForProject->count('bucket_' . $bucket->getSequence(), [
|
||||
Query::greaterThanEqual('transformedAt', $last30Days),
|
||||
]);
|
||||
try {
|
||||
$imageTransformations = $dbForProject->count('bucket_' . $bucket->getSequence(), [
|
||||
Query::greaterThanEqual('transformedAt', $last30Days),
|
||||
]);
|
||||
} catch (Throwable $th) {
|
||||
call_user_func_array($this->logError, [$th, "StatsResources", "count_for_image_transformations_bucket_{$bucket->getSequence()}"]);
|
||||
return;
|
||||
}
|
||||
|
||||
$metric = str_replace('{bucketInternalId}', $bucket->getSequence(), METRIC_BUCKET_ID_FILES_IMAGES_TRANSFORMED);
|
||||
$this->createStatsDocuments($region, $metric, $imageTransformations);
|
||||
$totalImageTransformations += $imageTransformations;
|
||||
|
||||
@@ -168,12 +168,15 @@ class Webhooks extends Action
|
||||
|
||||
$webhook->setAttribute('logs', $logs);
|
||||
|
||||
$updatePayload = ['logs' => $logs];
|
||||
|
||||
if ($attempts >= \intval(System::getEnv('_APP_WEBHOOK_MAX_FAILED_ATTEMPTS', '10'))) {
|
||||
$webhook->setAttribute('enabled', false);
|
||||
$updatePayload['enabled'] = false;
|
||||
$this->sendEmailAlert($attempts, $statusCode, $webhook, $project, $dbForPlatform, $queueForMails, $plan);
|
||||
}
|
||||
|
||||
$dbForPlatform->updateDocument('webhooks', $webhook->getId(), $webhook);
|
||||
$dbForPlatform->updateDocument('webhooks', $webhook->getId(), new Document($updatePayload));
|
||||
$dbForPlatform->purgeCachedDocument('projects', $project->getId());
|
||||
|
||||
$this->errors[] = $logs;
|
||||
@@ -184,8 +187,9 @@ class Webhooks extends Action
|
||||
|
||||
|
||||
} else {
|
||||
$webhook->setAttribute('attempts', 0); // Reset attempts on success
|
||||
$dbForPlatform->updateDocument('webhooks', $webhook->getId(), $webhook);
|
||||
$dbForPlatform->updateDocument('webhooks', $webhook->getId(), new Document([
|
||||
'attempts' => 0,
|
||||
]));
|
||||
$dbForPlatform->purgeCachedDocument('projects', $project->getId());
|
||||
$queueForStatsUsage
|
||||
->addMetric(METRIC_WEBHOOKS_SENT, 1)
|
||||
|
||||
@@ -6,7 +6,7 @@ use Appwrite\Utopia\Request\Filter;
|
||||
|
||||
class V21 extends Filter
|
||||
{
|
||||
// Convert 1.8.0 params to 1.8.1
|
||||
// Convert 1.8.0 params to 1.9.0
|
||||
public function parse(array $content, string $model): array
|
||||
{
|
||||
switch ($model) {
|
||||
@@ -14,6 +14,12 @@ class V21 extends Filter
|
||||
case 'sites.createTemplateDeployment':
|
||||
$content = $this->convertVersionToTypeAndReference($content);
|
||||
break;
|
||||
case 'functions.create':
|
||||
case 'sites.create':
|
||||
case 'functions.update':
|
||||
case 'sites.update':
|
||||
$content = $this->convertSpecs($content);
|
||||
break;
|
||||
}
|
||||
return $content;
|
||||
}
|
||||
@@ -31,4 +37,15 @@ class V21 extends Filter
|
||||
}
|
||||
return $content;
|
||||
}
|
||||
|
||||
protected function convertSpecs(array $content): array
|
||||
{
|
||||
if (!empty($content['specification'])) {
|
||||
$content['buildSpecification'] = $content['specification'];
|
||||
$content['runtimeSpecification'] = $content['specification'];
|
||||
unset($content['specification']);
|
||||
}
|
||||
|
||||
return $content;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Utopia\Response\Filters;
|
||||
|
||||
use Appwrite\Utopia\Response;
|
||||
use Appwrite\Utopia\Response\Filter;
|
||||
|
||||
// Convert 1.9 Data format to 1.8 format
|
||||
class V21 extends Filter
|
||||
{
|
||||
public function parse(array $content, string $model): array
|
||||
{
|
||||
$parsedResponse = $content;
|
||||
|
||||
return match ($model) {
|
||||
Response::MODEL_SITE => $this->parseSite($content),
|
||||
Response::MODEL_SITE_LIST => $this->handleList(
|
||||
$content,
|
||||
"sites",
|
||||
fn ($item) => $this->parseSite($item),
|
||||
),
|
||||
Response::MODEL_FUNCTION => $this->parseFunction($content),
|
||||
Response::MODEL_FUNCTION_LIST => $this->handleList(
|
||||
$content,
|
||||
"functions",
|
||||
fn ($item) => $this->parseFunction($item),
|
||||
),
|
||||
default => $parsedResponse,
|
||||
};
|
||||
}
|
||||
|
||||
protected function parseSite(array $content): array
|
||||
{
|
||||
$content = $this->parseSpecs($content);
|
||||
return $content;
|
||||
}
|
||||
|
||||
protected function parseFunction(array $content): array
|
||||
{
|
||||
$content = $this->parseSpecs($content);
|
||||
return $content;
|
||||
}
|
||||
|
||||
protected function parseSpecs(array $content): array
|
||||
{
|
||||
$content['specification'] = $content['buildSpecification'] ?? $content['specification'] ?? null;
|
||||
unset($content['buildSpecification']);
|
||||
unset($content['runtimeSpecification']);
|
||||
return $content;
|
||||
}
|
||||
}
|
||||
@@ -65,6 +65,12 @@ class Func extends Model
|
||||
'default' => '',
|
||||
'example' => 'python-3.8',
|
||||
])
|
||||
->addRule('deploymentRetention', [
|
||||
'type' => self::TYPE_INTEGER,
|
||||
'description' => 'How many days to keep the non-active deployments before they will be automatically deleted.',
|
||||
'default' => 0,
|
||||
'example' => 7,
|
||||
])
|
||||
->addRule('deploymentId', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Function\'s active deployment ID.',
|
||||
@@ -176,9 +182,15 @@ class Func extends Model
|
||||
'default' => false,
|
||||
'example' => false,
|
||||
])
|
||||
->addRule('specification', [
|
||||
->addRule('buildSpecification', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Machine specification for builds and executions.',
|
||||
'description' => 'Machine specification for deployment builds.',
|
||||
'default' => APP_COMPUTE_SPECIFICATION_DEFAULT,
|
||||
'example' => APP_COMPUTE_SPECIFICATION_DEFAULT,
|
||||
])
|
||||
->addRule('runtimeSpecification', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Machine specification for executions.',
|
||||
'default' => APP_COMPUTE_SPECIFICATION_DEFAULT,
|
||||
'example' => APP_COMPUTE_SPECIFICATION_DEFAULT,
|
||||
])
|
||||
|
||||
@@ -58,6 +58,12 @@ class Site extends Model
|
||||
'default' => '',
|
||||
'example' => 'react',
|
||||
])
|
||||
->addRule('deploymentRetention', [
|
||||
'type' => self::TYPE_INTEGER,
|
||||
'description' => 'How many days to keep the non-active deployments before they will be automatically deleted.',
|
||||
'default' => 0,
|
||||
'example' => 7,
|
||||
])
|
||||
->addRule('deploymentId', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Site\'s active deployment ID.',
|
||||
@@ -125,6 +131,12 @@ class Site extends Model
|
||||
'default' => '',
|
||||
'example' => 'npm run build',
|
||||
])
|
||||
->addRule('startCommand', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Custom command to use when starting site runtime.',
|
||||
'default' => '',
|
||||
'example' => 'node custom-server.mjs',
|
||||
])
|
||||
->addRule('outputDirectory', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'The directory where the site build output is located.',
|
||||
@@ -161,9 +173,15 @@ class Site extends Model
|
||||
'default' => false,
|
||||
'example' => false,
|
||||
])
|
||||
->addRule('specification', [
|
||||
->addRule('buildSpecification', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Machine specification for builds and executions.',
|
||||
'description' => 'Machine specification for deployment builds.',
|
||||
'default' => APP_COMPUTE_SPECIFICATION_DEFAULT,
|
||||
'example' => APP_COMPUTE_SPECIFICATION_DEFAULT,
|
||||
])
|
||||
->addRule('runtimeSpecification', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Machine specification for SSR executions.',
|
||||
'default' => APP_COMPUTE_SPECIFICATION_DEFAULT,
|
||||
'example' => APP_COMPUTE_SPECIFICATION_DEFAULT,
|
||||
])
|
||||
|
||||
@@ -251,7 +251,7 @@ class Comment
|
||||
$json = \base64_decode($state);
|
||||
|
||||
$builds = \json_decode($json, true);
|
||||
$this->builds = $builds;
|
||||
$this->builds = \is_array($builds) ? $builds : [];
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
@@ -128,7 +128,7 @@ class HooksTest extends Scope
|
||||
'cookie' => $cookie,
|
||||
]);
|
||||
|
||||
$this->assertEquals(401, $response['headers']['status-code']);
|
||||
$this->assertEquals(403, $response['headers']['status-code']);
|
||||
|
||||
/**
|
||||
* Test for api controllers
|
||||
@@ -140,7 +140,7 @@ class HooksTest extends Scope
|
||||
'cookie' => $cookie,
|
||||
]);
|
||||
|
||||
$this->assertEquals(401, $response['headers']['status-code']);
|
||||
$this->assertEquals(403, $response['headers']['status-code']);
|
||||
$this->assertEquals(Exception::USER_BLOCKED, $response['body']['type']);
|
||||
|
||||
/**
|
||||
|
||||
@@ -958,7 +958,8 @@ class UsageTest extends Scope
|
||||
],
|
||||
'schedule' => '0 0 1 1 *',
|
||||
'timeout' => 10,
|
||||
'specification' => Specification::S_8VCPU_8GB
|
||||
'buildSpecification' => Specification::S_8VCPU_8GB,
|
||||
'runtimeSpecification' => Specification::S_4VCPU_4GB,
|
||||
]
|
||||
);
|
||||
|
||||
|
||||
@@ -102,67 +102,79 @@ trait ProjectCustom
|
||||
$this->assertEquals(201, $project['headers']['status-code'], 'Project creation failed with status: ' . $project['headers']['status-code']);
|
||||
$this->assertNotEmpty($project['body']);
|
||||
|
||||
$key = $this->client->call(Client::METHOD_POST, '/projects/' . $project['body']['$id'] . '/keys', [
|
||||
'origin' => 'http://localhost',
|
||||
'content-type' => 'application/json',
|
||||
'cookie' => 'a_session_console=' . $this->getRoot()['session'],
|
||||
'x-appwrite-project' => 'console',
|
||||
], [
|
||||
'keyId' => ID::unique(),
|
||||
'name' => 'Demo Project Key',
|
||||
'scopes' => [
|
||||
'users.read',
|
||||
'users.write',
|
||||
'teams.read',
|
||||
'teams.write',
|
||||
'databases.read',
|
||||
'databases.write',
|
||||
'collections.read',
|
||||
'collections.write',
|
||||
'tables.read',
|
||||
'tables.write',
|
||||
'documents.read',
|
||||
'documents.write',
|
||||
'rows.read',
|
||||
'rows.write',
|
||||
'files.read',
|
||||
'files.write',
|
||||
'buckets.read',
|
||||
'buckets.write',
|
||||
'sites.read',
|
||||
'sites.write',
|
||||
'functions.read',
|
||||
'functions.write',
|
||||
'sites.read',
|
||||
'sites.write',
|
||||
'execution.read',
|
||||
'execution.write',
|
||||
'log.read',
|
||||
'log.write',
|
||||
'locale.read',
|
||||
'avatars.read',
|
||||
'health.read',
|
||||
'rules.read',
|
||||
'rules.write',
|
||||
'sessions.write',
|
||||
'targets.read',
|
||||
'targets.write',
|
||||
'providers.read',
|
||||
'providers.write',
|
||||
'messages.read',
|
||||
'messages.write',
|
||||
'topics.write',
|
||||
'topics.read',
|
||||
'subscribers.write',
|
||||
'subscribers.read',
|
||||
'migrations.write',
|
||||
'migrations.read',
|
||||
'tokens.read',
|
||||
'tokens.write',
|
||||
],
|
||||
]);
|
||||
$key = null;
|
||||
for ($i = 0; $i < $maxRetries; $i++) {
|
||||
$key = $this->client->call(Client::METHOD_POST, '/projects/' . $project['body']['$id'] . '/keys', [
|
||||
'origin' => 'http://localhost',
|
||||
'content-type' => 'application/json',
|
||||
'cookie' => 'a_session_console=' . $this->getRoot()['session'],
|
||||
'x-appwrite-project' => 'console',
|
||||
], [
|
||||
'keyId' => ID::unique(),
|
||||
'name' => 'Demo Project Key',
|
||||
'scopes' => [
|
||||
'users.read',
|
||||
'users.write',
|
||||
'teams.read',
|
||||
'teams.write',
|
||||
'databases.read',
|
||||
'databases.write',
|
||||
'collections.read',
|
||||
'collections.write',
|
||||
'tables.read',
|
||||
'tables.write',
|
||||
'documents.read',
|
||||
'documents.write',
|
||||
'rows.read',
|
||||
'rows.write',
|
||||
'files.read',
|
||||
'files.write',
|
||||
'buckets.read',
|
||||
'buckets.write',
|
||||
'sites.read',
|
||||
'sites.write',
|
||||
'functions.read',
|
||||
'functions.write',
|
||||
'sites.read',
|
||||
'sites.write',
|
||||
'execution.read',
|
||||
'execution.write',
|
||||
'log.read',
|
||||
'log.write',
|
||||
'locale.read',
|
||||
'avatars.read',
|
||||
'health.read',
|
||||
'rules.read',
|
||||
'rules.write',
|
||||
'sessions.write',
|
||||
'targets.read',
|
||||
'targets.write',
|
||||
'providers.read',
|
||||
'providers.write',
|
||||
'messages.read',
|
||||
'messages.write',
|
||||
'topics.write',
|
||||
'topics.read',
|
||||
'subscribers.write',
|
||||
'subscribers.read',
|
||||
'migrations.write',
|
||||
'migrations.read',
|
||||
'tokens.read',
|
||||
'tokens.write',
|
||||
],
|
||||
]);
|
||||
|
||||
$this->assertEquals(201, $key['headers']['status-code']);
|
||||
if ($key['headers']['status-code'] === 201) {
|
||||
break;
|
||||
}
|
||||
|
||||
if ($key['headers']['status-code'] === 401 && $i < $maxRetries - 1) {
|
||||
\usleep(500000);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
$this->assertEquals(201, $key['headers']['status-code'], 'Key creation failed with status: ' . $key['headers']['status-code']);
|
||||
$this->assertNotEmpty($key['body']);
|
||||
$this->assertNotEmpty($key['body']['secret']);
|
||||
|
||||
|
||||
+72
-37
@@ -59,12 +59,21 @@ abstract class Scope extends TestCase
|
||||
|
||||
$root = $this->getRoot();
|
||||
|
||||
$response = $this->client->call(Client::METHOD_GET, '/console/variables', [
|
||||
'origin' => 'http://localhost',
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => 'console',
|
||||
'cookie' => 'a_session_console=' . $root['session'],
|
||||
]);
|
||||
for ($i = 0; $i < 3; $i++) {
|
||||
$response = $this->client->call(Client::METHOD_GET, '/console/variables', [
|
||||
'origin' => 'http://localhost',
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => 'console',
|
||||
'cookie' => 'a_session_console=' . $root['session'],
|
||||
]);
|
||||
|
||||
if ($response['headers']['status-code'] === 200 && !empty($response['body'])) {
|
||||
self::$consoleVariables = $response['body'];
|
||||
return self::$consoleVariables;
|
||||
}
|
||||
|
||||
\usleep(500000);
|
||||
}
|
||||
|
||||
self::$consoleVariables = $response['body'] ?? [];
|
||||
|
||||
@@ -140,7 +149,7 @@ abstract class Scope extends TestCase
|
||||
*/
|
||||
protected function getMaxIndexLength(): int
|
||||
{
|
||||
return $this->getConsoleVariables()['maxIndexLength'] ?? 768;
|
||||
return $this->getConsoleVariables()['maxIndexLength'] ?? 767;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -432,41 +441,67 @@ abstract class Scope extends TestCase
|
||||
return self::$root;
|
||||
}
|
||||
|
||||
// Use more entropy to avoid collisions in parallel test execution
|
||||
$email = uniqid('', true) . getmypid() . bin2hex(random_bytes(4)) . '@localhost.test';
|
||||
$password = 'password';
|
||||
$name = 'User Name';
|
||||
$maxRetries = 5;
|
||||
|
||||
$root = $this->client->call(Client::METHOD_POST, '/account', [
|
||||
'origin' => 'http://localhost',
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => 'console',
|
||||
], [
|
||||
'userId' => ID::unique(),
|
||||
'email' => $email,
|
||||
'password' => $password,
|
||||
'name' => $name,
|
||||
]);
|
||||
for ($attempt = 0; $attempt < $maxRetries; $attempt++) {
|
||||
// Use more entropy to avoid collisions in parallel test execution
|
||||
$email = uniqid('', true) . getmypid() . bin2hex(random_bytes(4)) . '@localhost.test';
|
||||
$password = 'password';
|
||||
$name = 'User Name';
|
||||
|
||||
$this->assertEquals(201, $root['headers']['status-code']);
|
||||
$root = $this->client->call(Client::METHOD_POST, '/account', [
|
||||
'origin' => 'http://localhost',
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => 'console',
|
||||
], [
|
||||
'userId' => ID::unique(),
|
||||
'email' => $email,
|
||||
'password' => $password,
|
||||
'name' => $name,
|
||||
]);
|
||||
|
||||
$session = $this->client->call(Client::METHOD_POST, '/account/sessions/email', [
|
||||
'origin' => 'http://localhost',
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => 'console',
|
||||
], [
|
||||
'email' => $email,
|
||||
'password' => $password,
|
||||
]);
|
||||
if ($root['headers']['status-code'] !== 201) {
|
||||
\usleep(500000);
|
||||
continue;
|
||||
}
|
||||
|
||||
self::$root = [
|
||||
'$id' => ID::custom($root['body']['$id']),
|
||||
'name' => $root['body']['name'],
|
||||
'email' => $root['body']['email'],
|
||||
'session' => $session['cookies']['a_session_console'],
|
||||
];
|
||||
$session = $this->client->call(Client::METHOD_POST, '/account/sessions/email', [
|
||||
'origin' => 'http://localhost',
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => 'console',
|
||||
], [
|
||||
'email' => $email,
|
||||
'password' => $password,
|
||||
]);
|
||||
|
||||
return self::$root;
|
||||
if (empty($session['cookies']['a_session_console'])) {
|
||||
\usleep(500000);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Verify session is valid before returning
|
||||
$verify = $this->client->call(Client::METHOD_GET, '/account', [
|
||||
'origin' => 'http://localhost',
|
||||
'content-type' => 'application/json',
|
||||
'cookie' => 'a_session_console=' . $session['cookies']['a_session_console'],
|
||||
'x-appwrite-project' => 'console',
|
||||
]);
|
||||
|
||||
if ($verify['headers']['status-code'] === 200) {
|
||||
self::$root = [
|
||||
'$id' => ID::custom($root['body']['$id']),
|
||||
'name' => $root['body']['name'],
|
||||
'email' => $root['body']['email'],
|
||||
'session' => $session['cookies']['a_session_console'],
|
||||
];
|
||||
|
||||
return self::$root;
|
||||
}
|
||||
|
||||
\usleep(500000);
|
||||
}
|
||||
|
||||
$this->fail('Failed to create and verify root session after ' . $maxRetries . ' attempts');
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -2360,7 +2360,7 @@ class AccountCustomClientTest extends Scope
|
||||
'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
|
||||
]));
|
||||
|
||||
$this->assertEquals(401, $response['headers']['status-code']);
|
||||
$this->assertEquals(403, $response['headers']['status-code']);
|
||||
|
||||
$response = $this->client->call(Client::METHOD_POST, '/account/sessions/email', array_merge([
|
||||
'origin' => 'http://localhost',
|
||||
@@ -2371,7 +2371,7 @@ class AccountCustomClientTest extends Scope
|
||||
'password' => $password,
|
||||
]);
|
||||
|
||||
$this->assertEquals(401, $response['headers']['status-code']);
|
||||
$this->assertEquals(403, $response['headers']['status-code']);
|
||||
}
|
||||
|
||||
|
||||
@@ -2440,7 +2440,7 @@ class AccountCustomClientTest extends Scope
|
||||
'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session,
|
||||
]));
|
||||
|
||||
$this->assertEquals(401, $response['headers']['status-code']);
|
||||
$this->assertEquals(403, $response['headers']['status-code']);
|
||||
|
||||
$response = $this->client->call(Client::METHOD_POST, '/account/sessions/email', array_merge([
|
||||
'origin' => 'http://localhost',
|
||||
@@ -2451,7 +2451,7 @@ class AccountCustomClientTest extends Scope
|
||||
'password' => $password,
|
||||
]);
|
||||
|
||||
$this->assertEquals(401, $response['headers']['status-code']);
|
||||
$this->assertEquals(403, $response['headers']['status-code']);
|
||||
}
|
||||
|
||||
public function testCreateJWT(): void
|
||||
|
||||
@@ -1270,8 +1270,7 @@ trait DatabasesBase
|
||||
]);
|
||||
|
||||
$this->assertEquals(400, $attribute['headers']['status-code']);
|
||||
$maxLength = $this->getMaxIndexLength();
|
||||
$this->assertStringContainsString('Index length is longer than the maximum: '.$maxLength, $attribute['body']['message']);
|
||||
$this->assertStringContainsString('Index length is longer than the maximum:', $attribute['body']['message']);
|
||||
}
|
||||
|
||||
public function testUpdateAttributeEnum(): void
|
||||
|
||||
@@ -6,6 +6,7 @@ use Tests\E2E\Client;
|
||||
use Tests\E2E\Scopes\ProjectCustom;
|
||||
use Tests\E2E\Scopes\Scope;
|
||||
use Tests\E2E\Scopes\SideConsole;
|
||||
use Utopia\Console;
|
||||
use Utopia\Database\Helpers\ID;
|
||||
use Utopia\Database\Helpers\Role;
|
||||
|
||||
@@ -653,4 +654,52 @@ class FunctionsConsoleClientTest extends Scope
|
||||
|
||||
$this->cleanupFunction($functionId);
|
||||
}
|
||||
|
||||
public function testFunctionDeploymentRetentionWithMaintenance(): void
|
||||
{
|
||||
$functionId = $this->setupFunction([
|
||||
'functionId' => ID::unique(),
|
||||
'name' => 'Test retention function',
|
||||
'runtime' => 'node-22',
|
||||
'entrypoint' => 'index.js',
|
||||
'deploymentRetention' => 180
|
||||
]);
|
||||
$this->assertNotEmpty($functionId);
|
||||
|
||||
$deploymentIdInactive = $this->setupDeployment($functionId, [
|
||||
'code' => $this->packageFunction('node'),
|
||||
'activate' => true
|
||||
]);
|
||||
$this->assertNotEmpty($deploymentIdInactive);
|
||||
|
||||
$deploymentIdInactiveOld = $this->setupDeployment($functionId, [
|
||||
'code' => $this->packageFunction('node'),
|
||||
'activate' => true
|
||||
]);
|
||||
$this->assertNotEmpty($deploymentIdInactiveOld);
|
||||
|
||||
$deploymentIdActive = $this->setupDeployment($functionId, [
|
||||
'code' => $this->packageFunction('node'),
|
||||
'activate' => true
|
||||
]);
|
||||
$this->assertNotEmpty($deploymentIdActive);
|
||||
|
||||
$stdout = '';
|
||||
$stderr = '';
|
||||
$code = Console::execute("docker exec appwrite time-travel --projectId={$this->getProject()['$id']} --resourceType=deployment --resourceId={$deploymentIdInactiveOld} --createdAt=2020-01-01T00:00:00Z", '', $stdout, $stderr);
|
||||
$this->assertSame(0, $code, "Time-travel command failed with code $code: $stderr ($stdout)");
|
||||
|
||||
$stdout = '';
|
||||
$stderr = '';
|
||||
$code = Console::execute("docker exec appwrite maintenance --type=trigger", '', $stdout, $stderr);
|
||||
$this->assertSame(0, $code, "Maintenance command failed with code $code: $stderr ($stdout)");
|
||||
|
||||
$this->assertEventually(function () use ($functionId) {
|
||||
$response = $this->listDeployments($functionId);
|
||||
$this->assertSame(200, $response['headers']['status-code']);
|
||||
$this->assertSame(2, $response['body']['total']);
|
||||
});
|
||||
|
||||
$this->cleanupFunction($functionId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -196,25 +196,33 @@ class FunctionsCustomServerTest extends Scope
|
||||
{
|
||||
$specifications = $this->listSpecifications();
|
||||
$this->assertEquals(200, $specifications['headers']['status-code']);
|
||||
$this->assertGreaterThan(0, $specifications['body']['total']);
|
||||
$this->assertGreaterThanOrEqual(2, $specifications['body']['total']);
|
||||
$this->assertArrayHasKey(0, $specifications['body']['specifications']);
|
||||
$this->assertArrayHasKey(1, $specifications['body']['specifications']);
|
||||
$this->assertArrayHasKey('memory', $specifications['body']['specifications'][0]);
|
||||
$this->assertArrayHasKey('cpus', $specifications['body']['specifications'][0]);
|
||||
$this->assertArrayHasKey('enabled', $specifications['body']['specifications'][0]);
|
||||
$this->assertArrayHasKey('slug', $specifications['body']['specifications'][0]);
|
||||
$this->assertArrayHasKey('memory', $specifications['body']['specifications'][1]);
|
||||
$this->assertArrayHasKey('cpus', $specifications['body']['specifications'][1]);
|
||||
$this->assertArrayHasKey('enabled', $specifications['body']['specifications'][1]);
|
||||
$this->assertArrayHasKey('slug', $specifications['body']['specifications'][1]);
|
||||
|
||||
$function = $this->createFunction([
|
||||
'functionId' => ID::unique(),
|
||||
'name' => 'Specs function',
|
||||
'runtime' => 'node-22',
|
||||
'specification' => $specifications['body']['specifications'][0]['slug']
|
||||
'buildSpecification' => $specifications['body']['specifications'][0]['slug'],
|
||||
'runtimeSpecification' => $specifications['body']['specifications'][1]['slug'],
|
||||
]);
|
||||
$this->assertEquals(201, $function['headers']['status-code']);
|
||||
$this->assertEquals($specifications['body']['specifications'][0]['slug'], $function['body']['specification']);
|
||||
$this->assertEquals($specifications['body']['specifications'][0]['slug'], $function['body']['buildSpecification']);
|
||||
$this->assertEquals($specifications['body']['specifications'][1]['slug'], $function['body']['runtimeSpecification']);
|
||||
|
||||
$function = $this->getFunction($function['body']['$id']);
|
||||
$this->assertEquals(200, $function['headers']['status-code']);
|
||||
$this->assertEquals($specifications['body']['specifications'][0]['slug'], $function['body']['specification']);
|
||||
$this->assertEquals($specifications['body']['specifications'][0]['slug'], $function['body']['buildSpecification']);
|
||||
$this->assertEquals($specifications['body']['specifications'][1]['slug'], $function['body']['runtimeSpecification']);
|
||||
|
||||
$this->cleanupFunction($function['body']['$id']);
|
||||
|
||||
@@ -222,7 +230,15 @@ class FunctionsCustomServerTest extends Scope
|
||||
'functionId' => ID::unique(),
|
||||
'name' => 'Specs function',
|
||||
'runtime' => 'node-22',
|
||||
'specification' => 'cheap-please'
|
||||
'buildSpecification' => 'cheap-please'
|
||||
]);
|
||||
$this->assertEquals(400, $function['headers']['status-code']);
|
||||
|
||||
$function = $this->createFunction([
|
||||
'functionId' => ID::unique(),
|
||||
'name' => 'Specs function',
|
||||
'runtime' => 'node-22',
|
||||
'runtimeSpecification' => 'cheap-please'
|
||||
]);
|
||||
$this->assertEquals(400, $function['headers']['status-code']);
|
||||
}
|
||||
@@ -1552,12 +1568,12 @@ class FunctionsCustomServerTest extends Scope
|
||||
'timeout' => 15,
|
||||
'runtime' => 'node-22',
|
||||
'entrypoint' => 'index.js',
|
||||
'specification' => Specification::S_1VCPU_1GB,
|
||||
'runtimeSpecification' => Specification::S_1VCPU_1GB,
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $function['headers']['status-code']);
|
||||
$this->assertNotEmpty($function['body']['$id']);
|
||||
$this->assertEquals(Specification::S_1VCPU_1GB, $function['body']['specification']);
|
||||
$this->assertEquals(Specification::S_1VCPU_1GB, $function['body']['runtimeSpecification']);
|
||||
|
||||
// Verify the updated specs
|
||||
$execution = $this->createExecution($functionId);
|
||||
@@ -1586,12 +1602,12 @@ class FunctionsCustomServerTest extends Scope
|
||||
'timeout' => 15,
|
||||
'runtime' => 'node-22',
|
||||
'entrypoint' => 'index.js',
|
||||
'specification' => Specification::S_1VCPU_512MB,
|
||||
'runtimeSpecification' => Specification::S_1VCPU_512MB,
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $function['headers']['status-code']);
|
||||
$this->assertNotEmpty($function['body']['$id']);
|
||||
$this->assertEquals(Specification::S_1VCPU_512MB, $function['body']['specification']);
|
||||
$this->assertEquals(Specification::S_1VCPU_512MB, $function['body']['runtimeSpecification']);
|
||||
|
||||
// Verify the updated specs
|
||||
$execution = $this->createExecution($functionId);
|
||||
@@ -1616,11 +1632,29 @@ class FunctionsCustomServerTest extends Scope
|
||||
'timeout' => 15,
|
||||
'runtime' => 'node-22',
|
||||
'entrypoint' => 'index.js',
|
||||
'specification' => 's-2vcpu-512mb', // Invalid specification
|
||||
'buildSpecification' => 's-2vcpu-512mb', // Invalid specification
|
||||
]);
|
||||
|
||||
$this->assertEquals(400, $function['headers']['status-code']);
|
||||
$this->assertStringStartsWith('Invalid `specification` param: Specification must be one of:', $function['body']['message']);
|
||||
$this->assertStringStartsWith('Invalid `buildSpecification` param: Specification must be one of:', $function['body']['message']);
|
||||
|
||||
$function = $this->client->call(Client::METHOD_PUT, '/functions/' . $data['functionId'], array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'name' => 'Test1',
|
||||
'events' => [
|
||||
'users.*.update.name',
|
||||
'users.*.update.email',
|
||||
],
|
||||
'timeout' => 15,
|
||||
'runtime' => 'node-22',
|
||||
'entrypoint' => 'index.js',
|
||||
'runtimeSpecification' => 's-2vcpu-512mb', // Invalid specification
|
||||
]);
|
||||
|
||||
$this->assertEquals(400, $function['headers']['status-code']);
|
||||
$this->assertStringStartsWith('Invalid `runtimeSpecification` param: Specification must be one of:', $function['body']['message']);
|
||||
}
|
||||
|
||||
public function testDeleteDeployment(): void
|
||||
@@ -2221,6 +2255,8 @@ class FunctionsCustomServerTest extends Scope
|
||||
$this->assertEquals(201, $response['headers']['status-code']);
|
||||
$this->assertArrayNotHasKey('scopes', $response['body']);
|
||||
$this->assertArrayNotHasKey('specification', $response['body']);
|
||||
$this->assertArrayNotHasKey('buildSpecification', $response['body']);
|
||||
$this->assertArrayNotHasKey('runtimeSpecification', $response['body']);
|
||||
|
||||
// get function with 1.5.0 response format header
|
||||
$function = $this->client->call(Client::METHOD_GET, '/functions/' . $response['body']['$id'], array_merge([
|
||||
@@ -2231,13 +2267,31 @@ class FunctionsCustomServerTest extends Scope
|
||||
|
||||
$this->assertEquals(200, $function['headers']['status-code']);
|
||||
$this->assertArrayNotHasKey('scopes', $function['body']);
|
||||
$this->assertArrayNotHasKey('buildSpecification', $function['body']);
|
||||
$this->assertArrayNotHasKey('runtimeSpecification', $function['body']);
|
||||
$this->assertArrayNotHasKey('specification', $function['body']);
|
||||
|
||||
$function = $this->getFunction($function['body']['$id']);
|
||||
// get function with 1.8.0 response format header
|
||||
$function = $this->client->call(Client::METHOD_GET, '/functions/' . $response['body']['$id'], array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-response-format' => '1.8.0', // add response format header
|
||||
], $this->getHeaders()));
|
||||
|
||||
$this->assertEquals(200, $function['headers']['status-code']);
|
||||
$this->assertArrayHasKey('scopes', $function['body']);
|
||||
$this->assertArrayHasKey('specification', $function['body']);
|
||||
$this->assertArrayNotHasKey('buildSpecification', $function['body']);
|
||||
$this->assertArrayNotHasKey('runtimeSpecification', $function['body']);
|
||||
|
||||
// get function with latest version
|
||||
$function = $this->getFunction($function['body']['$id']);
|
||||
|
||||
$this->assertEquals(200, $function['headers']['status-code']);
|
||||
$this->assertArrayHasKey('scopes', $function['body']);
|
||||
$this->assertArrayNotHasKey('specification', $function['body']);
|
||||
$this->assertArrayHasKey('buildSpecification', $function['body']);
|
||||
$this->assertArrayHasKey('runtimeSpecification', $function['body']);
|
||||
|
||||
$functionId = $function['body']['$id'] ?? '';
|
||||
$this->cleanupFunction($functionId);
|
||||
@@ -2387,15 +2441,17 @@ class FunctionsCustomServerTest extends Scope
|
||||
'entrypoint' => 'index.js',
|
||||
'logging' => false,
|
||||
'execute' => ['any'],
|
||||
'specification' => Specification::S_2VCPU_2GB,
|
||||
'buildSpecification' => Specification::S_2VCPU_2GB,
|
||||
'runtimeSpecification' => Specification::S_1VCPU_1GB,
|
||||
'commands' => 'echo $APPWRITE_FUNCTION_MEMORY:$APPWRITE_FUNCTION_CPUS',
|
||||
]);
|
||||
|
||||
$this->assertEquals(201, $function['headers']['status-code']);
|
||||
$this->assertEquals(Specification::S_2VCPU_2GB, $function['body']['specification']);
|
||||
$this->assertEquals(Specification::S_2VCPU_2GB, $function['body']['buildSpecification']);
|
||||
$this->assertEquals(Specification::S_1VCPU_1GB, $function['body']['runtimeSpecification']);
|
||||
$this->assertNotEmpty($function['body']['$id']);
|
||||
|
||||
$functionId = $functionId = $function['body']['$id'] ?? '';
|
||||
$functionId = $function['body']['$id'] ?? '';
|
||||
|
||||
$deploymentId = $this->setupDeployment($functionId, [
|
||||
'code' => $this->packageFunction('basic'),
|
||||
@@ -2414,8 +2470,8 @@ class FunctionsCustomServerTest extends Scope
|
||||
$this->assertNotEmpty($execution['body']['$id']);
|
||||
|
||||
$executionResponse = json_decode($execution['body']['responseBody'], true);
|
||||
$this->assertEquals('2048', $executionResponse['APPWRITE_FUNCTION_MEMORY']);
|
||||
$this->assertEquals('2', $executionResponse['APPWRITE_FUNCTION_CPUS']);
|
||||
$this->assertEquals('1024', $executionResponse['APPWRITE_FUNCTION_MEMORY']);
|
||||
$this->assertEquals('1', $executionResponse['APPWRITE_FUNCTION_CPUS']);
|
||||
|
||||
$this->cleanupFunction($functionId);
|
||||
}
|
||||
@@ -2771,7 +2827,6 @@ class FunctionsCustomServerTest extends Scope
|
||||
$this->assertLessThanOrEqual(APP_FUNCTION_LOG_LENGTH_LIMIT, strlen($logs));
|
||||
$this->assertStringStartsWith('[WARNING] Logs truncated', $logs);
|
||||
|
||||
$this->assertStringNotContainsString('z', $logs);
|
||||
$this->assertStringContainsString('a', $logs);
|
||||
|
||||
// Verify errors are truncated and warning message is present at the beginning
|
||||
@@ -2779,9 +2834,123 @@ class FunctionsCustomServerTest extends Scope
|
||||
$this->assertLessThanOrEqual(APP_FUNCTION_ERROR_LENGTH_LIMIT, strlen($errors));
|
||||
$this->assertStringStartsWith('[WARNING] Errors truncated', $errors);
|
||||
|
||||
$this->assertStringNotContainsString('z', $errors);
|
||||
$this->assertStringContainsString('a', $errors);
|
||||
|
||||
$this->cleanupFunction($functionId);
|
||||
}
|
||||
|
||||
public function testFunctionDeploymentRetention(): void
|
||||
{
|
||||
$functionIds = [];
|
||||
|
||||
// Default
|
||||
$response = $this->createFunction([
|
||||
'functionId' => ID::unique(),
|
||||
'name' => 'Test retention function',
|
||||
'runtime' => 'node-22',
|
||||
]);
|
||||
$this->assertSame(201, $response['headers']['status-code']);
|
||||
$this->assertSame(0, $response['body']['deploymentRetention']);
|
||||
$functionIds[] = $response['body']['$id'];
|
||||
|
||||
$response = $this->getFunction($response['body']['$id']);
|
||||
$this->assertSame(200, $response['headers']['status-code']);
|
||||
$this->assertSame(0, $response['body']['deploymentRetention']);
|
||||
|
||||
// Success values
|
||||
$response = $this->createFunction([
|
||||
'functionId' => ID::unique(),
|
||||
'name' => 'Test retention function',
|
||||
'runtime' => 'node-22',
|
||||
'deploymentRetention' => 0
|
||||
]);
|
||||
$this->assertSame(201, $response['headers']['status-code']);
|
||||
$this->assertSame(0, $response['body']['deploymentRetention']);
|
||||
$functionIds[] = $response['body']['$id'];
|
||||
|
||||
$response = $this->getFunction($response['body']['$id']);
|
||||
$this->assertSame(200, $response['headers']['status-code']);
|
||||
$this->assertSame(0, $response['body']['deploymentRetention']);
|
||||
|
||||
$response = $this->createFunction([
|
||||
'functionId' => ID::unique(),
|
||||
'name' => 'Test retention function',
|
||||
'runtime' => 'node-22',
|
||||
'deploymentRetention' => 180
|
||||
]);
|
||||
$this->assertSame(201, $response['headers']['status-code']);
|
||||
$this->assertSame(180, $response['body']['deploymentRetention']);
|
||||
$functionIds[] = $response['body']['$id'];
|
||||
|
||||
$response = $this->getFunction($response['body']['$id']);
|
||||
$this->assertSame(200, $response['headers']['status-code']);
|
||||
$this->assertSame(180, $response['body']['deploymentRetention']);
|
||||
|
||||
// Failure values
|
||||
$response = $this->createFunction([
|
||||
'functionId' => ID::unique(),
|
||||
'name' => 'Test retention function',
|
||||
'runtime' => 'node-22',
|
||||
'deploymentRetention' => 999999
|
||||
]);
|
||||
$this->assertSame(400, $response['headers']['status-code']);
|
||||
|
||||
$response = $this->createFunction([
|
||||
'functionId' => ID::unique(),
|
||||
'name' => 'Test retention function',
|
||||
'runtime' => 'node-22',
|
||||
'deploymentRetention' => -1
|
||||
]);
|
||||
$this->assertSame(400, $response['headers']['status-code']);
|
||||
|
||||
// Update flow
|
||||
$response = $this->createFunction([
|
||||
'functionId' => ID::unique(),
|
||||
'name' => 'Test retention function',
|
||||
'runtime' => 'node-22',
|
||||
'deploymentRetention' => 180
|
||||
]);
|
||||
$this->assertSame(201, $response['headers']['status-code']);
|
||||
$this->assertSame(180, $response['body']['deploymentRetention']);
|
||||
$functionIds[] = $response['body']['$id'];
|
||||
$functionIdForUpdate = $response['body']['$id'];
|
||||
|
||||
$response = $this->updateFunction($functionIdForUpdate, [
|
||||
'name' => 'Test retention function',
|
||||
'deploymentRetention' => 90
|
||||
]);
|
||||
$this->assertSame(200, $response['headers']['status-code']);
|
||||
$this->assertSame(90, $response['body']['deploymentRetention']);
|
||||
|
||||
$response = $this->getFunction($functionIdForUpdate);
|
||||
$this->assertSame(200, $response['headers']['status-code']);
|
||||
$this->assertSame(90, $response['body']['deploymentRetention']);
|
||||
|
||||
$response = $this->updateFunction($functionIdForUpdate, [
|
||||
'name' => 'Test retention function',
|
||||
]);
|
||||
$this->assertSame(200, $response['headers']['status-code']);
|
||||
$this->assertSame(0, $response['body']['deploymentRetention']);
|
||||
|
||||
$response = $this->getFunction($functionIdForUpdate);
|
||||
$this->assertSame(200, $response['headers']['status-code']);
|
||||
$this->assertSame(0, $response['body']['deploymentRetention']);
|
||||
|
||||
// Failed update flow
|
||||
$response = $this->updateFunction($functionIdForUpdate, [
|
||||
'name' => 'Test retention function',
|
||||
'deploymentRetention' => -1
|
||||
]);
|
||||
$this->assertSame(400, $response['headers']['status-code']);
|
||||
|
||||
$response = $this->updateFunction($functionIdForUpdate, [
|
||||
'name' => 'Test retention function',
|
||||
'deploymentRetention' => 999999
|
||||
]);
|
||||
$this->assertSame(400, $response['headers']['status-code']);
|
||||
|
||||
foreach ($functionIds as $functionId) {
|
||||
$this->cleanupFunction($functionId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,26 +26,39 @@ trait ProjectsBase
|
||||
return self::$cachedProjectData;
|
||||
}
|
||||
|
||||
$team = $this->client->call(Client::METHOD_POST, '/teams', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'teamId' => ID::unique(),
|
||||
'name' => 'Project Test',
|
||||
]);
|
||||
|
||||
$this->assertEquals(201, $team['headers']['status-code']);
|
||||
|
||||
$project = $this->client->call(Client::METHOD_POST, '/projects', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'projectId' => ID::unique(),
|
||||
'name' => 'Project Test',
|
||||
'teamId' => $team['body']['$id'],
|
||||
'region' => System::getEnv('_APP_REGION', 'default')
|
||||
]);
|
||||
$teamId = ID::unique();
|
||||
$team = null;
|
||||
for ($i = 0; $i < 3; $i++) {
|
||||
$team = $this->client->call(Client::METHOD_POST, '/teams', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'teamId' => $teamId,
|
||||
'name' => 'Project Test',
|
||||
]);
|
||||
if (\in_array($team['headers']['status-code'], [201, 409])) {
|
||||
break;
|
||||
}
|
||||
\usleep(500000);
|
||||
}
|
||||
$this->assertContains($team['headers']['status-code'], [201, 409]);
|
||||
|
||||
$project = null;
|
||||
for ($i = 0; $i < 3; $i++) {
|
||||
$project = $this->client->call(Client::METHOD_POST, '/projects', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'projectId' => ID::unique(),
|
||||
'name' => 'Project Test',
|
||||
'teamId' => $team['body']['$id'] ?? $teamId,
|
||||
'region' => System::getEnv('_APP_REGION', 'default')
|
||||
]);
|
||||
if ($project['headers']['status-code'] === 201) {
|
||||
break;
|
||||
}
|
||||
\usleep(500000);
|
||||
}
|
||||
$this->assertEquals(201, $project['headers']['status-code']);
|
||||
|
||||
self::$cachedProjectData = [
|
||||
@@ -396,27 +409,42 @@ trait ProjectsBase
|
||||
protected function setupProject(mixed $params, ?string $teamId = null, bool $newTeam = true): string
|
||||
{
|
||||
if ($newTeam) {
|
||||
$team = $this->client->call(Client::METHOD_POST, '/teams', array_merge([
|
||||
$generatedTeamId = $teamId ?? ID::unique();
|
||||
$team = null;
|
||||
for ($i = 0; $i < 3; $i++) {
|
||||
$team = $this->client->call(Client::METHOD_POST, '/teams', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'teamId' => $generatedTeamId,
|
||||
'name' => 'Project Test',
|
||||
]);
|
||||
if (\in_array($team['headers']['status-code'], [201, 409])) {
|
||||
break;
|
||||
}
|
||||
\usleep(500000);
|
||||
}
|
||||
|
||||
$this->assertContains($team['headers']['status-code'], [201, 409], 'Setup team failed with status code: ' . $team['headers']['status-code'] . ' and response: ' . json_encode($team['body'], JSON_PRETTY_PRINT));
|
||||
|
||||
$teamId = $team['body']['$id'] ?? $generatedTeamId;
|
||||
}
|
||||
|
||||
$project = null;
|
||||
for ($i = 0; $i < 3; $i++) {
|
||||
$project = $this->client->call(Client::METHOD_POST, '/projects', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'teamId' => $teamId ?? ID::unique(),
|
||||
'name' => 'Project Test',
|
||||
...$params,
|
||||
'teamId' => $teamId,
|
||||
]);
|
||||
|
||||
$this->assertEquals(201, $team['headers']['status-code'], 'Setup team failed with status code: ' . $team['headers']['status-code'] . ' and response: ' . json_encode($team['body'], JSON_PRETTY_PRINT));
|
||||
|
||||
$teamId = $team['body']['$id'];
|
||||
if ($project['headers']['status-code'] === 201) {
|
||||
break;
|
||||
}
|
||||
\usleep(500000);
|
||||
}
|
||||
|
||||
$project = $this->client->call(Client::METHOD_POST, '/projects', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
...$params,
|
||||
'teamId' => $teamId,
|
||||
]);
|
||||
|
||||
$this->assertEquals(201, $project['headers']['status-code'], 'Setup project failed with status code: ' . $project['headers']['status-code'] . ' and response: ' . json_encode($project['body'], JSON_PRETTY_PRINT));
|
||||
|
||||
return $project['body']['$id'];
|
||||
|
||||
@@ -951,26 +951,22 @@ class ProjectsConsoleClientTest extends Scope
|
||||
|
||||
$this->assertEquals(204, $response['headers']['status-code']);
|
||||
|
||||
$emails = $this->getLastEmail(2);
|
||||
$this->assertCount(2, $emails);
|
||||
$this->assertEquals('custommailer@appwrite.io', $emails[0]['from'][0]['address']);
|
||||
$this->assertEquals('Custom Mailer', $emails[0]['from'][0]['name']);
|
||||
$this->assertEquals('reply@appwrite.io', $emails[0]['replyTo'][0]['address']);
|
||||
$this->assertEquals('Custom Mailer', $emails[0]['replyTo'][0]['name']);
|
||||
$this->assertEquals('Custom SMTP email sample', $emails[0]['subject']);
|
||||
$this->assertStringContainsStringIgnoringCase('working correctly', $emails[0]['text']);
|
||||
$this->assertStringContainsStringIgnoringCase('working correctly', $emails[0]['html']);
|
||||
$this->assertStringContainsStringIgnoringCase('251 Little Falls Drive', $emails[0]['text']);
|
||||
$this->assertStringContainsStringIgnoringCase('251 Little Falls Drive', $emails[0]['html']);
|
||||
$smtpProbe = function ($email) {
|
||||
$this->assertEquals('Custom SMTP email sample', $email['subject']);
|
||||
};
|
||||
$email1 = $this->getLastEmailByAddress('testuser@appwrite.io', $smtpProbe);
|
||||
$email2 = $this->getLastEmailByAddress('testusertwo@appwrite.io', $smtpProbe);
|
||||
|
||||
$to = [
|
||||
$emails[0]['to'][0]['address'],
|
||||
$emails[1]['to'][0]['address']
|
||||
];
|
||||
\sort($to);
|
||||
|
||||
$this->assertEquals('testuser@appwrite.io', $to[0]);
|
||||
$this->assertEquals('testusertwo@appwrite.io', $to[1]);
|
||||
$this->assertEquals('custommailer@appwrite.io', $email1['from'][0]['address']);
|
||||
$this->assertEquals('Custom Mailer', $email1['from'][0]['name']);
|
||||
$this->assertEquals('reply@appwrite.io', $email1['replyTo'][0]['address']);
|
||||
$this->assertEquals('Custom Mailer', $email1['replyTo'][0]['name']);
|
||||
$this->assertEquals('Custom SMTP email sample', $email1['subject']);
|
||||
$this->assertStringContainsStringIgnoringCase('working correctly', $email1['text']);
|
||||
$this->assertStringContainsStringIgnoringCase('working correctly', $email1['html']);
|
||||
$this->assertStringContainsStringIgnoringCase('251 Little Falls Drive', $email1['text']);
|
||||
$this->assertStringContainsStringIgnoringCase('251 Little Falls Drive', $email1['html']);
|
||||
$this->assertEquals('custommailer@appwrite.io', $email2['from'][0]['address']);
|
||||
|
||||
$response = $this->client->call(Client::METHOD_POST, '/projects/' . $id . '/smtp/tests', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
@@ -6427,11 +6423,12 @@ class ProjectsConsoleClientTest extends Scope
|
||||
|
||||
$userId = $response['body']['userId'];
|
||||
|
||||
$lastEmail = $this->getLastEmail(1, function ($email) use ($url) {
|
||||
$userEmail = $this->getUser()['email'];
|
||||
|
||||
$lastEmail = $this->getLastEmailByAddress($userEmail, function ($email) use ($url) {
|
||||
$this->assertStringContainsString($url, $email['html'] ?? '');
|
||||
});
|
||||
|
||||
$this->assertEquals($this->getUser()['email'], $lastEmail['to'][0]['address']);
|
||||
$this->assertEquals('Password Reset for ' . $this->getProject()['name'], $lastEmail['subject']);
|
||||
|
||||
$expectedUrl = $url . "&userId=" . $userId . "&secret=";
|
||||
@@ -6450,7 +6447,7 @@ class ProjectsConsoleClientTest extends Scope
|
||||
], $this->getHeaders()),
|
||||
[
|
||||
'userId' => ID::unique(),
|
||||
'email' => $this->getUser()['email'],
|
||||
'email' => $userEmail,
|
||||
'url' => $url,
|
||||
]
|
||||
);
|
||||
@@ -6460,11 +6457,10 @@ class ProjectsConsoleClientTest extends Scope
|
||||
|
||||
$userId = $response['body']['userId'];
|
||||
|
||||
$lastEmail = $this->getLastEmail(1, function ($email) use ($url) {
|
||||
$lastEmail = $this->getLastEmailByAddress($userEmail, function ($email) use ($url) {
|
||||
$this->assertStringContainsString($url, $email['html'] ?? '');
|
||||
});
|
||||
|
||||
$this->assertEquals($this->getUser()['email'], $lastEmail['to'][0]['address']);
|
||||
$this->assertEquals('Password Reset for ' . $this->getProject()['name'], $lastEmail['subject']);
|
||||
|
||||
$expectedUrl = $url . "&userId=" . $userId . "&secret=";
|
||||
@@ -6483,7 +6479,7 @@ class ProjectsConsoleClientTest extends Scope
|
||||
], $this->getHeaders()),
|
||||
[
|
||||
'userId' => ID::unique(),
|
||||
'email' => $this->getUser()['email'],
|
||||
'email' => $userEmail,
|
||||
'url' => $url,
|
||||
]
|
||||
);
|
||||
@@ -6493,11 +6489,10 @@ class ProjectsConsoleClientTest extends Scope
|
||||
|
||||
$userId = $response['body']['userId'];
|
||||
|
||||
$lastEmail = $this->getLastEmail(1, function ($email) use ($url, $userId) {
|
||||
$lastEmail = $this->getLastEmailByAddress($userEmail, function ($email) use ($url, $userId) {
|
||||
$this->assertStringContainsString($url . '?userId=' . $userId, $email['html'] ?? '');
|
||||
});
|
||||
|
||||
$this->assertEquals($this->getUser()['email'], $lastEmail['to'][0]['address']);
|
||||
$this->assertEquals('Password Reset for ' . $this->getProject()['name'], $lastEmail['subject']);
|
||||
|
||||
$expectedUrl = $url . "?userId=" . $userId . "&secret=";
|
||||
@@ -6516,7 +6511,7 @@ class ProjectsConsoleClientTest extends Scope
|
||||
], $this->getHeaders()),
|
||||
[
|
||||
'userId' => ID::unique(),
|
||||
'email' => $this->getUser()['email'],
|
||||
'email' => $userEmail,
|
||||
'url' => $url,
|
||||
]
|
||||
);
|
||||
@@ -6526,11 +6521,10 @@ class ProjectsConsoleClientTest extends Scope
|
||||
|
||||
$userId = $response['body']['userId'];
|
||||
|
||||
$lastEmail = $this->getLastEmail(1, function ($email) use ($url, $userId) {
|
||||
$lastEmail = $this->getLastEmailByAddress($userEmail, function ($email) use ($url, $userId) {
|
||||
$this->assertStringContainsString($url . '?userId=' . $userId, $email['html'] ?? '');
|
||||
});
|
||||
|
||||
$this->assertEquals($this->getUser()['email'], $lastEmail['to'][0]['address']);
|
||||
$this->assertEquals('Password Reset for ' . $this->getProject()['name'], $lastEmail['subject']);
|
||||
|
||||
$expectedUrl = $url . "?userId=" . $userId . "&secret=";
|
||||
@@ -6549,7 +6543,7 @@ class ProjectsConsoleClientTest extends Scope
|
||||
], $this->getHeaders()),
|
||||
[
|
||||
'userId' => ID::unique(),
|
||||
'email' => $this->getUser()['email'],
|
||||
'email' => $userEmail,
|
||||
'url' => $url,
|
||||
]
|
||||
);
|
||||
@@ -6559,11 +6553,10 @@ class ProjectsConsoleClientTest extends Scope
|
||||
|
||||
$userId = $response['body']['userId'];
|
||||
|
||||
$lastEmail = $this->getLastEmail(1, function ($email) {
|
||||
$lastEmail = $this->getLastEmailByAddress($userEmail, function ($email) {
|
||||
$this->assertStringContainsString('INJECTED', $email['html'] ?? '');
|
||||
});
|
||||
|
||||
$this->assertEquals($this->getUser()['email'], $lastEmail['to'][0]['address']);
|
||||
$this->assertEquals('Password Reset for ' . $this->getProject()['name'], $lastEmail['subject']);
|
||||
|
||||
$this->assertStringContainsString('INJECTED', $lastEmail['html']);
|
||||
|
||||
@@ -16,26 +16,39 @@ trait SchedulesBase
|
||||
return self::$cachedScheduleProjectData;
|
||||
}
|
||||
|
||||
$team = $this->client->call(Client::METHOD_POST, '/teams', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'teamId' => ID::unique(),
|
||||
'name' => 'Schedule Test Team',
|
||||
]);
|
||||
|
||||
$this->assertEquals(201, $team['headers']['status-code']);
|
||||
|
||||
$project = $this->client->call(Client::METHOD_POST, '/projects', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'projectId' => ID::unique(),
|
||||
'name' => 'Schedule Test Project',
|
||||
'teamId' => $team['body']['$id'],
|
||||
'region' => System::getEnv('_APP_REGION', 'default'),
|
||||
]);
|
||||
$teamId = ID::unique();
|
||||
$team = null;
|
||||
for ($i = 0; $i < 3; $i++) {
|
||||
$team = $this->client->call(Client::METHOD_POST, '/teams', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'teamId' => $teamId,
|
||||
'name' => 'Schedule Test Team',
|
||||
]);
|
||||
if (\in_array($team['headers']['status-code'], [201, 409])) {
|
||||
break;
|
||||
}
|
||||
\usleep(500000);
|
||||
}
|
||||
$this->assertContains($team['headers']['status-code'], [201, 409]);
|
||||
|
||||
$project = null;
|
||||
for ($i = 0; $i < 3; $i++) {
|
||||
$project = $this->client->call(Client::METHOD_POST, '/projects', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'projectId' => ID::unique(),
|
||||
'name' => 'Schedule Test Project',
|
||||
'teamId' => $team['body']['$id'] ?? $teamId,
|
||||
'region' => System::getEnv('_APP_REGION', 'default'),
|
||||
]);
|
||||
if ($project['headers']['status-code'] === 201) {
|
||||
break;
|
||||
}
|
||||
\usleep(500000);
|
||||
}
|
||||
$this->assertEquals(201, $project['headers']['status-code']);
|
||||
|
||||
$projectId = $project['body']['$id'];
|
||||
|
||||
@@ -7,6 +7,7 @@ use Tests\E2E\Client;
|
||||
use Tests\E2E\Scopes\ProjectCustom;
|
||||
use Tests\E2E\Scopes\Scope;
|
||||
use Tests\E2E\Scopes\SideConsole;
|
||||
use Utopia\Console;
|
||||
use Utopia\Database\Helpers\ID;
|
||||
|
||||
class SitesConsoleClientTest extends Scope
|
||||
@@ -147,4 +148,52 @@ class SitesConsoleClientTest extends Scope
|
||||
|
||||
$this->cleanupSite($siteId);
|
||||
}
|
||||
|
||||
public function testSiteDeploymentRetentionWithMaintenance(): void
|
||||
{
|
||||
$siteId = $this->setupSite([
|
||||
'siteId' => ID::unique(),
|
||||
'name' => 'Test retention site',
|
||||
'framework' => 'other',
|
||||
'deploymentRetention' => 180,
|
||||
'buildRuntime' => 'node-22',
|
||||
]);
|
||||
$this->assertNotEmpty($siteId);
|
||||
|
||||
$deploymentIdInactive = $this->setupDeployment($siteId, [
|
||||
'code' => $this->packageSite('static'),
|
||||
'activate' => true
|
||||
]);
|
||||
$this->assertNotEmpty($deploymentIdInactive);
|
||||
|
||||
$deploymentIdInactiveOld = $this->setupDeployment($siteId, [
|
||||
'code' => $this->packageSite('static'),
|
||||
'activate' => true
|
||||
]);
|
||||
$this->assertNotEmpty($deploymentIdInactiveOld);
|
||||
|
||||
$deploymentIdActive = $this->setupDeployment($siteId, [
|
||||
'code' => $this->packageSite('static'),
|
||||
'activate' => true
|
||||
]);
|
||||
$this->assertNotEmpty($deploymentIdActive);
|
||||
|
||||
$stdout = '';
|
||||
$stderr = '';
|
||||
$code = Console::execute("docker exec appwrite-task-maintenance time-travel --projectId={$this->getProject()['$id']} --resourceType=deployment --resourceId={$deploymentIdInactiveOld} --createdAt=2020-01-01T00:00:00Z", '', $stdout, $stderr);
|
||||
$this->assertSame(0, $code, "Time-travel command failed with code $code: $stderr ($stdout)");
|
||||
|
||||
$stdout = '';
|
||||
$stderr = '';
|
||||
$code = Console::execute("docker exec appwrite-task-maintenance maintenance --type=trigger", '', $stdout, $stderr);
|
||||
$this->assertSame(0, $code, "Maintenance command failed with code $code: $stderr ($stdout)");
|
||||
|
||||
$this->assertEventually(function () use ($siteId) {
|
||||
$response = $this->listDeployments($siteId);
|
||||
$this->assertSame(200, $response['headers']['status-code']);
|
||||
$this->assertSame(2, $response['body']['total']);
|
||||
});
|
||||
|
||||
$this->cleanupSite($siteId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,26 +26,34 @@ class SitesCustomServerTest extends Scope
|
||||
{
|
||||
$specifications = $this->listSpecifications();
|
||||
$this->assertEquals(200, $specifications['headers']['status-code']);
|
||||
$this->assertGreaterThan(0, $specifications['body']['total']);
|
||||
$this->assertGreaterThanOrEqual(2, $specifications['body']['total']);
|
||||
$this->assertArrayHasKey(0, $specifications['body']['specifications']);
|
||||
$this->assertArrayHasKey(1, $specifications['body']['specifications']);
|
||||
$this->assertArrayHasKey('memory', $specifications['body']['specifications'][0]);
|
||||
$this->assertArrayHasKey('cpus', $specifications['body']['specifications'][0]);
|
||||
$this->assertArrayHasKey('enabled', $specifications['body']['specifications'][0]);
|
||||
$this->assertArrayHasKey('slug', $specifications['body']['specifications'][0]);
|
||||
$this->assertArrayHasKey('memory', $specifications['body']['specifications'][1]);
|
||||
$this->assertArrayHasKey('cpus', $specifications['body']['specifications'][1]);
|
||||
$this->assertArrayHasKey('enabled', $specifications['body']['specifications'][1]);
|
||||
$this->assertArrayHasKey('slug', $specifications['body']['specifications'][1]);
|
||||
|
||||
$site = $this->createSite([
|
||||
'buildRuntime' => 'node-22',
|
||||
'framework' => 'other',
|
||||
'name' => 'Specs site',
|
||||
'siteId' => ID::unique(),
|
||||
'specification' => $specifications['body']['specifications'][0]['slug']
|
||||
'buildSpecification' => $specifications['body']['specifications'][0]['slug'],
|
||||
'runtimeSpecification' => $specifications['body']['specifications'][1]['slug'],
|
||||
]);
|
||||
$this->assertEquals(201, $site['headers']['status-code']);
|
||||
$this->assertEquals($specifications['body']['specifications'][0]['slug'], $site['body']['specification']);
|
||||
$this->assertEquals($specifications['body']['specifications'][0]['slug'], $site['body']['buildSpecification']);
|
||||
$this->assertEquals($specifications['body']['specifications'][1]['slug'], $site['body']['runtimeSpecification']);
|
||||
|
||||
$site = $this->getSite($site['body']['$id']);
|
||||
$this->assertEquals(200, $site['headers']['status-code']);
|
||||
$this->assertEquals($specifications['body']['specifications'][0]['slug'], $site['body']['specification']);
|
||||
$this->assertEquals($specifications['body']['specifications'][0]['slug'], $site['body']['buildSpecification']);
|
||||
$this->assertEquals($specifications['body']['specifications'][1]['slug'], $site['body']['runtimeSpecification']);
|
||||
|
||||
$this->cleanupSite($site['body']['$id']);
|
||||
|
||||
@@ -54,7 +62,16 @@ class SitesCustomServerTest extends Scope
|
||||
'framework' => 'other',
|
||||
'name' => 'Specs site',
|
||||
'siteId' => ID::unique(),
|
||||
'specification' => 'cheap-please'
|
||||
'buildSpecification' => 'cheap-please'
|
||||
]);
|
||||
$this->assertEquals(400, $site['headers']['status-code']);
|
||||
|
||||
$site = $this->createSite([
|
||||
'buildRuntime' => 'node-22',
|
||||
'framework' => 'other',
|
||||
'name' => 'Specs site',
|
||||
'siteId' => ID::unique(),
|
||||
'runtimeSpecification' => 'cheap-please'
|
||||
]);
|
||||
$this->assertEquals(400, $site['headers']['status-code']);
|
||||
}
|
||||
@@ -1292,12 +1309,12 @@ class SitesCustomServerTest extends Scope
|
||||
'providerBranch' => 'main',
|
||||
'providerRootDirectory' => './',
|
||||
'$id' => $siteId,
|
||||
'specification' => Specification::S_1VCPU_1GB,
|
||||
'runtimeSpecification' => Specification::S_1VCPU_1GB,
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $site['headers']['status-code']);
|
||||
$this->assertNotEmpty($site['body']['$id']);
|
||||
$this->assertEquals(Specification::S_1VCPU_1GB, $site['body']['specification']);
|
||||
$this->assertEquals(Specification::S_1VCPU_1GB, $site['body']['runtimeSpecification']);
|
||||
|
||||
// Change the specs to 1vcpu 512mb
|
||||
$site = $this->updateSite([
|
||||
@@ -1309,12 +1326,12 @@ class SitesCustomServerTest extends Scope
|
||||
'providerBranch' => 'main',
|
||||
'providerRootDirectory' => './',
|
||||
'$id' => $siteId,
|
||||
'specification' => Specification::S_1VCPU_512MB,
|
||||
'runtimeSpecification' => Specification::S_1VCPU_512MB,
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $site['headers']['status-code']);
|
||||
$this->assertNotEmpty($site['body']['$id']);
|
||||
$this->assertEquals(Specification::S_1VCPU_512MB, $site['body']['specification']);
|
||||
$this->assertEquals(Specification::S_1VCPU_512MB, $site['body']['runtimeSpecification']);
|
||||
|
||||
/**
|
||||
* Test for FAILURE
|
||||
@@ -1329,11 +1346,26 @@ class SitesCustomServerTest extends Scope
|
||||
'providerBranch' => 'main',
|
||||
'providerRootDirectory' => './',
|
||||
'$id' => $siteId,
|
||||
'specification' => 's-2vcpu-512mb', // Invalid specification
|
||||
'buildSpecification' => 's-2vcpu-512mb', // Invalid specification
|
||||
]);
|
||||
|
||||
$this->assertEquals(400, $site['headers']['status-code']);
|
||||
$this->assertStringStartsWith('Invalid `specification` param: Specification must be one of:', $site['body']['message']);
|
||||
$this->assertStringStartsWith('Invalid `buildSpecification` param: Specification must be one of:', $site['body']['message']);
|
||||
|
||||
$site = $this->updateSite([
|
||||
'buildRuntime' => 'node-22',
|
||||
'fallbackFile' => '',
|
||||
'framework' => 'other',
|
||||
'name' => 'Test Site',
|
||||
'outputDirectory' => './',
|
||||
'providerBranch' => 'main',
|
||||
'providerRootDirectory' => './',
|
||||
'$id' => $siteId,
|
||||
'runtimeSpecification' => 's-2vcpu-512mb', // Invalid specification
|
||||
]);
|
||||
|
||||
$this->assertEquals(400, $site['headers']['status-code']);
|
||||
$this->assertStringStartsWith('Invalid `runtimeSpecification` param: Specification must be one of:', $site['body']['message']);
|
||||
|
||||
$this->cleanupSite($siteId);
|
||||
}
|
||||
@@ -3006,4 +3038,244 @@ class SitesCustomServerTest extends Scope
|
||||
|
||||
$this->cleanupSite($siteId);
|
||||
}
|
||||
|
||||
public function testSiteCustomStartCommand(): void
|
||||
{
|
||||
$siteId = $this->setupSite([
|
||||
'siteId' => ID::unique(),
|
||||
'name' => 'Astro site',
|
||||
'framework' => 'astro',
|
||||
'adapter' => 'ssr',
|
||||
'startCommand' => 'node custom-server.js',
|
||||
'buildRuntime' => 'node-22',
|
||||
'outputDirectory' => './dist',
|
||||
'buildCommand' => 'npm run build',
|
||||
'installCommand' => 'npm install',
|
||||
'fallbackFile' => '',
|
||||
]);
|
||||
|
||||
$this->assertNotEmpty($siteId);
|
||||
|
||||
$domain = $this->setupSiteDomain($siteId);
|
||||
|
||||
$deploymentId = $this->setupDeployment($siteId, [
|
||||
'code' => $this->packageSite('astro-custom-start-command'),
|
||||
'activate' => 'true'
|
||||
]);
|
||||
|
||||
$this->assertNotEmpty($deploymentId);
|
||||
|
||||
$domain = $this->getSiteDomain($siteId);
|
||||
$proxyClient = new Client();
|
||||
$proxyClient->setEndpoint('http://' . $domain);
|
||||
|
||||
$response = $proxyClient->call(Client::METHOD_GET, '/');
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertStringContainsString("Homepage OK", $response['body']);
|
||||
|
||||
$response = $proxyClient->call(Client::METHOD_GET, '/ssr');
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertStringContainsString("SSR OK", $response['body']);
|
||||
$originalBody = $response['body'];
|
||||
$response = $proxyClient->call(Client::METHOD_GET, '/ssr');
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertStringContainsString("SSR OK", $response['body']);
|
||||
$this->assertNotEquals($originalBody, $response['body']); // Includes Date.now()
|
||||
|
||||
$response = $proxyClient->call(Client::METHOD_GET, '/ssr-custom');
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertStringContainsString("Custom SSR OK", $response['body']);
|
||||
$originalBody = $response['body'];
|
||||
$response = $proxyClient->call(Client::METHOD_GET, '/ssr-custom');
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertStringContainsString("Custom SSR OK", $response['body']);
|
||||
$this->assertNotEquals($originalBody, $response['body']); // Includes Date.now()
|
||||
|
||||
$response = $proxyClient->call(Client::METHOD_GET, '/non-existing');
|
||||
$this->assertEquals(500, $response['headers']['status-code']);
|
||||
$this->assertStringContainsString("Custom error", $response['body']);
|
||||
|
||||
$this->cleanupSite($siteId);
|
||||
}
|
||||
|
||||
public function testSiteSpecifications()
|
||||
{
|
||||
// Check if the site specifications are correctly set in builds
|
||||
$site = $this->createSite([
|
||||
'siteId' => ID::unique(),
|
||||
'name' => 'Astro site',
|
||||
'framework' => 'astro',
|
||||
'adapter' => 'ssr',
|
||||
'buildRuntime' => 'node-22',
|
||||
'outputDirectory' => './dist',
|
||||
'buildCommand' => 'npm run build && echo $APPWRITE_SITE_MEMORY:$APPWRITE_SITE_CPUS',
|
||||
'installCommand' => 'npm ci',
|
||||
'fallbackFile' => '',
|
||||
'buildSpecification' => Specification::S_1VCPU_1GB,
|
||||
'runtimeSpecification' => Specification::S_05VCPU_512MB,
|
||||
]);
|
||||
|
||||
$this->assertEquals(201, $site['headers']['status-code']);
|
||||
$this->assertEquals(Specification::S_1VCPU_1GB, $site['body']['buildSpecification']);
|
||||
$this->assertEquals(Specification::S_05VCPU_512MB, $site['body']['runtimeSpecification']);
|
||||
$this->assertNotEmpty($site['body']['$id']);
|
||||
|
||||
$siteId = $site['body']['$id'] ?? '';
|
||||
|
||||
$domain = $this->setupSiteDomain($siteId);
|
||||
|
||||
$deploymentId = $this->setupDeployment($siteId, [
|
||||
'code' => $this->packageSite('astro'),
|
||||
'activate' => true
|
||||
]);
|
||||
|
||||
$this->assertEventually(function () use ($siteId, $deploymentId) {
|
||||
$deployment = $this->getDeployment($siteId, $deploymentId);
|
||||
// TODO: This assertion is not testing what we set in create function, because build worker currently overrides to minimal build specs
|
||||
$this->assertStringContainsString('2048:1', $deployment['body']['buildLogs']);
|
||||
}, 10000, 500);
|
||||
|
||||
// Check if the sites specifications are correctly set in executions
|
||||
$proxyClient = new Client();
|
||||
$proxyClient->setEndpoint('http://' . $domain);
|
||||
|
||||
$response = $proxyClient->call(Client::METHOD_GET, '/specs');
|
||||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertNotEmpty($response['body']);
|
||||
$this->assertEquals('512', $response['body']['APPWRITE_SITE_MEMORY']);
|
||||
$this->assertEquals('0.5', $response['body']['APPWRITE_SITE_CPUS']);
|
||||
|
||||
$this->cleanupSite($siteId);
|
||||
}
|
||||
|
||||
public function testSiteDeploymentRetention(): void
|
||||
{
|
||||
$siteIds = [];
|
||||
|
||||
// Default
|
||||
$response = $this->createSite([
|
||||
'siteId' => ID::unique(),
|
||||
'name' => 'Test retention site',
|
||||
'framework' => 'other',
|
||||
'buildRuntime' => 'node-22',
|
||||
]);
|
||||
$this->assertSame(201, $response['headers']['status-code']);
|
||||
$this->assertSame(0, $response['body']['deploymentRetention']);
|
||||
$siteIds[] = $response['body']['$id'];
|
||||
|
||||
$response = $this->getSite($response['body']['$id']);
|
||||
$this->assertSame(200, $response['headers']['status-code']);
|
||||
$this->assertSame(0, $response['body']['deploymentRetention']);
|
||||
|
||||
// Success values
|
||||
$response = $this->createSite([
|
||||
'siteId' => ID::unique(),
|
||||
'name' => 'Test retention site',
|
||||
'framework' => 'other',
|
||||
'buildRuntime' => 'node-22',
|
||||
'deploymentRetention' => 0
|
||||
]);
|
||||
$this->assertSame(201, $response['headers']['status-code']);
|
||||
$this->assertSame(0, $response['body']['deploymentRetention']);
|
||||
$siteIds[] = $response['body']['$id'];
|
||||
|
||||
$response = $this->getSite($response['body']['$id']);
|
||||
$this->assertSame(200, $response['headers']['status-code']);
|
||||
$this->assertSame(0, $response['body']['deploymentRetention']);
|
||||
|
||||
$response = $this->createSite([
|
||||
'siteId' => ID::unique(),
|
||||
'name' => 'Test retention site',
|
||||
'framework' => 'other',
|
||||
'buildRuntime' => 'node-22',
|
||||
'deploymentRetention' => 180
|
||||
]);
|
||||
$this->assertSame(201, $response['headers']['status-code']);
|
||||
$this->assertSame(180, $response['body']['deploymentRetention']);
|
||||
$siteIds[] = $response['body']['$id'];
|
||||
|
||||
$response = $this->getSite($response['body']['$id']);
|
||||
$this->assertSame(200, $response['headers']['status-code']);
|
||||
$this->assertSame(180, $response['body']['deploymentRetention']);
|
||||
|
||||
// Failure values
|
||||
$response = $this->createSite([
|
||||
'siteId' => ID::unique(),
|
||||
'name' => 'Test retention site',
|
||||
'framework' => 'other',
|
||||
'buildRuntime' => 'node-22',
|
||||
'deploymentRetention' => 999999
|
||||
]);
|
||||
$this->assertSame(400, $response['headers']['status-code']);
|
||||
|
||||
$response = $this->createSite([
|
||||
'siteId' => ID::unique(),
|
||||
'name' => 'Test retention site',
|
||||
'framework' => 'other',
|
||||
'buildRuntime' => 'node-22',
|
||||
'deploymentRetention' => -1
|
||||
]);
|
||||
$this->assertSame(400, $response['headers']['status-code']);
|
||||
|
||||
// Update flow
|
||||
$response = $this->createSite([
|
||||
'siteId' => ID::unique(),
|
||||
'name' => 'Test retention site',
|
||||
'framework' => 'other',
|
||||
'buildRuntime' => 'node-22',
|
||||
'deploymentRetention' => 180
|
||||
]);
|
||||
$this->assertSame(201, $response['headers']['status-code']);
|
||||
$this->assertSame(180, $response['body']['deploymentRetention']);
|
||||
$siteIds[] = $response['body']['$id'];
|
||||
$siteIdToUpdate = $response['body']['$id'];
|
||||
|
||||
$response = $this->updateSite([
|
||||
'$id' => $siteIdToUpdate,
|
||||
'name' => 'Test retention site',
|
||||
'framework' => 'other',
|
||||
'deploymentRetention' => 90
|
||||
]);
|
||||
|
||||
$this->assertSame(200, $response['headers']['status-code']);
|
||||
$this->assertSame(90, $response['body']['deploymentRetention']);
|
||||
|
||||
$response = $this->getSite($siteIdToUpdate);
|
||||
$this->assertSame(200, $response['headers']['status-code']);
|
||||
$this->assertSame(90, $response['body']['deploymentRetention']);
|
||||
|
||||
$response = $this->updateSite([
|
||||
'$id' => $siteIdToUpdate,
|
||||
'name' => 'Test retention site',
|
||||
'framework' => 'other',
|
||||
]);
|
||||
$this->assertSame(200, $response['headers']['status-code']);
|
||||
$this->assertSame(0, $response['body']['deploymentRetention']);
|
||||
|
||||
$response = $this->getSite($siteIdToUpdate);
|
||||
$this->assertSame(200, $response['headers']['status-code']);
|
||||
$this->assertSame(0, $response['body']['deploymentRetention']);
|
||||
|
||||
// Failed update flow
|
||||
$response = $this->updateSite([
|
||||
'$id' => $siteIdToUpdate,
|
||||
'name' => 'Test retention site',
|
||||
'framework' => 'other',
|
||||
'deploymentRetention' => -1
|
||||
]);
|
||||
$this->assertSame(400, $response['headers']['status-code']);
|
||||
|
||||
$response = $this->updateSite([
|
||||
'$id' => $siteIdToUpdate,
|
||||
'name' => 'Test retention site',
|
||||
'framework' => 'other',
|
||||
'deploymentRetention' => 999999
|
||||
]);
|
||||
$this->assertSame(400, $response['headers']['status-code']);
|
||||
|
||||
foreach ($siteIds as $siteId) {
|
||||
$this->cleanupSite($siteId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
# build output
|
||||
dist/
|
||||
# generated types
|
||||
.astro/
|
||||
|
||||
# dependencies
|
||||
node_modules/
|
||||
|
||||
# logs
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
|
||||
|
||||
# environment variables
|
||||
.env
|
||||
.env.production
|
||||
|
||||
# macOS-specific files
|
||||
.DS_Store
|
||||
|
||||
# jetbrains setting folder
|
||||
.idea/
|
||||
@@ -0,0 +1,10 @@
|
||||
// @ts-check
|
||||
import { defineConfig } from 'astro/config';
|
||||
import node from '@astrojs/node';
|
||||
|
||||
// https://astro.build/config
|
||||
export default defineConfig({
|
||||
adapter: node({
|
||||
mode: 'middleware',
|
||||
}),
|
||||
});
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user