mirror of
https://github.com/appwrite/appwrite.git
synced 2026-05-26 13:51:13 +00:00
Merge remote-tracking branch 'origin/1.9.x' into big-int
This commit is contained in:
@@ -0,0 +1,29 @@
|
||||
# Patch Release Checklist for Appwrite
|
||||
|
||||
When bumping a patch version (e.g., `1.9.0` -> `1.9.1`), follow this checklist.
|
||||
|
||||
## Checklist
|
||||
|
||||
### Bump console image
|
||||
|
||||
Update the console Docker image tag in both files:
|
||||
- [ ] `docker-compose.yml` -- update `image: appwrite/console:X.Y.Z`
|
||||
- [ ] `app/views/install/compose.phtml` -- update `image: <?php echo $organization; ?>/console:X.Y.Z`
|
||||
|
||||
### Bump Appwrite version
|
||||
|
||||
- [ ] **`app/init/constants.php`** -- update `APP_VERSION_STABLE` to the new version (e.g., `'1.9.1'`). In same file, increment `APP_CACHE_BUSTER` by 1.
|
||||
- [ ] **`README.md`** -- update the Docker image tag `appwrite/appwrite:X.Y.Z` in all 3 install code blocks (Unix, Windows CMD, PowerShell).
|
||||
- [ ] **`README-CN.md`** -- same Docker image tag update in all 3 install code blocks.
|
||||
- [ ] **`src/Appwrite/Migration/Migration.php`** -- add the new version to the `$versions` array, mapping it to a migration class. If new class exists, use that, otherwise use sle same class as previous version
|
||||
|
||||
### Update CHANGES.md
|
||||
|
||||
- [ ] Add a new `# Version X.Y.Z` section at the top of `CHANGES.md` with subsections: `### Notable changes`, `### Fixes`, `### Miscellaneous`
|
||||
|
||||
## Final review
|
||||
|
||||
- [ ] Ask user to review changes before commiting
|
||||
- [ ] Ask user to update `CHANGES.md` with PRs
|
||||
- [ ] Ask user to generate specs, if needed
|
||||
- [ ] Ask user to add request and response filters, if needed
|
||||
@@ -512,7 +512,7 @@ jobs:
|
||||
# Services that rely on sequential test method execution (shared static state)
|
||||
FUNCTIONAL_FLAG="--functional"
|
||||
case "${{ matrix.service }}" in
|
||||
Databases|TablesDB|Functions|Realtime) FUNCTIONAL_FLAG="" ;;
|
||||
Databases|TablesDB|Functions|Realtime|GraphQL|ProjectWebhooks) FUNCTIONAL_FLAG="" ;;
|
||||
esac
|
||||
|
||||
docker compose exec -T \
|
||||
|
||||
@@ -115,6 +115,10 @@ Common injections: `$response`, `$request`, `$dbForProject`, `$dbForPlatform`, `
|
||||
- Never hardcode credentials -- use environment variables.
|
||||
- Code changes may require container restart. No central log location -- check relevant containers.
|
||||
|
||||
## Patch release process
|
||||
|
||||
For bumping patch versions (e.g., `1.9.0` -> `1.9.1`), follow the checklist in `.claude/skills/patch-release-checklist/SKILL.md`. It covers the 4 files that must be updated, console image bumps, CHANGES.md updates, and common pitfalls to avoid.
|
||||
|
||||
## Cross-repo context
|
||||
|
||||
Appwrite is the base server for `appwrite/cloud`. Changes to the Action pattern, module structure, DI system, or response models affect cloud. The `feat-dedicated-db` feature spans cloud, edge, and console.
|
||||
|
||||
+3
-3
@@ -72,7 +72,7 @@ docker run -it --rm \
|
||||
--volume /var/run/docker.sock:/var/run/docker.sock \
|
||||
--volume "$(pwd)"/appwrite:/usr/src/code/appwrite:rw \
|
||||
--entrypoint="install" \
|
||||
appwrite/appwrite:1.9.0
|
||||
appwrite/appwrite:1.9.1
|
||||
```
|
||||
|
||||
### Windows
|
||||
@@ -84,7 +84,7 @@ docker run -it --rm ^
|
||||
--volume //var/run/docker.sock:/var/run/docker.sock ^
|
||||
--volume "%cd%"/appwrite:/usr/src/code/appwrite:rw ^
|
||||
--entrypoint="install" ^
|
||||
appwrite/appwrite:1.9.0
|
||||
appwrite/appwrite:1.9.1
|
||||
```
|
||||
|
||||
#### PowerShell
|
||||
@@ -94,7 +94,7 @@ docker run -it --rm `
|
||||
--volume /var/run/docker.sock:/var/run/docker.sock `
|
||||
--volume ${pwd}/appwrite:/usr/src/code/appwrite:rw `
|
||||
--entrypoint="install" `
|
||||
appwrite/appwrite:1.9.0
|
||||
appwrite/appwrite:1.9.1
|
||||
```
|
||||
|
||||
运行后,可以在浏览器上访问 http://localhost 找到 Appwrite 控制台。在非 Linux 的本机主机上完成安装后,服务器可能需要几分钟才能启动。
|
||||
|
||||
@@ -75,7 +75,7 @@ docker run -it --rm \
|
||||
--volume /var/run/docker.sock:/var/run/docker.sock \
|
||||
--volume "$(pwd)"/appwrite:/usr/src/code/appwrite:rw \
|
||||
--entrypoint="install" \
|
||||
appwrite/appwrite:1.9.0
|
||||
appwrite/appwrite:1.9.1
|
||||
```
|
||||
|
||||
### Windows
|
||||
@@ -88,7 +88,7 @@ docker run -it --rm ^
|
||||
--volume //var/run/docker.sock:/var/run/docker.sock ^
|
||||
--volume "%cd%"/appwrite:/usr/src/code/appwrite:rw ^
|
||||
--entrypoint="install" ^
|
||||
appwrite/appwrite:1.9.0
|
||||
appwrite/appwrite:1.9.1
|
||||
```
|
||||
|
||||
#### PowerShell
|
||||
@@ -99,7 +99,7 @@ docker run -it --rm `
|
||||
--volume /var/run/docker.sock:/var/run/docker.sock `
|
||||
--volume ${pwd}/appwrite:/usr/src/code/appwrite:rw `
|
||||
--entrypoint="install" `
|
||||
appwrite/appwrite:1.9.0
|
||||
appwrite/appwrite:1.9.1
|
||||
```
|
||||
|
||||
Once the Docker installation is complete, go to http://localhost to access the Appwrite console from your browser. Please note that on non-Linux native hosts, the server might take a few minutes to start after completing the installation.
|
||||
|
||||
+10
-8
@@ -2,12 +2,12 @@
|
||||
|
||||
require_once __DIR__ . '/init.php';
|
||||
|
||||
use Appwrite\Event\Certificate;
|
||||
use Appwrite\Event\Delete;
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Event\Func;
|
||||
use Appwrite\Event\Publisher\Certificate as CertificatePublisher;
|
||||
use Appwrite\Event\Publisher\StatsResources as StatsResourcesPublisher;
|
||||
use Appwrite\Event\Publisher\Usage as UsagePublisher;
|
||||
use Appwrite\Event\StatsResources;
|
||||
use Appwrite\Platform\Appwrite;
|
||||
use Appwrite\Runtimes\Runtimes;
|
||||
use Appwrite\Usage\Context as UsageContext;
|
||||
@@ -253,18 +253,20 @@ $container->set('publisherForUsage', fn (Publisher $publisher) => new UsagePubli
|
||||
$publisher,
|
||||
new Queue(System::getEnv('_APP_STATS_USAGE_QUEUE_NAME', Event::STATS_USAGE_QUEUE_NAME))
|
||||
), ['publisher']);
|
||||
$container->set('queueForStatsResources', function (Publisher $publisher) {
|
||||
return new StatsResources($publisher);
|
||||
}, ['publisher']);
|
||||
$container->set('publisherForCertificates', fn (Publisher $publisher) => new CertificatePublisher(
|
||||
$publisher,
|
||||
new Queue(System::getEnv('_APP_CERTIFICATES_QUEUE_NAME', Event::CERTIFICATES_QUEUE_NAME))
|
||||
), ['publisher']);
|
||||
$container->set('publisherForStatsResources', fn (Publisher $publisher) => new StatsResourcesPublisher(
|
||||
$publisher,
|
||||
new Queue(System::getEnv('_APP_STATS_RESOURCES_QUEUE_NAME', Event::STATS_RESOURCES_QUEUE_NAME))
|
||||
), ['publisher']);
|
||||
$container->set('queueForFunctions', function (Publisher $publisher) {
|
||||
return new Func($publisher);
|
||||
}, ['publisher']);
|
||||
$container->set('queueForDeletes', function (Publisher $publisher) {
|
||||
return new Delete($publisher);
|
||||
}, ['publisher']);
|
||||
$container->set('queueForCertificates', function (Publisher $publisher) {
|
||||
return new Certificate($publisher);
|
||||
}, ['publisher']);
|
||||
$container->set('logError', function (Registry $register) {
|
||||
return function (Throwable $error, string $namespace, string $action) use ($register) {
|
||||
Console::error('[Error] Timestamp: ' . date('c', time()));
|
||||
|
||||
@@ -9,8 +9,8 @@ return [
|
||||
'key' => 'graphql',
|
||||
'name' => 'GraphQL',
|
||||
],
|
||||
'realtime' => [
|
||||
'key' => 'realtime',
|
||||
'name' => 'Realtime',
|
||||
'websocket' => [
|
||||
'key' => 'websocket',
|
||||
'name' => 'Websocket',
|
||||
],
|
||||
];
|
||||
@@ -137,7 +137,7 @@ return [
|
||||
'docs' => true,
|
||||
'docsUrl' => '',
|
||||
'tests' => false,
|
||||
'optional' => false,
|
||||
'optional' => true,
|
||||
'icon' => '',
|
||||
'platforms' => ['client', 'server', 'console'],
|
||||
],
|
||||
@@ -193,7 +193,7 @@ return [
|
||||
'docs' => false,
|
||||
'docsUrl' => '',
|
||||
'tests' => false,
|
||||
'optional' => false,
|
||||
'optional' => true,
|
||||
'icon' => '',
|
||||
'platforms' => ['client', 'server', 'console'],
|
||||
],
|
||||
@@ -235,7 +235,7 @@ return [
|
||||
'docs' => true,
|
||||
'docsUrl' => 'https://appwrite.io/docs/proxy',
|
||||
'tests' => false,
|
||||
'optional' => false,
|
||||
'optional' => true,
|
||||
'icon' => '/images/services/proxy.png',
|
||||
'platforms' => ['client', 'server', 'console'],
|
||||
],
|
||||
@@ -291,7 +291,7 @@ return [
|
||||
'docs' => true,
|
||||
'docsUrl' => 'https://appwrite.io/docs/migrations',
|
||||
'tests' => true,
|
||||
'optional' => false,
|
||||
'optional' => true,
|
||||
'icon' => '/images/services/migrations.png',
|
||||
'platforms' => ['client', 'server', 'console'],
|
||||
],
|
||||
|
||||
@@ -872,18 +872,18 @@ return [
|
||||
],
|
||||
[
|
||||
'name' => '_APP_FUNCTIONS_BUILD_TIMEOUT',
|
||||
'description' => 'Deprecated since 1.7.0. The maximum number of seconds allowed as a timeout value when building a new function. The default value is 900 seconds.',
|
||||
'description' => 'Deprecated since 1.7.0. The maximum number of seconds allowed as a timeout value when building a new function. The default value is 2700 seconds.',
|
||||
'introduction' => '0.13.0',
|
||||
'default' => '900',
|
||||
'default' => '2700',
|
||||
'required' => false,
|
||||
'question' => '',
|
||||
'filter' => ''
|
||||
],
|
||||
[
|
||||
'name' => '_APP_COMPUTE_BUILD_TIMEOUT',
|
||||
'description' => 'The maximum number of seconds allowed as a timeout value when building a new function or site. The default value is 900 seconds.',
|
||||
'description' => 'The maximum number of seconds allowed as a timeout value when building a new function or site. The default value is 2700 seconds.',
|
||||
'introduction' => '1.7.0',
|
||||
'default' => '900',
|
||||
'default' => '2700',
|
||||
'required' => false,
|
||||
'question' => '',
|
||||
'filter' => ''
|
||||
|
||||
+34
-170
@@ -9,6 +9,7 @@ use Appwrite\Auth\Validator\PasswordDictionary;
|
||||
use Appwrite\Auth\Validator\PasswordHistory;
|
||||
use Appwrite\Auth\Validator\PersonalData;
|
||||
use Appwrite\Auth\Validator\Phone;
|
||||
use Appwrite\Bus\Events\SessionCreated;
|
||||
use Appwrite\Detector\Detector;
|
||||
use Appwrite\Event\Delete;
|
||||
use Appwrite\Event\Event;
|
||||
@@ -41,6 +42,7 @@ use Utopia\Auth\Proofs\Code as ProofsCode;
|
||||
use Utopia\Auth\Proofs\Password as ProofsPassword;
|
||||
use Utopia\Auth\Proofs\Token as ProofsToken;
|
||||
use Utopia\Auth\Store;
|
||||
use Utopia\Bus\Bus;
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\DateTime;
|
||||
@@ -75,139 +77,8 @@ use Utopia\Validator\WhiteList;
|
||||
$oauthDefaultSuccess = '/console/auth/oauth2/success';
|
||||
$oauthDefaultFailure = '/console/auth/oauth2/failure';
|
||||
|
||||
function sendSessionAlert(Locale $locale, Document $user, Document $project, array $platform, Document $session, Mail $queueForMails)
|
||||
{
|
||||
$subject = $locale->getText("emails.sessionAlert.subject");
|
||||
$preview = $locale->getText("emails.sessionAlert.preview");
|
||||
$customTemplate = $project->getAttribute('templates', [])['email.sessionAlert-' . $locale->default] ?? [];
|
||||
$smtpBaseTemplate = $project->getAttribute('smtpBaseTemplate', 'email-base');
|
||||
|
||||
$validator = new FileName();
|
||||
if (!$validator->isValid($smtpBaseTemplate)) {
|
||||
throw new Exception(Exception::GENERAL_BAD_REQUEST, 'Invalid template path');
|
||||
}
|
||||
|
||||
$bodyTemplate = __DIR__ . '/../../config/locale/templates/' . $smtpBaseTemplate . '.tpl';
|
||||
|
||||
$message = Template::fromFile(__DIR__ . '/../../config/locale/templates/email-session-alert.tpl');
|
||||
$message
|
||||
->setParam('{{hello}}', $locale->getText("emails.sessionAlert.hello"))
|
||||
->setParam('{{body}}', $locale->getText("emails.sessionAlert.body"))
|
||||
->setParam('{{listDevice}}', $locale->getText("emails.sessionAlert.listDevice"))
|
||||
->setParam('{{listIpAddress}}', $locale->getText("emails.sessionAlert.listIpAddress"))
|
||||
->setParam('{{listCountry}}', $locale->getText("emails.sessionAlert.listCountry"))
|
||||
->setParam('{{footer}}', $locale->getText("emails.sessionAlert.footer"))
|
||||
->setParam('{{thanks}}', $locale->getText("emails.sessionAlert.thanks"))
|
||||
->setParam('{{signature}}', $locale->getText("emails.sessionAlert.signature"));
|
||||
|
||||
$body = $message->render();
|
||||
|
||||
$smtp = $project->getAttribute('smtp', []);
|
||||
$smtpEnabled = $smtp['enabled'] ?? false;
|
||||
|
||||
$senderEmail = System::getEnv('_APP_SYSTEM_EMAIL_ADDRESS', APP_EMAIL_TEAM);
|
||||
$senderName = System::getEnv('_APP_SYSTEM_EMAIL_NAME', APP_NAME . ' Server');
|
||||
$replyTo = "";
|
||||
|
||||
if ($smtpEnabled) {
|
||||
if (!empty($smtp['senderEmail'])) {
|
||||
$senderEmail = $smtp['senderEmail'];
|
||||
}
|
||||
if (!empty($smtp['senderName'])) {
|
||||
$senderName = $smtp['senderName'];
|
||||
}
|
||||
if (!empty($smtp['replyTo'])) {
|
||||
$replyTo = $smtp['replyTo'];
|
||||
}
|
||||
|
||||
$queueForMails
|
||||
->setSmtpHost($smtp['host'] ?? '')
|
||||
->setSmtpPort($smtp['port'] ?? '')
|
||||
->setSmtpUsername($smtp['username'] ?? '')
|
||||
->setSmtpPassword($smtp['password'] ?? '')
|
||||
->setSmtpSecure($smtp['secure'] ?? '');
|
||||
|
||||
if (!empty($customTemplate)) {
|
||||
if (!empty($customTemplate['senderEmail'])) {
|
||||
$senderEmail = $customTemplate['senderEmail'];
|
||||
}
|
||||
if (!empty($customTemplate['senderName'])) {
|
||||
$senderName = $customTemplate['senderName'];
|
||||
}
|
||||
if (!empty($customTemplate['replyTo'])) {
|
||||
$replyTo = $customTemplate['replyTo'];
|
||||
}
|
||||
|
||||
$body = $customTemplate['message'] ?? '';
|
||||
$subject = $customTemplate['subject'] ?? $subject;
|
||||
}
|
||||
|
||||
$queueForMails
|
||||
->setSmtpReplyTo($replyTo)
|
||||
->setSmtpSenderEmail($senderEmail)
|
||||
->setSmtpSenderName($senderName);
|
||||
}
|
||||
|
||||
// session alerts should always have a client name!
|
||||
$clientName = $session->getAttribute('clientName');
|
||||
if (empty($clientName)) {
|
||||
// fallback to the user agent and then unknown!
|
||||
$userAgent = $session->getAttribute('userAgent');
|
||||
$clientName = !empty($userAgent) ? $userAgent : 'UNKNOWN';
|
||||
|
||||
$session->setAttribute('clientName', $clientName);
|
||||
}
|
||||
|
||||
$projectName = $project->getAttribute('name');
|
||||
if ($project->getId() === 'console') {
|
||||
$projectName = $platform['platformName'];
|
||||
}
|
||||
|
||||
$emailVariables = [
|
||||
'direction' => $locale->getText('settings.direction'),
|
||||
'date' => (new \DateTime())->format('F j'),
|
||||
'year' => (new \DateTime())->format('YYYY'),
|
||||
'time' => (new \DateTime())->format('H:i:s'),
|
||||
'user' => $user->getAttribute('name'),
|
||||
'project' => $projectName,
|
||||
'device' => $session->getAttribute('clientName'),
|
||||
'ipAddress' => $session->getAttribute('ip'),
|
||||
'country' => $locale->getText('countries.' . $session->getAttribute('countryCode'), $locale->getText('locale.country.unknown')),
|
||||
];
|
||||
|
||||
if ($smtpBaseTemplate === APP_BRANDED_EMAIL_BASE_TEMPLATE) {
|
||||
$emailVariables = array_merge($emailVariables, [
|
||||
'accentColor' => $platform['accentColor'],
|
||||
'logoUrl' => $platform['logoUrl'],
|
||||
'twitter' => $platform['twitterUrl'],
|
||||
'discord' => $platform['discordUrl'],
|
||||
'github' => $platform['githubUrl'],
|
||||
'terms' => $platform['termsUrl'],
|
||||
'privacy' => $platform['privacyUrl'],
|
||||
'platform' => $platform['platformName'],
|
||||
]);
|
||||
}
|
||||
|
||||
$email = $user->getAttribute('email');
|
||||
|
||||
$queueForMails
|
||||
->setSubject($subject)
|
||||
->setPreview($preview)
|
||||
->setBody($body)
|
||||
->setBodyTemplate($bodyTemplate)
|
||||
->appendVariables($emailVariables)
|
||||
->setRecipient($email);
|
||||
|
||||
// since this is console project, set email sender name!
|
||||
if ($smtpBaseTemplate === APP_BRANDED_EMAIL_BASE_TEMPLATE) {
|
||||
$queueForMails->setSenderName($platform['emailSenderName']);
|
||||
}
|
||||
|
||||
$queueForMails->trigger();
|
||||
}
|
||||
|
||||
|
||||
$createSession = function (string $userId, string $secret, Request $request, Response $response, User $user, Database $dbForProject, Document $project, array $platform, Locale $locale, Reader $geodb, Event $queueForEvents, Mail $queueForMails, Store $store, ProofsToken $proofForToken, ProofsCode $proofForCode, bool $domainVerification, ?string $cookieDomain, Authorization $authorization) {
|
||||
$createSession = function (string $userId, string $secret, Request $request, Response $response, User $user, Database $dbForProject, Document $project, array $platform, Locale $locale, Reader $geodb, Event $queueForEvents, Bus $bus, Store $store, ProofsToken $proofForToken, ProofsCode $proofForCode, bool $domainVerification, ?string $cookieDomain, Authorization $authorization) {
|
||||
|
||||
// Attempt to decode secret as a JWT (used by OAuth2 token flow to carry provider info)
|
||||
$oauthProvider = null;
|
||||
@@ -318,23 +189,12 @@ $createSession = function (string $userId, string $secret, Request $request, Res
|
||||
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Failed saving user to DB');
|
||||
}
|
||||
|
||||
$isAllowedTokenType = match ($verifiedToken->getAttribute('type')) {
|
||||
TOKEN_TYPE_MAGIC_URL,
|
||||
TOKEN_TYPE_EMAIL => false,
|
||||
default => true
|
||||
};
|
||||
|
||||
$hasUserEmail = $user->getAttribute('email', false) !== false;
|
||||
|
||||
$isSessionAlertsEnabled = $project->getAttribute('auths', [])['sessionAlerts'] ?? false;
|
||||
|
||||
$isNotFirstSession = $dbForProject->count('sessions', [
|
||||
Query::equal('userId', [$user->getId()]),
|
||||
]) !== 1;
|
||||
|
||||
if ($isAllowedTokenType && $hasUserEmail && $isSessionAlertsEnabled && $isNotFirstSession) {
|
||||
sendSessionAlert($locale, $user, $project, $platform, $session, $queueForMails);
|
||||
}
|
||||
$bus->dispatch(new SessionCreated(
|
||||
user: $user->getArrayCopy(),
|
||||
project: $project->getArrayCopy(),
|
||||
session: $session->getArrayCopy(),
|
||||
locale: $locale->default,
|
||||
));
|
||||
|
||||
$queueForEvents
|
||||
->setParam('userId', $user->getId())
|
||||
@@ -614,19 +474,26 @@ Http::delete('/v1/account')
|
||||
->inject('dbForProject')
|
||||
->inject('queueForEvents')
|
||||
->inject('queueForDeletes')
|
||||
->action(function (Document $user, Document $project, Response $response, Database $dbForProject, Event $queueForEvents, Delete $queueForDeletes) {
|
||||
->inject('authorization')
|
||||
->action(function (Document $user, Document $project, Response $response, Database $dbForProject, Event $queueForEvents, Delete $queueForDeletes, Authorization $authorization) {
|
||||
if ($user->isEmpty()) {
|
||||
throw new Exception(Exception::USER_NOT_FOUND);
|
||||
}
|
||||
|
||||
if ($project->getId() === 'console') {
|
||||
// get all memberships
|
||||
$memberships = $user->getAttribute('memberships', []);
|
||||
foreach ($memberships as $membership) {
|
||||
// prevent deletion if at least one active membership
|
||||
if ($membership->getAttribute('confirm', false)) {
|
||||
throw new Exception(Exception::USER_DELETION_PROHIBITED);
|
||||
if (!$membership->getAttribute('confirm', false)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$team = $dbForProject->getDocument('teams', $membership->getAttribute('teamId'));
|
||||
if ($team->isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Team is left as-is — we don't promote non-owner members to owner.
|
||||
// Orphan teams are cleaned up later by Cloud's inactive project cleanup.
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1034,7 +901,7 @@ Http::post('/v1/account/sessions/email')
|
||||
->inject('locale')
|
||||
->inject('geodb')
|
||||
->inject('queueForEvents')
|
||||
->inject('queueForMails')
|
||||
->inject('bus')
|
||||
->inject('hooks')
|
||||
->inject('store')
|
||||
->inject('proofForPassword')
|
||||
@@ -1042,7 +909,7 @@ Http::post('/v1/account/sessions/email')
|
||||
->inject('domainVerification')
|
||||
->inject('cookieDomain')
|
||||
->inject('authorization')
|
||||
->action(function (string $email, string $password, Request $request, Response $response, User $user, Database $dbForProject, Document $project, array $platform, Locale $locale, Reader $geodb, Event $queueForEvents, Mail $queueForMails, Hooks $hooks, Store $store, ProofsPassword $proofForPassword, ProofsToken $proofForToken, bool $domainVerification, ?string $cookieDomain, Authorization $authorization) {
|
||||
->action(function (string $email, string $password, Request $request, Response $response, User $user, Database $dbForProject, Document $project, array $platform, Locale $locale, Reader $geodb, Event $queueForEvents, Bus $bus, Hooks $hooks, Store $store, ProofsPassword $proofForPassword, ProofsToken $proofForToken, bool $domainVerification, ?string $cookieDomain, Authorization $authorization) {
|
||||
$email = \strtolower($email);
|
||||
$protocol = $request->getProtocol();
|
||||
|
||||
@@ -1141,15 +1008,12 @@ Http::post('/v1/account/sessions/email')
|
||||
->setParam('sessionId', $session->getId())
|
||||
;
|
||||
|
||||
if ($project->getAttribute('auths', [])['sessionAlerts'] ?? false) {
|
||||
if (
|
||||
$dbForProject->count('sessions', [
|
||||
Query::equal('userId', [$user->getId()]),
|
||||
]) !== 1
|
||||
) {
|
||||
sendSessionAlert($locale, $user, $project, $platform, $session, $queueForMails);
|
||||
}
|
||||
}
|
||||
$bus->dispatch(new SessionCreated(
|
||||
user: $user->getArrayCopy(),
|
||||
project: $project->getArrayCopy(),
|
||||
session: $session->getArrayCopy(),
|
||||
locale: $locale->default,
|
||||
));
|
||||
|
||||
$response->dynamic($session, Response::MODEL_SESSION);
|
||||
});
|
||||
@@ -1343,7 +1207,7 @@ Http::post('/v1/account/sessions/token')
|
||||
->inject('locale')
|
||||
->inject('geodb')
|
||||
->inject('queueForEvents')
|
||||
->inject('queueForMails')
|
||||
->inject('bus')
|
||||
->inject('store')
|
||||
->inject('proofForToken')
|
||||
->inject('proofForCode')
|
||||
@@ -2897,16 +2761,16 @@ Http::put('/v1/account/sessions/magic-url')
|
||||
->inject('locale')
|
||||
->inject('geodb')
|
||||
->inject('queueForEvents')
|
||||
->inject('queueForMails')
|
||||
->inject('bus')
|
||||
->inject('store')
|
||||
->inject('proofForCode')
|
||||
->inject('domainVerification')
|
||||
->inject('cookieDomain')
|
||||
->inject('authorization')
|
||||
->action(function ($userId, $secret, $request, $response, $user, $dbForProject, $project, $platform, $locale, $geodb, $queueForEvents, $queueForMails, $store, $proofForCode, $domainVerification, $cookieDomain, $authorization) use ($createSession) {
|
||||
->action(function ($userId, $secret, $request, $response, $user, $dbForProject, $project, $platform, $locale, $geodb, $queueForEvents, $bus, $store, $proofForCode, $domainVerification, $cookieDomain, $authorization) use ($createSession) {
|
||||
$proofForToken = new ProofsToken(TOKEN_LENGTH_MAGIC_URL);
|
||||
$proofForToken->setHash(new Sha());
|
||||
$createSession($userId, $secret, $request, $response, $user, $dbForProject, $project, $platform, $locale, $geodb, $queueForEvents, $queueForMails, $store, $proofForToken, $proofForCode, $domainVerification, $cookieDomain, $authorization);
|
||||
$createSession($userId, $secret, $request, $response, $user, $dbForProject, $project, $platform, $locale, $geodb, $queueForEvents, $bus, $store, $proofForToken, $proofForCode, $domainVerification, $cookieDomain, $authorization);
|
||||
});
|
||||
|
||||
Http::put('/v1/account/sessions/phone')
|
||||
@@ -2948,7 +2812,7 @@ Http::put('/v1/account/sessions/phone')
|
||||
->inject('locale')
|
||||
->inject('geodb')
|
||||
->inject('queueForEvents')
|
||||
->inject('queueForMails')
|
||||
->inject('bus')
|
||||
->inject('store')
|
||||
->inject('proofForToken')
|
||||
->inject('proofForCode')
|
||||
|
||||
@@ -231,7 +231,7 @@ function execute(
|
||||
$validations = GraphQL::getStandardValidationRules();
|
||||
|
||||
if (System::getEnv('_APP_GRAPHQL_INTROSPECTION', 'enabled') === 'disabled') {
|
||||
$validations[] = new DisableIntrospection();
|
||||
$validations[] = new DisableIntrospection(DisableIntrospection::ENABLED);
|
||||
}
|
||||
|
||||
if (System::getEnv('_APP_OPTIONS_ABUSE', 'enabled') !== 'disabled') {
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
<?php
|
||||
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Event\Migration;
|
||||
use Appwrite\Event\Message\Migration as MigrationMessage;
|
||||
use Appwrite\Event\Publisher\Migration as MigrationPublisher;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\OpenSSL\OpenSSL;
|
||||
use Appwrite\SDK\AuthType;
|
||||
@@ -90,10 +91,9 @@ Http::post('/v1/migrations/appwrite')
|
||||
->inject('dbForProject')
|
||||
->inject('project')
|
||||
->inject('platform')
|
||||
->inject('user')
|
||||
->inject('queueForEvents')
|
||||
->inject('queueForMigrations')
|
||||
->action(function (array $resources, string $endpoint, string $projectId, string $apiKey, Response $response, Database $dbForProject, Document $project, array $platform, Document $user, Event $queueForEvents, Migration $queueForMigrations) {
|
||||
->inject('publisherForMigrations')
|
||||
->action(function (array $resources, string $endpoint, string $projectId, string $apiKey, Response $response, Database $dbForProject, Document $project, array $platform, Event $queueForEvents, MigrationPublisher $publisherForMigrations) {
|
||||
$migration = $dbForProject->createDocument('migrations', new Document([
|
||||
'$id' => ID::unique(),
|
||||
'status' => 'pending',
|
||||
@@ -114,12 +114,11 @@ Http::post('/v1/migrations/appwrite')
|
||||
$queueForEvents->setParam('migrationId', $migration->getId());
|
||||
|
||||
// Trigger Transfer
|
||||
$queueForMigrations
|
||||
->setMigration($migration)
|
||||
->setProject($project)
|
||||
->setPlatform($platform)
|
||||
->setUser($user)
|
||||
->trigger();
|
||||
$publisherForMigrations->enqueue(new MigrationMessage(
|
||||
project: $project,
|
||||
migration: $migration,
|
||||
platform: $platform,
|
||||
));
|
||||
|
||||
$response
|
||||
->setStatusCode(Response::STATUS_CODE_ACCEPTED)
|
||||
@@ -151,10 +150,9 @@ Http::post('/v1/migrations/firebase')
|
||||
->inject('dbForProject')
|
||||
->inject('project')
|
||||
->inject('platform')
|
||||
->inject('user')
|
||||
->inject('queueForEvents')
|
||||
->inject('queueForMigrations')
|
||||
->action(function (array $resources, string $serviceAccount, Response $response, Database $dbForProject, Document $project, array $platform, Document $user, Event $queueForEvents, Migration $queueForMigrations) {
|
||||
->inject('publisherForMigrations')
|
||||
->action(function (array $resources, string $serviceAccount, Response $response, Database $dbForProject, Document $project, array $platform, Event $queueForEvents, MigrationPublisher $publisherForMigrations) {
|
||||
$serviceAccountData = json_decode($serviceAccount, true);
|
||||
|
||||
if (empty($serviceAccountData)) {
|
||||
@@ -183,12 +181,11 @@ Http::post('/v1/migrations/firebase')
|
||||
$queueForEvents->setParam('migrationId', $migration->getId());
|
||||
|
||||
// Trigger Transfer
|
||||
$queueForMigrations
|
||||
->setMigration($migration)
|
||||
->setProject($project)
|
||||
->setPlatform($platform)
|
||||
->setUser($user)
|
||||
->trigger();
|
||||
$publisherForMigrations->enqueue(new MigrationMessage(
|
||||
project: $project,
|
||||
migration: $migration,
|
||||
platform: $platform,
|
||||
));
|
||||
|
||||
$response
|
||||
->setStatusCode(Response::STATUS_CODE_ACCEPTED)
|
||||
@@ -225,10 +222,9 @@ Http::post('/v1/migrations/supabase')
|
||||
->inject('dbForProject')
|
||||
->inject('project')
|
||||
->inject('platform')
|
||||
->inject('user')
|
||||
->inject('queueForEvents')
|
||||
->inject('queueForMigrations')
|
||||
->action(function (array $resources, string $endpoint, string $apiKey, string $databaseHost, string $username, string $password, int $port, Response $response, Database $dbForProject, Document $project, array $platform, Document $user, Event $queueForEvents, Migration $queueForMigrations) {
|
||||
->inject('publisherForMigrations')
|
||||
->action(function (array $resources, string $endpoint, string $apiKey, string $databaseHost, string $username, string $password, int $port, Response $response, Database $dbForProject, Document $project, array $platform, Event $queueForEvents, MigrationPublisher $publisherForMigrations) {
|
||||
$migration = $dbForProject->createDocument('migrations', new Document([
|
||||
'$id' => ID::unique(),
|
||||
'status' => 'pending',
|
||||
@@ -252,12 +248,11 @@ Http::post('/v1/migrations/supabase')
|
||||
$queueForEvents->setParam('migrationId', $migration->getId());
|
||||
|
||||
// Trigger Transfer
|
||||
$queueForMigrations
|
||||
->setMigration($migration)
|
||||
->setProject($project)
|
||||
->setPlatform($platform)
|
||||
->setUser($user)
|
||||
->trigger();
|
||||
$publisherForMigrations->enqueue(new MigrationMessage(
|
||||
project: $project,
|
||||
migration: $migration,
|
||||
platform: $platform,
|
||||
));
|
||||
|
||||
$response
|
||||
->setStatusCode(Response::STATUS_CODE_ACCEPTED)
|
||||
@@ -295,10 +290,9 @@ Http::post('/v1/migrations/nhost')
|
||||
->inject('dbForProject')
|
||||
->inject('project')
|
||||
->inject('platform')
|
||||
->inject('user')
|
||||
->inject('queueForEvents')
|
||||
->inject('queueForMigrations')
|
||||
->action(function (array $resources, string $subdomain, string $region, string $adminSecret, string $database, string $username, string $password, int $port, Response $response, Database $dbForProject, Document $project, array $platform, Document $user, Event $queueForEvents, Migration $queueForMigrations) {
|
||||
->inject('publisherForMigrations')
|
||||
->action(function (array $resources, string $subdomain, string $region, string $adminSecret, string $database, string $username, string $password, int $port, Response $response, Database $dbForProject, Document $project, array $platform, Event $queueForEvents, MigrationPublisher $publisherForMigrations) {
|
||||
$migration = $dbForProject->createDocument('migrations', new Document([
|
||||
'$id' => ID::unique(),
|
||||
'status' => 'pending',
|
||||
@@ -323,12 +317,11 @@ Http::post('/v1/migrations/nhost')
|
||||
$queueForEvents->setParam('migrationId', $migration->getId());
|
||||
|
||||
// Trigger Transfer
|
||||
$queueForMigrations
|
||||
->setMigration($migration)
|
||||
->setProject($project)
|
||||
->setPlatform($platform)
|
||||
->setUser($user)
|
||||
->trigger();
|
||||
$publisherForMigrations->enqueue(new MigrationMessage(
|
||||
project: $project,
|
||||
migration: $migration,
|
||||
platform: $platform,
|
||||
));
|
||||
|
||||
$response
|
||||
->setStatusCode(Response::STATUS_CODE_ACCEPTED)
|
||||
@@ -368,7 +361,7 @@ Http::post('/v1/migrations/csv/imports')
|
||||
->inject('deviceForFiles')
|
||||
->inject('deviceForMigrations')
|
||||
->inject('queueForEvents')
|
||||
->inject('queueForMigrations')
|
||||
->inject('publisherForMigrations')
|
||||
->action(function (
|
||||
string $bucketId,
|
||||
string $fileId,
|
||||
@@ -383,7 +376,7 @@ Http::post('/v1/migrations/csv/imports')
|
||||
Device $deviceForFiles,
|
||||
Device $deviceForMigrations,
|
||||
Event $queueForEvents,
|
||||
Migration $queueForMigrations
|
||||
MigrationPublisher $publisherForMigrations
|
||||
) {
|
||||
$bucket = $authorization->skip(function () use ($internalFile, $dbForPlatform, $dbForProject, $bucketId) {
|
||||
if ($internalFile) {
|
||||
@@ -479,11 +472,10 @@ Http::post('/v1/migrations/csv/imports')
|
||||
|
||||
$queueForEvents->setParam('migrationId', $migration->getId());
|
||||
|
||||
$queueForMigrations
|
||||
->setMigration($migration)
|
||||
->setProject($project)
|
||||
->setProject($project)
|
||||
->trigger();
|
||||
$publisherForMigrations->enqueue(new MigrationMessage(
|
||||
project: $project,
|
||||
migration: $migration,
|
||||
));
|
||||
|
||||
$response
|
||||
->setStatusCode(Response::STATUS_CODE_ACCEPTED)
|
||||
@@ -526,7 +518,7 @@ Http::post('/v1/migrations/csv/exports')
|
||||
->inject('project')
|
||||
->inject('platform')
|
||||
->inject('queueForEvents')
|
||||
->inject('queueForMigrations')
|
||||
->inject('publisherForMigrations')
|
||||
->action(function (
|
||||
string $resourceId,
|
||||
string $filename,
|
||||
@@ -545,7 +537,7 @@ Http::post('/v1/migrations/csv/exports')
|
||||
Document $project,
|
||||
array $platform,
|
||||
Event $queueForEvents,
|
||||
Migration $queueForMigrations
|
||||
MigrationPublisher $publisherForMigrations
|
||||
) {
|
||||
try {
|
||||
$parsedQueries = Query::parseQueries($queries);
|
||||
@@ -630,11 +622,11 @@ Http::post('/v1/migrations/csv/exports')
|
||||
|
||||
$queueForEvents->setParam('migrationId', $migration->getId());
|
||||
|
||||
$queueForMigrations
|
||||
->setMigration($migration)
|
||||
->setProject($project)
|
||||
->setPlatform($platform)
|
||||
->trigger();
|
||||
$publisherForMigrations->enqueue(new MigrationMessage(
|
||||
project: $project,
|
||||
migration: $migration,
|
||||
platform: $platform,
|
||||
));
|
||||
|
||||
$response
|
||||
->setStatusCode(Response::STATUS_CODE_ACCEPTED)
|
||||
@@ -673,7 +665,7 @@ Http::post('/v1/migrations/json/imports')
|
||||
->inject('deviceForFiles')
|
||||
->inject('deviceForMigrations')
|
||||
->inject('queueForEvents')
|
||||
->inject('queueForMigrations')
|
||||
->inject('publisherForMigrations')
|
||||
->action(function (
|
||||
string $bucketId,
|
||||
string $fileId,
|
||||
@@ -688,7 +680,7 @@ Http::post('/v1/migrations/json/imports')
|
||||
Device $deviceForFiles,
|
||||
Device $deviceForMigrations,
|
||||
Event $queueForEvents,
|
||||
Migration $queueForMigrations
|
||||
MigrationPublisher $publisherForMigrations
|
||||
) {
|
||||
$bucket = $authorization->skip(function () use ($internalFile, $dbForPlatform, $dbForProject, $bucketId) {
|
||||
if ($internalFile) {
|
||||
@@ -783,11 +775,11 @@ Http::post('/v1/migrations/json/imports')
|
||||
|
||||
$queueForEvents->setParam('migrationId', $migration->getId());
|
||||
|
||||
$queueForMigrations
|
||||
->setMigration($migration)
|
||||
->setProject($project)
|
||||
->setPlatform($platform)
|
||||
->trigger();
|
||||
$publisherForMigrations->enqueue(new MigrationMessage(
|
||||
project: $project,
|
||||
migration: $migration,
|
||||
platform: $platform,
|
||||
));
|
||||
|
||||
$response
|
||||
->setStatusCode(Response::STATUS_CODE_ACCEPTED)
|
||||
@@ -826,7 +818,7 @@ Http::post('/v1/migrations/json/exports')
|
||||
->inject('project')
|
||||
->inject('platform')
|
||||
->inject('queueForEvents')
|
||||
->inject('queueForMigrations')
|
||||
->inject('publisherForMigrations')
|
||||
->action(function (
|
||||
string $resourceId,
|
||||
string $filename,
|
||||
@@ -841,7 +833,7 @@ Http::post('/v1/migrations/json/exports')
|
||||
Document $project,
|
||||
array $platform,
|
||||
Event $queueForEvents,
|
||||
Migration $queueForMigrations
|
||||
MigrationPublisher $publisherForMigrations
|
||||
) {
|
||||
try {
|
||||
$parsedQueries = Query::parseQueries($queries);
|
||||
@@ -915,11 +907,11 @@ Http::post('/v1/migrations/json/exports')
|
||||
|
||||
$queueForEvents->setParam('migrationId', $migration->getId());
|
||||
|
||||
$queueForMigrations
|
||||
->setMigration($migration)
|
||||
->setProject($project)
|
||||
->setPlatform($platform)
|
||||
->trigger();
|
||||
$publisherForMigrations->enqueue(new MigrationMessage(
|
||||
project: $project,
|
||||
migration: $migration,
|
||||
platform: $platform,
|
||||
));
|
||||
|
||||
$response
|
||||
->setStatusCode(Response::STATUS_CODE_ACCEPTED)
|
||||
@@ -1216,9 +1208,8 @@ Http::patch('/v1/migrations/:migrationId')
|
||||
->inject('dbForProject')
|
||||
->inject('project')
|
||||
->inject('platform')
|
||||
->inject('user')
|
||||
->inject('queueForMigrations')
|
||||
->action(function (string $migrationId, Response $response, Database $dbForProject, Document $project, array $platform, Document $user, Migration $queueForMigrations) {
|
||||
->inject('publisherForMigrations')
|
||||
->action(function (string $migrationId, Response $response, Database $dbForProject, Document $project, array $platform, MigrationPublisher $publisherForMigrations) {
|
||||
$migration = $dbForProject->getDocument('migrations', $migrationId);
|
||||
|
||||
if ($migration->isEmpty()) {
|
||||
@@ -1234,12 +1225,11 @@ Http::patch('/v1/migrations/:migrationId')
|
||||
->setAttribute('dateUpdated', \time());
|
||||
|
||||
// Trigger Migration
|
||||
$queueForMigrations
|
||||
->setMigration($migration)
|
||||
->setProject($project)
|
||||
->setPlatform($platform)
|
||||
->setUser($user)
|
||||
->trigger();
|
||||
$publisherForMigrations->enqueue(new MigrationMessage(
|
||||
project: $project,
|
||||
migration: $migration,
|
||||
platform: $platform,
|
||||
));
|
||||
|
||||
$response->noContent();
|
||||
});
|
||||
|
||||
@@ -71,202 +71,20 @@ Http::get('/v1/projects/:projectId')
|
||||
$response->dynamic($project, Response::MODEL_PROJECT);
|
||||
});
|
||||
|
||||
Http::patch('/v1/projects/:projectId/service')
|
||||
->desc('Update service status')
|
||||
->groups(['api', 'projects'])
|
||||
->label('scope', 'projects.write')
|
||||
->label('sdk', new Method(
|
||||
namespace: 'projects',
|
||||
group: 'projects',
|
||||
name: 'updateServiceStatus',
|
||||
description: '/docs/references/projects/update-service-status.md',
|
||||
auth: [AuthType::ADMIN],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
model: Response::MODEL_PROJECT,
|
||||
)
|
||||
]
|
||||
))
|
||||
->param('projectId', '', fn (Database $dbForPlatform) => new UID($dbForPlatform->getAdapter()->getMaxUIDLength()), 'Project unique ID.', false, ['dbForPlatform'])
|
||||
->param('service', '', new WhiteList(array_keys(array_filter(Config::getParam('services'), fn ($element) => $element['optional'])), true), 'Service name.')
|
||||
->param('status', null, new Boolean(), 'Service status.')
|
||||
->inject('response')
|
||||
->inject('dbForPlatform')
|
||||
->action(function (string $projectId, string $service, bool $status, Response $response, Database $dbForPlatform) {
|
||||
|
||||
$project = $dbForPlatform->getDocument('projects', $projectId);
|
||||
|
||||
if ($project->isEmpty()) {
|
||||
throw new Exception(Exception::PROJECT_NOT_FOUND);
|
||||
}
|
||||
|
||||
$services = $project->getAttribute('services', []);
|
||||
$services[$service] = $status;
|
||||
|
||||
$project = $dbForPlatform->updateDocument('projects', $project->getId(), $project->setAttribute('services', $services));
|
||||
|
||||
$response->dynamic($project, Response::MODEL_PROJECT);
|
||||
});
|
||||
|
||||
Http::patch('/v1/projects/:projectId/service/all')
|
||||
->desc('Update all service status')
|
||||
->groups(['api', 'projects'])
|
||||
->label('scope', 'projects.write')
|
||||
->label('sdk', new Method(
|
||||
namespace: 'projects',
|
||||
group: 'projects',
|
||||
name: 'updateServiceStatusAll',
|
||||
description: '/docs/references/projects/update-service-status-all.md',
|
||||
auth: [AuthType::ADMIN],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
model: Response::MODEL_PROJECT,
|
||||
)
|
||||
]
|
||||
))
|
||||
->param('projectId', '', fn (Database $dbForPlatform) => new UID($dbForPlatform->getAdapter()->getMaxUIDLength()), 'Project unique ID.', false, ['dbForPlatform'])
|
||||
->param('status', null, new Boolean(), 'Service status.')
|
||||
->inject('response')
|
||||
->inject('dbForPlatform')
|
||||
->action(function (string $projectId, bool $status, Response $response, Database $dbForPlatform) {
|
||||
|
||||
$project = $dbForPlatform->getDocument('projects', $projectId);
|
||||
|
||||
if ($project->isEmpty()) {
|
||||
throw new Exception(Exception::PROJECT_NOT_FOUND);
|
||||
}
|
||||
|
||||
$allServices = array_keys(array_filter(Config::getParam('services'), fn ($element) => $element['optional']));
|
||||
|
||||
$services = [];
|
||||
foreach ($allServices as $service) {
|
||||
$services[$service] = $status;
|
||||
}
|
||||
|
||||
$project = $dbForPlatform->updateDocument('projects', $project->getId(), $project->setAttribute('services', $services));
|
||||
|
||||
$response->dynamic($project, Response::MODEL_PROJECT);
|
||||
});
|
||||
|
||||
Http::patch('/v1/projects/:projectId/api')
|
||||
->desc('Update API status')
|
||||
->groups(['api', 'projects'])
|
||||
->label('scope', 'projects.write')
|
||||
->label('sdk', [
|
||||
new Method(
|
||||
namespace: 'projects',
|
||||
group: 'projects',
|
||||
name: 'updateApiStatus',
|
||||
description: '/docs/references/projects/update-api-status.md',
|
||||
auth: [AuthType::ADMIN],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
model: Response::MODEL_PROJECT,
|
||||
)
|
||||
],
|
||||
deprecated: new Deprecated(
|
||||
since: '1.8.0',
|
||||
replaceWith: 'projects.updateAPIStatus',
|
||||
),
|
||||
public: false,
|
||||
),
|
||||
new Method(
|
||||
namespace: 'projects',
|
||||
group: 'projects',
|
||||
name: 'updateAPIStatus',
|
||||
description: '/docs/references/projects/update-api-status.md',
|
||||
auth: [AuthType::ADMIN],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
model: Response::MODEL_PROJECT,
|
||||
)
|
||||
]
|
||||
)
|
||||
])
|
||||
->param('projectId', '', fn (Database $dbForPlatform) => new UID($dbForPlatform->getAdapter()->getMaxUIDLength()), 'Project unique ID.', false, ['dbForPlatform'])
|
||||
->param('api', '', new WhiteList(array_keys(Config::getParam('apis')), true), 'API name.')
|
||||
->param('status', null, new Boolean(), 'API status.')
|
||||
->inject('response')
|
||||
->inject('dbForPlatform')
|
||||
->action(function (string $projectId, string $api, bool $status, Response $response, Database $dbForPlatform) {
|
||||
|
||||
$project = $dbForPlatform->getDocument('projects', $projectId);
|
||||
|
||||
if ($project->isEmpty()) {
|
||||
throw new Exception(Exception::PROJECT_NOT_FOUND);
|
||||
}
|
||||
|
||||
$apis = $project->getAttribute('apis', []);
|
||||
$apis[$api] = $status;
|
||||
|
||||
$project = $dbForPlatform->updateDocument('projects', $project->getId(), $project->setAttribute('apis', $apis));
|
||||
|
||||
$response->dynamic($project, Response::MODEL_PROJECT);
|
||||
->action(function () {
|
||||
throw new Exception(Exception::GENERAL_NOT_IMPLEMENTED, 'Bulk API no longer exists for services. Please change status individually.');
|
||||
});
|
||||
|
||||
Http::patch('/v1/projects/:projectId/api/all')
|
||||
->desc('Update all API status')
|
||||
->groups(['api', 'projects'])
|
||||
->label('scope', 'projects.write')
|
||||
->label('sdk', [
|
||||
new Method(
|
||||
namespace: 'projects',
|
||||
group: 'projects',
|
||||
name: 'updateApiStatusAll',
|
||||
description: '/docs/references/projects/update-api-status-all.md',
|
||||
auth: [AuthType::ADMIN],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
model: Response::MODEL_PROJECT,
|
||||
)
|
||||
],
|
||||
deprecated: new Deprecated(
|
||||
since: '1.8.0',
|
||||
replaceWith: 'projects.updateAPIStatusAll',
|
||||
),
|
||||
public: false,
|
||||
),
|
||||
new Method(
|
||||
namespace: 'projects',
|
||||
group: 'projects',
|
||||
name: 'updateAPIStatusAll',
|
||||
description: '/docs/references/projects/update-api-status-all.md',
|
||||
auth: [AuthType::ADMIN],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
model: Response::MODEL_PROJECT,
|
||||
)
|
||||
]
|
||||
)
|
||||
])
|
||||
->param('projectId', '', fn (Database $dbForPlatform) => new UID($dbForPlatform->getAdapter()->getMaxUIDLength()), 'Project unique ID.', false, ['dbForPlatform'])
|
||||
->param('status', null, new Boolean(), 'API status.')
|
||||
->inject('response')
|
||||
->inject('dbForPlatform')
|
||||
->action(function (string $projectId, bool $status, Response $response, Database $dbForPlatform) {
|
||||
|
||||
$project = $dbForPlatform->getDocument('projects', $projectId);
|
||||
|
||||
if ($project->isEmpty()) {
|
||||
throw new Exception(Exception::PROJECT_NOT_FOUND);
|
||||
}
|
||||
|
||||
$allApis = array_keys(Config::getParam('apis'));
|
||||
|
||||
$apis = [];
|
||||
foreach ($allApis as $api) {
|
||||
$apis[$api] = $status;
|
||||
}
|
||||
|
||||
$project = $dbForPlatform->updateDocument('projects', $project->getId(), $project->setAttribute('apis', $apis));
|
||||
|
||||
$response->dynamic($project, Response::MODEL_PROJECT);
|
||||
->action(function () {
|
||||
throw new Exception(Exception::GENERAL_NOT_IMPLEMENTED, 'Bulk API no longer exists for services. Please change status individually.');
|
||||
});
|
||||
|
||||
Http::patch('/v1/projects/:projectId/oauth2')
|
||||
|
||||
+19
-20
@@ -7,9 +7,9 @@ use Ahc\Jwt\JWTException;
|
||||
use Appwrite\Auth\Key;
|
||||
use Appwrite\Bus\Events\ExecutionCompleted;
|
||||
use Appwrite\Bus\Events\RequestCompleted;
|
||||
use Appwrite\Event\Certificate;
|
||||
use Appwrite\Event\Delete as DeleteEvent;
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Event\Publisher\Certificate;
|
||||
use Appwrite\Extend\Exception as AppwriteException;
|
||||
use Appwrite\Network\Cors;
|
||||
use Appwrite\Platform\Appwrite;
|
||||
@@ -25,6 +25,7 @@ use Appwrite\Utopia\Request\Filters\V18 as RequestV18;
|
||||
use Appwrite\Utopia\Request\Filters\V19 as RequestV19;
|
||||
use Appwrite\Utopia\Request\Filters\V20 as RequestV20;
|
||||
use Appwrite\Utopia\Request\Filters\V21 as RequestV21;
|
||||
use Appwrite\Utopia\Request\Filters\V22 as RequestV22;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Appwrite\Utopia\Response\Filters\V16 as ResponseV16;
|
||||
use Appwrite\Utopia\Response\Filters\V17 as ResponseV17;
|
||||
@@ -32,6 +33,7 @@ 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\Response\Filters\V22 as ResponseV22;
|
||||
use Appwrite\Utopia\View;
|
||||
use Executor\Executor;
|
||||
use MaxMind\Db\Reader;
|
||||
@@ -892,6 +894,9 @@ Http::init()
|
||||
if (version_compare($requestFormat, '1.9.0', '<')) {
|
||||
$request->addFilter(new RequestV21());
|
||||
}
|
||||
if (version_compare($requestFormat, '1.9.1', '<')) {
|
||||
$request->addFilter(new RequestV22());
|
||||
}
|
||||
}
|
||||
|
||||
$localeParam = (string) $request->getParam('locale', $request->getHeader('x-appwrite-locale', ''));
|
||||
@@ -916,6 +921,9 @@ Http::init()
|
||||
*/
|
||||
$responseFormat = $request->getHeader('x-appwrite-response-format', System::getEnv('_APP_SYSTEM_RESPONSE_FORMAT', ''));
|
||||
if ($responseFormat) {
|
||||
if (version_compare($responseFormat, '1.9.1', '<')) {
|
||||
$response->addFilter(new ResponseV22());
|
||||
}
|
||||
if (version_compare($responseFormat, '1.9.0', '<')) {
|
||||
$response->addFilter(new ResponseV21());
|
||||
}
|
||||
@@ -1006,11 +1014,11 @@ Http::init()
|
||||
->inject('request')
|
||||
->inject('console')
|
||||
->inject('dbForPlatform')
|
||||
->inject('queueForCertificates')
|
||||
->inject('publisherForCertificates')
|
||||
->inject('platform')
|
||||
->inject('authorization')
|
||||
->inject('certifiedDomains')
|
||||
->action(function (Request $request, Document $console, Database $dbForPlatform, Certificate $queueForCertificates, array $platform, Authorization $authorization, Table $certifiedDomains) {
|
||||
->action(function (Request $request, Document $console, Database $dbForPlatform, Certificate $publisherForCertificates, array $platform, Authorization $authorization, Table $certifiedDomains) {
|
||||
$hostname = $request->getHostname();
|
||||
$platformHostnames = $platform['hostnames'] ?? [];
|
||||
|
||||
@@ -1036,7 +1044,7 @@ Http::init()
|
||||
}
|
||||
|
||||
// 4. Check/create rule (requires DB access)
|
||||
$authorization->skip(function () use ($dbForPlatform, $domain, $console, $queueForCertificates, $certifiedDomains) {
|
||||
$authorization->skip(function () use ($dbForPlatform, $domain, $console, $publisherForCertificates, $certifiedDomains) {
|
||||
try {
|
||||
// TODO: (@Meldiron) Remove after 1.7.x migration
|
||||
$isMd5 = System::getEnv('_APP_RULES_FORMAT') === 'md5';
|
||||
@@ -1092,10 +1100,11 @@ Http::init()
|
||||
$dbForPlatform->createDocument('rules', $document);
|
||||
|
||||
Console::info('Issuing a TLS certificate for the main domain (' . $domain->get() . ') in a few seconds...');
|
||||
$queueForCertificates
|
||||
->setDomain($document)
|
||||
->setSkipRenewCheck(true)
|
||||
->trigger();
|
||||
$publisherForCertificates->enqueue(new \Appwrite\Event\Message\Certificate(
|
||||
project: $console,
|
||||
domain: $document,
|
||||
skipRenewCheck: true,
|
||||
));
|
||||
} catch (Duplicate $e) {
|
||||
Console::info('Certificate already exists');
|
||||
} finally {
|
||||
@@ -1168,15 +1177,6 @@ Http::error()
|
||||
->inject('devKey')
|
||||
->inject('authorization')
|
||||
->action(function (Throwable $error, Http $utopia, Request $request, Response $response, Document $project, ?Logger $logger, Log $log, Bus $bus, Document $devKey, Authorization $authorization) {
|
||||
$trace = $error->getTrace();
|
||||
|
||||
foreach (array_slice($trace, 0, 100) as $index => $traceEntry) {
|
||||
$file = isset($traceEntry['file']) ? $traceEntry['file'] : '[internal function]';
|
||||
$line = isset($traceEntry['line']) ? $traceEntry['line'] : '';
|
||||
$function = isset($traceEntry['function']) ? $traceEntry['function'] : '';
|
||||
Console::error("[$index] $file : $line -> $function()");
|
||||
}
|
||||
|
||||
$version = System::getEnv('_APP_VERSION', 'UNKNOWN');
|
||||
$route = $utopia->getRoute();
|
||||
$class = \get_class($error);
|
||||
@@ -1186,9 +1186,7 @@ Http::error()
|
||||
$line = $error->getLine();
|
||||
$trace = $error->getTrace();
|
||||
|
||||
if (php_sapi_name() === 'cli') {
|
||||
Span::error($error);
|
||||
}
|
||||
Span::error($error);
|
||||
|
||||
switch ($class) {
|
||||
case Utopia\Http\Exception::class:
|
||||
@@ -1430,6 +1428,7 @@ Http::error()
|
||||
case 402: // Error allowed publicly
|
||||
case 403: // Error allowed publicly
|
||||
case 404: // Error allowed publicly
|
||||
case 405: // Error allowed publicly
|
||||
case 408: // Error allowed publicly
|
||||
case 409: // Error allowed publicly
|
||||
case 412: // Error allowed publicly
|
||||
|
||||
@@ -3,15 +3,17 @@
|
||||
use Appwrite\Auth\Key;
|
||||
use Appwrite\Auth\MFA\Type\TOTP;
|
||||
use Appwrite\Bus\Events\RequestCompleted;
|
||||
use Appwrite\Event\Audit;
|
||||
use Appwrite\Event\Build;
|
||||
use Appwrite\Event\Context\Audit as AuditContext;
|
||||
use Appwrite\Event\Database as EventDatabase;
|
||||
use Appwrite\Event\Delete;
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Event\Func;
|
||||
use Appwrite\Event\Mail;
|
||||
use Appwrite\Event\Message\Audit as AuditMessage;
|
||||
use Appwrite\Event\Message\Usage as UsageMessage;
|
||||
use Appwrite\Event\Messaging;
|
||||
use Appwrite\Event\Publisher\Audit;
|
||||
use Appwrite\Event\Publisher\Usage as UsagePublisher;
|
||||
use Appwrite\Event\Realtime;
|
||||
use Appwrite\Event\Webhook;
|
||||
@@ -37,6 +39,7 @@ use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Database\Validator\Authorization\Input;
|
||||
use Utopia\Database\Validator\Roles;
|
||||
use Utopia\Http\Http;
|
||||
use Utopia\Span\Span;
|
||||
use Utopia\System\System;
|
||||
use Utopia\Telemetry\Adapter as Telemetry;
|
||||
use Utopia\Validator\WhiteList;
|
||||
@@ -87,7 +90,7 @@ Http::init()
|
||||
->inject('request')
|
||||
->inject('dbForPlatform')
|
||||
->inject('dbForProject')
|
||||
->inject('queueForAudits')
|
||||
->inject('auditContext')
|
||||
->inject('project')
|
||||
->inject('user')
|
||||
->inject('session')
|
||||
@@ -96,7 +99,7 @@ Http::init()
|
||||
->inject('team')
|
||||
->inject('apiKey')
|
||||
->inject('authorization')
|
||||
->action(function (Http $utopia, Request $request, Database $dbForPlatform, Database $dbForProject, Audit $queueForAudits, Document $project, User $user, ?Document $session, array $servers, string $mode, Document $team, ?Key $apiKey, Authorization $authorization) {
|
||||
->action(function (Http $utopia, Request $request, Database $dbForPlatform, Database $dbForProject, AuditContext $auditContext, Document $project, User $user, ?Document $session, array $servers, string $mode, Document $team, ?Key $apiKey, Authorization $authorization) {
|
||||
$route = $utopia->getRoute();
|
||||
if ($route === null) {
|
||||
throw new AppwriteException(AppwriteException::GENERAL_ROUTE_NOT_FOUND);
|
||||
@@ -192,7 +195,7 @@ Http::init()
|
||||
'name' => $apiKey->getName(),
|
||||
]);
|
||||
|
||||
$queueForAudits->setUser($user);
|
||||
$auditContext->user = $user;
|
||||
}
|
||||
|
||||
// For standard keys, update last accessed time
|
||||
@@ -263,7 +266,7 @@ Http::init()
|
||||
API_KEY_ORGANIZATION => ACTIVITY_TYPE_KEY_ORGANIZATION,
|
||||
default => ACTIVITY_TYPE_KEY_PROJECT,
|
||||
});
|
||||
$queueForAudits->setUser($userClone);
|
||||
$auditContext->user = $userClone;
|
||||
}
|
||||
|
||||
// Apply permission
|
||||
@@ -424,7 +427,7 @@ Http::init()
|
||||
}
|
||||
|
||||
if (! empty($method)) {
|
||||
$namespace = $method->getNamespace();
|
||||
$namespace = \strtolower($method->getNamespace());
|
||||
|
||||
if (
|
||||
array_key_exists($namespace, $project->getAttribute('services', []))
|
||||
@@ -435,6 +438,15 @@ Http::init()
|
||||
}
|
||||
}
|
||||
|
||||
// Step 8b: Check REST protocol status
|
||||
if (
|
||||
array_key_exists('rest', $project->getAttribute('apis', []))
|
||||
&& ! $project->getAttribute('apis', [])['rest']
|
||||
&& ! ($user->isPrivileged($authorization->getRoles()) || $user->isApp($authorization->getRoles()))
|
||||
) {
|
||||
throw new AppwriteException(AppwriteException::GENERAL_API_DISABLED);
|
||||
}
|
||||
|
||||
// Step 9: Validate scope permissions
|
||||
$allowed = (array) $route->getLabel('scope', 'none');
|
||||
if (empty(\array_intersect($allowed, $scopes))) {
|
||||
@@ -476,7 +488,7 @@ Http::init()
|
||||
->inject('user')
|
||||
->inject('queueForEvents')
|
||||
->inject('queueForMessaging')
|
||||
->inject('queueForAudits')
|
||||
->inject('auditContext')
|
||||
->inject('queueForDeletes')
|
||||
->inject('queueForDatabase')
|
||||
->inject('queueForBuilds')
|
||||
@@ -493,7 +505,7 @@ Http::init()
|
||||
->inject('telemetry')
|
||||
->inject('platform')
|
||||
->inject('authorization')
|
||||
->action(function (Http $utopia, Request $request, Response $response, Document $project, User $user, Event $queueForEvents, Messaging $queueForMessaging, Audit $queueForAudits, Delete $queueForDeletes, EventDatabase $queueForDatabase, Build $queueForBuilds, Context $usage, Func $queueForFunctions, Mail $queueForMails, Database $dbForProject, callable $timelimit, Document $resourceToken, string $mode, ?Key $apiKey, array $plan, Document $devKey, Telemetry $telemetry, array $platform, Authorization $authorization) {
|
||||
->action(function (Http $utopia, Request $request, Response $response, Document $project, User $user, Event $queueForEvents, Messaging $queueForMessaging, AuditContext $auditContext, Delete $queueForDeletes, EventDatabase $queueForDatabase, Build $queueForBuilds, Context $usage, Func $queueForFunctions, Mail $queueForMails, Database $dbForProject, callable $timelimit, Document $resourceToken, string $mode, ?Key $apiKey, array $plan, Document $devKey, Telemetry $telemetry, array $platform, Authorization $authorization) {
|
||||
|
||||
$response->setUser($user);
|
||||
$request->setUser($user);
|
||||
@@ -510,14 +522,6 @@ Http::init()
|
||||
default => '',
|
||||
};
|
||||
|
||||
if (
|
||||
array_key_exists('rest', $project->getAttribute('apis', []))
|
||||
&& ! $project->getAttribute('apis', [])['rest']
|
||||
&& ! ($user->isPrivileged($authorization->getRoles()) || $user->isApp($authorization->getRoles()))
|
||||
) {
|
||||
throw new AppwriteException(AppwriteException::GENERAL_API_DISABLED);
|
||||
}
|
||||
|
||||
/*
|
||||
* Abuse Check
|
||||
*/
|
||||
@@ -594,13 +598,12 @@ Http::init()
|
||||
->setProject($project)
|
||||
->setUser($user);
|
||||
|
||||
$queueForAudits
|
||||
->setMode($mode)
|
||||
->setUserAgent($request->getUserAgent(''))
|
||||
->setIP($request->getIP())
|
||||
->setHostname($request->getHostname())
|
||||
->setEvent($route->getLabel('audits.event', ''))
|
||||
->setProject($project);
|
||||
$auditContext->mode = $mode;
|
||||
$auditContext->userAgent = $request->getUserAgent('');
|
||||
$auditContext->ip = $request->getIP();
|
||||
$auditContext->hostname = $request->getHostname();
|
||||
$auditContext->event = $route->getLabel('audits.event', '');
|
||||
$auditContext->project = $project;
|
||||
|
||||
/* If a session exists, use the user associated with the session */
|
||||
if (! $user->isEmpty()) {
|
||||
@@ -609,7 +612,7 @@ Http::init()
|
||||
if (empty($user->getAttribute('type'))) {
|
||||
$userClone->setAttribute('type', $mode === APP_MODE_ADMIN ? ACTIVITY_TYPE_ADMIN : ACTIVITY_TYPE_USER);
|
||||
}
|
||||
$queueForAudits->setUser($userClone);
|
||||
$auditContext->user = $userClone;
|
||||
}
|
||||
|
||||
/* Auto-set projects */
|
||||
@@ -633,6 +636,7 @@ Http::init()
|
||||
$isDisabled = isset($plan['imageTransformations']) && $plan['imageTransformations'] === -1 && ! $user->isPrivileged($authorization->getRoles());
|
||||
|
||||
$key = $request->cacheIdentifier();
|
||||
Span::add('storage.cache.key', $key);
|
||||
$cacheLog = $authorization->skip(fn () => $dbForProject->getDocument('cache', $key));
|
||||
$cache = new Cache(
|
||||
new Filesystem(APP_STORAGE_CACHE . DIRECTORY_SEPARATOR . 'app-' . $project->getId())
|
||||
@@ -681,6 +685,8 @@ Http::init()
|
||||
if ($file->isEmpty()) {
|
||||
throw new Exception(Exception::STORAGE_FILE_NOT_FOUND);
|
||||
}
|
||||
Span::add('storage.bucket.id', $bucketId);
|
||||
Span::add('storage.file.id', $fileId);
|
||||
// Do not update transformedAt if it's a console user
|
||||
if (! $user->isPrivileged($authorization->getRoles())) {
|
||||
$transformedAt = $file->getAttribute('transformedAt', '');
|
||||
@@ -693,16 +699,27 @@ Http::init()
|
||||
}
|
||||
}
|
||||
|
||||
$accessedAt = $cacheLog->getAttribute('accessedAt', '');
|
||||
if (DateTime::formatTz(DateTime::addSeconds(new \DateTime(), -APP_CACHE_UPDATE)) > $accessedAt) {
|
||||
$authorization->skip(fn () => $dbForProject->updateDocument('cache', $cacheLog->getId(), new Document([
|
||||
'accessedAt' => DateTime::now(),
|
||||
])));
|
||||
// Refresh the filesystem file's mtime so TTL-based expiry in cache->load() stays valid
|
||||
$cache->save($key, $data);
|
||||
}
|
||||
|
||||
$response
|
||||
->addHeader('Cache-Control', sprintf('private, max-age=%d', $timestamp))
|
||||
->addHeader('X-Appwrite-Cache', 'hit')
|
||||
->setContentType($cacheLog->getAttribute('mimeType'));
|
||||
$storageCacheOperationsCounter->add(1, ['result' => 'hit']);
|
||||
if (! $isImageTransformation || ! $isDisabled) {
|
||||
Span::add('storage.cache.hit', true);
|
||||
$response->send($data);
|
||||
}
|
||||
} else {
|
||||
$storageCacheOperationsCounter->add(1, ['result' => 'miss']);
|
||||
Span::add('storage.cache.hit', false);
|
||||
$response
|
||||
->addHeader('Cache-Control', 'no-cache, no-store, must-revalidate')
|
||||
->addHeader('Pragma', 'no-cache')
|
||||
@@ -774,7 +791,8 @@ Http::shutdown()
|
||||
->inject('project')
|
||||
->inject('user')
|
||||
->inject('queueForEvents')
|
||||
->inject('queueForAudits')
|
||||
->inject('auditContext')
|
||||
->inject('publisherForAudits')
|
||||
->inject('usage')
|
||||
->inject('publisherForUsage')
|
||||
->inject('queueForDeletes')
|
||||
@@ -791,7 +809,7 @@ Http::shutdown()
|
||||
->inject('bus')
|
||||
->inject('apiKey')
|
||||
->inject('mode')
|
||||
->action(function (Http $utopia, Request $request, Response $response, Document $project, User $user, Event $queueForEvents, Audit $queueForAudits, Context $usage, UsagePublisher $publisherForUsage, Delete $queueForDeletes, EventDatabase $queueForDatabase, Build $queueForBuilds, Messaging $queueForMessaging, Func $queueForFunctions, Event $queueForWebhooks, Realtime $queueForRealtime, Database $dbForProject, Authorization $authorization, callable $timelimit, EventProcessor $eventProcessor, Bus $bus, ?Key $apiKey, string $mode) use ($parseLabel) {
|
||||
->action(function (Http $utopia, Request $request, Response $response, Document $project, User $user, Event $queueForEvents, AuditContext $auditContext, Audit $publisherForAudits, Context $usage, UsagePublisher $publisherForUsage, Delete $queueForDeletes, EventDatabase $queueForDatabase, Build $queueForBuilds, Messaging $queueForMessaging, Func $queueForFunctions, Event $queueForWebhooks, Realtime $queueForRealtime, Database $dbForProject, Authorization $authorization, callable $timelimit, EventProcessor $eventProcessor, Bus $bus, ?Key $apiKey, string $mode) use ($parseLabel) {
|
||||
|
||||
$responsePayload = $response->getPayload();
|
||||
|
||||
@@ -886,7 +904,7 @@ Http::shutdown()
|
||||
if (! empty($pattern)) {
|
||||
$resource = $parseLabel($pattern, $responsePayload, $requestParams, $user);
|
||||
if (! empty($resource) && $resource !== $pattern) {
|
||||
$queueForAudits->setResource($resource);
|
||||
$auditContext->resource = $resource;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -896,8 +914,8 @@ Http::shutdown()
|
||||
if (empty($user->getAttribute('type'))) {
|
||||
$userClone->setAttribute('type', $mode === APP_MODE_ADMIN ? ACTIVITY_TYPE_ADMIN : ACTIVITY_TYPE_USER);
|
||||
}
|
||||
$queueForAudits->setUser($userClone);
|
||||
} elseif ($queueForAudits->getUser() === null || $queueForAudits->getUser()->isEmpty()) {
|
||||
$auditContext->user = $userClone;
|
||||
} elseif ($auditContext->user === null || $auditContext->user->isEmpty()) {
|
||||
/**
|
||||
* User in the request is empty, and no user was set for auditing previously.
|
||||
* This indicates:
|
||||
@@ -915,24 +933,21 @@ Http::shutdown()
|
||||
'name' => 'Guest',
|
||||
]);
|
||||
|
||||
$queueForAudits->setUser($user);
|
||||
$auditContext->user = $user;
|
||||
}
|
||||
|
||||
if (! empty($queueForAudits->getResource()) && ! $queueForAudits->getUser()->isEmpty()) {
|
||||
$auditUser = $auditContext->user;
|
||||
if (! empty($auditContext->resource) && ! \is_null($auditUser) && ! $auditUser->isEmpty()) {
|
||||
/**
|
||||
* audits.payload is switched to default true
|
||||
* in order to auto audit payload for all endpoints
|
||||
*/
|
||||
$pattern = $route->getLabel('audits.payload', true);
|
||||
if (! empty($pattern)) {
|
||||
$queueForAudits->setPayload($responsePayload);
|
||||
$auditContext->payload = $responsePayload;
|
||||
}
|
||||
|
||||
foreach ($queueForEvents->getParams() as $key => $value) {
|
||||
$queueForAudits->setParam($key, $value);
|
||||
}
|
||||
|
||||
$queueForAudits->trigger();
|
||||
$publisherForAudits->enqueue(AuditMessage::fromContext($auditContext));
|
||||
}
|
||||
|
||||
if (! empty($queueForDeletes->getType())) {
|
||||
|
||||
+1
-2
@@ -72,8 +72,6 @@ $swooleAdapter = new Server(
|
||||
container: $container,
|
||||
);
|
||||
|
||||
$container->set('container', fn () => fn () => $swooleAdapter->getContainer());
|
||||
|
||||
$http = $swooleAdapter->getServer();
|
||||
|
||||
/**
|
||||
@@ -533,6 +531,7 @@ $swooleAdapter->onRequest(function ($utopiaRequest, $utopiaResponse) use ($files
|
||||
}
|
||||
|
||||
$requestContainer = $swooleAdapter->getContainer();
|
||||
$requestContainer->set('container', fn () => $requestContainer);
|
||||
$requestContainer->set('request', fn () => $request);
|
||||
$requestContainer->set('response', fn () => $response);
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ Config::load('runtimes-v2', __DIR__ . '/../config/runtimes-v2.php', $configAdapt
|
||||
Config::load('template-runtimes', __DIR__ . '/../config/template-runtimes.php', $configAdapter);
|
||||
Config::load('events', __DIR__ . '/../config/events.php', $configAdapter);
|
||||
Config::load('auth', __DIR__ . '/../config/auth.php', $configAdapter);
|
||||
Config::load('apis', __DIR__ . '/../config/apis.php', $configAdapter); // List of APIs
|
||||
Config::load('protocols', __DIR__ . '/../config/protocols.php', $configAdapter);
|
||||
Config::load('errors', __DIR__ . '/../config/errors.php', $configAdapter);
|
||||
Config::load('oAuthProviders', __DIR__ . '/../config/oAuthProviders.php', $configAdapter);
|
||||
Config::load('sdks', __DIR__ . '/../config/sdks.php', $configAdapter);
|
||||
|
||||
@@ -46,8 +46,8 @@ const APP_PROJECT_ACCESS = 24 * 60 * 60; // 24 hours
|
||||
const APP_RESOURCE_TOKEN_ACCESS = 24 * 60 * 60; // 24 hours
|
||||
const APP_FILE_ACCESS = 24 * 60 * 60; // 24 hours
|
||||
const APP_CACHE_UPDATE = 24 * 60 * 60; // 24 hours
|
||||
const APP_CACHE_BUSTER = 4321;
|
||||
const APP_VERSION_STABLE = '1.9.0';
|
||||
const APP_CACHE_BUSTER = 4322;
|
||||
const APP_VERSION_STABLE = '1.9.1';
|
||||
const APP_DATABASE_ATTRIBUTE_EMAIL = 'email';
|
||||
const APP_DATABASE_ATTRIBUTE_ENUM = 'enum';
|
||||
const APP_DATABASE_ATTRIBUTE_IP = 'ip';
|
||||
|
||||
@@ -0,0 +1,366 @@
|
||||
<?php
|
||||
|
||||
use Ahc\Jwt\JWT;
|
||||
use Ahc\Jwt\JWTException;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Network\Platform;
|
||||
use Appwrite\Network\Validator\Origin;
|
||||
use Appwrite\Utopia\Database\Documents\User;
|
||||
use Appwrite\Utopia\Request;
|
||||
use Utopia\Auth\Hashes\Sha;
|
||||
use Utopia\Auth\Proofs\Token;
|
||||
use Utopia\Auth\Store;
|
||||
use Utopia\Database\DateTime as DatabaseDateTime;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\DI\Container;
|
||||
use Utopia\System\System;
|
||||
use Utopia\Validator\URL;
|
||||
use Utopia\Validator\WhiteList;
|
||||
|
||||
/**
|
||||
* Register the minimal per-connection resources required by realtime.
|
||||
*/
|
||||
return function (Container $container): void {
|
||||
$getProjectId = static function (Request $request): string {
|
||||
$projectId = $request->getHeader('x-appwrite-project', '');
|
||||
|
||||
if (!empty($projectId)) {
|
||||
return $projectId;
|
||||
}
|
||||
|
||||
$projectId = $request->getParam('project', '');
|
||||
|
||||
return \is_string($projectId) ? $projectId : '';
|
||||
};
|
||||
|
||||
$getMode = static function (Request $request, Document $project) use ($getProjectId): string {
|
||||
$mode = $request->getParam('mode', $request->getHeader('x-appwrite-mode', APP_MODE_DEFAULT));
|
||||
$projectId = $getProjectId($request);
|
||||
|
||||
if (!empty($projectId) && $project->getId() !== $projectId) {
|
||||
$mode = APP_MODE_ADMIN;
|
||||
}
|
||||
|
||||
return $mode;
|
||||
};
|
||||
|
||||
$getDbForPlatform = static function (Authorization $authorization) {
|
||||
$database = getConsoleDB();
|
||||
$database->setAuthorization($authorization);
|
||||
|
||||
return $database;
|
||||
};
|
||||
|
||||
$getDbForProject = static function (Document $project, Authorization $authorization) use ($getDbForPlatform) {
|
||||
if ($project->isEmpty() || $project->getId() === 'console') {
|
||||
return $getDbForPlatform($authorization);
|
||||
}
|
||||
|
||||
$database = getProjectDB($project);
|
||||
$database->setAuthorization($authorization);
|
||||
|
||||
return $database;
|
||||
};
|
||||
|
||||
$findRule = static function (Request $request, Document $project, Authorization $authorization) use ($getDbForPlatform): Document {
|
||||
$domain = \parse_url($request->getOrigin(), PHP_URL_HOST);
|
||||
|
||||
if (empty($domain)) {
|
||||
$domain = \parse_url($request->getReferer(), PHP_URL_HOST);
|
||||
}
|
||||
|
||||
if (empty($domain)) {
|
||||
return new Document();
|
||||
}
|
||||
|
||||
$dbForPlatform = $getDbForPlatform($authorization);
|
||||
$isMd5 = System::getEnv('_APP_RULES_FORMAT') === 'md5';
|
||||
|
||||
$rule = $authorization->skip(function () use ($dbForPlatform, $domain, $isMd5) {
|
||||
if ($isMd5) {
|
||||
return $dbForPlatform->getDocument('rules', md5($domain));
|
||||
}
|
||||
|
||||
return $dbForPlatform->findOne('rules', [
|
||||
Query::equal('domain', [$domain]),
|
||||
]) ?? new Document();
|
||||
});
|
||||
|
||||
$permitsCurrentProject = $rule->getAttribute('projectInternalId', '') === $project->getSequence();
|
||||
|
||||
if (!$permitsCurrentProject && !$rule->isEmpty() && !empty($rule->getAttribute('projectId', ''))) {
|
||||
$trustedProjects = [];
|
||||
foreach (\explode(',', System::getEnv('_APP_CONSOLE_TRUSTED_PROJECTS', '')) as $trustedProject) {
|
||||
if (empty($trustedProject)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$trustedProjects[] = $trustedProject;
|
||||
}
|
||||
|
||||
if (\in_array($rule->getAttribute('projectId', ''), $trustedProjects, true)) {
|
||||
$permitsCurrentProject = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$permitsCurrentProject) {
|
||||
return new Document();
|
||||
}
|
||||
|
||||
return $rule;
|
||||
};
|
||||
|
||||
$findDevKey = static function (Request $request, Document $project, array $servers, Authorization $authorization) use ($getDbForPlatform): Document {
|
||||
$devKey = $request->getHeader('x-appwrite-dev-key', $request->getParam('devKey', ''));
|
||||
$key = $project->find('secret', $devKey, 'devKeys');
|
||||
|
||||
if (!$key) {
|
||||
return new Document([]);
|
||||
}
|
||||
|
||||
$expire = $key->getAttribute('expire');
|
||||
if (!empty($expire) && $expire < DatabaseDateTime::formatTz(DatabaseDateTime::now())) {
|
||||
return new Document([]);
|
||||
}
|
||||
|
||||
$dbForPlatform = $getDbForPlatform($authorization);
|
||||
$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(), new Document([
|
||||
'accessedAt' => $key->getAttribute('accessedAt'),
|
||||
])));
|
||||
$dbForPlatform->purgeCachedDocument('projects', $project->getId());
|
||||
}
|
||||
|
||||
$sdkValidator = new WhiteList($servers, true);
|
||||
$sdk = \strtolower($request->getHeader('x-sdk-name', 'UNKNOWN'));
|
||||
|
||||
if ($sdk !== 'UNKNOWN' && $sdkValidator->isValid($sdk)) {
|
||||
$sdks = $key->getAttribute('sdks', []);
|
||||
|
||||
if (!\in_array($sdk, $sdks, true)) {
|
||||
$sdks[] = $sdk;
|
||||
$key->setAttribute('sdks', $sdks);
|
||||
$key->setAttribute('accessedAt', DatabaseDateTime::now());
|
||||
|
||||
$key = $authorization->skip(fn () => $dbForPlatform->updateDocument('devKeys', $key->getId(), new Document([
|
||||
'sdks' => $key->getAttribute('sdks'),
|
||||
'accessedAt' => $key->getAttribute('accessedAt'),
|
||||
])));
|
||||
$dbForPlatform->purgeCachedDocument('projects', $project->getId());
|
||||
}
|
||||
}
|
||||
|
||||
return $key;
|
||||
};
|
||||
|
||||
$container->set('authorization', function () {
|
||||
return new Authorization();
|
||||
}, []);
|
||||
|
||||
$container->set('project', function (Request $request, Document $console, Authorization $authorization) use ($getProjectId, $getDbForPlatform) {
|
||||
$projectId = $getProjectId($request);
|
||||
|
||||
if (empty($projectId) || $projectId === 'console') {
|
||||
return $console;
|
||||
}
|
||||
|
||||
$dbForPlatform = $getDbForPlatform($authorization);
|
||||
|
||||
return $authorization->skip(fn () => $dbForPlatform->getDocument('projects', $projectId));
|
||||
}, ['request', 'console', 'authorization']);
|
||||
|
||||
$container->set('originValidator', function (array $platform, Request $request, Document $project, array $servers, Authorization $authorization) use ($findDevKey, $findRule) {
|
||||
$devKey = $findDevKey($request, $project, $servers, $authorization);
|
||||
|
||||
if (!$devKey->isEmpty()) {
|
||||
return new URL();
|
||||
}
|
||||
|
||||
$allowedHostnames = [...($platform['hostnames'] ?? [])];
|
||||
if (!$project->isEmpty() && $project->getId() !== 'console') {
|
||||
$allowedHostnames = [...$allowedHostnames, ...Platform::getHostnames($project->getAttribute('platforms', []))];
|
||||
}
|
||||
|
||||
$rule = $findRule($request, $project, $authorization);
|
||||
if (!$rule->isEmpty() && !empty($rule->getAttribute('domain', ''))) {
|
||||
$allowedHostnames[] = $rule->getAttribute('domain', '');
|
||||
}
|
||||
|
||||
$originHostname = \parse_url($request->getOrigin(), PHP_URL_HOST);
|
||||
$refererHostname = \parse_url($request->getReferer(), PHP_URL_HOST);
|
||||
$hostname = $originHostname ?: $refererHostname;
|
||||
|
||||
if ($request->getMethod() === 'OPTIONS' && !empty($hostname)) {
|
||||
$allowedHostnames[] = $hostname;
|
||||
}
|
||||
|
||||
$allowedSchemes = [...($platform['schemas'] ?? [])];
|
||||
if (!$project->isEmpty() && $project->getId() !== 'console') {
|
||||
$allowedSchemes[] = 'exp';
|
||||
$allowedSchemes[] = 'appwrite-callback-' . $project->getId();
|
||||
$allowedSchemes = [...$allowedSchemes, ...Platform::getSchemes($project->getAttribute('platforms', []))];
|
||||
}
|
||||
|
||||
return new Origin(\array_unique($allowedHostnames), \array_unique($allowedSchemes));
|
||||
}, ['platform', 'request', 'project', 'servers', 'authorization']);
|
||||
|
||||
$container->set('user', function (Request $request, Document $project, Document $console, Authorization $authorization) use ($getMode, $getDbForPlatform, $getDbForProject) {
|
||||
$mode = $getMode($request, $project);
|
||||
$store = new Store();
|
||||
$proofForToken = new Token();
|
||||
$proofForToken->setHash(new Sha());
|
||||
|
||||
$authorization->setDefaultStatus(true);
|
||||
|
||||
$dbForPlatform = $getDbForPlatform($authorization);
|
||||
$dbForProject = $getDbForProject($project, $authorization);
|
||||
|
||||
$store->setKey('a_session_' . $project->getId());
|
||||
if ($mode === APP_MODE_ADMIN) {
|
||||
$store->setKey('a_session_' . $console->getId());
|
||||
}
|
||||
|
||||
$store->decode(
|
||||
$request->getCookie(
|
||||
$store->getKey(),
|
||||
$request->getCookie($store->getKey() . '_legacy', '')
|
||||
)
|
||||
);
|
||||
|
||||
if (empty($store->getProperty('id', '')) && empty($store->getProperty('secret', ''))) {
|
||||
$sessionHeader = $request->getHeader('x-appwrite-session', '');
|
||||
|
||||
if (!empty($sessionHeader)) {
|
||||
$store->decode($sessionHeader);
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($store->getProperty('id', '')) && empty($store->getProperty('secret', ''))) {
|
||||
$fallback = \json_decode($request->getHeader('x-fallback-cookies', ''), true);
|
||||
$store->decode((\is_array($fallback) && isset($fallback[$store->getKey()])) ? $fallback[$store->getKey()] : '');
|
||||
}
|
||||
|
||||
$user = null;
|
||||
if ($mode === APP_MODE_ADMIN) {
|
||||
/** @var User $user */
|
||||
$user = $dbForPlatform->getDocument('users', $store->getProperty('id', ''));
|
||||
} else {
|
||||
if ($project->isEmpty()) {
|
||||
$user = new User([]);
|
||||
} elseif (!empty($store->getProperty('id', ''))) {
|
||||
if ($project->getId() === 'console') {
|
||||
/** @var User $user */
|
||||
$user = $dbForPlatform->getDocument('users', $store->getProperty('id', ''));
|
||||
} else {
|
||||
/** @var User $user */
|
||||
$user = $dbForProject->getDocument('users', $store->getProperty('id', ''));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
!$user
|
||||
|| $user->isEmpty()
|
||||
|| !$user->sessionVerify($store->getProperty('secret', ''), $proofForToken)
|
||||
) {
|
||||
$user = new User([]);
|
||||
}
|
||||
|
||||
$authJWT = $request->getHeader('x-appwrite-jwt', '');
|
||||
if (!empty($authJWT) && !$project->isEmpty()) {
|
||||
if (!$user->isEmpty()) {
|
||||
throw new Exception(Exception::USER_JWT_AND_COOKIE_SET);
|
||||
}
|
||||
|
||||
$jwt = new JWT(System::getEnv('_APP_OPENSSL_KEY_V1'), 'HS256', 3600, 0);
|
||||
|
||||
try {
|
||||
$payload = $jwt->decode($authJWT);
|
||||
} catch (JWTException $error) {
|
||||
throw new Exception(Exception::USER_JWT_INVALID, 'Failed to verify JWT. ' . $error->getMessage());
|
||||
}
|
||||
|
||||
$jwtUserId = $payload['userId'] ?? '';
|
||||
if (!empty($jwtUserId)) {
|
||||
if ($mode === APP_MODE_ADMIN) {
|
||||
$user = $dbForPlatform->getDocument('users', $jwtUserId);
|
||||
} else {
|
||||
$user = $dbForProject->getDocument('users', $jwtUserId);
|
||||
}
|
||||
}
|
||||
|
||||
$jwtSessionId = $payload['sessionId'] ?? '';
|
||||
if (!empty($jwtSessionId) && empty($user->find('$id', $jwtSessionId, 'sessions'))) {
|
||||
$user = new User([]);
|
||||
}
|
||||
}
|
||||
|
||||
$accountKey = $request->getHeader('x-appwrite-key', '');
|
||||
$accountKeyUserId = $request->getHeader('x-appwrite-user', '');
|
||||
|
||||
if (!empty($accountKeyUserId) && !empty($accountKey)) {
|
||||
if (!$user->isEmpty()) {
|
||||
throw new Exception(Exception::USER_API_KEY_AND_SESSION_SET);
|
||||
}
|
||||
|
||||
$accountKeyUser = $authorization->skip(fn () => $dbForPlatform->getDocument('users', $accountKeyUserId));
|
||||
if (!$accountKeyUser->isEmpty()) {
|
||||
$key = $accountKeyUser->find(
|
||||
key: 'secret',
|
||||
find: $accountKey,
|
||||
subject: 'keys'
|
||||
);
|
||||
|
||||
if (!empty($key)) {
|
||||
$expire = $key->getAttribute('expire');
|
||||
if (!empty($expire) && $expire < DatabaseDateTime::formatTz(DatabaseDateTime::now())) {
|
||||
throw new Exception(Exception::ACCOUNT_KEY_EXPIRED);
|
||||
}
|
||||
|
||||
$user = $accountKeyUser;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$impersonateUserId = $request->getHeader('x-appwrite-impersonate-user-id', '');
|
||||
$impersonateEmail = $request->getHeader('x-appwrite-impersonate-user-email', '');
|
||||
$impersonatePhone = $request->getHeader('x-appwrite-impersonate-user-phone', '');
|
||||
|
||||
if (!$user->isEmpty() && $user->getAttribute('impersonator', false)) {
|
||||
$userDb = ($mode === APP_MODE_ADMIN || $project->getId() === 'console') ? $dbForPlatform : $dbForProject;
|
||||
$targetUser = null;
|
||||
|
||||
if (!empty($impersonateUserId)) {
|
||||
$targetUser = $authorization->skip(fn () => $userDb->getDocument('users', $impersonateUserId));
|
||||
} elseif (!empty($impersonateEmail)) {
|
||||
$targetUser = $authorization->skip(fn () => $userDb->findOne('users', [
|
||||
Query::equal('email', [\strtolower($impersonateEmail)]),
|
||||
]));
|
||||
} elseif (!empty($impersonatePhone)) {
|
||||
$targetUser = $authorization->skip(fn () => $userDb->findOne('users', [
|
||||
Query::equal('phone', [$impersonatePhone]),
|
||||
]));
|
||||
}
|
||||
|
||||
if ($targetUser !== null && !$targetUser->isEmpty()) {
|
||||
$impersonator = clone $user;
|
||||
$user = clone $targetUser;
|
||||
$user->setAttribute('impersonatorUserId', $impersonator->getId());
|
||||
$user->setAttribute('impersonatorUserInternalId', $impersonator->getSequence());
|
||||
$user->setAttribute('impersonatorUserName', $impersonator->getAttribute('name', ''));
|
||||
$user->setAttribute('impersonatorUserEmail', $impersonator->getAttribute('email', ''));
|
||||
$user->setAttribute('impersonatorAccessedAt', $impersonator->getAttribute('accessedAt', 0));
|
||||
}
|
||||
}
|
||||
|
||||
$dbForPlatform->setMetadata('user', $user->getId());
|
||||
$dbForProject->setMetadata('user', $user->getId());
|
||||
|
||||
return $user;
|
||||
}, ['request', 'project', 'console', 'authorization']);
|
||||
};
|
||||
@@ -1,6 +1,12 @@
|
||||
<?php
|
||||
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Event\Publisher\Audit as AuditPublisher;
|
||||
use Appwrite\Event\Publisher\Certificate as CertificatePublisher;
|
||||
use Appwrite\Event\Publisher\Execution as ExecutionPublisher;
|
||||
use Appwrite\Event\Publisher\Migration as MigrationPublisher;
|
||||
use Appwrite\Event\Publisher\Screenshot as ScreenshotPublisher;
|
||||
use Appwrite\Event\Publisher\StatsResources as StatsResourcesPublisher;
|
||||
use Appwrite\Event\Publisher\Usage as UsagePublisher;
|
||||
use Appwrite\Utopia\Database\Documents\User;
|
||||
use Executor\Executor;
|
||||
@@ -78,10 +84,34 @@ $container->set('publisherMessaging', function (Publisher $publisher) {
|
||||
$container->set('publisherWebhooks', function (Publisher $publisher) {
|
||||
return $publisher;
|
||||
}, ['publisher']);
|
||||
$container->set('publisherForAudits', fn (Publisher $publisher) => new AuditPublisher(
|
||||
$publisher,
|
||||
new Queue(System::getEnv('_APP_AUDITS_QUEUE_NAME', Event::AUDITS_QUEUE_NAME))
|
||||
), ['publisher']);
|
||||
$container->set('publisherForCertificates', fn (Publisher $publisher) => new CertificatePublisher(
|
||||
$publisher,
|
||||
new Queue(System::getEnv('_APP_CERTIFICATES_QUEUE_NAME', Event::CERTIFICATES_QUEUE_NAME))
|
||||
), ['publisher']);
|
||||
$container->set('publisherForScreenshots', fn (Publisher $publisher) => new ScreenshotPublisher(
|
||||
$publisher,
|
||||
new Queue(System::getEnv('_APP_SCREENSHOTS_QUEUE_NAME', Event::SCREENSHOTS_QUEUE_NAME))
|
||||
), ['publisher']);
|
||||
$container->set('publisherForUsage', fn (Publisher $publisher) => new UsagePublisher(
|
||||
$publisher,
|
||||
new Queue(System::getEnv('_APP_STATS_USAGE_QUEUE_NAME', Event::STATS_USAGE_QUEUE_NAME))
|
||||
), ['publisher']);
|
||||
$container->set('publisherForExecutions', fn (Publisher $publisher) => new ExecutionPublisher(
|
||||
$publisher,
|
||||
new Queue(System::getEnv('_APP_EXECUTIONS_QUEUE_NAME', Event::EXECUTIONS_QUEUE_NAME))
|
||||
), ['publisher']);
|
||||
$container->set('publisherForMigrations', fn (Publisher $publisher) => new MigrationPublisher(
|
||||
$publisher,
|
||||
new Queue(System::getEnv('_APP_MIGRATIONS_QUEUE_NAME', Event::MIGRATIONS_QUEUE_NAME))
|
||||
), ['publisher']);
|
||||
$container->set('publisherForStatsResources', fn (Publisher $publisher) => new StatsResourcesPublisher(
|
||||
$publisher,
|
||||
new Queue(System::getEnv('_APP_STATS_RESOURCES_QUEUE_NAME', Event::STATS_RESOURCES_QUEUE_NAME))
|
||||
), ['publisher']);
|
||||
|
||||
/**
|
||||
* Platform configuration
|
||||
|
||||
@@ -4,19 +4,15 @@ use Ahc\Jwt\JWT;
|
||||
use Ahc\Jwt\JWTException;
|
||||
use Appwrite\Auth\Key;
|
||||
use Appwrite\Databases\TransactionState;
|
||||
use Appwrite\Event\Audit as AuditEvent;
|
||||
use Appwrite\Event\Build;
|
||||
use Appwrite\Event\Certificate;
|
||||
use Appwrite\Event\Context\Audit as AuditContext;
|
||||
use Appwrite\Event\Database as EventDatabase;
|
||||
use Appwrite\Event\Delete;
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Event\Func;
|
||||
use Appwrite\Event\Mail;
|
||||
use Appwrite\Event\Messaging;
|
||||
use Appwrite\Event\Migration;
|
||||
use Appwrite\Event\Realtime;
|
||||
use Appwrite\Event\Screenshot;
|
||||
use Appwrite\Event\StatsResources;
|
||||
use Appwrite\Event\Webhook;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Functions\EventProcessor;
|
||||
@@ -130,9 +126,6 @@ return function (Container $container): void {
|
||||
$container->set('queueForBuilds', function (Publisher $publisher) {
|
||||
return new Build($publisher);
|
||||
}, ['publisher']);
|
||||
$container->set('queueForScreenshots', function (Publisher $publisher) {
|
||||
return new Screenshot($publisher);
|
||||
}, ['publisher']);
|
||||
$container->set('queueForDatabase', function (Publisher $publisher) {
|
||||
return new EventDatabase($publisher);
|
||||
}, ['publisher']);
|
||||
@@ -151,25 +144,13 @@ return function (Container $container): void {
|
||||
$container->set('usage', function () {
|
||||
return new UsageContext();
|
||||
}, []);
|
||||
$container->set('queueForAudits', function (Publisher $publisher) {
|
||||
return new AuditEvent($publisher);
|
||||
}, ['publisher']);
|
||||
$container->set('auditContext', fn () => new AuditContext(), []);
|
||||
$container->set('queueForFunctions', function (Publisher $publisher) {
|
||||
return new Func($publisher);
|
||||
}, ['publisher']);
|
||||
$container->set('eventProcessor', function () {
|
||||
return new EventProcessor();
|
||||
}, []);
|
||||
$container->set('queueForCertificates', function (Publisher $publisher) {
|
||||
return new Certificate($publisher);
|
||||
}, ['publisher']);
|
||||
$container->set('queueForMigrations', function (Publisher $publisher) {
|
||||
return new Migration($publisher);
|
||||
}, ['publisher']);
|
||||
$container->set('queueForStatsResources', function (Publisher $publisher) {
|
||||
return new StatsResources($publisher);
|
||||
}, ['publisher']);
|
||||
|
||||
$container->set('dbForPlatform', function (Group $pools, Cache $cache, Authorization $authorization) {
|
||||
$adapter = new DatabasePool($pools->get('console'));
|
||||
$database = new Database($adapter, $cache);
|
||||
|
||||
@@ -1,17 +1,13 @@
|
||||
<?php
|
||||
|
||||
use Appwrite\Event\Audit;
|
||||
use Appwrite\Event\Build;
|
||||
use Appwrite\Event\Certificate;
|
||||
use Appwrite\Event\Database as EventDatabase;
|
||||
use Appwrite\Event\Delete;
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Event\Func;
|
||||
use Appwrite\Event\Mail;
|
||||
use Appwrite\Event\Messaging;
|
||||
use Appwrite\Event\Migration;
|
||||
use Appwrite\Event\Realtime;
|
||||
use Appwrite\Event\Screenshot;
|
||||
use Appwrite\Event\Webhook;
|
||||
use Appwrite\Usage\Context;
|
||||
use Appwrite\Utopia\Database\Documents\User;
|
||||
@@ -312,10 +308,6 @@ return function (Container $container): void {
|
||||
return new Build($publisher);
|
||||
}, ['publisher']);
|
||||
|
||||
$container->set('queueForScreenshots', function (Publisher $publisher) {
|
||||
return new Screenshot($publisher);
|
||||
}, ['publisher']);
|
||||
|
||||
$container->set('queueForDeletes', function (Publisher $publisher) {
|
||||
return new Delete($publisher);
|
||||
}, ['publisher']);
|
||||
@@ -324,10 +316,6 @@ return function (Container $container): void {
|
||||
return new Event($publisher);
|
||||
}, ['publisher']);
|
||||
|
||||
$container->set('queueForAudits', function (Publisher $publisher) {
|
||||
return new Audit($publisher);
|
||||
}, ['publisher']);
|
||||
|
||||
$container->set('queueForWebhooks', function (Publisher $publisher) {
|
||||
return new Webhook($publisher);
|
||||
}, ['publisher']);
|
||||
@@ -340,14 +328,6 @@ return function (Container $container): void {
|
||||
return new Realtime();
|
||||
}, []);
|
||||
|
||||
$container->set('queueForCertificates', function (Publisher $publisher) {
|
||||
return new Certificate($publisher);
|
||||
}, ['publisher']);
|
||||
|
||||
$container->set('queueForMigrations', function (Publisher $publisher) {
|
||||
return new Migration($publisher);
|
||||
}, ['publisher']);
|
||||
|
||||
$container->set('deviceForSites', function (Document $project, Telemetry $telemetry) {
|
||||
return new TelemetryDevice($telemetry, getDevice(APP_STORAGE_SITES . '/app-' . $project->getId()));
|
||||
}, ['project', 'telemetry']);
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
<?php
|
||||
|
||||
use Appwrite\Bus\Listeners\Log;
|
||||
use Appwrite\Bus\Listeners\Mails;
|
||||
use Appwrite\Bus\Listeners\Usage;
|
||||
|
||||
return [
|
||||
new Log(),
|
||||
new Mails(),
|
||||
new Usage(),
|
||||
];
|
||||
|
||||
+151
-43
@@ -35,8 +35,6 @@ use Utopia\Database\Query;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\DI\Container;
|
||||
use Utopia\DSN\DSN;
|
||||
use Utopia\Http\Adapter\FPM\Server as HttpServer;
|
||||
use Utopia\Http\Http;
|
||||
use Utopia\Logger\Log;
|
||||
use Utopia\Pools\Group;
|
||||
use Utopia\Registry\Registry;
|
||||
@@ -45,12 +43,12 @@ use Utopia\Telemetry\Adapter\None as NoTelemetry;
|
||||
use Utopia\WebSocket\Adapter;
|
||||
use Utopia\WebSocket\Server;
|
||||
|
||||
/**
|
||||
* @var Registry $register
|
||||
*/
|
||||
require_once __DIR__ . '/init.php';
|
||||
|
||||
$registerRequestResources ??= require __DIR__ . '/init/resources/request.php';
|
||||
/** @var Registry $register */
|
||||
$register = $GLOBALS['register'] ?? throw new \RuntimeException('Registry not initialized');
|
||||
|
||||
$registerConnectionResources ??= require __DIR__ . '/init/realtime/connection.php';
|
||||
|
||||
Runtime::enableCoroutine(SWOOLE_HOOK_ALL);
|
||||
|
||||
@@ -450,7 +448,7 @@ $server->onWorkerStart(function (int $workerId) use ($server, $register, $stats,
|
||||
]
|
||||
];
|
||||
|
||||
$server->send($realtime->getSubscribers($event), json_encode([
|
||||
$server->send(array_keys($realtime->getSubscribers($event)), json_encode([
|
||||
'type' => 'event',
|
||||
'data' => $event['data']
|
||||
]));
|
||||
@@ -557,7 +555,7 @@ $server->onWorkerStart(function (int $workerId) use ($server, $register, $stats,
|
||||
|
||||
$receivers = $realtime->getSubscribers($event);
|
||||
|
||||
if (Http::isDevelopment() && !empty($receivers)) {
|
||||
if (System::getEnv('_APP_ENV', 'production') === 'development' && !empty($receivers)) {
|
||||
Console::log("[Debug][Worker {$workerId}] Receivers: " . count($receivers));
|
||||
Console::log("[Debug][Worker {$workerId}] Connection IDs: " . json_encode(array_keys($receivers)));
|
||||
Console::log("[Debug][Worker {$workerId}] Matched: " . json_encode(array_values($receivers)));
|
||||
@@ -623,7 +621,7 @@ $server->onWorkerStart(function (int $workerId) use ($server, $register, $stats,
|
||||
Console::error('Failed to restart pub/sub...');
|
||||
});
|
||||
|
||||
$server->onOpen(function (int $connection, SwooleRequest $request) use ($server, $register, $stats, &$realtime, $registerRequestResources) {
|
||||
$server->onOpen(function (int $connection, SwooleRequest $request) use ($server, $register, $stats, &$realtime, $registerConnectionResources) {
|
||||
global $container;
|
||||
$request = new Request($request);
|
||||
$response = new Response(new SwooleResponse());
|
||||
@@ -631,14 +629,9 @@ $server->onOpen(function (int $connection, SwooleRequest $request) use ($server,
|
||||
Console::info("Connection open (user: {$connection})");
|
||||
|
||||
$connectionContainer = new Container($container);
|
||||
|
||||
$adapter = new HttpServer($connectionContainer);
|
||||
$app = new Http($adapter, 'UTC');
|
||||
$connectionContainer->set('utopia', fn () => $app);
|
||||
$connectionContainer->set('request', fn () => $request);
|
||||
$connectionContainer->set('response', fn () => $response);
|
||||
|
||||
$registerRequestResources($connectionContainer);
|
||||
$registerConnectionResources($connectionContainer);
|
||||
|
||||
$project = null;
|
||||
$logUser = null;
|
||||
@@ -646,8 +639,8 @@ $server->onOpen(function (int $connection, SwooleRequest $request) use ($server,
|
||||
|
||||
try {
|
||||
/** @var Document $project */
|
||||
$project = $app->getResource('project');
|
||||
$authorization = $app->getResource('authorization');
|
||||
$project = $connectionContainer->get('project');
|
||||
$authorization = $connectionContainer->get('authorization');
|
||||
|
||||
/*
|
||||
* Project Check
|
||||
@@ -656,13 +649,15 @@ $server->onOpen(function (int $connection, SwooleRequest $request) use ($server,
|
||||
throw new Exception(Exception::REALTIME_POLICY_VIOLATION, 'Missing or unknown project ID');
|
||||
}
|
||||
|
||||
$timelimit = $app->getResource('timelimit');
|
||||
$user = $app->getResource('user'); /** @var User $user */
|
||||
$timelimit = $connectionContainer->get('timelimit');
|
||||
$user = $connectionContainer->get('user'); /** @var User $user */
|
||||
$logUser = $user;
|
||||
|
||||
$apis = $project->getAttribute('apis', []);
|
||||
// Websocket is what to check, but realtime is checked too for backwards compatibility
|
||||
$websocketEnabled = $apis['websocket'] ?? $apis['realtime'] ?? true;
|
||||
if (
|
||||
array_key_exists('realtime', $project->getAttribute('apis', []))
|
||||
&& !$project->getAttribute('apis', [])['realtime']
|
||||
!$websocketEnabled
|
||||
&& !($user->isPrivileged($authorization->getRoles()) || $user->isApp($authorization->getRoles()))
|
||||
) {
|
||||
throw new AppwriteException(AppwriteException::GENERAL_API_DISABLED);
|
||||
@@ -702,7 +697,7 @@ $server->onOpen(function (int $connection, SwooleRequest $request) use ($server,
|
||||
* Skip this check for non-web platforms which are not required to send an origin header.
|
||||
*/
|
||||
$origin = $request->getOrigin();
|
||||
$originValidator = $app->getResource('originValidator');
|
||||
$originValidator = $connectionContainer->get('originValidator');
|
||||
|
||||
if (!empty($origin) && !$originValidator->isValid($origin) && $project->getId() !== 'console') {
|
||||
throw new Exception(Exception::REALTIME_POLICY_VIOLATION, $originValidator->getDescription());
|
||||
@@ -712,11 +707,43 @@ $server->onOpen(function (int $connection, SwooleRequest $request) use ($server,
|
||||
|
||||
$channels = Realtime::convertChannels($request->getQuery('channels', []), $user->getId());
|
||||
|
||||
$updateStats = static function (string $projectId, ?string $teamId, string $payloadJson) use ($register, $stats): void {
|
||||
$register->get('telemetry.connectionCounter')->add(1);
|
||||
$register->get('telemetry.connectionCreatedCounter')->add(1);
|
||||
|
||||
$stats->set($projectId, [
|
||||
'projectId' => $projectId,
|
||||
'teamId' => $teamId
|
||||
]);
|
||||
$stats->incr($projectId, 'connections');
|
||||
$stats->incr($projectId, 'connectionsTotal');
|
||||
|
||||
triggerStats([
|
||||
METRIC_REALTIME_CONNECTIONS => 1,
|
||||
METRIC_REALTIME_OUTBOUND => \strlen($payloadJson),
|
||||
], $projectId);
|
||||
};
|
||||
|
||||
/**
|
||||
* Channels Check
|
||||
*/
|
||||
if (empty($channels)) {
|
||||
throw new Exception(Exception::REALTIME_POLICY_VIOLATION, 'Missing channels');
|
||||
// in case of message based 'subscribe' channels will be empty at first and only projectId and roles will be available
|
||||
$sanitizedUser = empty($user->getId()) ? null : $response->output($user, Response::MODEL_ACCOUNT);
|
||||
$connectedPayloadJson = json_encode([
|
||||
'type' => 'connected',
|
||||
'data' => [
|
||||
'channels' => [],
|
||||
'subscriptions' => [],
|
||||
'user' => $sanitizedUser
|
||||
]
|
||||
]);
|
||||
|
||||
$realtime->subscribe($project->getId(), $connection, '', $roles, [], [], $user->getId());
|
||||
$realtime->connections[$connection]['authorization'] = $authorization;
|
||||
$server->send([$connection], $connectedPayloadJson);
|
||||
$updateStats($project->getId(), $project->getAttribute('teamId'), $connectedPayloadJson);
|
||||
return;
|
||||
}
|
||||
|
||||
$names = array_keys($channels);
|
||||
@@ -740,7 +767,8 @@ $server->onOpen(function (int $connection, SwooleRequest $request) use ($server,
|
||||
$subscriptionId,
|
||||
$roles,
|
||||
$subscription['channels'],
|
||||
$subscription['queries']
|
||||
$subscription['queries'],
|
||||
$user->getId()
|
||||
);
|
||||
|
||||
$mapping[$index] = $subscriptionId;
|
||||
@@ -760,20 +788,7 @@ $server->onOpen(function (int $connection, SwooleRequest $request) use ($server,
|
||||
]);
|
||||
|
||||
$server->send([$connection], $connectedPayloadJson);
|
||||
|
||||
$register->get('telemetry.connectionCounter')->add(1);
|
||||
$register->get('telemetry.connectionCreatedCounter')->add(1);
|
||||
|
||||
$stats->set($project->getId(), [
|
||||
'projectId' => $project->getId(),
|
||||
'teamId' => $project->getAttribute('teamId')
|
||||
]);
|
||||
$stats->incr($project->getId(), 'connections');
|
||||
$stats->incr($project->getId(), 'connectionsTotal');
|
||||
|
||||
$connectedOutboundBytes = \strlen($connectedPayloadJson);
|
||||
|
||||
triggerStats([METRIC_REALTIME_CONNECTIONS => 1, METRIC_REALTIME_OUTBOUND => $connectedOutboundBytes], $project->getId());
|
||||
$updateStats($project->getId(), $project->getAttribute('teamId'), $connectedPayloadJson);
|
||||
|
||||
|
||||
} catch (Throwable $th) {
|
||||
@@ -789,7 +804,7 @@ $server->onOpen(function (int $connection, SwooleRequest $request) use ($server,
|
||||
|
||||
// sanitize 0 && 5xx errors
|
||||
$realtimeViolation = $th instanceof AppwriteException && $th->getType() === AppwriteException::REALTIME_POLICY_VIOLATION;
|
||||
if (($code === 0 || $code >= 500) && !$realtimeViolation && !Http::isDevelopment()) {
|
||||
if (($code === 0 || $code >= 500) && !$realtimeViolation && System::getEnv('_APP_ENV', 'production') !== 'development') {
|
||||
$message = 'Error: Server Error';
|
||||
}
|
||||
|
||||
@@ -804,7 +819,7 @@ $server->onOpen(function (int $connection, SwooleRequest $request) use ($server,
|
||||
$server->send([$connection], json_encode($response));
|
||||
$server->close($connection, $code);
|
||||
|
||||
if (Http::isDevelopment()) {
|
||||
if (System::getEnv('_APP_ENV', 'production') === 'development') {
|
||||
Console::error('[Error] Connection Error');
|
||||
Console::error('[Error] Code: ' . $response['data']['code']);
|
||||
Console::error('[Error] Message: ' . $response['data']['message']);
|
||||
@@ -815,7 +830,6 @@ $server->onOpen(function (int $connection, SwooleRequest $request) use ($server,
|
||||
$server->onMessage(function (int $connection, string $message) use ($server, $realtime, $containerId) {
|
||||
$project = null;
|
||||
$authorization = null;
|
||||
|
||||
try {
|
||||
$rawSize = \strlen($message);
|
||||
$response = new Response(new SwooleResponse());
|
||||
@@ -941,7 +955,8 @@ $server->onMessage(function (int $connection, string $message) use ($server, $re
|
||||
$subscriptionId,
|
||||
$roles,
|
||||
$subscription['channels'] ?? [],
|
||||
$queries
|
||||
$queries,
|
||||
$user->getId()
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -975,6 +990,99 @@ $server->onMessage(function (int $connection, string $message) use ($server, $re
|
||||
|
||||
break;
|
||||
|
||||
case 'subscribe':
|
||||
/**
|
||||
* Message based upsertion of a subscription
|
||||
* If subscriptionId is given then it will match subId of the connection and update the subscription with channels and queries
|
||||
* If non-existing subid is given or not given a new subid will be generated
|
||||
* Similar to what we have now -> two subscribe() block with same channels and queries still two different subscriptions
|
||||
*
|
||||
* structure of the payload -> array of maps
|
||||
* 'data' : [subscriptionId:"" , channels:[] , queries:[]]
|
||||
*/
|
||||
if (!is_array($message['data']) || !array_is_list($message['data'])) {
|
||||
throw new Exception(Exception::REALTIME_MESSAGE_FORMAT_INVALID, 'Payload is not valid.');
|
||||
}
|
||||
|
||||
$roles = $realtime->connections[$connection]['roles'] ?? [Role::guests()->toString()];
|
||||
$userId = $realtime->connections[$connection]['userId'] ?? '';
|
||||
|
||||
// bulk validation + parsing before subscribing
|
||||
$parsedPayloads = [];
|
||||
foreach ($message['data'] as $payload) {
|
||||
if (!\is_array($payload)) {
|
||||
throw new Exception(Exception::REALTIME_MESSAGE_FORMAT_INVALID, 'Each subscribe payload must be an object.');
|
||||
}
|
||||
if (!array_key_exists('channels', $payload)) {
|
||||
throw new Exception(Exception::REALTIME_MESSAGE_FORMAT_INVALID, 'channels is not present in payload.');
|
||||
}
|
||||
if (!is_array($payload['channels']) || !array_is_list($payload['channels'])) {
|
||||
throw new Exception(Exception::REALTIME_MESSAGE_FORMAT_INVALID, 'channels is not a valid array.');
|
||||
}
|
||||
// registering the queries if not present and check in the same payload later on
|
||||
if (!array_key_exists('queries', $payload)) {
|
||||
$payload['queries'] = [];
|
||||
}
|
||||
if (!is_array($payload['queries']) || !array_is_list($payload['queries'])) {
|
||||
throw new Exception(Exception::REALTIME_MESSAGE_FORMAT_INVALID, 'queries is not a valid array.');
|
||||
}
|
||||
|
||||
$subscriptionId = \array_key_exists('subscriptionId', $payload)
|
||||
? $payload['subscriptionId']
|
||||
: ID::unique();
|
||||
|
||||
try {
|
||||
$convertedQueries = Realtime::convertQueries($payload['queries']);
|
||||
} catch (QueryException $e) {
|
||||
throw new Exception(Exception::REALTIME_MESSAGE_FORMAT_INVALID, 'Invalid query: ' . $e->getMessage());
|
||||
}
|
||||
|
||||
$parsedPayloads[] = [
|
||||
'subscriptionId' => $subscriptionId,
|
||||
'channels' => $payload['channels'],
|
||||
'queries' => $convertedQueries,
|
||||
];
|
||||
}
|
||||
|
||||
foreach ($parsedPayloads as $parsedPayload) {
|
||||
$subscriptionId = $parsedPayload['subscriptionId'];
|
||||
$channels = \array_keys(Realtime::convertChannels($parsedPayload['channels'], $userId));
|
||||
$queries = $parsedPayload['queries'];
|
||||
$realtime->subscribe($projectId, $connection, $subscriptionId, $roles, $channels, $queries);
|
||||
}
|
||||
|
||||
// subscribe() overwrites the connection entry; restore auth so later onMessage uses the same context.
|
||||
$realtime->connections[$connection]['authorization'] = $authorization;
|
||||
|
||||
$responsePayload = json_encode([
|
||||
'type' => 'response',
|
||||
'data' => [
|
||||
'to' => 'subscribe',
|
||||
'success' => true,
|
||||
'subscriptions' => \array_map(function (array $parsedPayload) {
|
||||
return [
|
||||
'subscriptionId' => $parsedPayload['subscriptionId'],
|
||||
'channels' => $parsedPayload['channels'],
|
||||
'queries' => \array_map(fn ($q) => $q->toString(), $parsedPayload['queries']),
|
||||
];
|
||||
}, $parsedPayloads),
|
||||
]
|
||||
]);
|
||||
|
||||
$server->send([$connection], $responsePayload);
|
||||
|
||||
if ($project !== null && !$project->isEmpty()) {
|
||||
$subscribeOutboundBytes = \strlen($responsePayload);
|
||||
|
||||
if ($subscribeOutboundBytes > 0) {
|
||||
triggerStats([
|
||||
METRIC_REALTIME_OUTBOUND => $subscribeOutboundBytes,
|
||||
], $project->getId());
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new Exception(Exception::REALTIME_MESSAGE_FORMAT_INVALID, 'Message type is not valid.');
|
||||
}
|
||||
@@ -988,7 +1096,7 @@ $server->onMessage(function (int $connection, string $message) use ($server, $re
|
||||
$message = $th->getMessage();
|
||||
|
||||
// sanitize 0 && 5xx errors
|
||||
if (($code === 0 || $code >= 500) && !Http::isDevelopment()) {
|
||||
if (($code === 0 || $code >= 500) && System::getEnv('_APP_ENV', 'production') !== 'development') {
|
||||
$message = 'Error: Server Error';
|
||||
}
|
||||
|
||||
|
||||
@@ -532,6 +532,35 @@ $hostPath = rtrim($this->getParam('hostPath', ''), '/');
|
||||
- _APP_DB_ADAPTER
|
||||
- _APP_LOGGING_CONFIG
|
||||
|
||||
appwrite-worker-executions:
|
||||
image: <?php echo $organization; ?>/<?php echo $image; ?>:<?php echo $version."\n"; ?>
|
||||
entrypoint: worker-executions
|
||||
<<: *x-logging
|
||||
container_name: appwrite-worker-executions
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- appwrite
|
||||
depends_on:
|
||||
redis:
|
||||
condition: service_healthy
|
||||
<?= $dbService ?>:
|
||||
condition: service_healthy
|
||||
environment:
|
||||
- _APP_ENV
|
||||
- _APP_WORKER_PER_CORE
|
||||
- _APP_REDIS_HOST
|
||||
- _APP_REDIS_PORT
|
||||
- _APP_REDIS_USER
|
||||
- _APP_REDIS_PASS
|
||||
- _APP_ENV
|
||||
- _APP_DB_ADAPTER
|
||||
- _APP_DB_HOST
|
||||
- _APP_DB_PORT
|
||||
- _APP_DB_SCHEMA
|
||||
- _APP_DB_USER
|
||||
- _APP_DB_PASS
|
||||
- _APP_LOGGING_CONFIG
|
||||
|
||||
appwrite-worker-functions:
|
||||
image: <?php echo $organization; ?>/<?php echo $image; ?>:<?php echo $version."\n"; ?>
|
||||
entrypoint: worker-functions
|
||||
@@ -964,7 +993,7 @@ $hostPath = rtrim($this->getParam('hostPath', ''), '/');
|
||||
<<: *x-logging
|
||||
restart: unless-stopped
|
||||
stop_signal: SIGINT
|
||||
image: openruntimes/executor:0.7.22
|
||||
image: openruntimes/executor:0.11.4
|
||||
networks:
|
||||
- appwrite
|
||||
- runtimes
|
||||
|
||||
+1
-1
@@ -1,8 +1,8 @@
|
||||
<?php
|
||||
|
||||
require_once __DIR__ . '/init.php';
|
||||
|
||||
$registerWorkerMessageResources = require __DIR__ . '/init/worker/message.php';
|
||||
|
||||
use Appwrite\Certificates\LetsEncrypt;
|
||||
use Appwrite\Platform\Appwrite;
|
||||
use Swoole\Runtime;
|
||||
|
||||
+1
-1
@@ -93,7 +93,7 @@
|
||||
"chillerlan/php-qrcode": "4.3.*",
|
||||
"adhocore/jwt": "1.1.*",
|
||||
"spomky-labs/otphp": "11.*",
|
||||
"webonyx/graphql-php": "14.11.*",
|
||||
"webonyx/graphql-php": "15.31.*",
|
||||
"league/csv": "9.14.*",
|
||||
"enshrined/svg-sanitize": "0.22.*"
|
||||
},
|
||||
|
||||
Generated
+95
-81
@@ -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": "0b5e2a1b7bedbd6bf37f522a14fcac65",
|
||||
"content-hash": "bf05e373a346cd9f07f1dcccd5e7eff1",
|
||||
"packages": [
|
||||
{
|
||||
"name": "adhocore/jwt",
|
||||
@@ -1996,16 +1996,16 @@
|
||||
},
|
||||
{
|
||||
"name": "phpseclib/phpseclib",
|
||||
"version": "3.0.50",
|
||||
"version": "3.0.51",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/phpseclib/phpseclib.git",
|
||||
"reference": "aa6ad8321ed103dc3624fb600a25b66ebf78ec7b"
|
||||
"reference": "d59c94077f9c9915abb51ddb52ce85188ece1748"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/aa6ad8321ed103dc3624fb600a25b66ebf78ec7b",
|
||||
"reference": "aa6ad8321ed103dc3624fb600a25b66ebf78ec7b",
|
||||
"url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/d59c94077f9c9915abb51ddb52ce85188ece1748",
|
||||
"reference": "d59c94077f9c9915abb51ddb52ce85188ece1748",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -2086,7 +2086,7 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/phpseclib/phpseclib/issues",
|
||||
"source": "https://github.com/phpseclib/phpseclib/tree/3.0.50"
|
||||
"source": "https://github.com/phpseclib/phpseclib/tree/3.0.51"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -2102,7 +2102,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2026-03-19T02:57:58+00:00"
|
||||
"time": "2026-04-10T01:33:53+00:00"
|
||||
},
|
||||
{
|
||||
"name": "psr/clock",
|
||||
@@ -2887,16 +2887,16 @@
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-mbstring",
|
||||
"version": "v1.33.0",
|
||||
"version": "v1.34.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/polyfill-mbstring.git",
|
||||
"reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493"
|
||||
"reference": "6a21eb99c6973357967f6ce3708cd55a6bec6315"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/6d857f4d76bd4b343eac26d6b539585d2bc56493",
|
||||
"reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/6a21eb99c6973357967f6ce3708cd55a6bec6315",
|
||||
"reference": "6a21eb99c6973357967f6ce3708cd55a6bec6315",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -2948,7 +2948,7 @@
|
||||
"shim"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/polyfill-mbstring/tree/v1.33.0"
|
||||
"source": "https://github.com/symfony/polyfill-mbstring/tree/v1.34.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -2968,20 +2968,20 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2024-12-23T08:48:59+00:00"
|
||||
"time": "2026-04-10T17:25:58+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-php82",
|
||||
"version": "v1.33.0",
|
||||
"version": "v1.34.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/polyfill-php82.git",
|
||||
"reference": "5d2ed36f7734637dacc025f179698031951b1692"
|
||||
"reference": "34808efe3e68f69685796f7c253a2f1d8ea9df59"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-php82/zipball/5d2ed36f7734637dacc025f179698031951b1692",
|
||||
"reference": "5d2ed36f7734637dacc025f179698031951b1692",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-php82/zipball/34808efe3e68f69685796f7c253a2f1d8ea9df59",
|
||||
"reference": "34808efe3e68f69685796f7c253a2f1d8ea9df59",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -3028,7 +3028,7 @@
|
||||
"shim"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/polyfill-php82/tree/v1.33.0"
|
||||
"source": "https://github.com/symfony/polyfill-php82/tree/v1.34.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -3048,20 +3048,20 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2024-09-09T11:45:10+00:00"
|
||||
"time": "2026-04-10T16:19:22+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-php83",
|
||||
"version": "v1.33.0",
|
||||
"version": "v1.34.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/polyfill-php83.git",
|
||||
"reference": "17f6f9a6b1735c0f163024d959f700cfbc5155e5"
|
||||
"reference": "3600c2cb22399e25bb226e4a135ce91eeb2a6149"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/17f6f9a6b1735c0f163024d959f700cfbc5155e5",
|
||||
"reference": "17f6f9a6b1735c0f163024d959f700cfbc5155e5",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/3600c2cb22399e25bb226e4a135ce91eeb2a6149",
|
||||
"reference": "3600c2cb22399e25bb226e4a135ce91eeb2a6149",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -3108,7 +3108,7 @@
|
||||
"shim"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/polyfill-php83/tree/v1.33.0"
|
||||
"source": "https://github.com/symfony/polyfill-php83/tree/v1.34.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -3128,20 +3128,20 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2025-07-08T02:45:35+00:00"
|
||||
"time": "2026-04-10T17:25:58+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-php85",
|
||||
"version": "v1.33.0",
|
||||
"version": "v1.34.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/polyfill-php85.git",
|
||||
"reference": "d4e5fcd4ab3d998ab16c0db48e6cbb9a01993f91"
|
||||
"reference": "2c408a6bb0313e6001a83628dc5506100474254e"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-php85/zipball/d4e5fcd4ab3d998ab16c0db48e6cbb9a01993f91",
|
||||
"reference": "d4e5fcd4ab3d998ab16c0db48e6cbb9a01993f91",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-php85/zipball/2c408a6bb0313e6001a83628dc5506100474254e",
|
||||
"reference": "2c408a6bb0313e6001a83628dc5506100474254e",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -3188,7 +3188,7 @@
|
||||
"shim"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/polyfill-php85/tree/v1.33.0"
|
||||
"source": "https://github.com/symfony/polyfill-php85/tree/v1.34.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -3208,7 +3208,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2025-06-23T16:12:55+00:00"
|
||||
"time": "2026-04-10T16:50:15+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/service-contracts",
|
||||
@@ -3854,12 +3854,12 @@
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/utopia-php/database.git",
|
||||
"reference": "0e43fd44f2190da36cac816a17650d697e034507"
|
||||
"reference": "7315ba260141830ec1f51963955e12695988c281"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/utopia-php/database/zipball/0e43fd44f2190da36cac816a17650d697e034507",
|
||||
"reference": "0e43fd44f2190da36cac816a17650d697e034507",
|
||||
"url": "https://api.github.com/repos/utopia-php/database/zipball/7315ba260141830ec1f51963955e12695988c281",
|
||||
"reference": "7315ba260141830ec1f51963955e12695988c281",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -3905,7 +3905,7 @@
|
||||
"issues": "https://github.com/utopia-php/database/issues",
|
||||
"source": "https://github.com/utopia-php/database/tree/big-init"
|
||||
},
|
||||
"time": "2026-04-09T11:05:50+00:00"
|
||||
"time": "2026-04-16T08:36:02+00:00"
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/detector",
|
||||
@@ -4271,16 +4271,16 @@
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/http",
|
||||
"version": "0.34.19",
|
||||
"version": "0.34.20",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/utopia-php/http.git",
|
||||
"reference": "995c119f31866cacd42d63b1f922bf86eabb396c"
|
||||
"reference": "d6b360d555022d16c16d40be51f86180364819f8"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/utopia-php/http/zipball/995c119f31866cacd42d63b1f922bf86eabb396c",
|
||||
"reference": "995c119f31866cacd42d63b1f922bf86eabb396c",
|
||||
"url": "https://api.github.com/repos/utopia-php/http/zipball/d6b360d555022d16c16d40be51f86180364819f8",
|
||||
"reference": "d6b360d555022d16c16d40be51f86180364819f8",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -4319,9 +4319,9 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/utopia-php/http/issues",
|
||||
"source": "https://github.com/utopia-php/http/tree/0.34.19"
|
||||
"source": "https://github.com/utopia-php/http/tree/0.34.20"
|
||||
},
|
||||
"time": "2026-04-08T10:23:17+00:00"
|
||||
"time": "2026-04-12T14:25:22+00:00"
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/image",
|
||||
@@ -5381,38 +5381,48 @@
|
||||
},
|
||||
{
|
||||
"name": "webonyx/graphql-php",
|
||||
"version": "v14.11.10",
|
||||
"version": "v15.31.5",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/webonyx/graphql-php.git",
|
||||
"reference": "d9c2fdebc6aa01d831bc2969da00e8588cffef19"
|
||||
"reference": "089c4ef7e112df85788cfe06596278a8f99f4aa9"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/webonyx/graphql-php/zipball/d9c2fdebc6aa01d831bc2969da00e8588cffef19",
|
||||
"reference": "d9c2fdebc6aa01d831bc2969da00e8588cffef19",
|
||||
"url": "https://api.github.com/repos/webonyx/graphql-php/zipball/089c4ef7e112df85788cfe06596278a8f99f4aa9",
|
||||
"reference": "089c4ef7e112df85788cfe06596278a8f99f4aa9",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-json": "*",
|
||||
"ext-mbstring": "*",
|
||||
"php": "^7.1 || ^8"
|
||||
"php": "^7.4 || ^8"
|
||||
},
|
||||
"require-dev": {
|
||||
"amphp/amp": "^2.3",
|
||||
"doctrine/coding-standard": "^6.0",
|
||||
"nyholm/psr7": "^1.2",
|
||||
"amphp/amp": "^2.6",
|
||||
"amphp/http-server": "^2.1",
|
||||
"dms/phpunit-arraysubset-asserts": "dev-master",
|
||||
"ergebnis/composer-normalize": "^2.28",
|
||||
"friendsofphp/php-cs-fixer": "3.94.2",
|
||||
"mll-lab/php-cs-fixer-config": "5.13.0",
|
||||
"nyholm/psr7": "^1.5",
|
||||
"phpbench/phpbench": "^1.2",
|
||||
"phpstan/extension-installer": "^1.0",
|
||||
"phpstan/phpstan": "0.12.82",
|
||||
"phpstan/phpstan-phpunit": "0.12.18",
|
||||
"phpstan/phpstan-strict-rules": "0.12.9",
|
||||
"phpunit/phpunit": "^7.2 || ^8.5",
|
||||
"psr/http-message": "^1.0",
|
||||
"react/promise": "2.*",
|
||||
"simpod/php-coveralls-mirror": "^3.0"
|
||||
"phpstan/extension-installer": "^1.1",
|
||||
"phpstan/phpstan": "2.1.46",
|
||||
"phpstan/phpstan-phpunit": "2.0.16",
|
||||
"phpstan/phpstan-strict-rules": "2.0.10",
|
||||
"phpunit/phpunit": "^9.5 || ^10.5.21 || ^11",
|
||||
"psr/http-message": "^1 || ^2",
|
||||
"react/http": "^1.6",
|
||||
"react/promise": "^2.0 || ^3.0",
|
||||
"rector/rector": "^2.0",
|
||||
"symfony/polyfill-php81": "^1.23",
|
||||
"symfony/var-exporter": "^5 || ^6 || ^7 || ^8",
|
||||
"thecodingmachine/safe": "^1.3 || ^2 || ^3",
|
||||
"ticketswap/phpstan-error-formatter": "1.3.0"
|
||||
},
|
||||
"suggest": {
|
||||
"amphp/http-server": "To leverage async resolving with webserver on AMPHP platform",
|
||||
"psr/http-message": "To use standard GraphQL server",
|
||||
"react/promise": "To leverage async resolving on React PHP platform"
|
||||
},
|
||||
@@ -5434,30 +5444,34 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/webonyx/graphql-php/issues",
|
||||
"source": "https://github.com/webonyx/graphql-php/tree/v14.11.10"
|
||||
"source": "https://github.com/webonyx/graphql-php/tree/v15.31.5"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://github.com/spawnia",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://opencollective.com/webonyx-graphql-php",
|
||||
"type": "open_collective"
|
||||
}
|
||||
],
|
||||
"time": "2023-07-05T14:23:37+00:00"
|
||||
"time": "2026-04-11T18:06:15+00:00"
|
||||
}
|
||||
],
|
||||
"packages-dev": [
|
||||
{
|
||||
"name": "appwrite/sdk-generator",
|
||||
"version": "1.17.8",
|
||||
"version": "1.17.11",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/appwrite/sdk-generator.git",
|
||||
"reference": "b7109e13cec89ed56ad80111973b12646e4eb5f7"
|
||||
"reference": "c714ee52659ef5968b3372ff4da0e407140a6250"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/b7109e13cec89ed56ad80111973b12646e4eb5f7",
|
||||
"reference": "b7109e13cec89ed56ad80111973b12646e4eb5f7",
|
||||
"url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/c714ee52659ef5968b3372ff4da0e407140a6250",
|
||||
"reference": "c714ee52659ef5968b3372ff4da0e407140a6250",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -5493,9 +5507,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.17.8"
|
||||
"source": "https://github.com/appwrite/sdk-generator/tree/1.17.11"
|
||||
},
|
||||
"time": "2026-04-09T05:08:21+00:00"
|
||||
"time": "2026-04-11T02:42:32+00:00"
|
||||
},
|
||||
{
|
||||
"name": "brianium/paratest",
|
||||
@@ -7764,16 +7778,16 @@
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-ctype",
|
||||
"version": "v1.33.0",
|
||||
"version": "v1.34.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/polyfill-ctype.git",
|
||||
"reference": "a3cc8b044a6ea513310cbd48ef7333b384945638"
|
||||
"reference": "141046a8f9477948ff284fa65be2095baafb94f2"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/a3cc8b044a6ea513310cbd48ef7333b384945638",
|
||||
"reference": "a3cc8b044a6ea513310cbd48ef7333b384945638",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/141046a8f9477948ff284fa65be2095baafb94f2",
|
||||
"reference": "141046a8f9477948ff284fa65be2095baafb94f2",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -7823,7 +7837,7 @@
|
||||
"portable"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/polyfill-ctype/tree/v1.33.0"
|
||||
"source": "https://github.com/symfony/polyfill-ctype/tree/v1.34.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -7843,20 +7857,20 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2024-09-09T11:45:10+00:00"
|
||||
"time": "2026-04-10T16:19:22+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-intl-grapheme",
|
||||
"version": "v1.33.0",
|
||||
"version": "v1.34.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/polyfill-intl-grapheme.git",
|
||||
"reference": "380872130d3a5dd3ace2f4010d95125fde5d5c70"
|
||||
"reference": "ad1b7b9092976d6c948b8a187cec9faaea9ec1df"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/380872130d3a5dd3ace2f4010d95125fde5d5c70",
|
||||
"reference": "380872130d3a5dd3ace2f4010d95125fde5d5c70",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/ad1b7b9092976d6c948b8a187cec9faaea9ec1df",
|
||||
"reference": "ad1b7b9092976d6c948b8a187cec9faaea9ec1df",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -7905,7 +7919,7 @@
|
||||
"shim"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.33.0"
|
||||
"source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.34.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -7925,11 +7939,11 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2025-06-27T09:58:17+00:00"
|
||||
"time": "2026-04-10T16:19:22+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-intl-normalizer",
|
||||
"version": "v1.33.0",
|
||||
"version": "v1.34.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/polyfill-intl-normalizer.git",
|
||||
@@ -7990,7 +8004,7 @@
|
||||
"shim"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.33.0"
|
||||
"source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.34.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -8014,7 +8028,7 @@
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-php81",
|
||||
"version": "v1.33.0",
|
||||
"version": "v1.34.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/polyfill-php81.git",
|
||||
@@ -8070,7 +8084,7 @@
|
||||
"shim"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/polyfill-php81/tree/v1.33.0"
|
||||
"source": "https://github.com/symfony/polyfill-php81/tree/v1.34.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
|
||||
@@ -1288,6 +1288,7 @@ services:
|
||||
image: mongo:8.2.5
|
||||
container_name: appwrite-mongodb
|
||||
<<: *x-logging
|
||||
restart: on-failure:3
|
||||
networks:
|
||||
- appwrite
|
||||
volumes:
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Bus\Events;
|
||||
|
||||
use Utopia\Bus\Event;
|
||||
|
||||
class SessionCreated implements Event
|
||||
{
|
||||
/**
|
||||
* @param array<string, mixed> $user
|
||||
* @param array<string, mixed> $project
|
||||
* @param array<string, mixed> $session
|
||||
*/
|
||||
public function __construct(
|
||||
public readonly array $user,
|
||||
public readonly array $project,
|
||||
public readonly array $session,
|
||||
public readonly string $locale,
|
||||
) {
|
||||
}
|
||||
}
|
||||
@@ -3,10 +3,12 @@
|
||||
namespace Appwrite\Bus\Listeners;
|
||||
|
||||
use Appwrite\Bus\Events\ExecutionCompleted;
|
||||
use Appwrite\Event\Execution;
|
||||
use Appwrite\Event\Message\Execution as ExecutionMessage;
|
||||
use Appwrite\Event\Publisher\Execution as ExecutionPublisher;
|
||||
use Utopia\Bus\Listener;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Queue\Publisher;
|
||||
use Utopia\Span\Span;
|
||||
use Utopia\System\System;
|
||||
|
||||
class Log extends Listener
|
||||
{
|
||||
@@ -24,16 +26,33 @@ class Log extends Listener
|
||||
{
|
||||
$this
|
||||
->desc('Persists execution logs to database via queue')
|
||||
->inject('publisher')
|
||||
->inject('publisherForExecutions')
|
||||
->callback($this->handle(...));
|
||||
}
|
||||
|
||||
public function handle(ExecutionCompleted $event, Publisher $publisher): void
|
||||
public function handle(ExecutionCompleted $event, ExecutionPublisher $publisherForExecutions): void
|
||||
{
|
||||
$queueForExecutions = new Execution($publisher);
|
||||
$queueForExecutions
|
||||
->setExecution(new Document($event->execution))
|
||||
->setProject(new Document($event->project))
|
||||
->trigger();
|
||||
$project = new Document($event->project);
|
||||
$execution = new Document($event->execution);
|
||||
if ($execution->getAttribute('resourceType', '') === 'functions') {
|
||||
$traceProjectId = System::getEnv('_APP_TRACE_PROJECT_ID', '');
|
||||
$traceFunctionId = System::getEnv('_APP_TRACE_FUNCTION_ID', '');
|
||||
$resourceId = $execution->getAttribute('resourceId', '');
|
||||
if ($traceProjectId !== '' && $traceFunctionId !== '' && $project->getId() === $traceProjectId && $resourceId === $traceFunctionId) {
|
||||
Span::init('execution.trace.v1_executions_enqueue');
|
||||
Span::add('datetime', gmdate('c'));
|
||||
Span::add('projectId', $project->getId());
|
||||
Span::add('functionId', $resourceId);
|
||||
Span::add('executionId', $execution->getId());
|
||||
Span::add('deploymentId', $execution->getAttribute('deploymentId', ''));
|
||||
Span::add('status', $execution->getAttribute('status', ''));
|
||||
Span::current()?->finish();
|
||||
}
|
||||
}
|
||||
|
||||
$publisherForExecutions->enqueue(new ExecutionMessage(
|
||||
project: $project,
|
||||
execution: $execution,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,154 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Bus\Listeners;
|
||||
|
||||
use Appwrite\Auth\MFA\Type;
|
||||
use Appwrite\Bus\Events\SessionCreated;
|
||||
use Appwrite\Event\Mail;
|
||||
use Appwrite\Template\Template;
|
||||
use Utopia\Bus\Listener;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\Locale\Locale;
|
||||
use Utopia\Queue\Publisher;
|
||||
use Utopia\Storage\Validator\FileName;
|
||||
use Utopia\System\System;
|
||||
|
||||
class Mails extends Listener
|
||||
{
|
||||
public static function getName(): string
|
||||
{
|
||||
return 'mails';
|
||||
}
|
||||
|
||||
public static function getEvents(): array
|
||||
{
|
||||
return [SessionCreated::class];
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->desc('Sends session alert emails')
|
||||
->inject('publisher')
|
||||
->inject('locale')
|
||||
->inject('platform')
|
||||
->inject('dbForProject')
|
||||
->callback($this->handle(...));
|
||||
}
|
||||
|
||||
public function handle(SessionCreated $event, Publisher $publisher, Locale $locale, array $platform, Database $dbForProject): void
|
||||
{
|
||||
$project = new Document($event->project);
|
||||
|
||||
if (!($project->getAttribute('auths', [])['sessionAlerts'] ?? false)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (empty($event->user['email'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$provider = $event->session['provider'] ?? '';
|
||||
$factors = $event->session['factors'] ?? [];
|
||||
|
||||
if (\in_array($provider, [SESSION_PROVIDER_MAGIC_URL, SESSION_PROVIDER_TOKEN]) && \in_array(Type::EMAIL, $factors)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($dbForProject->count('sessions', [Query::equal('userId', [$event->user['$id']])]) === 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
$locale->setDefault($event->locale);
|
||||
|
||||
$session = new Document($event->session);
|
||||
$smtp = $project->getAttribute('smtp', []);
|
||||
$smtpBaseTemplate = $project->getAttribute('smtpBaseTemplate', 'email-base');
|
||||
|
||||
if (!(new FileName())->isValid($smtpBaseTemplate)) {
|
||||
throw new \Exception('Invalid template path');
|
||||
}
|
||||
|
||||
$customTemplate = $project->getAttribute('templates', [])["email.sessionAlert-$event->locale"] ?? [];
|
||||
$isBranded = $smtpBaseTemplate === APP_BRANDED_EMAIL_BASE_TEMPLATE;
|
||||
|
||||
$subject = $customTemplate['subject'] ?? $locale->getText('emails.sessionAlert.subject');
|
||||
$preview = $locale->getText('emails.sessionAlert.preview');
|
||||
|
||||
$body = empty($customTemplate['message'])
|
||||
? Template::fromFile(__DIR__ . '/../../../../app/config/locale/templates/email-session-alert.tpl')
|
||||
->setParam('{{hello}}', $locale->getText('emails.sessionAlert.hello'))
|
||||
->setParam('{{body}}', $locale->getText('emails.sessionAlert.body'))
|
||||
->setParam('{{listDevice}}', $locale->getText('emails.sessionAlert.listDevice'))
|
||||
->setParam('{{listIpAddress}}', $locale->getText('emails.sessionAlert.listIpAddress'))
|
||||
->setParam('{{listCountry}}', $locale->getText('emails.sessionAlert.listCountry'))
|
||||
->setParam('{{footer}}', $locale->getText('emails.sessionAlert.footer'))
|
||||
->setParam('{{thanks}}', $locale->getText('emails.sessionAlert.thanks'))
|
||||
->setParam('{{signature}}', $locale->getText('emails.sessionAlert.signature'))
|
||||
->render()
|
||||
: $customTemplate['message'];
|
||||
|
||||
$clientName = $session->getAttribute('clientName')
|
||||
?: ($session->getAttribute('userAgent') ?: 'UNKNOWN');
|
||||
|
||||
$projectName = $project->getId() === 'console'
|
||||
? $platform['platformName']
|
||||
: $project->getAttribute('name');
|
||||
|
||||
$emailVariables = [
|
||||
'direction' => $locale->getText('settings.direction'),
|
||||
'date' => (new \DateTime())->format('F j'),
|
||||
'year' => (new \DateTime())->format('YYYY'),
|
||||
'time' => (new \DateTime())->format('H:i:s'),
|
||||
'user' => $event->user['name'] ?? '',
|
||||
'project' => $projectName,
|
||||
'device' => $clientName,
|
||||
'ipAddress' => $session->getAttribute('ip'),
|
||||
'country' => $locale->getText('countries.' . $session->getAttribute('countryCode'), $locale->getText('locale.country.unknown')),
|
||||
];
|
||||
|
||||
if ($isBranded) {
|
||||
$emailVariables += [
|
||||
'accentColor' => $platform['accentColor'],
|
||||
'logoUrl' => $platform['logoUrl'],
|
||||
'twitter' => $platform['twitterUrl'],
|
||||
'discord' => $platform['discordUrl'],
|
||||
'github' => $platform['githubUrl'],
|
||||
'terms' => $platform['termsUrl'],
|
||||
'privacy' => $platform['privacyUrl'],
|
||||
'platform' => $platform['platformName'],
|
||||
];
|
||||
}
|
||||
|
||||
$queueForMails = new Mail($publisher);
|
||||
|
||||
if ($smtp['enabled'] ?? false) {
|
||||
$queueForMails
|
||||
->setSmtpHost($smtp['host'] ?? '')
|
||||
->setSmtpPort($smtp['port'] ?? '')
|
||||
->setSmtpUsername($smtp['username'] ?? '')
|
||||
->setSmtpPassword($smtp['password'] ?? '')
|
||||
->setSmtpSecure($smtp['secure'] ?? '')
|
||||
->setSmtpReplyTo($customTemplate['replyTo'] ?? $smtp['replyTo'] ?? '')
|
||||
->setSmtpSenderEmail($customTemplate['senderEmail'] ?? $smtp['senderEmail'] ?? System::getEnv('_APP_SYSTEM_EMAIL_ADDRESS', APP_EMAIL_TEAM))
|
||||
->setSmtpSenderName($customTemplate['senderName'] ?? $smtp['senderName'] ?? System::getEnv('_APP_SYSTEM_EMAIL_NAME', APP_NAME . ' Server'));
|
||||
}
|
||||
|
||||
$queueForMails
|
||||
->setProject($project)
|
||||
->setSubject($subject)
|
||||
->setPreview($preview)
|
||||
->setBody($body)
|
||||
->setBodyTemplate(__DIR__ . '/../../../../app/config/locale/templates/' . $smtpBaseTemplate . '.tpl')
|
||||
->appendVariables($emailVariables)
|
||||
->setRecipient($event->user['email']);
|
||||
|
||||
if ($isBranded) {
|
||||
$queueForMails->setSenderName($platform['emailSenderName']);
|
||||
}
|
||||
|
||||
$queueForMails->trigger();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Event\Context;
|
||||
|
||||
use Utopia\Database\Document;
|
||||
|
||||
class Audit
|
||||
{
|
||||
public function __construct(
|
||||
public ?Document $project = null,
|
||||
public ?Document $user = null,
|
||||
public string $mode = '',
|
||||
public string $userAgent = '',
|
||||
public string $ip = '',
|
||||
public string $hostname = '',
|
||||
public string $event = '',
|
||||
public string $resource = '',
|
||||
public array $payload = [],
|
||||
) {
|
||||
}
|
||||
|
||||
public function isEmpty(): bool
|
||||
{
|
||||
return $this->project === null
|
||||
&& $this->user === null
|
||||
&& $this->mode === ''
|
||||
&& $this->userAgent === ''
|
||||
&& $this->ip === ''
|
||||
&& $this->hostname === ''
|
||||
&& $this->event === ''
|
||||
&& $this->resource === ''
|
||||
&& $this->payload === [];
|
||||
}
|
||||
}
|
||||
@@ -637,9 +637,11 @@ class Event
|
||||
*/
|
||||
$eventValues = \array_values($events);
|
||||
|
||||
/**
|
||||
* Return a combined list of table, collection events and if tablesdb present then include all for backward compatibility
|
||||
*/
|
||||
$databaseType = $database?->getAttribute('type', 'legacy');
|
||||
if ($database !== null && !\in_array($databaseType, ['legacy', 'tablesdb'], true)) {
|
||||
return $eventValues;
|
||||
}
|
||||
|
||||
return Event::mirrorCollectionEvents($pattern, $eventValues[0], $eventValues);
|
||||
}
|
||||
|
||||
@@ -662,21 +664,30 @@ class Event
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds `table` events for `collection` events.
|
||||
* Adds table/collection counterpart events for backward compatibility.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* `databases.*.collections.*.documents.*.update` →\
|
||||
* `[databases.*.collections.*.documents.*.update, databases.*.tables.*.rows.*.update]`
|
||||
*
|
||||
* `databases.*.tables.*.rows.*.update` →\
|
||||
* `[databases.*.tables.*.rows.*.update, databases.*.collections.*.documents.*.update]`
|
||||
*/
|
||||
private static function mirrorCollectionEvents(string $pattern, string $firstEvent, array $events): array
|
||||
{
|
||||
$tableEventMap = [
|
||||
$collectionsToTablesMap = [
|
||||
'documents' => 'rows',
|
||||
'collections' => 'tables',
|
||||
'attributes' => 'columns',
|
||||
];
|
||||
|
||||
$tablesToCollectionsMap = [
|
||||
'rows' => 'documents',
|
||||
'tables' => 'collections',
|
||||
'columns' => 'attributes',
|
||||
];
|
||||
|
||||
$databasesEventMap = [
|
||||
'tablesdb' => 'databases',
|
||||
'tables' => 'collections',
|
||||
@@ -687,7 +698,10 @@ class Event
|
||||
if (
|
||||
(
|
||||
str_contains($pattern, 'databases.') &&
|
||||
str_contains($firstEvent, 'collections')
|
||||
(
|
||||
str_contains($firstEvent, 'collections') ||
|
||||
str_contains($firstEvent, 'tables')
|
||||
)
|
||||
) ||
|
||||
(
|
||||
str_contains($firstEvent, 'tablesdb.')
|
||||
@@ -698,25 +712,16 @@ class Event
|
||||
$pairedEvents[] = $event;
|
||||
// tablesdb needs databases event with tables and collections
|
||||
if (str_contains($event, 'tablesdb')) {
|
||||
$databasesSideEvent = str_replace(
|
||||
array_keys($databasesEventMap),
|
||||
array_values($databasesEventMap),
|
||||
$event
|
||||
);
|
||||
$databasesSideEvent = self::replaceEventSegments($event, $databasesEventMap);
|
||||
$pairedEvents[] = $databasesSideEvent;
|
||||
$tableSideEvent = str_replace(
|
||||
array_keys($tableEventMap),
|
||||
array_values($tableEventMap),
|
||||
$databasesSideEvent
|
||||
);
|
||||
$tableSideEvent = self::replaceEventSegments($databasesSideEvent, $collectionsToTablesMap);
|
||||
$pairedEvents[] = $tableSideEvent;
|
||||
} elseif (str_contains($event, 'collections')) {
|
||||
$tableSideEvent = str_replace(
|
||||
array_keys($tableEventMap),
|
||||
array_values($tableEventMap),
|
||||
$event
|
||||
);
|
||||
$tableSideEvent = self::replaceEventSegments($event, $collectionsToTablesMap);
|
||||
$pairedEvents[] = $tableSideEvent;
|
||||
} elseif (str_contains($event, 'tables')) {
|
||||
$collectionSideEvent = self::replaceEventSegments($event, $tablesToCollectionsMap);
|
||||
$pairedEvents[] = $collectionSideEvent;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -728,6 +733,20 @@ class Event
|
||||
return array_values(array_unique($events));
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace only exact event path segments, never partial substrings.
|
||||
*/
|
||||
private static function replaceEventSegments(string $event, array $map): string
|
||||
{
|
||||
$parts = \explode('.', $event);
|
||||
$parts = \array_map(
|
||||
fn (string $part) => $map[$part] ?? $part,
|
||||
$parts
|
||||
);
|
||||
|
||||
return \implode('.', $parts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps event terminology based on database type
|
||||
*/
|
||||
|
||||
@@ -53,4 +53,23 @@ class Execution extends Event
|
||||
'execution' => $this->execution,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Trim payload for the execution event.
|
||||
* Only the project ID is needed — the worker DI fetches the full project from the platform database.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function trimPayload(): array
|
||||
{
|
||||
$trimmed = [];
|
||||
|
||||
if ($this->project) {
|
||||
$trimmed['project'] = new Document([
|
||||
'$id' => $this->project->getId(),
|
||||
]);
|
||||
}
|
||||
|
||||
return $trimmed;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -101,7 +101,6 @@ class Mail extends Event
|
||||
/**
|
||||
* Sets preview for the mail event.
|
||||
*
|
||||
* @param string $preview
|
||||
* @return self
|
||||
*/
|
||||
public function setPreview(string $preview): self
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Event\Message;
|
||||
|
||||
use Appwrite\Event\Context\Audit as AuditContext;
|
||||
use Utopia\Database\Document;
|
||||
|
||||
final class Audit extends Base
|
||||
{
|
||||
public function __construct(
|
||||
public readonly string $event,
|
||||
public readonly array $payload,
|
||||
public readonly Document $project = new Document(),
|
||||
public readonly Document $user = new Document(),
|
||||
public readonly string $resource = '',
|
||||
public readonly string $mode = '',
|
||||
public readonly string $ip = '',
|
||||
public readonly string $userAgent = '',
|
||||
public readonly string $hostname = '',
|
||||
) {
|
||||
}
|
||||
|
||||
public function toArray(): array
|
||||
{
|
||||
return [
|
||||
'project' => [
|
||||
'$id' => $this->project->getId(),
|
||||
'$sequence' => $this->project->getSequence(),
|
||||
'database' => $this->project->getAttribute('database', ''),
|
||||
],
|
||||
'user' => $this->user->getArrayCopy(),
|
||||
'payload' => $this->payload,
|
||||
'resource' => $this->resource,
|
||||
'mode' => $this->mode,
|
||||
'ip' => $this->ip,
|
||||
'userAgent' => $this->userAgent,
|
||||
'event' => $this->event,
|
||||
'hostname' => $this->hostname,
|
||||
];
|
||||
}
|
||||
|
||||
public static function fromArray(array $data): static
|
||||
{
|
||||
return new self(
|
||||
event: $data['event'] ?? '',
|
||||
payload: $data['payload'] ?? [],
|
||||
project: new Document($data['project'] ?? []),
|
||||
user: new Document($data['user'] ?? []),
|
||||
resource: $data['resource'] ?? '',
|
||||
mode: $data['mode'] ?? '',
|
||||
ip: $data['ip'] ?? '',
|
||||
userAgent: $data['userAgent'] ?? '',
|
||||
hostname: $data['hostname'] ?? '',
|
||||
);
|
||||
}
|
||||
|
||||
public static function fromContext(AuditContext $context): static
|
||||
{
|
||||
return new self(
|
||||
event: $context->event,
|
||||
payload: $context->payload,
|
||||
project: $context->project ?? new Document(),
|
||||
user: $context->user ?? new Document(),
|
||||
resource: $context->resource,
|
||||
mode: $context->mode,
|
||||
ip: $context->ip,
|
||||
userAgent: $context->userAgent,
|
||||
hostname: $context->hostname,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Event\Message;
|
||||
|
||||
use Utopia\Database\Document;
|
||||
|
||||
final class Certificate extends Base
|
||||
{
|
||||
public function __construct(
|
||||
public readonly Document $project,
|
||||
public readonly Document $domain,
|
||||
public readonly bool $skipRenewCheck = false,
|
||||
public readonly ?string $validationDomain = null,
|
||||
public readonly string $action = \Appwrite\Event\Certificate::ACTION_GENERATION,
|
||||
) {
|
||||
}
|
||||
|
||||
public function toArray(): array
|
||||
{
|
||||
return [
|
||||
'project' => [
|
||||
'$id' => $this->project->getId(),
|
||||
'$sequence' => $this->project->getSequence(),
|
||||
'database' => $this->project->getAttribute('database', ''),
|
||||
],
|
||||
'domain' => $this->domain->getArrayCopy(),
|
||||
'skipRenewCheck' => $this->skipRenewCheck,
|
||||
'validationDomain' => $this->validationDomain,
|
||||
'action' => $this->action,
|
||||
];
|
||||
}
|
||||
|
||||
public static function fromArray(array $data): static
|
||||
{
|
||||
return new self(
|
||||
project: new Document($data['project'] ?? []),
|
||||
domain: new Document($data['domain'] ?? []),
|
||||
skipRenewCheck: $data['skipRenewCheck'] ?? false,
|
||||
validationDomain: $data['validationDomain'] ?? null,
|
||||
action: $data['action'] ?? \Appwrite\Event\Certificate::ACTION_GENERATION,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Event\Message;
|
||||
|
||||
use Utopia\Database\Document;
|
||||
|
||||
final class Execution extends Base
|
||||
{
|
||||
public function __construct(
|
||||
public readonly Document $project,
|
||||
public readonly Document $execution,
|
||||
) {
|
||||
}
|
||||
|
||||
public function toArray(): array
|
||||
{
|
||||
return [
|
||||
'project' => $this->project->getArrayCopy(),
|
||||
'execution' => $this->execution->getArrayCopy(),
|
||||
];
|
||||
}
|
||||
|
||||
public static function fromArray(array $data): static
|
||||
{
|
||||
return new self(
|
||||
project: new Document($data['project'] ?? []),
|
||||
execution: new Document($data['execution'] ?? []),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Event\Message;
|
||||
|
||||
use Utopia\Database\Document;
|
||||
|
||||
final class Migration extends Base
|
||||
{
|
||||
public function __construct(
|
||||
public readonly Document $project,
|
||||
public readonly Document $migration,
|
||||
public readonly array $platform = [],
|
||||
) {
|
||||
}
|
||||
|
||||
public function toArray(): array
|
||||
{
|
||||
return [
|
||||
'project' => $this->project->getArrayCopy(),
|
||||
'migration' => $this->migration->getArrayCopy(),
|
||||
'platform' => $this->platform,
|
||||
];
|
||||
}
|
||||
|
||||
public static function fromArray(array $data): static
|
||||
{
|
||||
return new self(
|
||||
project: new Document($data['project'] ?? []),
|
||||
migration: new Document($data['migration'] ?? []),
|
||||
platform: $data['platform'] ?? [],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Event\Message;
|
||||
|
||||
use Utopia\Database\Document;
|
||||
|
||||
final class Screenshot extends Base
|
||||
{
|
||||
public function __construct(
|
||||
public readonly Document $project,
|
||||
public readonly string $deploymentId,
|
||||
) {
|
||||
}
|
||||
|
||||
public function toArray(): array
|
||||
{
|
||||
return [
|
||||
'project' => [
|
||||
'$id' => $this->project->getId(),
|
||||
'$sequence' => $this->project->getSequence(),
|
||||
'database' => $this->project->getAttribute('database', ''),
|
||||
],
|
||||
'deploymentId' => $this->deploymentId,
|
||||
];
|
||||
}
|
||||
|
||||
public static function fromArray(array $data): static
|
||||
{
|
||||
return new self(
|
||||
project: new Document($data['project'] ?? []),
|
||||
deploymentId: $data['deploymentId'] ?? '',
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Event\Message;
|
||||
|
||||
use Utopia\Database\Document;
|
||||
|
||||
final class StatsResources extends Base
|
||||
{
|
||||
public function __construct(
|
||||
public readonly Document $project,
|
||||
) {
|
||||
}
|
||||
|
||||
public function toArray(): array
|
||||
{
|
||||
return [
|
||||
'project' => $this->project->getArrayCopy(),
|
||||
];
|
||||
}
|
||||
|
||||
public static function fromArray(array $data): static
|
||||
{
|
||||
return new self(
|
||||
project: new Document($data['project'] ?? []),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Event\Publisher;
|
||||
|
||||
use Appwrite\Event\Message\Audit as AuditMessage;
|
||||
use Utopia\Console;
|
||||
use Utopia\Queue\Publisher;
|
||||
use Utopia\Queue\Queue;
|
||||
|
||||
readonly class Audit extends Base
|
||||
{
|
||||
public function __construct(
|
||||
Publisher $publisher,
|
||||
protected Queue $queue
|
||||
) {
|
||||
parent::__construct($publisher);
|
||||
}
|
||||
|
||||
public function enqueue(AuditMessage $message): string|bool
|
||||
{
|
||||
// Audit delivery is best-effort and should never fail the request lifecycle.
|
||||
try {
|
||||
return $this->publish($this->queue, $message);
|
||||
} catch (\Throwable $th) {
|
||||
Console::error('[Audit] Failed to publish audit message: ' . $th->getMessage());
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public function getSize(bool $failed = false): int
|
||||
{
|
||||
return $this->getQueueSize($this->queue, $failed);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Event\Publisher;
|
||||
|
||||
use Appwrite\Event\Message\Certificate as CertificateMessage;
|
||||
use Utopia\Queue\Publisher;
|
||||
use Utopia\Queue\Queue;
|
||||
|
||||
readonly class Certificate extends Base
|
||||
{
|
||||
public function __construct(
|
||||
Publisher $publisher,
|
||||
protected Queue $queue
|
||||
) {
|
||||
parent::__construct($publisher);
|
||||
}
|
||||
|
||||
public function enqueue(CertificateMessage $message): string|bool
|
||||
{
|
||||
return $this->publish($this->queue, $message);
|
||||
}
|
||||
|
||||
public function getSize(bool $failed = false): int
|
||||
{
|
||||
return $this->getQueueSize($this->queue, $failed);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Event\Publisher;
|
||||
|
||||
use Appwrite\Event\Message\Execution as ExecutionMessage;
|
||||
use Utopia\Queue\Publisher;
|
||||
use Utopia\Queue\Queue;
|
||||
|
||||
readonly class Execution extends Base
|
||||
{
|
||||
public function __construct(
|
||||
Publisher $publisher,
|
||||
protected Queue $queue
|
||||
) {
|
||||
parent::__construct($publisher);
|
||||
}
|
||||
|
||||
public function enqueue(ExecutionMessage $message): string|bool
|
||||
{
|
||||
return $this->publish($this->queue, $message);
|
||||
}
|
||||
|
||||
public function getSize(bool $failed = false): int
|
||||
{
|
||||
return $this->getQueueSize($this->queue, $failed);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Event\Publisher;
|
||||
|
||||
use Appwrite\Event\Message\Migration as MigrationMessage;
|
||||
use Utopia\Queue\Publisher;
|
||||
use Utopia\Queue\Queue;
|
||||
|
||||
readonly class Migration extends Base
|
||||
{
|
||||
public function __construct(
|
||||
Publisher $publisher,
|
||||
protected Queue $queue
|
||||
) {
|
||||
parent::__construct($publisher);
|
||||
}
|
||||
|
||||
public function enqueue(MigrationMessage $message): string|bool
|
||||
{
|
||||
return $this->publish($this->queue, $message);
|
||||
}
|
||||
|
||||
public function getSize(bool $failed = false): int
|
||||
{
|
||||
return $this->getQueueSize($this->queue, $failed);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Event\Publisher;
|
||||
|
||||
use Appwrite\Event\Message\Screenshot as ScreenshotMessage;
|
||||
use Utopia\Queue\Publisher;
|
||||
use Utopia\Queue\Queue;
|
||||
|
||||
readonly class Screenshot extends Base
|
||||
{
|
||||
public function __construct(
|
||||
Publisher $publisher,
|
||||
protected Queue $queue
|
||||
) {
|
||||
parent::__construct($publisher);
|
||||
}
|
||||
|
||||
public function enqueue(ScreenshotMessage $message): string|bool
|
||||
{
|
||||
return $this->publish($this->queue, $message);
|
||||
}
|
||||
|
||||
public function getSize(bool $failed = false): int
|
||||
{
|
||||
return $this->getQueueSize($this->queue, $failed);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Event\Publisher;
|
||||
|
||||
use Appwrite\Event\Message\StatsResources as StatsResourcesMessage;
|
||||
use Utopia\Console;
|
||||
use Utopia\Queue\Publisher;
|
||||
use Utopia\Queue\Queue;
|
||||
|
||||
readonly class StatsResources extends Base
|
||||
{
|
||||
public function __construct(
|
||||
Publisher $publisher,
|
||||
protected Queue $queue
|
||||
) {
|
||||
parent::__construct($publisher);
|
||||
}
|
||||
|
||||
public function enqueue(StatsResourcesMessage $message): string|bool
|
||||
{
|
||||
// Resource stats are best-effort; publishing failures should not interrupt the scheduler loop.
|
||||
try {
|
||||
return $this->publish($this->queue, $message);
|
||||
} catch (\Throwable $th) {
|
||||
Console::error('[StatsResources] Failed to publish stats resources message: ' . $th->getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public function getSize(bool $failed = false): int
|
||||
{
|
||||
return $this->getQueueSize($this->queue, $failed);
|
||||
}
|
||||
}
|
||||
@@ -61,6 +61,26 @@ class Realtime extends Event
|
||||
return $this->subscribers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the event state for long-lived worker processes.
|
||||
*
|
||||
* `Event::reset()` clears params/sensitive/event/payload only. Realtime routing also
|
||||
* depends on `context`, `subscribers`, and `project`/`user` fields, so we clear them too
|
||||
* to prevent stale state from affecting subsequent triggers.
|
||||
*/
|
||||
public function reset(): self
|
||||
{
|
||||
parent::reset();
|
||||
|
||||
$this->subscribers = [];
|
||||
$this->context = [];
|
||||
$this->project = null;
|
||||
$this->user = null;
|
||||
$this->userId = null;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute Event.
|
||||
*
|
||||
|
||||
@@ -81,8 +81,8 @@ abstract class Adapter implements PromiseAdapter
|
||||
/**
|
||||
* Create a new promise that resolves when all passed in promises resolve.
|
||||
*
|
||||
* @param array $promisesOrValues
|
||||
* @param iterable $promisesOrValues
|
||||
* @return GQLPromise
|
||||
*/
|
||||
abstract public function all(array $promisesOrValues): GQLPromise;
|
||||
abstract public function all(iterable $promisesOrValues): GQLPromise;
|
||||
}
|
||||
|
||||
@@ -35,8 +35,12 @@ class Swoole extends Adapter
|
||||
return new GQLPromise($promise, $this);
|
||||
}
|
||||
|
||||
public function all(array $promisesOrValues): GQLPromise
|
||||
public function all(iterable $promisesOrValues): GQLPromise
|
||||
{
|
||||
if ($promisesOrValues instanceof \Traversable) {
|
||||
$promisesOrValues = \iterator_to_array($promisesOrValues);
|
||||
}
|
||||
|
||||
return new GQLPromise(SwoolePromise::all($promisesOrValues), $this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\GraphQL;
|
||||
|
||||
use Swoole\Coroutine;
|
||||
use Swoole\Coroutine\Channel;
|
||||
|
||||
final class ResolverLock
|
||||
{
|
||||
public Channel $channel;
|
||||
public ?int $owner = null;
|
||||
public int $depth = 0;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->channel = new Channel(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Acquire the lock. Re-entering from the same coroutine only
|
||||
* increments depth to avoid self-deadlock.
|
||||
*/
|
||||
public function acquire(): void
|
||||
{
|
||||
$cid = Coroutine::getCid();
|
||||
|
||||
if ($this->owner === $cid) {
|
||||
$this->depth++;
|
||||
return;
|
||||
}
|
||||
|
||||
$this->channel->push(true);
|
||||
$this->owner = $cid;
|
||||
$this->depth = 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Release the lock.
|
||||
*/
|
||||
public function release(): void
|
||||
{
|
||||
if ($this->owner !== Coroutine::getCid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->depth--;
|
||||
|
||||
if ($this->depth > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->owner = null;
|
||||
$this->channel->pop();
|
||||
}
|
||||
}
|
||||
+237
-107
@@ -6,6 +6,7 @@ use Appwrite\GraphQL\Exception as GQLException;
|
||||
use Appwrite\Promises\Swoole;
|
||||
use Appwrite\Utopia\Request;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Utopia\DI\Container;
|
||||
use Utopia\Http\Exception;
|
||||
use Utopia\Http\Http;
|
||||
use Utopia\Http\Route;
|
||||
@@ -13,6 +14,75 @@ use Utopia\System\System;
|
||||
|
||||
class Resolvers
|
||||
{
|
||||
/**
|
||||
* Request-scoped locks keyed by the per-request GraphQL Http instance.
|
||||
*
|
||||
* @var array<string, ResolverLock>
|
||||
*/
|
||||
private static array $locks = [];
|
||||
|
||||
/**
|
||||
* Preserve response side effects that callers depend on, such as session
|
||||
* cookies created by account auth routes.
|
||||
*/
|
||||
private static function mergeResponseSideEffects(Response $from, Response $to): void
|
||||
{
|
||||
foreach ($from->getCookies() as $cookie) {
|
||||
$to->removeCookie($cookie['name']);
|
||||
$to->addCookie(
|
||||
$cookie['name'],
|
||||
$cookie['value'],
|
||||
$cookie['expire'],
|
||||
$cookie['path'],
|
||||
$cookie['domain'],
|
||||
$cookie['secure'],
|
||||
$cookie['httponly'],
|
||||
$cookie['samesite']
|
||||
);
|
||||
}
|
||||
|
||||
$headers = $from->getHeaders();
|
||||
$fallbackCookies = $headers['X-Fallback-Cookies'] ?? null;
|
||||
if ($fallbackCookies === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
$to->removeHeader('X-Fallback-Cookies');
|
||||
foreach ((array) $fallbackCookies as $value) {
|
||||
$to->addHeader('X-Fallback-Cookies', $value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current request container.
|
||||
*/
|
||||
private static function getResolverContainer(Http $utopia): Container
|
||||
{
|
||||
$container = $utopia->getResource('container');
|
||||
|
||||
if ($container instanceof Container || (\is_object($container) && \method_exists($container, 'get') && \method_exists($container, 'set'))) {
|
||||
/** @var Container $container */
|
||||
return $container;
|
||||
}
|
||||
|
||||
/** @var callable(): Container $container */
|
||||
return $container();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the request-scoped lock shared by GraphQL resolver coroutines
|
||||
* for the current HTTP request.
|
||||
*/
|
||||
private static function getLock(Http $utopia): ResolverLock
|
||||
{
|
||||
$key = \spl_object_hash($utopia);
|
||||
if (!isset(self::$locks[$key])) {
|
||||
self::$locks[$key] = new ResolverLock();
|
||||
}
|
||||
|
||||
return self::$locks[$key];
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a resolver for a given API {@see Route}.
|
||||
*
|
||||
@@ -24,34 +94,39 @@ class Resolvers
|
||||
Http $utopia,
|
||||
?Route $route,
|
||||
): callable {
|
||||
return static fn ($type, $args, $context, $info) => new Swoole(
|
||||
function (callable $resolve, callable $reject) use ($utopia, $route, $args) {
|
||||
$utopia = $utopia->getResource('utopia:graphql');
|
||||
$request = $utopia->getResource('request');
|
||||
$response = $utopia->getResource('response');
|
||||
return static fn ($type, $args, $context, $info) => new Swoole(function (callable $resolve, callable $reject) use ($utopia, $route, $args) {
|
||||
$utopia = $utopia->getResource('utopia:graphql');
|
||||
$request = $utopia->getResource('request');
|
||||
$response = $utopia->getResource('response');
|
||||
|
||||
$path = $route->getPath();
|
||||
foreach ($args as $key => $value) {
|
||||
if (\str_contains($path, '/:' . $key)) {
|
||||
$path = \str_replace(':' . $key, $value, $path);
|
||||
self::resolve(
|
||||
$utopia,
|
||||
$request,
|
||||
$response,
|
||||
$resolve,
|
||||
$reject,
|
||||
prepareRequest: static function (Request $request) use ($route, $args): void {
|
||||
$path = $route->getPath();
|
||||
foreach ($args as $key => $value) {
|
||||
if (\str_contains($path, '/:' . $key)) {
|
||||
$path = \str_replace(':' . $key, $value, $path);
|
||||
}
|
||||
}
|
||||
|
||||
$request->setMethod($route->getMethod());
|
||||
$request->setURI($path);
|
||||
|
||||
switch ($route->getMethod()) {
|
||||
case 'GET':
|
||||
$request->setQueryString($args);
|
||||
break;
|
||||
default:
|
||||
$request->setPayload($args);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$request->setMethod($route->getMethod());
|
||||
$request->setURI($path);
|
||||
|
||||
switch ($route->getMethod()) {
|
||||
case 'GET':
|
||||
$request->setQueryString($args);
|
||||
break;
|
||||
default:
|
||||
$request->setPayload($args);
|
||||
break;
|
||||
}
|
||||
|
||||
self::resolve($utopia, $request, $response, $resolve, $reject);
|
||||
}
|
||||
);
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -91,18 +166,23 @@ class Resolvers
|
||||
string $collectionId,
|
||||
callable $url,
|
||||
): callable {
|
||||
return static fn ($type, $args, $context, $info) => new Swoole(
|
||||
function (callable $resolve, callable $reject) use ($utopia, $databaseId, $collectionId, $url, $args) {
|
||||
$utopia = $utopia->getResource('utopia:graphql');
|
||||
$request = $utopia->getResource('request');
|
||||
$response = $utopia->getResource('response');
|
||||
return static fn ($type, $args, $context, $info) => new Swoole(function (callable $resolve, callable $reject) use ($utopia, $databaseId, $collectionId, $url, $args) {
|
||||
$utopia = $utopia->getResource('utopia:graphql');
|
||||
$request = $utopia->getResource('request');
|
||||
$response = $utopia->getResource('response');
|
||||
|
||||
$request->setMethod('GET');
|
||||
$request->setURI($url($databaseId, $collectionId, $args));
|
||||
|
||||
self::resolve($utopia, $request, $response, $resolve, $reject);
|
||||
}
|
||||
);
|
||||
self::resolve(
|
||||
$utopia,
|
||||
$request,
|
||||
$response,
|
||||
$resolve,
|
||||
$reject,
|
||||
prepareRequest: static function (Request $request) use ($databaseId, $collectionId, $url, $args): void {
|
||||
$request->setMethod('GET');
|
||||
$request->setURI($url($databaseId, $collectionId, $args));
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -122,23 +202,29 @@ class Resolvers
|
||||
callable $url,
|
||||
callable $params,
|
||||
): callable {
|
||||
return static fn ($type, $args, $context, $info) => new Swoole(
|
||||
function (callable $resolve, callable $reject) use ($utopia, $databaseId, $collectionId, $url, $params, $args) {
|
||||
$utopia = $utopia->getResource('utopia:graphql');
|
||||
$request = $utopia->getResource('request');
|
||||
$response = $utopia->getResource('response');
|
||||
return static fn ($type, $args, $context, $info) => new Swoole(function (callable $resolve, callable $reject) use ($utopia, $databaseId, $collectionId, $url, $params, $args) {
|
||||
$utopia = $utopia->getResource('utopia:graphql');
|
||||
$request = $utopia->getResource('request');
|
||||
$response = $utopia->getResource('response');
|
||||
|
||||
$request->setMethod('GET');
|
||||
$request->setURI($url($databaseId, $collectionId, $args));
|
||||
$request->setQueryString($params($databaseId, $collectionId, $args));
|
||||
$beforeResolve = function ($payload) {
|
||||
return $payload['documents'];
|
||||
};
|
||||
|
||||
$beforeResolve = function ($payload) {
|
||||
return $payload['documents'];
|
||||
};
|
||||
|
||||
self::resolve($utopia, $request, $response, $resolve, $reject, $beforeResolve);
|
||||
}
|
||||
);
|
||||
self::resolve(
|
||||
$utopia,
|
||||
$request,
|
||||
$response,
|
||||
$resolve,
|
||||
$reject,
|
||||
beforeResolve: $beforeResolve,
|
||||
prepareRequest: static function (Request $request) use ($databaseId, $collectionId, $url, $params, $args): void {
|
||||
$request->setMethod('GET');
|
||||
$request->setURI($url($databaseId, $collectionId, $args));
|
||||
$request->setQueryString($params($databaseId, $collectionId, $args));
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -158,19 +244,24 @@ class Resolvers
|
||||
callable $url,
|
||||
callable $params,
|
||||
): callable {
|
||||
return static fn ($type, $args, $context, $info) => new Swoole(
|
||||
function (callable $resolve, callable $reject) use ($utopia, $databaseId, $collectionId, $url, $params, $args) {
|
||||
$utopia = $utopia->getResource('utopia:graphql');
|
||||
$request = $utopia->getResource('request');
|
||||
$response = $utopia->getResource('response');
|
||||
return static fn ($type, $args, $context, $info) => new Swoole(function (callable $resolve, callable $reject) use ($utopia, $databaseId, $collectionId, $url, $params, $args) {
|
||||
$utopia = $utopia->getResource('utopia:graphql');
|
||||
$request = $utopia->getResource('request');
|
||||
$response = $utopia->getResource('response');
|
||||
|
||||
$request->setMethod('POST');
|
||||
$request->setURI($url($databaseId, $collectionId, $args));
|
||||
$request->setPayload($params($databaseId, $collectionId, $args));
|
||||
|
||||
self::resolve($utopia, $request, $response, $resolve, $reject);
|
||||
}
|
||||
);
|
||||
self::resolve(
|
||||
$utopia,
|
||||
$request,
|
||||
$response,
|
||||
$resolve,
|
||||
$reject,
|
||||
prepareRequest: static function (Request $request) use ($databaseId, $collectionId, $url, $params, $args): void {
|
||||
$request->setMethod('POST');
|
||||
$request->setURI($url($databaseId, $collectionId, $args));
|
||||
$request->setPayload($params($databaseId, $collectionId, $args));
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -190,19 +281,24 @@ class Resolvers
|
||||
callable $url,
|
||||
callable $params,
|
||||
): callable {
|
||||
return static fn ($type, $args, $context, $info) => new Swoole(
|
||||
function (callable $resolve, callable $reject) use ($utopia, $databaseId, $collectionId, $url, $params, $args) {
|
||||
$utopia = $utopia->getResource('utopia:graphql');
|
||||
$request = $utopia->getResource('request');
|
||||
$response = $utopia->getResource('response');
|
||||
return static fn ($type, $args, $context, $info) => new Swoole(function (callable $resolve, callable $reject) use ($utopia, $databaseId, $collectionId, $url, $params, $args) {
|
||||
$utopia = $utopia->getResource('utopia:graphql');
|
||||
$request = $utopia->getResource('request');
|
||||
$response = $utopia->getResource('response');
|
||||
|
||||
$request->setMethod('PATCH');
|
||||
$request->setURI($url($databaseId, $collectionId, $args));
|
||||
$request->setPayload($params($databaseId, $collectionId, $args));
|
||||
|
||||
self::resolve($utopia, $request, $response, $resolve, $reject);
|
||||
}
|
||||
);
|
||||
self::resolve(
|
||||
$utopia,
|
||||
$request,
|
||||
$response,
|
||||
$resolve,
|
||||
$reject,
|
||||
prepareRequest: static function (Request $request) use ($databaseId, $collectionId, $url, $params, $args): void {
|
||||
$request->setMethod('PATCH');
|
||||
$request->setURI($url($databaseId, $collectionId, $args));
|
||||
$request->setPayload($params($databaseId, $collectionId, $args));
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -220,18 +316,23 @@ class Resolvers
|
||||
string $collectionId,
|
||||
callable $url,
|
||||
): callable {
|
||||
return static fn ($type, $args, $context, $info) => new Swoole(
|
||||
function (callable $resolve, callable $reject) use ($utopia, $databaseId, $collectionId, $url, $args) {
|
||||
$utopia = $utopia->getResource('utopia:graphql');
|
||||
$request = $utopia->getResource('request');
|
||||
$response = $utopia->getResource('response');
|
||||
return static fn ($type, $args, $context, $info) => new Swoole(function (callable $resolve, callable $reject) use ($utopia, $databaseId, $collectionId, $url, $args) {
|
||||
$utopia = $utopia->getResource('utopia:graphql');
|
||||
$request = $utopia->getResource('request');
|
||||
$response = $utopia->getResource('response');
|
||||
|
||||
$request->setMethod('DELETE');
|
||||
$request->setURI($url($databaseId, $collectionId, $args));
|
||||
|
||||
self::resolve($utopia, $request, $response, $resolve, $reject);
|
||||
}
|
||||
);
|
||||
self::resolve(
|
||||
$utopia,
|
||||
$request,
|
||||
$response,
|
||||
$resolve,
|
||||
$reject,
|
||||
prepareRequest: static function (Request $request) use ($databaseId, $collectionId, $url, $args): void {
|
||||
$request->setMethod('DELETE');
|
||||
$request->setURI($url($databaseId, $collectionId, $args));
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -241,7 +342,7 @@ class Resolvers
|
||||
* @param callable $resolve
|
||||
* @param callable $reject
|
||||
* @param callable|null $beforeResolve
|
||||
* @param callable|null $beforeReject
|
||||
* @param callable|null $prepareRequest
|
||||
* @return void
|
||||
* @throws Exception
|
||||
*/
|
||||
@@ -252,38 +353,67 @@ class Resolvers
|
||||
callable $resolve,
|
||||
callable $reject,
|
||||
?callable $beforeResolve = null,
|
||||
?callable $beforeReject = null,
|
||||
?callable $prepareRequest = null,
|
||||
): void {
|
||||
// Drop json content type so post args are used directly
|
||||
if (\str_starts_with($request->getHeader('content-type'), 'application/json')) {
|
||||
$request->removeHeader('content-type');
|
||||
}
|
||||
$lock = self::getLock($utopia);
|
||||
|
||||
$request = clone $request;
|
||||
$utopia->setResource('request', static fn () => $request);
|
||||
$response->setContentType(Response::CONTENT_TYPE_NULL);
|
||||
$lock->acquire();
|
||||
|
||||
$original = $utopia->getRoute();
|
||||
try {
|
||||
$route = $utopia->match($request, fresh: true);
|
||||
$request = clone $request;
|
||||
|
||||
$utopia->execute($route, $request);
|
||||
} catch (\Throwable $e) {
|
||||
if ($beforeReject) {
|
||||
$e = $beforeReject($e);
|
||||
// Drop json content type so post args are used directly.
|
||||
if (\str_starts_with($request->getHeader('content-type'), 'application/json')) {
|
||||
$request->removeHeader('content-type');
|
||||
}
|
||||
|
||||
if ($prepareRequest) {
|
||||
$prepareRequest($request);
|
||||
}
|
||||
|
||||
/** @var Response $resolverResponse */
|
||||
$resolverResponse = clone $utopia->getResource('response');
|
||||
$container = self::getResolverContainer($utopia);
|
||||
$container->set('request', static fn () => $request);
|
||||
$container->set('response', static fn () => $resolverResponse);
|
||||
$resolverResponse->setContentType(Response::CONTENT_TYPE_NULL);
|
||||
$resolverResponse->setSent(false);
|
||||
|
||||
$route = $utopia->match($request, fresh: true);
|
||||
$request->setRoute($route);
|
||||
|
||||
$utopia->execute($route, $request, $resolverResponse);
|
||||
|
||||
self::mergeResponseSideEffects($resolverResponse, $response);
|
||||
|
||||
if ($resolverResponse->isSent()) {
|
||||
$response
|
||||
->setStatusCode($resolverResponse->getStatusCode())
|
||||
->setSent(true);
|
||||
|
||||
$resolve(null);
|
||||
return;
|
||||
}
|
||||
|
||||
$payload = $resolverResponse->getPayload();
|
||||
$statusCode = $resolverResponse->getStatusCode();
|
||||
} catch (\Throwable $e) {
|
||||
$reject($e);
|
||||
return;
|
||||
} finally {
|
||||
if ($original !== null) {
|
||||
$utopia->setRoute($original);
|
||||
}
|
||||
|
||||
$lock->release();
|
||||
unset(self::$locks[\spl_object_hash($utopia)]);
|
||||
}
|
||||
|
||||
$payload = $response->getPayload();
|
||||
|
||||
if ($response->getStatusCode() < 200 || $response->getStatusCode() >= 400) {
|
||||
if ($beforeReject) {
|
||||
$payload = $beforeReject($payload);
|
||||
}
|
||||
if ($statusCode < 200 || $statusCode >= 400) {
|
||||
$reject(new GQLException(
|
||||
message: $payload['message'],
|
||||
code: $response->getStatusCode()
|
||||
code: $statusCode
|
||||
));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -3,12 +3,13 @@
|
||||
namespace Appwrite\GraphQL\Types;
|
||||
|
||||
use GraphQL\Language\AST\Node;
|
||||
use GraphQL\Language\AST\StringValueNode;
|
||||
|
||||
// https://github.com/webonyx/graphql-php/issues/129#issuecomment-309366803
|
||||
class Assoc extends Json
|
||||
{
|
||||
public $name = 'Assoc';
|
||||
public $description = 'The `Assoc` scalar type represents associative array values.';
|
||||
public string $name = 'Assoc';
|
||||
public ?string $description = 'The `Assoc` scalar type represents associative array values.';
|
||||
|
||||
public function serialize($value)
|
||||
{
|
||||
@@ -30,6 +31,10 @@ class Assoc extends Json
|
||||
|
||||
public function parseLiteral(Node $valueNode, ?array $variables = null)
|
||||
{
|
||||
return \json_decode($valueNode->value, true);
|
||||
if ($valueNode instanceof StringValueNode) {
|
||||
return \json_decode($valueNode->value, true);
|
||||
}
|
||||
|
||||
return parent::parseLiteral($valueNode, $variables);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,8 +8,8 @@ use GraphQL\Type\Definition\ScalarType;
|
||||
|
||||
class InputFile extends ScalarType
|
||||
{
|
||||
public $name = 'InputFile';
|
||||
public $description = 'The `InputFile` special type represents a file to be uploaded in the same HTTP request as specified by
|
||||
public string $name = 'InputFile';
|
||||
public ?string $description = 'The `InputFile` special type represents a file to be uploaded in the same HTTP request as specified by
|
||||
[graphql-multipart-request-spec](https://github.com/jaydenseric/graphql-multipart-request-spec).';
|
||||
|
||||
public function serialize($value)
|
||||
|
||||
@@ -14,8 +14,8 @@ use GraphQL\Type\Definition\ScalarType;
|
||||
// https://github.com/webonyx/graphql-php/issues/129#issuecomment-309366803
|
||||
class Json extends ScalarType
|
||||
{
|
||||
public $name = 'Json';
|
||||
public $description = 'The `JSON` scalar type represents JSON values as specified by
|
||||
public string $name = 'Json';
|
||||
public ?string $description = 'The `JSON` scalar type represents JSON values as specified by
|
||||
[ECMA-404](https://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf).';
|
||||
|
||||
public function serialize($value)
|
||||
|
||||
@@ -20,6 +20,7 @@ class Realtime extends MessagingAdapter
|
||||
* [CONNECTION_ID] ->
|
||||
* 'projectId' -> [PROJECT_ID]
|
||||
* 'roles' -> [ROLE_x, ROLE_Y]
|
||||
* 'userId' -> [USER_ID]
|
||||
* 'channels' -> [CHANNEL_NAME_X, CHANNEL_NAME_Y, CHANNEL_NAME_Z]
|
||||
*/
|
||||
public array $connections = [];
|
||||
@@ -67,25 +68,35 @@ class Realtime extends MessagingAdapter
|
||||
* @param array $queryGroup Array of Query objects for this subscription (AND logic within subscription)
|
||||
* @return void
|
||||
*/
|
||||
public function subscribe(string $projectId, mixed $identifier, string $subscriptionId, array $roles, array $channels, array $queryGroup = []): void
|
||||
{
|
||||
public function subscribe(
|
||||
string $projectId,
|
||||
mixed $identifier,
|
||||
string $subscriptionId,
|
||||
array $roles,
|
||||
array $channels,
|
||||
array $queryGroup = [],
|
||||
?string $userId = null
|
||||
): void {
|
||||
if (!isset($this->subscriptions[$projectId])) { // Init Project
|
||||
$this->subscriptions[$projectId] = [];
|
||||
}
|
||||
|
||||
$strings = [];
|
||||
if (empty($queryGroup)) {
|
||||
$strings[] = Query::select(['*'])->toString();
|
||||
} else {
|
||||
foreach ($queryGroup as $query) {
|
||||
$strings[] = $query->toString();
|
||||
}
|
||||
}
|
||||
$data = [];
|
||||
|
||||
$data = [
|
||||
'strings' => $strings,
|
||||
'compiled' => RuntimeQuery::compile($queryGroup),
|
||||
];
|
||||
if (!empty($channels)) {
|
||||
if (empty($queryGroup)) {
|
||||
$strings[] = Query::select(['*'])->toString();
|
||||
} else {
|
||||
foreach ($queryGroup as $query) {
|
||||
$strings[] = $query->toString();
|
||||
}
|
||||
}
|
||||
$data = [
|
||||
'strings' => $strings,
|
||||
'compiled' => RuntimeQuery::compile($queryGroup),
|
||||
];
|
||||
}
|
||||
|
||||
foreach ($roles as $role) {
|
||||
if (!isset($this->subscriptions[$projectId][$role])) {
|
||||
@@ -103,10 +114,12 @@ class Realtime extends MessagingAdapter
|
||||
}
|
||||
}
|
||||
|
||||
// Update connection info
|
||||
// Keep userId from onOpen/authentication when provided.
|
||||
// Fallback to existing stored value for subsequent subscribe upserts.
|
||||
$this->connections[$identifier] = [
|
||||
'projectId' => $projectId,
|
||||
'roles' => $roles,
|
||||
'userId' => $userId ?? ($this->connections[$identifier]['userId'] ?? ''),
|
||||
'channels' => $channels
|
||||
];
|
||||
}
|
||||
|
||||
@@ -93,6 +93,7 @@ abstract class Migration
|
||||
'1.8.0' => 'V23',
|
||||
'1.8.1' => 'V23',
|
||||
'1.9.0' => 'V24',
|
||||
'1.9.1' => 'V24',
|
||||
];
|
||||
|
||||
/**
|
||||
|
||||
+24
-7
@@ -50,6 +50,11 @@ class Create extends Action
|
||||
return UtopiaResponse::MODEL_DOCUMENT_LIST;
|
||||
}
|
||||
|
||||
protected function getSupportForEmptyDocument()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
@@ -139,30 +144,42 @@ class Create extends Action
|
||||
->inject('eventProcessor')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(string $databaseId, string $documentId, string $collectionId, string|array $data, ?array $permissions, ?array $documents, ?string $transactionId, UtopiaResponse $response, Database $dbForProject, callable $getDatabasesDB, User $user, Event $queueForEvents, Context $usage, Event $queueForRealtime, Event $queueForFunctions, Event $queueForWebhooks, array $plan, Authorization $authorization, EventProcessor $eventProcessor): void
|
||||
{
|
||||
$data = \is_string($data)
|
||||
? \json_decode($data, true)
|
||||
: $data;
|
||||
|
||||
$supportsEmptyDocument = $this->getSupportForEmptyDocument();
|
||||
$hasData = !empty($data);
|
||||
$hasDocuments = !empty($documents);
|
||||
|
||||
/**
|
||||
* Determine which internal path to call, single or bulk
|
||||
*/
|
||||
if (empty($data) && empty($documents)) {
|
||||
if (!$supportsEmptyDocument && !$hasData && !$hasDocuments) {
|
||||
// No single or bulk documents provided
|
||||
throw new Exception($this->getMissingDataException());
|
||||
}
|
||||
if (!empty($data) && !empty($documents)) {
|
||||
|
||||
// When empty documents are supported, an empty payload should still be treated as single create.
|
||||
if ($supportsEmptyDocument && !$hasData && !$hasDocuments) {
|
||||
$data = [];
|
||||
$hasData = true;
|
||||
}
|
||||
|
||||
if ($hasData && $hasDocuments) {
|
||||
// Both single and bulk documents provided
|
||||
throw new Exception(Exception::GENERAL_BAD_REQUEST, 'You can only send one of the following parameters: data, ' . $this->getSDKGroup());
|
||||
}
|
||||
if (!empty($data) && empty($documentId)) {
|
||||
if ($hasData && empty($documentId)) {
|
||||
// Single document provided without document ID
|
||||
$document = $this->isCollectionsAPI() ? 'Document' : 'Row';
|
||||
$message = "$document ID is required when creating a single " . strtolower($document) . '.';
|
||||
throw new Exception($this->getMissingDataException(), $message);
|
||||
}
|
||||
if (!empty($documents) && !empty($documentId)) {
|
||||
if ($hasDocuments && !empty($documentId)) {
|
||||
// Bulk documents provided with document ID
|
||||
$documentId = $this->isCollectionsAPI() ? 'documentId' : 'rowId';
|
||||
throw new Exception(
|
||||
@@ -170,13 +187,13 @@ class Create extends Action
|
||||
"Param \"$documentId\" is not allowed when creating multiple " . $this->getSDKGroup() . ', set "$id" on each instead.'
|
||||
);
|
||||
}
|
||||
if (!empty($documents) && !empty($permissions)) {
|
||||
if ($hasDocuments && !empty($permissions)) {
|
||||
// Bulk documents provided with permissions
|
||||
throw new Exception(Exception::GENERAL_BAD_REQUEST, 'Param "permissions" is disallowed when creating multiple ' . $this->getSDKGroup() . ', set "$permissions" on each instead');
|
||||
}
|
||||
|
||||
$isBulk = true;
|
||||
if (!empty($data)) {
|
||||
$isBulk = $hasDocuments;
|
||||
if ($hasData) {
|
||||
// Single document provided, convert to single item array
|
||||
// But remember that it was single to respond with a single document
|
||||
$isBulk = false;
|
||||
|
||||
+18
-5
@@ -145,7 +145,11 @@ class XList extends Action
|
||||
$documentsField = $this->getListCacheField($collection, $roles, $queries, self::LIST_CACHE_FIELD_DOCUMENTS);
|
||||
|
||||
$documentsCacheHit = false;
|
||||
$cachedDocuments = $dbForProject->getCache()->load($cacheKey, $ttl, $documentsField);
|
||||
try {
|
||||
$cachedDocuments = $dbForProject->getCache()->load($cacheKey, $ttl, $documentsField);
|
||||
} catch (\Throwable) {
|
||||
$cachedDocuments = null;
|
||||
}
|
||||
|
||||
if ($cachedDocuments !== null &&
|
||||
$cachedDocuments !== false &&
|
||||
@@ -157,21 +161,30 @@ class XList extends Action
|
||||
} else {
|
||||
$documents = $find();
|
||||
|
||||
// Convert Document objects to arrays for caching
|
||||
$documentsArray = \array_map(function ($doc) {
|
||||
return $doc->getArrayCopy();
|
||||
}, $documents);
|
||||
$dbForProject->getCache()->save($cacheKey, $documentsArray, $documentsField);
|
||||
try {
|
||||
$dbForProject->getCache()->save($cacheKey, $documentsArray, $documentsField);
|
||||
} catch (\Throwable) {
|
||||
}
|
||||
}
|
||||
|
||||
if ($includeTotal) {
|
||||
$totalField = $this->getListCacheField($collection, $roles, $queries, self::LIST_CACHE_FIELD_TOTAL);
|
||||
$cachedTotal = $dbForProject->getCache()->load($cacheKey, $ttl, $totalField);
|
||||
try {
|
||||
$cachedTotal = $dbForProject->getCache()->load($cacheKey, $ttl, $totalField);
|
||||
} catch (\Throwable) {
|
||||
$cachedTotal = null;
|
||||
}
|
||||
if ($cachedTotal !== null && $cachedTotal !== false) {
|
||||
$total = $cachedTotal;
|
||||
} else {
|
||||
$total = $dbForDatabases->count($collectionTableId, $queries, APP_LIMIT_COUNT);
|
||||
$dbForProject->getCache()->save($cacheKey, $total, $totalField);
|
||||
try {
|
||||
$dbForProject->getCache()->save($cacheKey, $total, $totalField);
|
||||
} catch (\Throwable) {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$total = 0;
|
||||
|
||||
+6
@@ -34,6 +34,12 @@ class Create extends DocumentCreate
|
||||
return UtopiaResponse::MODEL_DOCUMENT_LIST;
|
||||
}
|
||||
|
||||
protected function getSupportForEmptyDocument()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
|
||||
-59
@@ -1,59 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Modules\Databases\Http\DocumentsDB\Collections\Documents\Logs;
|
||||
|
||||
use Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Documents\Logs\XList as DocumentLogXList;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\ContentType;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Utopia\Database\Validator\Queries;
|
||||
use Utopia\Database\Validator\Query\Limit;
|
||||
use Utopia\Database\Validator\Query\Offset;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Http\Adapter\Swoole\Response as SwooleResponse;
|
||||
|
||||
class XList extends DocumentLogXList
|
||||
{
|
||||
public static function getName(): string
|
||||
{
|
||||
return 'listDocumentsDBDocumentLogs';
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->setHttpMethod(self::HTTP_REQUEST_METHOD_GET)
|
||||
->setHttpPath('/v1/documentsdb/:databaseId/collections/:collectionId/documents/:documentId/logs')
|
||||
->desc('List document logs')
|
||||
->groups(['api', 'database'])
|
||||
->label('scope', 'documents.read')
|
||||
->label('resourceType', RESOURCE_TYPE_DATABASES)
|
||||
->label('sdk', new Method(
|
||||
namespace: 'documentsDB',
|
||||
group: 'logs',
|
||||
name: 'listDocumentLogs',
|
||||
description: '/docs/references/documentsdb/get-document-logs.md',
|
||||
auth: [AuthType::ADMIN],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: SwooleResponse::STATUS_CODE_OK,
|
||||
model: $this->getResponseModel(),
|
||||
)
|
||||
],
|
||||
contentType: ContentType::JSON,
|
||||
))
|
||||
->param('databaseId', '', new UID(), 'Database ID.')
|
||||
->param('collectionId', '', new UID(), 'Collection ID.')
|
||||
->param('documentId', '', new UID(), 'Document ID.')
|
||||
->param('queries', [], new Queries([new Limit(), new Offset()]), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Only supported methods are limit and offset', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('getDatabasesDB')
|
||||
->inject('locale')
|
||||
->inject('geodb')
|
||||
->inject('authorization')
|
||||
->inject('audit')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Modules\Databases\Http\DocumentsDB\Collections\Logs;
|
||||
|
||||
use Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Logs\XList as CollectionLogXList;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\ContentType;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Validator\Queries;
|
||||
use Utopia\Database\Validator\Query\Limit;
|
||||
use Utopia\Database\Validator\Query\Offset;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Http\Adapter\Swoole\Response as SwooleResponse;
|
||||
|
||||
class XList extends CollectionLogXList
|
||||
{
|
||||
public static function getName(): string
|
||||
{
|
||||
return 'listDocumentsDBCollectionLogs';
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->setHttpMethod(self::HTTP_REQUEST_METHOD_GET)
|
||||
->setHttpPath('/v1/documentsdb/:databaseId/collections/:collectionId/logs')
|
||||
->desc('List collection logs')
|
||||
->groups(['api', 'database'])
|
||||
->label('scope', 'collections.read')
|
||||
->label('resourceType', RESOURCE_TYPE_DATABASES)
|
||||
->label('sdk', new Method(
|
||||
namespace: 'documentsDB',
|
||||
group: $this->getSdkGroup(),
|
||||
name: 'listCollectionLogs',
|
||||
description: '/docs/references/documentsdb/get-collection-logs.md',
|
||||
auth: [AuthType::ADMIN],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: SwooleResponse::STATUS_CODE_OK,
|
||||
model: $this->getResponseModel(),
|
||||
)
|
||||
],
|
||||
contentType: ContentType::JSON
|
||||
))
|
||||
->param('databaseId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Database ID.', false, ['dbForProject'])
|
||||
->param('collectionId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Collection ID.', false, ['dbForProject'])
|
||||
->param('queries', [], new Queries([new Limit(), new Offset()]), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Only supported methods are limit and offset', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('locale')
|
||||
->inject('geodb')
|
||||
->inject('authorization')
|
||||
->inject('audit')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
}
|
||||
-59
@@ -1,59 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Modules\Databases\Http\VectorsDB\Collections\Documents\Logs;
|
||||
|
||||
use Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Documents\Logs\XList as DocumentLogXList;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\ContentType;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Utopia\Database\Validator\Queries;
|
||||
use Utopia\Database\Validator\Query\Limit;
|
||||
use Utopia\Database\Validator\Query\Offset;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Http\Adapter\Swoole\Response as SwooleResponse;
|
||||
|
||||
class XList extends DocumentLogXList
|
||||
{
|
||||
public static function getName(): string
|
||||
{
|
||||
return 'listVectorsDBDocumentLogs';
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->setHttpMethod(self::HTTP_REQUEST_METHOD_GET)
|
||||
->setHttpPath('/v1/vectorsdb/:databaseId/collections/:collectionId/documents/:documentId/logs')
|
||||
->desc('List document logs')
|
||||
->groups(['api', 'database'])
|
||||
->label('scope', 'documents.read')
|
||||
->label('resourceType', RESOURCE_TYPE_DATABASES)
|
||||
->label('sdk', new Method(
|
||||
namespace: 'vectorsDB',
|
||||
group: 'logs',
|
||||
name: 'listDocumentLogs',
|
||||
description: '/docs/references/vectorsdb/get-document-logs.md',
|
||||
auth: [AuthType::ADMIN],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: SwooleResponse::STATUS_CODE_OK,
|
||||
model: $this->getResponseModel(),
|
||||
)
|
||||
],
|
||||
contentType: ContentType::JSON,
|
||||
))
|
||||
->param('databaseId', '', new UID(), 'Database ID.')
|
||||
->param('collectionId', '', new UID(), 'Collection ID.')
|
||||
->param('documentId', '', new UID(), 'Document ID.')
|
||||
->param('queries', [], new Queries([new Limit(), new Offset()]), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Only supported methods are limit and offset', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('getDatabasesDB')
|
||||
->inject('locale')
|
||||
->inject('geodb')
|
||||
->inject('authorization')
|
||||
->inject('audit')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Modules\Databases\Http\VectorsDB\Collections\Logs;
|
||||
|
||||
use Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Logs\XList as CollectionLogXList;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\ContentType;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Utopia\Database\Validator\Queries;
|
||||
use Utopia\Database\Validator\Query\Limit;
|
||||
use Utopia\Database\Validator\Query\Offset;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Http\Adapter\Swoole\Response as SwooleResponse;
|
||||
|
||||
class XList extends CollectionLogXList
|
||||
{
|
||||
public static function getName(): string
|
||||
{
|
||||
return 'listVectorsDBCollectionLogs';
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->setHttpMethod(self::HTTP_REQUEST_METHOD_GET)
|
||||
->setHttpPath('/v1/vectorsdb/:databaseId/collections/:collectionId/logs')
|
||||
->desc('List collection logs')
|
||||
->groups(['api', 'database'])
|
||||
->label('scope', 'collections.read')
|
||||
->label('resourceType', RESOURCE_TYPE_DATABASES)
|
||||
->label('sdk', new Method(
|
||||
namespace: 'vectorsDB',
|
||||
group: $this->getSdkGroup(),
|
||||
name: 'listCollectionLogs',
|
||||
description: '/docs/references/vectorsdb/get-collection-logs.md',
|
||||
auth: [AuthType::ADMIN],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: SwooleResponse::STATUS_CODE_OK,
|
||||
model: $this->getResponseModel(),
|
||||
)
|
||||
],
|
||||
contentType: ContentType::JSON
|
||||
))
|
||||
->param('databaseId', '', new UID(), 'Database ID.')
|
||||
->param('collectionId', '', new UID(), 'Collection ID.')
|
||||
->param('queries', [], new Queries([new Limit(), new Offset()]), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Only supported methods are limit and offset', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('locale')
|
||||
->inject('geodb')
|
||||
->inject('authorization')
|
||||
->inject('audit')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,6 @@ use Appwrite\Platform\Modules\Databases\Http\DocumentsDB\Collections\Documents\B
|
||||
use Appwrite\Platform\Modules\Databases\Http\DocumentsDB\Collections\Documents\Create as CreateRow;
|
||||
use Appwrite\Platform\Modules\Databases\Http\DocumentsDB\Collections\Documents\Delete as DeleteRow;
|
||||
use Appwrite\Platform\Modules\Databases\Http\DocumentsDB\Collections\Documents\Get as GetRow;
|
||||
use Appwrite\Platform\Modules\Databases\Http\DocumentsDB\Collections\Documents\Logs\XList as ListRowLogs;
|
||||
use Appwrite\Platform\Modules\Databases\Http\DocumentsDB\Collections\Documents\Update as UpdateRow;
|
||||
use Appwrite\Platform\Modules\Databases\Http\DocumentsDB\Collections\Documents\Upsert as UpsertRow;
|
||||
use Appwrite\Platform\Modules\Databases\Http\DocumentsDB\Collections\Documents\XList as ListRows;
|
||||
@@ -21,7 +20,6 @@ use Appwrite\Platform\Modules\Databases\Http\DocumentsDB\Collections\Indexes\Cre
|
||||
use Appwrite\Platform\Modules\Databases\Http\DocumentsDB\Collections\Indexes\Delete as DeleteColumnIndex;
|
||||
use Appwrite\Platform\Modules\Databases\Http\DocumentsDB\Collections\Indexes\Get as GetColumnIndex;
|
||||
use Appwrite\Platform\Modules\Databases\Http\DocumentsDB\Collections\Indexes\XList as ListColumnIndexes;
|
||||
use Appwrite\Platform\Modules\Databases\Http\DocumentsDB\Collections\Logs\XList as ListTableLogs;
|
||||
use Appwrite\Platform\Modules\Databases\Http\DocumentsDB\Collections\Update as UpdateTable;
|
||||
use Appwrite\Platform\Modules\Databases\Http\DocumentsDB\Collections\Usage\Get as GetTableUsage;
|
||||
use Appwrite\Platform\Modules\Databases\Http\DocumentsDB\Collections\XList as ListTables;
|
||||
@@ -69,7 +67,6 @@ class DocumentsDB extends Base
|
||||
$service->addAction(UpdateTable::getName(), new UpdateTable());
|
||||
$service->addAction(DeleteTable::getName(), new DeleteTable());
|
||||
$service->addAction(ListTables::getName(), new ListTables());
|
||||
$service->addAction(ListTableLogs::getName(), new ListTableLogs());
|
||||
$service->addAction(GetTableUsage::getName(), new GetTableUsage());
|
||||
}
|
||||
|
||||
@@ -92,7 +89,6 @@ class DocumentsDB extends Base
|
||||
$service->addAction(DeleteRow::getName(), new DeleteRow());
|
||||
$service->addAction(DeleteRows::getName(), new DeleteRows());
|
||||
$service->addAction(ListRows::getName(), new ListRows());
|
||||
$service->addAction(ListRowLogs::getName(), new ListRowLogs());
|
||||
$service->addAction(IncrementRowColumn::getName(), new IncrementRowColumn());
|
||||
$service->addAction(DecrementRowColumn::getName(), new DecrementRowColumn());
|
||||
}
|
||||
|
||||
@@ -10,7 +10,6 @@ use Appwrite\Platform\Modules\Databases\Http\VectorsDB\Collections\Documents\Bul
|
||||
use Appwrite\Platform\Modules\Databases\Http\VectorsDB\Collections\Documents\Create as CreateDocument;
|
||||
use Appwrite\Platform\Modules\Databases\Http\VectorsDB\Collections\Documents\Delete as DeleteDocument;
|
||||
use Appwrite\Platform\Modules\Databases\Http\VectorsDB\Collections\Documents\Get as GetDocument;
|
||||
use Appwrite\Platform\Modules\Databases\Http\VectorsDB\Collections\Documents\Logs\XList as ListDocumentLogs;
|
||||
use Appwrite\Platform\Modules\Databases\Http\VectorsDB\Collections\Documents\Update as UpdateDocument;
|
||||
use Appwrite\Platform\Modules\Databases\Http\VectorsDB\Collections\Documents\Upsert as UpsertDocument;
|
||||
use Appwrite\Platform\Modules\Databases\Http\VectorsDB\Collections\Documents\XList as ListDocuments;
|
||||
@@ -19,7 +18,6 @@ use Appwrite\Platform\Modules\Databases\Http\VectorsDB\Collections\Indexes\Creat
|
||||
use Appwrite\Platform\Modules\Databases\Http\VectorsDB\Collections\Indexes\Delete as DeleteIndex;
|
||||
use Appwrite\Platform\Modules\Databases\Http\VectorsDB\Collections\Indexes\Get as GetIndex;
|
||||
use Appwrite\Platform\Modules\Databases\Http\VectorsDB\Collections\Indexes\XList as ListIndexes;
|
||||
use Appwrite\Platform\Modules\Databases\Http\VectorsDB\Collections\Logs\XList as ListCollectionLogs;
|
||||
use Appwrite\Platform\Modules\Databases\Http\VectorsDB\Collections\Update as UpdateCollection;
|
||||
use Appwrite\Platform\Modules\Databases\Http\VectorsDB\Collections\Usage\Get as GetCollectionUsage;
|
||||
use Appwrite\Platform\Modules\Databases\Http\VectorsDB\Collections\XList as ListCollections;
|
||||
@@ -69,7 +67,6 @@ class VectorsDB extends Base
|
||||
$service->addAction(UpdateCollection::getName(), new UpdateCollection());
|
||||
$service->addAction(DeleteCollection::getName(), new DeleteCollection());
|
||||
$service->addAction(ListCollections::getName(), new ListCollections());
|
||||
$service->addAction(ListCollectionLogs::getName(), new ListCollectionLogs());
|
||||
$service->addAction(GetCollectionUsage::getName(), new GetCollectionUsage());
|
||||
}
|
||||
|
||||
@@ -92,7 +89,6 @@ class VectorsDB extends Base
|
||||
$service->addAction(UpdateDocuments::getName(), new UpdateDocuments());
|
||||
$service->addAction(UpsertDocuments::getName(), new UpsertDocuments());
|
||||
$service->addAction(DeleteDocuments::getName(), new DeleteDocuments());
|
||||
$service->addAction(ListDocumentLogs::getName(), new ListDocumentLogs());
|
||||
}
|
||||
|
||||
private function registerTransactionActions(Service $service): void
|
||||
|
||||
@@ -650,26 +650,30 @@ class Databases extends Action
|
||||
Document|null $attribute = null,
|
||||
Document|null $index = null,
|
||||
): void {
|
||||
$queueForRealtime
|
||||
->setProject($project)
|
||||
->setSubscribers(['console'])
|
||||
->setEvent($event)
|
||||
->setParam('databaseId', $database->getId())
|
||||
->setParam('tableId', $collection->getId())
|
||||
->setParam('collectionId', $collection->getId());
|
||||
try {
|
||||
$queueForRealtime
|
||||
->setProject($project)
|
||||
->setSubscribers(['console'])
|
||||
->setEvent($event)
|
||||
->setParam('databaseId', $database->getId())
|
||||
->setParam('tableId', $collection->getId())
|
||||
->setParam('collectionId', $collection->getId());
|
||||
|
||||
if (! empty($attribute)) {
|
||||
$queueForRealtime
|
||||
->setParam('columnId', $attribute->getId())
|
||||
->setParam('attributeId', $attribute->getId())
|
||||
->setPayload($attribute->getArrayCopy());
|
||||
}
|
||||
if (! empty($index)) {
|
||||
$queueForRealtime
|
||||
->setParam('indexId', $index->getId())
|
||||
->setPayload($index->getArrayCopy());
|
||||
if (! empty($attribute)) {
|
||||
$queueForRealtime
|
||||
->setParam('columnId', $attribute->getId())
|
||||
->setParam('attributeId', $attribute->getId())
|
||||
->setPayload($attribute->getArrayCopy());
|
||||
}
|
||||
if (! empty($index)) {
|
||||
$queueForRealtime
|
||||
->setParam('indexId', $index->getId())
|
||||
->setPayload($index->getArrayCopy());
|
||||
}
|
||||
$queueForRealtime->trigger();
|
||||
} finally {
|
||||
$queueForRealtime->reset();
|
||||
}
|
||||
|
||||
$queueForRealtime->trigger();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -305,9 +305,7 @@ class Create extends Base
|
||||
|
||||
if ($async) {
|
||||
if (is_null($scheduledAt)) {
|
||||
if ($project->getId() != '6862e6a6000cce69f9da') {
|
||||
$execution = $authorization->skip(fn () => $dbForProject->createDocument('executions', $execution));
|
||||
}
|
||||
$execution = $authorization->skip(fn () => $dbForProject->createDocument('executions', $execution));
|
||||
$queueForFunctions
|
||||
->setType('http')
|
||||
->setExecution($execution)
|
||||
@@ -348,9 +346,7 @@ class Create extends Base
|
||||
->setAttribute('scheduleInternalId', $schedule->getSequence())
|
||||
->setAttribute('scheduledAt', $scheduledAt);
|
||||
|
||||
if ($project->getId() != '6862e6a6000cce69f9da') {
|
||||
$execution = $authorization->skip(fn () => $dbForProject->createDocument('executions', $execution));
|
||||
}
|
||||
$execution = $authorization->skip(fn () => $dbForProject->createDocument('executions', $execution));
|
||||
}
|
||||
|
||||
if ($executionsRetentionCount > 0 && ENABLE_EXECUTIONS_LIMIT_ON_ROUTE) {
|
||||
@@ -516,9 +512,7 @@ class Create extends Base
|
||||
->addMetric(str_replace(['{resourceType}', '{resourceInternalId}'], [RESOURCE_TYPE_FUNCTIONS, $function->getSequence()], METRIC_RESOURCE_TYPE_ID_EXECUTIONS_MB_SECONDS), (int)(($spec['memory'] ?? APP_COMPUTE_MEMORY_DEFAULT) * $execution->getAttribute('duration', 0) * ($spec['cpus'] ?? APP_COMPUTE_CPUS_DEFAULT)))
|
||||
;
|
||||
|
||||
if ($project->getId() != '6862e6a6000cce69f9da') {
|
||||
$execution = $authorization->skip(fn () => $dbForProject->createDocument('executions', $execution));
|
||||
}
|
||||
$execution = $authorization->skip(fn () => $dbForProject->createDocument('executions', $execution));
|
||||
}
|
||||
|
||||
$executionResponse['headers']['x-appwrite-execution-id'] = $execution->getId();
|
||||
|
||||
@@ -6,9 +6,9 @@ use Ahc\Jwt\JWT;
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Event\Func;
|
||||
use Appwrite\Event\Message\Usage as UsageMessage;
|
||||
use Appwrite\Event\Publisher\Screenshot;
|
||||
use Appwrite\Event\Publisher\Usage as UsagePublisher;
|
||||
use Appwrite\Event\Realtime;
|
||||
use Appwrite\Event\Screenshot;
|
||||
use Appwrite\Event\Webhook;
|
||||
use Appwrite\Filter\BranchDomain as BranchDomainFilter;
|
||||
use Appwrite\Usage\Context;
|
||||
@@ -58,7 +58,7 @@ class Builds extends Action
|
||||
->inject('project')
|
||||
->inject('dbForPlatform')
|
||||
->inject('queueForEvents')
|
||||
->inject('queueForScreenshots')
|
||||
->inject('publisherForScreenshots')
|
||||
->inject('queueForWebhooks')
|
||||
->inject('queueForFunctions')
|
||||
->inject('queueForRealtime')
|
||||
@@ -84,7 +84,7 @@ class Builds extends Action
|
||||
Document $project,
|
||||
Database $dbForPlatform,
|
||||
Event $queueForEvents,
|
||||
Screenshot $queueForScreenshots,
|
||||
Screenshot $publisherForScreenshots,
|
||||
Webhook $queueForWebhooks,
|
||||
Func $queueForFunctions,
|
||||
Realtime $queueForRealtime,
|
||||
@@ -126,7 +126,7 @@ class Builds extends Action
|
||||
$deviceForFunctions,
|
||||
$deviceForSites,
|
||||
$deviceForFiles,
|
||||
$queueForScreenshots,
|
||||
$publisherForScreenshots,
|
||||
$queueForWebhooks,
|
||||
$queueForFunctions,
|
||||
$queueForRealtime,
|
||||
@@ -161,7 +161,7 @@ class Builds extends Action
|
||||
Device $deviceForFunctions,
|
||||
Device $deviceForSites,
|
||||
Device $deviceForFiles,
|
||||
Screenshot $queueForScreenshots,
|
||||
Screenshot $publisherForScreenshots,
|
||||
Webhook $queueForWebhooks,
|
||||
Func $queueForFunctions,
|
||||
Realtime $queueForRealtime,
|
||||
@@ -1120,10 +1120,10 @@ class Builds extends Action
|
||||
|
||||
/** Screenshot site */
|
||||
if ($resource->getCollection() === 'sites') {
|
||||
$queueForScreenshots
|
||||
->setDeploymentId($deployment->getId())
|
||||
->setProject($project)
|
||||
->trigger();
|
||||
$publisherForScreenshots->enqueue(new \Appwrite\Event\Message\Screenshot(
|
||||
project: $project,
|
||||
deploymentId: $deployment->getId(),
|
||||
));
|
||||
|
||||
Console::log('Site screenshot queued');
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace Appwrite\Platform\Modules\Functions\Workers;
|
||||
|
||||
use Ahc\Jwt\JWT;
|
||||
use Appwrite\Event\Message\Screenshot;
|
||||
use Appwrite\Event\Realtime;
|
||||
use Appwrite\Permission;
|
||||
use Appwrite\Role;
|
||||
@@ -62,9 +63,11 @@ class Screenshots extends Action
|
||||
throw new \Exception('Missing payload');
|
||||
}
|
||||
|
||||
$screenshotMessage = Screenshot::fromArray($payload);
|
||||
|
||||
Console::log('Site screenshot started');
|
||||
|
||||
$deploymentId = $payload['deploymentId'] ?? null;
|
||||
$deploymentId = $screenshotMessage->deploymentId;
|
||||
$deployment = $dbForProject->getDocument('deployments', $deploymentId);
|
||||
|
||||
if ($deployment->isEmpty()) {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
namespace Appwrite\Platform\Modules\Health\Http\Health\Queue\Audits;
|
||||
|
||||
use Appwrite\Event\Audit;
|
||||
use Appwrite\Event\Publisher\Audit;
|
||||
use Appwrite\Platform\Modules\Health\Http\Health\Queue\Base;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\ContentType;
|
||||
@@ -42,16 +42,16 @@ class Get extends Base
|
||||
contentType: ContentType::JSON
|
||||
))
|
||||
->param('threshold', 5000, new Integer(true), 'Queue size threshold. When hit (equal or higher), endpoint returns server error. Default value is 5000.', true)
|
||||
->inject('queueForAudits')
|
||||
->inject('publisherForAudits')
|
||||
->inject('response')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(int|string $threshold, Audit $queueForAudits, Response $response): void
|
||||
public function action(int|string $threshold, Audit $publisherForAudits, Response $response): void
|
||||
{
|
||||
$threshold = (int) $threshold;
|
||||
|
||||
$size = $queueForAudits->getSize();
|
||||
$size = $publisherForAudits->getSize();
|
||||
|
||||
$this->assertQueueThreshold($size, $threshold);
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
namespace Appwrite\Platform\Modules\Health\Http\Health\Queue\Certificates;
|
||||
|
||||
use Appwrite\Event\Certificate;
|
||||
use Appwrite\Event\Publisher\Certificate;
|
||||
use Appwrite\Platform\Modules\Health\Http\Health\Queue\Base;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\ContentType;
|
||||
@@ -42,16 +42,16 @@ class Get extends Base
|
||||
contentType: ContentType::JSON
|
||||
))
|
||||
->param('threshold', 5000, new Integer(true), 'Queue size threshold. When hit (equal or higher), endpoint returns server error. Default value is 5000.', true)
|
||||
->inject('queueForCertificates')
|
||||
->inject('publisherForCertificates')
|
||||
->inject('response')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(int|string $threshold, Certificate $queueForCertificates, Response $response): void
|
||||
public function action(int|string $threshold, Certificate $publisherForCertificates, Response $response): void
|
||||
{
|
||||
$threshold = (int) $threshold;
|
||||
|
||||
$size = $queueForCertificates->getSize();
|
||||
$size = $publisherForCertificates->getSize();
|
||||
|
||||
$this->assertQueueThreshold($size, $threshold);
|
||||
|
||||
|
||||
@@ -2,19 +2,19 @@
|
||||
|
||||
namespace Appwrite\Platform\Modules\Health\Http\Health\Queue\Failed;
|
||||
|
||||
use Appwrite\Event\Audit;
|
||||
use Appwrite\Event\Build;
|
||||
use Appwrite\Event\Certificate;
|
||||
use Appwrite\Event\Database;
|
||||
use Appwrite\Event\Delete;
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Event\Func;
|
||||
use Appwrite\Event\Mail;
|
||||
use Appwrite\Event\Messaging;
|
||||
use Appwrite\Event\Migration;
|
||||
use Appwrite\Event\Publisher\Audit;
|
||||
use Appwrite\Event\Publisher\Certificate;
|
||||
use Appwrite\Event\Publisher\Migration as MigrationPublisher;
|
||||
use Appwrite\Event\Publisher\Screenshot;
|
||||
use Appwrite\Event\Publisher\StatsResources as StatsResourcesPublisher;
|
||||
use Appwrite\Event\Publisher\Usage as UsagePublisher;
|
||||
use Appwrite\Event\Screenshot;
|
||||
use Appwrite\Event\StatsResources;
|
||||
use Appwrite\Event\Webhook;
|
||||
use Appwrite\Platform\Modules\Health\Http\Health\Queue\Base;
|
||||
use Appwrite\SDK\AuthType;
|
||||
@@ -75,17 +75,17 @@ class Get extends Base
|
||||
->inject('response')
|
||||
->inject('queueForDatabase')
|
||||
->inject('queueForDeletes')
|
||||
->inject('queueForAudits')
|
||||
->inject('publisherForAudits')
|
||||
->inject('queueForMails')
|
||||
->inject('queueForFunctions')
|
||||
->inject('queueForStatsResources')
|
||||
->inject('publisherForStatsResources')
|
||||
->inject('publisherForUsage')
|
||||
->inject('queueForWebhooks')
|
||||
->inject('queueForCertificates')
|
||||
->inject('publisherForCertificates')
|
||||
->inject('queueForBuilds')
|
||||
->inject('queueForMessaging')
|
||||
->inject('queueForMigrations')
|
||||
->inject('queueForScreenshots')
|
||||
->inject('publisherForMigrations')
|
||||
->inject('publisherForScreenshots')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
@@ -95,34 +95,34 @@ class Get extends Base
|
||||
Response $response,
|
||||
Database $queueForDatabase,
|
||||
Delete $queueForDeletes,
|
||||
Audit $queueForAudits,
|
||||
Audit $publisherForAudits,
|
||||
Mail $queueForMails,
|
||||
Func $queueForFunctions,
|
||||
StatsResources $queueForStatsResources,
|
||||
StatsResourcesPublisher $publisherForStatsResources,
|
||||
UsagePublisher $publisherForUsage,
|
||||
Webhook $queueForWebhooks,
|
||||
Certificate $queueForCertificates,
|
||||
Certificate $publisherForCertificates,
|
||||
Build $queueForBuilds,
|
||||
Messaging $queueForMessaging,
|
||||
Migration $queueForMigrations,
|
||||
Screenshot $queueForScreenshots,
|
||||
MigrationPublisher $publisherForMigrations,
|
||||
Screenshot $publisherForScreenshots,
|
||||
): void {
|
||||
$threshold = (int) $threshold;
|
||||
|
||||
$queue = match ($name) {
|
||||
System::getEnv('_APP_DATABASE_QUEUE_NAME', Event::DATABASE_QUEUE_NAME) => $queueForDatabase,
|
||||
System::getEnv('_APP_DELETE_QUEUE_NAME', Event::DELETE_QUEUE_NAME) => $queueForDeletes,
|
||||
System::getEnv('_APP_AUDITS_QUEUE_NAME', Event::AUDITS_QUEUE_NAME) => $queueForAudits,
|
||||
System::getEnv('_APP_AUDITS_QUEUE_NAME', Event::AUDITS_QUEUE_NAME) => $publisherForAudits,
|
||||
System::getEnv('_APP_MAILS_QUEUE_NAME', Event::MAILS_QUEUE_NAME) => $queueForMails,
|
||||
System::getEnv('_APP_FUNCTIONS_QUEUE_NAME', Event::FUNCTIONS_QUEUE_NAME) => $queueForFunctions,
|
||||
System::getEnv('_APP_STATS_RESOURCES_QUEUE_NAME', Event::STATS_RESOURCES_QUEUE_NAME) => $queueForStatsResources,
|
||||
System::getEnv('_APP_STATS_RESOURCES_QUEUE_NAME', Event::STATS_RESOURCES_QUEUE_NAME) => $publisherForStatsResources,
|
||||
System::getEnv('_APP_STATS_USAGE_QUEUE_NAME', Event::STATS_USAGE_QUEUE_NAME) => $publisherForUsage,
|
||||
System::getEnv('_APP_WEBHOOK_QUEUE_NAME', Event::WEBHOOK_QUEUE_NAME) => $queueForWebhooks,
|
||||
System::getEnv('_APP_CERTIFICATES_QUEUE_NAME', Event::CERTIFICATES_QUEUE_NAME) => $queueForCertificates,
|
||||
System::getEnv('_APP_CERTIFICATES_QUEUE_NAME', Event::CERTIFICATES_QUEUE_NAME) => $publisherForCertificates,
|
||||
System::getEnv('_APP_BUILDS_QUEUE_NAME', Event::BUILDS_QUEUE_NAME) => $queueForBuilds,
|
||||
System::getEnv('_APP_SCREENSHOTS_QUEUE_NAME', Event::SCREENSHOTS_QUEUE_NAME) => $queueForScreenshots,
|
||||
System::getEnv('_APP_SCREENSHOTS_QUEUE_NAME', Event::SCREENSHOTS_QUEUE_NAME) => $publisherForScreenshots,
|
||||
System::getEnv('_APP_MESSAGING_QUEUE_NAME', Event::MESSAGING_QUEUE_NAME) => $queueForMessaging,
|
||||
System::getEnv('_APP_MIGRATIONS_QUEUE_NAME', Event::MIGRATIONS_QUEUE_NAME) => $queueForMigrations,
|
||||
System::getEnv('_APP_MIGRATIONS_QUEUE_NAME', Event::MIGRATIONS_QUEUE_NAME) => $publisherForMigrations,
|
||||
};
|
||||
$failed = $queue->getSize(failed: true);
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
namespace Appwrite\Platform\Modules\Health\Http\Health\Queue\Logs;
|
||||
|
||||
use Appwrite\Event\Audit;
|
||||
use Appwrite\Event\Publisher\Audit;
|
||||
use Appwrite\Platform\Modules\Health\Http\Health\Queue\Base;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\ContentType;
|
||||
@@ -42,16 +42,16 @@ class Get extends Base
|
||||
contentType: ContentType::JSON
|
||||
))
|
||||
->param('threshold', 5000, new Integer(true), 'Queue size threshold. When hit (equal or higher), endpoint returns server error. Default value is 5000.', true)
|
||||
->inject('queueForAudits')
|
||||
->inject('publisherForAudits')
|
||||
->inject('response')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(int|string $threshold, Audit $queueForAudits, Response $response): void
|
||||
public function action(int|string $threshold, Audit $publisherForAudits, Response $response): void
|
||||
{
|
||||
$threshold = (int) $threshold;
|
||||
|
||||
$size = $queueForAudits->getSize();
|
||||
$size = $publisherForAudits->getSize();
|
||||
|
||||
$this->assertQueueThreshold($size, $threshold);
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
namespace Appwrite\Platform\Modules\Health\Http\Health\Queue\Migrations;
|
||||
|
||||
use Appwrite\Event\Migration;
|
||||
use Appwrite\Event\Publisher\Migration as MigrationPublisher;
|
||||
use Appwrite\Platform\Modules\Health\Http\Health\Queue\Base;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\ContentType;
|
||||
@@ -42,16 +42,16 @@ class Get extends Base
|
||||
contentType: ContentType::JSON
|
||||
))
|
||||
->param('threshold', 5000, new Integer(true), 'Queue size threshold. When hit (equal or higher), endpoint returns server error. Default value is 5000.', true)
|
||||
->inject('queueForMigrations')
|
||||
->inject('publisherForMigrations')
|
||||
->inject('response')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(int|string $threshold, Migration $queueForMigrations, Response $response): void
|
||||
public function action(int|string $threshold, MigrationPublisher $publisherForMigrations, Response $response): void
|
||||
{
|
||||
$threshold = (int) $threshold;
|
||||
|
||||
$size = $queueForMigrations->getSize();
|
||||
$size = $publisherForMigrations->getSize();
|
||||
|
||||
$this->assertQueueThreshold($size, $threshold);
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
namespace Appwrite\Platform\Modules\Health\Http\Health\Queue\StatsResources;
|
||||
|
||||
use Appwrite\Event\StatsResources;
|
||||
use Appwrite\Event\Publisher\StatsResources as StatsResourcesPublisher;
|
||||
use Appwrite\Platform\Modules\Health\Http\Health\Queue\Base;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\ContentType;
|
||||
@@ -42,16 +42,16 @@ class Get extends Base
|
||||
contentType: ContentType::JSON
|
||||
))
|
||||
->param('threshold', 5000, new Integer(true), 'Queue size threshold. When hit (equal or higher), endpoint returns server error. Default value is 5000.', true)
|
||||
->inject('queueForStatsResources')
|
||||
->inject('publisherForStatsResources')
|
||||
->inject('response')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(int|string $threshold, StatsResources $queueForStatsResources, Response $response): void
|
||||
public function action(int|string $threshold, StatsResourcesPublisher $publisherForStatsResources, Response $response): void
|
||||
{
|
||||
$threshold = (int) $threshold;
|
||||
|
||||
$size = $queueForStatsResources->getSize();
|
||||
$size = $publisherForStatsResources->getSize();
|
||||
|
||||
$this->assertQueueThreshold($size, $threshold);
|
||||
|
||||
|
||||
@@ -62,7 +62,7 @@ class Create extends Base
|
||||
))
|
||||
->param('keyId', '', fn (Database $dbForPlatform) => new CustomId(false, $dbForPlatform->getAdapter()->getMaxUIDLength()), 'Key ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.', false, ['dbForPlatform'])
|
||||
->param('name', null, new Text(128), 'Key name. Max length: 128 chars.')
|
||||
->param('scopes', null, new Nullable(new ArrayList(new WhiteList(array_keys(Config::getParam('projectScopes')), true), APP_LIMIT_ARRAY_PARAMS_SIZE)), 'Key scopes list. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' scopes are allowed.')
|
||||
->param('scopes', [], new ArrayList(new WhiteList(array_keys(Config::getParam('projectScopes')), true), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Key scopes list. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' scopes are allowed.', optional: false)
|
||||
->param('expire', null, new Nullable(new Datetime()), 'Expiration time in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. Use null for unlimited expiration.', true)
|
||||
->inject('response')
|
||||
->inject('queueForEvents')
|
||||
@@ -72,13 +72,10 @@ class Create extends Base
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string>|null $scopes
|
||||
*/
|
||||
public function action(
|
||||
string $keyId,
|
||||
string $name,
|
||||
?array $scopes,
|
||||
array $scopes,
|
||||
?string $expire,
|
||||
Response $response,
|
||||
QueueEvent $queueForEvents,
|
||||
@@ -95,7 +92,7 @@ class Create extends Base
|
||||
'resourceId' => $project->getId(),
|
||||
'resourceType' => 'projects',
|
||||
'name' => $name,
|
||||
'scopes' => $scopes ?? [],
|
||||
'scopes' => $scopes,
|
||||
'expire' => $expire,
|
||||
'sdks' => [],
|
||||
'accessedAt' => null,
|
||||
|
||||
@@ -60,7 +60,7 @@ class Update extends Base
|
||||
))
|
||||
->param('keyId', '', fn (Database $dbForPlatform) => new UID($dbForPlatform->getAdapter()->getMaxUIDLength()), 'Key ID.', false, ['dbForPlatform'])
|
||||
->param('name', null, new Text(128), 'Key name. Max length: 128 chars.')
|
||||
->param('scopes', null, new Nullable(new ArrayList(new WhiteList(array_keys(Config::getParam('projectScopes')), true), APP_LIMIT_ARRAY_PARAMS_SIZE)), 'Key scopes list. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' scopes are allowed.')
|
||||
->param('scopes', [], new ArrayList(new WhiteList(array_keys(Config::getParam('projectScopes')), true), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Key scopes list. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' scopes are allowed.', optional: false)
|
||||
->param('expire', null, new Nullable(new Datetime()), 'Expiration time in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. Use null for unlimited expiration.', true)
|
||||
->inject('response')
|
||||
->inject('queueForEvents')
|
||||
@@ -70,13 +70,10 @@ class Update extends Base
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string>|null $scopes
|
||||
*/
|
||||
public function action(
|
||||
string $keyId,
|
||||
string $name,
|
||||
?array $scopes,
|
||||
array $scopes,
|
||||
?string $expire,
|
||||
Response $response,
|
||||
QueueEvent $queueForEvents,
|
||||
@@ -92,7 +89,7 @@ class Update extends Base
|
||||
|
||||
$updates = new Document([
|
||||
'name' => $name,
|
||||
'scopes' => $scopes ?? [],
|
||||
'scopes' => $scopes,
|
||||
'expire' => $expire,
|
||||
]);
|
||||
|
||||
|
||||
@@ -64,7 +64,7 @@ class Create extends Action
|
||||
))
|
||||
->param('platformId', '', fn (Database $dbForPlatform) => new CustomId(false, $dbForPlatform->getAdapter()->getMaxUIDLength()), 'Platform ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.', false, ['dbForPlatform'])
|
||||
->param('name', null, new Text(128), 'Platform name. Max length: 128 chars.')
|
||||
->param('hostname', '', new Hostname(), 'Platform web hostname. Max length: 256 chars.', optional: true) // Optional for backwards compatibility
|
||||
->param('hostname', '', new Hostname(), 'Platform web hostname. Max length: 256 chars.', optional: true, example: 'app.example.com') // Optional for backwards compatibility
|
||||
->param('key', '', new Text(256), 'Deprecated: Package name for Android or bundle ID for iOS or macOS. Max length: 256 chars.', optional: true, deprecated: true) // Exists for backwards compatibility
|
||||
->param('type', '', new Text(256), 'Deprecated: Platform type. Max length: 256 chars.', optional: true, deprecated: true) // Exists for backwards compatibility
|
||||
->inject('request')
|
||||
|
||||
@@ -56,7 +56,7 @@ class Update extends Action
|
||||
))
|
||||
->param('platformId', '', fn (Database $dbForPlatform) => new UID($dbForPlatform->getAdapter()->getMaxUIDLength()), 'Platform ID.', false, ['dbForPlatform'])
|
||||
->param('name', null, new Text(128), 'Platform name. Max length: 128 chars.')
|
||||
->param('hostname', '', new Hostname(), 'Platform web hostname. Max length: 256 chars.', optional: true) // Optional for backwards compatibility
|
||||
->param('hostname', '', new Hostname(), 'Platform web hostname. Max length: 256 chars.', optional: true, example: 'app.example.com') // Optional for backwards compatibility
|
||||
->param('key', '', new Text(256), 'Package name for Android or bundle ID for iOS or macOS. Max length: 256 chars.', optional: true, deprecated: true) // Exists for backwards compatibility
|
||||
->inject('response')
|
||||
->inject('queueForEvents')
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Modules\Project\Http\Project\Protocols\Status;
|
||||
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Platform\Action;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Platform\Scope\HTTP;
|
||||
use Utopia\Validator\Boolean;
|
||||
use Utopia\Validator\WhiteList;
|
||||
|
||||
class Update extends Action
|
||||
{
|
||||
use HTTP;
|
||||
|
||||
public static function getName()
|
||||
{
|
||||
return 'updateProjectProtocolStatus';
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->setHttpMethod(Action::HTTP_REQUEST_METHOD_PATCH)
|
||||
->setHttpPath('/v1/project/protocols/:protocolId/status')
|
||||
->httpAlias('/v1/projects/:projectId/api')
|
||||
->desc('Update project protocol status')
|
||||
->groups(['api', 'project'])
|
||||
->label('scope', 'project.write')
|
||||
->label('event', 'protocols.[protocolId].update')
|
||||
->label('audits.event', 'project.protocols.[protocolId].update')
|
||||
->label('audits.resource', 'project.protocols/{response.$id}')
|
||||
->label('sdk', new Method(
|
||||
namespace: 'project',
|
||||
group: null,
|
||||
name: 'updateProtocolStatus',
|
||||
description: <<<EOT
|
||||
Update the status of a specific protocol. Use this endpoint to enable or disable a protocol in your project.
|
||||
EOT,
|
||||
auth: [AuthType::ADMIN, AuthType::KEY],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
model: Response::MODEL_PROJECT,
|
||||
)
|
||||
],
|
||||
))
|
||||
->param('protocolId', '', new WhiteList(array_keys(Config::getParam('protocols')), true), 'Protocol name. Can be one of: ' . \implode(', ', array_keys(Config::getParam('protocols'))))
|
||||
->param('enabled', null, new Boolean(), 'Protocol status.')
|
||||
->inject('response')
|
||||
->inject('dbForPlatform')
|
||||
->inject('project')
|
||||
->inject('authorization')
|
||||
->inject('queueForEvents')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(
|
||||
string $protocolId,
|
||||
bool $enabled,
|
||||
Response $response,
|
||||
Database $dbForPlatform,
|
||||
Document $project,
|
||||
Authorization $authorization,
|
||||
Event $queueForEvents,
|
||||
): void {
|
||||
$protocols = $project->getAttribute('apis', []);
|
||||
$protocols[$protocolId] = $enabled;
|
||||
|
||||
$project = $authorization->skip(fn () => $dbForPlatform->updateDocument('projects', $project->getId(), new Document([
|
||||
'apis' => $protocols,
|
||||
])));
|
||||
|
||||
$queueForEvents->setParam('protocolId', $protocolId);
|
||||
|
||||
$response->dynamic($project, Response::MODEL_PROJECT);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Modules\Project\Http\Project\Services\Status;
|
||||
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Platform\Action;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Platform\Scope\HTTP;
|
||||
use Utopia\Validator\Boolean;
|
||||
use Utopia\Validator\WhiteList;
|
||||
|
||||
class Update extends Action
|
||||
{
|
||||
use HTTP;
|
||||
|
||||
public static function getName()
|
||||
{
|
||||
return 'updateProjectServiceStatus';
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->setHttpMethod(Action::HTTP_REQUEST_METHOD_PATCH)
|
||||
->setHttpPath('/v1/project/services/:serviceId/status')
|
||||
->httpAlias('/v1/projects/:projectId/service')
|
||||
->desc('Update project service status')
|
||||
->groups(['api', 'project'])
|
||||
->label('scope', 'project.write')
|
||||
->label('event', 'services.[serviceId].update')
|
||||
->label('audits.event', 'project.services.[serviceId].update')
|
||||
->label('audits.resource', 'project.services/{response.$id}')
|
||||
->label('sdk', new Method(
|
||||
namespace: 'project',
|
||||
group: null,
|
||||
name: 'updateServiceStatus',
|
||||
description: <<<EOT
|
||||
Update the status of a specific service. Use this endpoint to enable or disable a service in your project.
|
||||
EOT,
|
||||
auth: [AuthType::ADMIN, AuthType::KEY],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
model: Response::MODEL_PROJECT,
|
||||
)
|
||||
],
|
||||
))
|
||||
->param('serviceId', '', new WhiteList(array_keys(array_filter(Config::getParam('services'), fn ($element) => $element['optional'])), true), 'Service name. Can be one of: '.\implode(', ', array_keys(array_filter(Config::getParam('services'), fn ($element) => $element['optional']))))
|
||||
->param('enabled', null, new Boolean(), 'Service status.')
|
||||
->inject('response')
|
||||
->inject('dbForPlatform')
|
||||
->inject('project')
|
||||
->inject('authorization')
|
||||
->inject('queueForEvents')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(
|
||||
string $serviceId,
|
||||
bool $enabled,
|
||||
Response $response,
|
||||
Database $dbForPlatform,
|
||||
Document $project,
|
||||
Authorization $authorization,
|
||||
Event $queueForEvents
|
||||
): void {
|
||||
$services = $project->getAttribute('services', []);
|
||||
$services[$serviceId] = $enabled;
|
||||
|
||||
$project = $authorization->skip(fn () => $dbForPlatform->updateDocument('projects', $project->getId(), new Document([
|
||||
'services' => $services,
|
||||
])));
|
||||
|
||||
$queueForEvents->setParam('serviceId', $serviceId);
|
||||
|
||||
$response->dynamic($project, Response::MODEL_PROJECT);
|
||||
}
|
||||
}
|
||||
@@ -53,7 +53,7 @@ class Create extends Action
|
||||
)
|
||||
],
|
||||
))
|
||||
->param('variableId', 'unique()', fn (Database $dbForProject) => new CustomId(false, $dbForProject->getAdapter()->getMaxUIDLength()), 'Variable ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.', true, ['dbForProject'])
|
||||
->param('variableId', '', fn (Database $dbForProject) => new CustomId(false, $dbForProject->getAdapter()->getMaxUIDLength()), 'Variable ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.', false, ['dbForProject'])
|
||||
->param('key', null, new Text(Database::LENGTH_KEY), 'Variable key. Max length: ' . Database::LENGTH_KEY . ' chars.')
|
||||
->param('value', null, new Text(8192, 0), 'Variable value. Max length: 8192 chars.')
|
||||
->param('secret', true, new Boolean(), 'Secret variables can be updated or deleted, but only projects can read them during build and runtime.', true)
|
||||
|
||||
@@ -22,6 +22,8 @@ use Appwrite\Platform\Modules\Project\Http\Project\Platforms\Web\Update as Updat
|
||||
use Appwrite\Platform\Modules\Project\Http\Project\Platforms\Windows\Create as CreateWindowsPlatform;
|
||||
use Appwrite\Platform\Modules\Project\Http\Project\Platforms\Windows\Update as UpdateWindowsPlatform;
|
||||
use Appwrite\Platform\Modules\Project\Http\Project\Platforms\XList as ListPlatforms;
|
||||
use Appwrite\Platform\Modules\Project\Http\Project\Protocols\Status\Update as UpdateProjectProtocolStatus;
|
||||
use Appwrite\Platform\Modules\Project\Http\Project\Services\Status\Update as UpdateProjectServiceStatus;
|
||||
use Appwrite\Platform\Modules\Project\Http\Project\Variables\Create as CreateVariable;
|
||||
use Appwrite\Platform\Modules\Project\Http\Project\Variables\Delete as DeleteVariable;
|
||||
use Appwrite\Platform\Modules\Project\Http\Project\Variables\Get as GetVariable;
|
||||
@@ -40,6 +42,8 @@ class Http extends Service
|
||||
|
||||
// Project
|
||||
$this->addAction(UpdateProjectLabels::getName(), new UpdateProjectLabels());
|
||||
$this->addAction(UpdateProjectProtocolStatus::getName(), new UpdateProjectProtocolStatus());
|
||||
$this->addAction(UpdateProjectServiceStatus::getName(), new UpdateProjectServiceStatus());
|
||||
|
||||
// Variables
|
||||
$this->addAction(CreateVariable::getName(), new CreateVariable());
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
namespace Appwrite\Platform\Modules\Proxy\Http\Rules\API;
|
||||
|
||||
use Appwrite\Event\Certificate;
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Event\Publisher\Certificate;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Platform\Modules\Proxy\Action;
|
||||
use Appwrite\SDK\AuthType;
|
||||
@@ -62,7 +62,7 @@ class Create extends Action
|
||||
->param('domain', null, new ValidatorDomain(), 'Domain name.')
|
||||
->inject('response')
|
||||
->inject('project')
|
||||
->inject('queueForCertificates')
|
||||
->inject('publisherForCertificates')
|
||||
->inject('queueForEvents')
|
||||
->inject('dbForPlatform')
|
||||
->inject('platform')
|
||||
@@ -70,7 +70,7 @@ class Create extends Action
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(string $domain, Response $response, Document $project, Certificate $queueForCertificates, Event $queueForEvents, Database $dbForPlatform, array $platform, Log $log)
|
||||
public function action(string $domain, Response $response, Document $project, Certificate $publisherForCertificates, Event $queueForEvents, Database $dbForPlatform, array $platform, Log $log)
|
||||
{
|
||||
$this->validateDomainRestrictions($domain, $platform);
|
||||
|
||||
@@ -114,13 +114,14 @@ class Create extends Action
|
||||
}
|
||||
|
||||
if ($rule->getAttribute('status', '') === RULE_STATUS_CERTIFICATE_GENERATING) {
|
||||
$queueForCertificates
|
||||
->setDomain(new Document([
|
||||
$publisherForCertificates->enqueue(new \Appwrite\Event\Message\Certificate(
|
||||
project: $project,
|
||||
domain: new Document([
|
||||
'domain' => $rule->getAttribute('domain'),
|
||||
'domainType' => $rule->getAttribute('deploymentResourceType', $rule->getAttribute('type')),
|
||||
]))
|
||||
->setAction(Certificate::ACTION_GENERATION)
|
||||
->trigger();
|
||||
]),
|
||||
action: \Appwrite\Event\Certificate::ACTION_GENERATION,
|
||||
));
|
||||
}
|
||||
|
||||
$queueForEvents->setParam('ruleId', $rule->getId());
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
namespace Appwrite\Platform\Modules\Proxy\Http\Rules\Function;
|
||||
|
||||
use Appwrite\Event\Certificate;
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Event\Publisher\Certificate;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Platform\Modules\Proxy\Action;
|
||||
use Appwrite\SDK\AuthType;
|
||||
@@ -66,7 +66,7 @@ class Create extends Action
|
||||
->param('branch', '', new Text(255, 0), 'Name of VCS branch to deploy changes automatically', true)
|
||||
->inject('response')
|
||||
->inject('project')
|
||||
->inject('queueForCertificates')
|
||||
->inject('publisherForCertificates')
|
||||
->inject('queueForEvents')
|
||||
->inject('dbForPlatform')
|
||||
->inject('dbForProject')
|
||||
@@ -75,7 +75,7 @@ class Create extends Action
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(string $domain, string $functionId, string $branch, Response $response, Document $project, Certificate $queueForCertificates, Event $queueForEvents, Database $dbForPlatform, Database $dbForProject, array $platform, Log $log)
|
||||
public function action(string $domain, string $functionId, string $branch, Response $response, Document $project, Certificate $publisherForCertificates, Event $queueForEvents, Database $dbForPlatform, Database $dbForProject, array $platform, Log $log)
|
||||
{
|
||||
$this->validateDomainRestrictions($domain, $platform);
|
||||
|
||||
@@ -132,13 +132,14 @@ class Create extends Action
|
||||
}
|
||||
|
||||
if ($rule->getAttribute('status', '') === RULE_STATUS_CERTIFICATE_GENERATING) {
|
||||
$queueForCertificates
|
||||
->setDomain(new Document([
|
||||
$publisherForCertificates->enqueue(new \Appwrite\Event\Message\Certificate(
|
||||
project: $project,
|
||||
domain: new Document([
|
||||
'domain' => $rule->getAttribute('domain'),
|
||||
'domainType' => $rule->getAttribute('deploymentResourceType', $rule->getAttribute('type')),
|
||||
]))
|
||||
->setAction(Certificate::ACTION_GENERATION)
|
||||
->trigger();
|
||||
]),
|
||||
action: \Appwrite\Event\Certificate::ACTION_GENERATION,
|
||||
));
|
||||
}
|
||||
|
||||
$queueForEvents->setParam('ruleId', $rule->getId());
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
namespace Appwrite\Platform\Modules\Proxy\Http\Rules\Redirect;
|
||||
|
||||
use Appwrite\Event\Certificate;
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Event\Publisher\Certificate;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Platform\Modules\Proxy\Action;
|
||||
use Appwrite\SDK\AuthType;
|
||||
@@ -69,7 +69,7 @@ class Create extends Action
|
||||
->param('resourceType', '', new WhiteList(['site', 'function']), 'Type of parent resource.')
|
||||
->inject('response')
|
||||
->inject('project')
|
||||
->inject('queueForCertificates')
|
||||
->inject('publisherForCertificates')
|
||||
->inject('queueForEvents')
|
||||
->inject('dbForPlatform')
|
||||
->inject('dbForProject')
|
||||
@@ -78,7 +78,7 @@ class Create extends Action
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(string $domain, string $url, int $statusCode, string $resourceId, string $resourceType, Response $response, Document $project, Certificate $queueForCertificates, Event $queueForEvents, Database $dbForPlatform, Database $dbForProject, array $platform, Log $log)
|
||||
public function action(string $domain, string $url, int $statusCode, string $resourceId, string $resourceType, Response $response, Document $project, Certificate $publisherForCertificates, Event $queueForEvents, Database $dbForPlatform, Database $dbForProject, array $platform, Log $log)
|
||||
{
|
||||
$this->validateDomainRestrictions($domain, $platform);
|
||||
|
||||
@@ -136,13 +136,14 @@ class Create extends Action
|
||||
}
|
||||
|
||||
if ($rule->getAttribute('status', '') === RULE_STATUS_CERTIFICATE_GENERATING) {
|
||||
$queueForCertificates
|
||||
->setDomain(new Document([
|
||||
$publisherForCertificates->enqueue(new \Appwrite\Event\Message\Certificate(
|
||||
project: $project,
|
||||
domain: new Document([
|
||||
'domain' => $rule->getAttribute('domain'),
|
||||
'domainType' => $rule->getAttribute('deploymentResourceType', $rule->getAttribute('type')),
|
||||
]))
|
||||
->setAction(Certificate::ACTION_GENERATION)
|
||||
->trigger();
|
||||
]),
|
||||
action: \Appwrite\Event\Certificate::ACTION_GENERATION,
|
||||
));
|
||||
}
|
||||
|
||||
$queueForEvents->setParam('ruleId', $rule->getId());
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
namespace Appwrite\Platform\Modules\Proxy\Http\Rules\Site;
|
||||
|
||||
use Appwrite\Event\Certificate;
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Event\Publisher\Certificate;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Platform\Modules\Proxy\Action;
|
||||
use Appwrite\SDK\AuthType;
|
||||
@@ -66,7 +66,7 @@ class Create extends Action
|
||||
->param('branch', '', new Text(255, 0), 'Name of VCS branch to deploy changes automatically', true)
|
||||
->inject('response')
|
||||
->inject('project')
|
||||
->inject('queueForCertificates')
|
||||
->inject('publisherForCertificates')
|
||||
->inject('queueForEvents')
|
||||
->inject('dbForPlatform')
|
||||
->inject('dbForProject')
|
||||
@@ -75,7 +75,7 @@ class Create extends Action
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(string $domain, string $siteId, ?string $branch, Response $response, Document $project, Certificate $queueForCertificates, Event $queueForEvents, Database $dbForPlatform, Database $dbForProject, array $platform, Log $log)
|
||||
public function action(string $domain, string $siteId, ?string $branch, Response $response, Document $project, Certificate $publisherForCertificates, Event $queueForEvents, Database $dbForPlatform, Database $dbForProject, array $platform, Log $log)
|
||||
{
|
||||
$this->validateDomainRestrictions($domain, $platform);
|
||||
|
||||
@@ -132,13 +132,14 @@ class Create extends Action
|
||||
}
|
||||
|
||||
if ($rule->getAttribute('status', '') === RULE_STATUS_CERTIFICATE_GENERATING) {
|
||||
$queueForCertificates
|
||||
->setDomain(new Document([
|
||||
$publisherForCertificates->enqueue(new \Appwrite\Event\Message\Certificate(
|
||||
project: $project,
|
||||
domain: new Document([
|
||||
'domain' => $rule->getAttribute('domain'),
|
||||
'domainType' => $rule->getAttribute('deploymentResourceType', $rule->getAttribute('type')),
|
||||
]))
|
||||
->setAction(Certificate::ACTION_GENERATION)
|
||||
->trigger();
|
||||
]),
|
||||
action: \Appwrite\Event\Certificate::ACTION_GENERATION,
|
||||
));
|
||||
}
|
||||
|
||||
$queueForEvents->setParam('ruleId', $rule->getId());
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
namespace Appwrite\Platform\Modules\Proxy\Http\Rules\Verification;
|
||||
|
||||
use Appwrite\Event\Certificate;
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Event\Publisher\Certificate;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Platform\Modules\Proxy\Action;
|
||||
use Appwrite\SDK\AuthType;
|
||||
@@ -56,7 +56,7 @@ class Update extends Action
|
||||
))
|
||||
->param('ruleId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Rule ID.', false, ['dbForProject'])
|
||||
->inject('response')
|
||||
->inject('queueForCertificates')
|
||||
->inject('publisherForCertificates')
|
||||
->inject('queueForEvents')
|
||||
->inject('project')
|
||||
->inject('dbForPlatform')
|
||||
@@ -67,7 +67,7 @@ class Update extends Action
|
||||
public function action(
|
||||
string $ruleId,
|
||||
Response $response,
|
||||
Certificate $queueForCertificates,
|
||||
Certificate $publisherForCertificates,
|
||||
Event $queueForEvents,
|
||||
Document $project,
|
||||
Database $dbForPlatform,
|
||||
@@ -110,12 +110,13 @@ class Update extends Action
|
||||
}
|
||||
|
||||
// Issue a TLS certificate when DNS verification is successful
|
||||
$queueForCertificates
|
||||
->setDomain(new Document([
|
||||
$publisherForCertificates->enqueue(new \Appwrite\Event\Message\Certificate(
|
||||
project: $project,
|
||||
domain: new Document([
|
||||
'domain' => $rule->getAttribute('domain'),
|
||||
'domainType' => $rule->getAttribute('deploymentResourceType', $rule->getAttribute('type')),
|
||||
]))
|
||||
->trigger();
|
||||
]),
|
||||
));
|
||||
|
||||
if (!empty($certificate)) {
|
||||
$rule->setAttribute('renewAt', $certificate->getAttribute('renewDate', ''));
|
||||
|
||||
@@ -79,7 +79,7 @@ class Delete extends Action
|
||||
Device $deviceForFiles,
|
||||
DeleteEvent $queueForDeletes,
|
||||
Authorization $authorization,
|
||||
User $user
|
||||
User $user,
|
||||
) {
|
||||
$bucket = $authorization->skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
|
||||
|
||||
@@ -110,10 +110,17 @@ class Delete extends Action
|
||||
|
||||
$deviceDeleted = false;
|
||||
if ($file->getAttribute('chunksTotal') !== $file->getAttribute('chunksUploaded')) {
|
||||
$deviceDeleted = $deviceForFiles->abort(
|
||||
$file->getAttribute('path'),
|
||||
($file->getAttribute('metadata', [])['uploadId'] ?? '')
|
||||
);
|
||||
try {
|
||||
$deviceDeleted = $deviceForFiles->abort(
|
||||
$file->getAttribute('path'),
|
||||
($file->getAttribute('metadata', [])['uploadId'] ?? '')
|
||||
);
|
||||
} catch (\Exception $e) {
|
||||
// If the partial upload chunks are already gone from the device
|
||||
// (e.g. the upload never wrote anything to disk), treat it as deleted
|
||||
// so the pending file document can still be removed from the database.
|
||||
$deviceDeleted = true;
|
||||
}
|
||||
} else {
|
||||
$deviceDeleted = $deviceForFiles->delete($file->getAttribute('path'));
|
||||
}
|
||||
|
||||
@@ -15,7 +15,6 @@ use Utopia\Compression\Algorithms\GZIP;
|
||||
use Utopia\Compression\Algorithms\Zstd;
|
||||
use Utopia\Compression\Compression;
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\Console;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\DateTime;
|
||||
use Utopia\Database\Document;
|
||||
@@ -26,6 +25,7 @@ use Utopia\Http\Adapter\Swoole\Request;
|
||||
use Utopia\Image\Image;
|
||||
use Utopia\Platform\Action;
|
||||
use Utopia\Platform\Scope\HTTP;
|
||||
use Utopia\Span\Span;
|
||||
use Utopia\Storage\Device;
|
||||
use Utopia\System\System;
|
||||
use Utopia\Validator\HexColor;
|
||||
@@ -54,6 +54,7 @@ class Get extends Action
|
||||
->label('cache', true)
|
||||
->label('cache.resourceType', 'bucket/{request.bucketId}')
|
||||
->label('cache.resource', 'file/{request.fileId}')
|
||||
->label('cache.params', ['width', 'height', 'gravity', 'quality', 'borderWidth', 'borderColor', 'borderRadius', 'opacity', 'rotation', 'background', 'output'])
|
||||
->label('sdk', new Method(
|
||||
namespace: 'storage',
|
||||
group: 'files',
|
||||
@@ -268,7 +269,17 @@ class Get extends Action
|
||||
|
||||
$totalTime = \microtime(true) - $startTime;
|
||||
|
||||
Console::info("File preview rendered,project=" . $project->getId() . ",bucket=" . $bucketId . ",file=" . $file->getId() . ",uri=" . $request->getURI() . ",total=" . $totalTime . ",rendering=" . $renderingTime . ",decryption=" . $decryptionTime . ",decompression=" . $decompressionTime . ",download=" . $downloadTime);
|
||||
Span::add('storage.file.id', $file->getId());
|
||||
Span::add('storage.bucket.id', $bucketId);
|
||||
Span::add('storage.file.size_bytes', $file->getAttribute('sizeActual'));
|
||||
if (!empty($type)) {
|
||||
Span::add('storage.file.extension', $type);
|
||||
}
|
||||
Span::add('storage.timing.download_seconds', $downloadTime);
|
||||
Span::add('storage.timing.decryption_seconds', $decryptionTime);
|
||||
Span::add('storage.timing.decompression_seconds', $decompressionTime);
|
||||
Span::add('storage.timing.rendering_seconds', $renderingTime);
|
||||
Span::add('storage.timing.total_seconds', $totalTime);
|
||||
|
||||
$contentType = (\array_key_exists($output, $outputs)) ? $outputs[$output] : $outputs['jpg'];
|
||||
|
||||
@@ -277,7 +288,9 @@ class Get extends Action
|
||||
$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'),
|
||||
])));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -126,6 +126,7 @@ class Delete extends Action
|
||||
if ($team->getAttribute('userInternalId') === $membership->getAttribute('userInternalId')) {
|
||||
$membership = $dbForProject->findOne('memberships', [
|
||||
Query::equal('teamInternalId', [$team->getSequence()]),
|
||||
Query::equal('confirm', [true]),
|
||||
]);
|
||||
|
||||
if (!$membership->isEmpty()) {
|
||||
|
||||
+1
@@ -307,6 +307,7 @@ class Create extends Action
|
||||
];
|
||||
}
|
||||
|
||||
$output->setAttribute('type', $type);
|
||||
$output->setAttribute('variables', $variables);
|
||||
|
||||
$response->dynamic($output, $type === 'framework' ? Response::MODEL_DETECTION_FRAMEWORK : Response::MODEL_DETECTION_RUNTIME);
|
||||
|
||||
@@ -21,6 +21,7 @@ use Utopia\Platform\Scope\HTTP;
|
||||
use Utopia\Validator\ArrayList;
|
||||
use Utopia\Validator\Boolean;
|
||||
use Utopia\Validator\Multiple;
|
||||
use Utopia\Validator\Nullable;
|
||||
use Utopia\Validator\Text;
|
||||
use Utopia\Validator\URL;
|
||||
|
||||
@@ -65,9 +66,10 @@ class Create extends Action
|
||||
->param('name', null, new Text(128), 'Webhook name. Max length: 128 chars.')
|
||||
->param('events', null, new ArrayList(new Event(), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Events list. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' events are allowed.')
|
||||
->param('enabled', true, new Boolean(), 'Enable or disable a webhook.', true)
|
||||
->param('security', false, new Boolean(), 'Certificate verification, false for disabled or true for enabled.', true)
|
||||
->param('httpUser', '', new Text(256), 'Webhook HTTP user. Max length: 256 chars.', true)
|
||||
->param('httpPass', '', new Text(256), 'Webhook HTTP password. Max length: 256 chars.', true)
|
||||
->param('tls', false, new Boolean(), 'Certificate verification, false for disabled or true for enabled.', true)
|
||||
->param('authUsername', '', new Text(256), 'Webhook HTTP user. Max length: 256 chars.', true)
|
||||
->param('authPassword', '', new Text(256), 'Webhook HTTP password. Max length: 256 chars.', true)
|
||||
->param('secret', null, new Nullable(new Text(256, 8)), 'Webhook secret key. If not provided, a new key will be generated automatically. Key must be at least 8 characters long, and at max 256 characters.', optional: true)
|
||||
->inject('response')
|
||||
->inject('project')
|
||||
->inject('queueForEvents')
|
||||
@@ -85,9 +87,10 @@ class Create extends Action
|
||||
string $name,
|
||||
array $events,
|
||||
bool $enabled,
|
||||
bool $security,
|
||||
string $httpUser,
|
||||
string $httpPass,
|
||||
bool $tls,
|
||||
string $authUsername,
|
||||
string $authPassword,
|
||||
?string $secret,
|
||||
Response $response,
|
||||
Document $project,
|
||||
QueueEvent $queueForEvents,
|
||||
@@ -104,10 +107,10 @@ class Create extends Action
|
||||
'name' => $name,
|
||||
'events' => $events,
|
||||
'url' => $url,
|
||||
'security' => $security,
|
||||
'httpUser' => $httpUser,
|
||||
'httpPass' => $httpPass,
|
||||
'signatureKey' => \bin2hex(\random_bytes(64)),
|
||||
'security' => $tls,
|
||||
'httpUser' => $authUsername,
|
||||
'httpPass' => $authPassword,
|
||||
'signatureKey' => $secret ?? \bin2hex(\random_bytes(64)),
|
||||
'enabled' => $enabled,
|
||||
]);
|
||||
|
||||
|
||||
@@ -72,6 +72,8 @@ class Get extends Action
|
||||
throw new Exception(Exception::WEBHOOK_NOT_FOUND);
|
||||
}
|
||||
|
||||
$webhook->removeAttribute('signatureKey');
|
||||
|
||||
$response->dynamic($webhook, Response::MODEL_WEBHOOK);
|
||||
}
|
||||
}
|
||||
|
||||
+11
-7
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Modules\Webhooks\Http\Webhooks\Signature;
|
||||
namespace Appwrite\Platform\Modules\Webhooks\Http\Webhooks\Secret;
|
||||
|
||||
use Appwrite\Event\Event as QueueEvent;
|
||||
use Appwrite\Extend\Exception;
|
||||
@@ -15,6 +15,8 @@ use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Platform\Action;
|
||||
use Utopia\Platform\Scope\HTTP;
|
||||
use Utopia\Validator\Nullable;
|
||||
use Utopia\Validator\Text;
|
||||
|
||||
class Update extends Action
|
||||
{
|
||||
@@ -22,15 +24,15 @@ class Update extends Action
|
||||
|
||||
public static function getName()
|
||||
{
|
||||
return 'updateWebhookSignature';
|
||||
return 'updateWebhookSecret';
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->setHttpMethod(Action::HTTP_REQUEST_METHOD_PATCH)
|
||||
->setHttpPath('/v1/webhooks/:webhookId/signature')
|
||||
->setHttpPath('/v1/webhooks/:webhookId/secret')
|
||||
->httpAlias('/v1/projects/:projectId/webhooks/:webhookId/signature')
|
||||
->desc('Update webhook signature key')
|
||||
->desc('Update webhook secret key')
|
||||
->groups(['api', 'webhooks'])
|
||||
->label('scope', 'webhooks.write')
|
||||
->label('event', 'webhooks.[webhookId].update')
|
||||
@@ -39,9 +41,9 @@ class Update extends Action
|
||||
->label('sdk', new Method(
|
||||
namespace: 'webhooks',
|
||||
group: null,
|
||||
name: 'updateSignature',
|
||||
name: 'updateSecret',
|
||||
description: <<<EOT
|
||||
Update the webhook signature key. This endpoint can be used to regenerate the signature key used to sign and validate payload deliveries for a specific webhook.
|
||||
Update the webhook signing key. This endpoint can be used to regenerate the signing key used to sign and validate payload deliveries for a specific webhook.
|
||||
EOT,
|
||||
auth: [AuthType::ADMIN, AuthType::KEY],
|
||||
responses: [
|
||||
@@ -52,6 +54,7 @@ class Update extends Action
|
||||
]
|
||||
))
|
||||
->param('webhookId', '', fn (Database $dbForPlatform) => new UID($dbForPlatform->getAdapter()->getMaxUIDLength()), 'Webhook ID.', false, ['dbForPlatform'])
|
||||
->param('secret', null, new Nullable(new Text(256, 8)), 'Webhook secret key. If not provided, a new key will be generated automatically. Key must be at least 8 characters long, and at max 256 characters.', optional: true)
|
||||
->inject('response')
|
||||
->inject('project')
|
||||
->inject('queueForEvents')
|
||||
@@ -62,6 +65,7 @@ class Update extends Action
|
||||
|
||||
public function action(
|
||||
string $webhookId,
|
||||
?string $secret,
|
||||
Response $response,
|
||||
Document $project,
|
||||
QueueEvent $queueForEvents,
|
||||
@@ -78,7 +82,7 @@ class Update extends Action
|
||||
}
|
||||
|
||||
$updates = new Document([
|
||||
'signatureKey' => \bin2hex(\random_bytes(64)),
|
||||
'signatureKey' => $secret ?? \bin2hex(\random_bytes(64)),
|
||||
]);
|
||||
|
||||
$webhook = $authorization->skip(fn () => $dbForPlatform->updateDocument('webhooks', $webhook->getId(), $updates));
|
||||
@@ -63,9 +63,9 @@ class Update extends Action
|
||||
->param('url', '', fn () => new Multiple([new URL(['http', 'https']), new PublicDomain()], Multiple::TYPE_STRING), 'Webhook URL.')
|
||||
->param('events', null, new ArrayList(new Event(), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Events list. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' events are allowed.')
|
||||
->param('enabled', true, new Boolean(), 'Enable or disable a webhook.', true)
|
||||
->param('security', false, new Boolean(), 'Certificate verification, false for disabled or true for enabled.', true)
|
||||
->param('httpUser', '', new Text(256), 'Webhook HTTP user. Max length: 256 chars.', true)
|
||||
->param('httpPass', '', new Text(256), 'Webhook HTTP password. Max length: 256 chars.', true)
|
||||
->param('tls', false, new Boolean(), 'Certificate verification, false for disabled or true for enabled.', true)
|
||||
->param('authUsername', '', new Text(256), 'Webhook HTTP user. Max length: 256 chars.', true)
|
||||
->param('authPassword', '', new Text(256), 'Webhook HTTP password. Max length: 256 chars.', true)
|
||||
->inject('response')
|
||||
->inject('project')
|
||||
->inject('queueForEvents')
|
||||
@@ -80,9 +80,9 @@ class Update extends Action
|
||||
string $url,
|
||||
array $events,
|
||||
bool $enabled,
|
||||
bool $security,
|
||||
string $httpUser,
|
||||
string $httpPass,
|
||||
bool $tls,
|
||||
string $authUsername,
|
||||
string $authPassword,
|
||||
Response $response,
|
||||
Document $project,
|
||||
QueueEvent $queueForEvents,
|
||||
@@ -102,9 +102,9 @@ class Update extends Action
|
||||
'name' => $name,
|
||||
'events' => $events,
|
||||
'url' => $url,
|
||||
'security' => $security,
|
||||
'httpUser' => $httpUser,
|
||||
'httpPass' => $httpPass,
|
||||
'security' => $tls,
|
||||
'httpUser' => $authUsername,
|
||||
'httpPass' => $authPassword,
|
||||
'enabled' => $enabled,
|
||||
]);
|
||||
|
||||
@@ -118,6 +118,8 @@ class Update extends Action
|
||||
|
||||
$queueForEvents->setParam('webhookId', $webhook->getId());
|
||||
|
||||
$webhook->removeAttribute('signatureKey');
|
||||
|
||||
$response->dynamic($webhook, Response::MODEL_WEBHOOK);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,6 +78,15 @@ class XList extends Action
|
||||
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
|
||||
}
|
||||
|
||||
foreach ($queries as $query) {
|
||||
$attribute = $query->getAttribute();
|
||||
if ($attribute === 'authUsername') {
|
||||
$query->setAttribute('httpUser');
|
||||
} elseif ($attribute === 'tls') {
|
||||
$query->setAttribute('security');
|
||||
}
|
||||
}
|
||||
|
||||
$queries[] = Query::equal('projectInternalId', [$project->getSequence()]);
|
||||
|
||||
$cursor = Query::getCursorQueries($queries, false);
|
||||
@@ -111,6 +120,10 @@ class XList extends Action
|
||||
throw new Exception(Exception::DATABASE_QUERY_ORDER_NULL, "The order attribute '{$e->getAttribute()}' had a null value. Cursor pagination requires all documents order attribute values are non-null.");
|
||||
}
|
||||
|
||||
foreach ($webhooks as $webhook) {
|
||||
$webhook->removeAttribute('signatureKey');
|
||||
}
|
||||
|
||||
$response->dynamic(new Document([
|
||||
'webhooks' => $webhooks,
|
||||
'total' => $total,
|
||||
|
||||
@@ -6,7 +6,7 @@ use Appwrite\Platform\Modules\Webhooks\Http\Init;
|
||||
use Appwrite\Platform\Modules\Webhooks\Http\Webhooks\Create as CreateWebhook;
|
||||
use Appwrite\Platform\Modules\Webhooks\Http\Webhooks\Delete as DeleteWebhook;
|
||||
use Appwrite\Platform\Modules\Webhooks\Http\Webhooks\Get as GetWebhook;
|
||||
use Appwrite\Platform\Modules\Webhooks\Http\Webhooks\Signature\Update as UpdateWebhookSignature;
|
||||
use Appwrite\Platform\Modules\Webhooks\Http\Webhooks\Secret\Update as UpdateWebhookSecret;
|
||||
use Appwrite\Platform\Modules\Webhooks\Http\Webhooks\Update as UpdateWebhook;
|
||||
use Appwrite\Platform\Modules\Webhooks\Http\Webhooks\XList as ListWebhooks;
|
||||
use Utopia\Platform\Service;
|
||||
@@ -26,6 +26,6 @@ class Http extends Service
|
||||
$this->addAction(GetWebhook::getName(), new GetWebhook());
|
||||
$this->addAction(DeleteWebhook::getName(), new DeleteWebhook());
|
||||
$this->addAction(UpdateWebhook::getName(), new UpdateWebhook());
|
||||
$this->addAction(UpdateWebhookSignature::getName(), new UpdateWebhookSignature());
|
||||
$this->addAction(UpdateWebhookSecret::getName(), new UpdateWebhookSecret());
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user