mirror of
https://github.com/appwrite/appwrite.git
synced 2026-05-26 13:51:13 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e97dfb041a | ||
|
|
1cef07a2d4 | ||
|
|
93e96a044d | ||
|
|
040ce6e335 | ||
|
|
4bdcdb6f96 | ||
|
|
bbd24e5029 | ||
|
|
8bef181ad8 | ||
|
|
ff92fd229b | ||
|
|
5ef5ead98f | ||
|
|
668b61e620 | ||
|
|
ea28f12fb4 | ||
|
|
45298f8e23 | ||
|
|
0e87d0b483 | ||
|
|
fde30794ec | ||
|
|
1f5ee53f3a | ||
|
|
3721e6b950 | ||
|
|
103e219675 | ||
|
|
994a4e0fc7 | ||
|
|
5fba2889cb | ||
|
|
9b4f8d7ac0 | ||
|
|
9056de0103 | ||
|
|
cf1bb1a1cc | ||
|
|
330b11a64b | ||
|
|
11acbc6db4 | ||
|
|
d8dbe15cb3 | ||
|
|
6ae113a969 | ||
|
|
0b2a9240b3 | ||
|
|
d949482094 | ||
|
|
c95901b879 | ||
|
|
0fb2e208ab | ||
|
|
76aed33a5c | ||
|
|
c26ff1849a | ||
|
|
702a8a83a0 | ||
|
|
5be05da5ca | ||
|
|
fe5e5b8891 | ||
|
|
a902c25363 | ||
|
|
04a6eaf5d4 | ||
|
|
f0fb7bf877 | ||
|
|
74fbbea2b3 | ||
|
|
4957f568cd | ||
|
|
64c9d8d85f | ||
|
|
7b5cb379c4 | ||
|
|
fd625fca7b | ||
|
|
e45e5a09f4 | ||
|
|
e3dc30ad93 | ||
|
|
0406d9e04d | ||
|
|
fbfde6cc77 | ||
|
|
c6f91e18c4 | ||
|
|
6db1d2e5c0 | ||
|
|
817172c460 | ||
|
|
cd445ceccf | ||
|
|
87a32f65ee | ||
|
|
698fde247f | ||
|
|
0e939ea9d7 | ||
|
|
a5ddc465e6 | ||
|
|
76a41d70b0 | ||
|
|
43777ee6d9 | ||
|
|
6ee2196fae | ||
|
|
5313460c7c | ||
|
|
c94ae409e8 | ||
|
|
4844a4bf99 | ||
|
|
687698001f | ||
|
|
acc0b2c184 | ||
|
|
10e4341db2 | ||
|
|
8a35e87cfd | ||
|
|
9295869279 | ||
|
|
5472bff265 | ||
|
|
27de465032 | ||
|
|
f2a59de804 | ||
|
|
4563706b76 | ||
|
|
be9bb5e34d | ||
|
|
0a582daff0 | ||
|
|
003647abd7 | ||
|
|
9100e06bbe | ||
|
|
fc83b4d986 | ||
|
|
4954655e8b | ||
|
|
319483d41d | ||
|
|
6da8c1cb12 | ||
|
|
5f6b1dda1a | ||
|
|
8df7a628c8 | ||
|
|
18ec96f124 | ||
|
|
d303d6f807 | ||
|
|
34075322d7 | ||
|
|
74b84ba38c | ||
|
|
07973dee2d | ||
|
|
5cfaa0807d | ||
|
|
e181954dd1 | ||
|
|
4b05a6cf8f | ||
|
|
29bbc7299a | ||
|
|
f36105a7af | ||
|
|
7648978cfa |
@@ -406,7 +406,7 @@ jobs:
|
||||
|
||||
e2e_service:
|
||||
name: Tests / E2E / ${{ matrix.database }} (${{ matrix.mode }}) / ${{ matrix.service }}
|
||||
runs-on: ${{ matrix.runner || 'ubuntu-latest' }}
|
||||
runs-on: ${{ matrix.runner || format('runs-on={0}/runner=4cpu-linux-x64/volume=120g/spot=false', github.run_id) }}
|
||||
needs: [build, matrix]
|
||||
permissions:
|
||||
contents: read
|
||||
@@ -445,19 +445,10 @@ jobs:
|
||||
]
|
||||
include:
|
||||
- service: Databases
|
||||
runner: runs-on=${{ github.run_id }}/runner=4cpu-linux-x64/family=c7/volume=120g
|
||||
runner: runs-on=${{ github.run_id }}/runner=8cpu-linux-x64/volume=120g/spot=false
|
||||
paratest_processes: 3
|
||||
timeout_minutes: 30
|
||||
- service: Sites
|
||||
runner: runs-on=${{ github.run_id }}/runner=4cpu-linux-x64/family=c7/volume=120g
|
||||
- service: Functions
|
||||
runner: runs-on=${{ github.run_id }}/runner=4cpu-linux-x64/family=c7/volume=120g
|
||||
- service: Avatars
|
||||
runner: runs-on=${{ github.run_id }}/runner=4cpu-linux-x64/family=c7/volume=120g
|
||||
- service: Realtime
|
||||
runner: runs-on=${{ github.run_id }}/runner=4cpu-linux-x64/family=c7/volume=120g
|
||||
- service: TablesDB
|
||||
runner: runs-on=${{ github.run_id }}/runner=4cpu-linux-x64/family=c7/volume=120g
|
||||
paratest_processes: 3
|
||||
timeout_minutes: 30
|
||||
- service: Migrations
|
||||
|
||||
@@ -320,6 +320,26 @@ return [
|
||||
'repoBranch' => 'main',
|
||||
'changelog' => \realpath(__DIR__ . '/../../docs/sdks/claude-plugin/CHANGELOG.md'),
|
||||
],
|
||||
[
|
||||
'key' => 'codex-plugin',
|
||||
'name' => 'CodexPlugin',
|
||||
'version' => '0.1.1',
|
||||
'url' => 'https://github.com/appwrite/codex-plugin.git',
|
||||
'enabled' => true,
|
||||
'beta' => false,
|
||||
'dev' => false,
|
||||
'hidden' => false,
|
||||
'spec' => 'static',
|
||||
'family' => APP_SDK_PLATFORM_STATIC,
|
||||
'prism' => 'codex-plugin',
|
||||
'source' => \realpath(__DIR__ . '/../sdks/static-codex-plugin'),
|
||||
'gitUrl' => 'git@github.com:appwrite/codex-plugin.git',
|
||||
'gitRepoName' => 'codex-plugin',
|
||||
'gitUserName' => 'appwrite',
|
||||
'gitBranch' => 'dev',
|
||||
'repoBranch' => 'main',
|
||||
'changelog' => \realpath(__DIR__ . '/../../docs/sdks/codex-plugin/CHANGELOG.md'),
|
||||
],
|
||||
],
|
||||
],
|
||||
|
||||
|
||||
@@ -1336,6 +1336,15 @@ return [
|
||||
'category' => 'Migrations',
|
||||
'description' => '',
|
||||
'variables' => [
|
||||
[
|
||||
'name' => '_APP_MIGRATION_HOST',
|
||||
'description' => 'Internal hostname the migrations worker uses to reach this instance\'s API (for migrations and CSV/JSON imports & exports). Defaults to \'appwrite\', the API service name in the standard Docker Compose setup. Only change this for non-standard deployments.',
|
||||
'introduction' => '1.9.0',
|
||||
'default' => 'appwrite',
|
||||
'required' => false,
|
||||
'question' => '',
|
||||
'filter' => ''
|
||||
],
|
||||
[
|
||||
'name' => '_APP_MIGRATIONS_FIREBASE_CLIENT_ID',
|
||||
'description' => 'Google OAuth client ID. You can find it in your GCP application settings.',
|
||||
|
||||
+126
-125
@@ -13,8 +13,10 @@ use Appwrite\Bus\Events\SessionCreated;
|
||||
use Appwrite\Detector\Detector;
|
||||
use Appwrite\Event\Delete;
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Event\Mail;
|
||||
use Appwrite\Event\Messaging;
|
||||
use Appwrite\Event\Message\Mail as MailMessage;
|
||||
use Appwrite\Event\Message\Messaging as MessagingMessage;
|
||||
use Appwrite\Event\Publisher\Mail as MailPublisher;
|
||||
use Appwrite\Event\Publisher\Messaging as MessagingPublisher;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Hooks\Hooks;
|
||||
use Appwrite\Network\Validator\Redirect;
|
||||
@@ -2113,12 +2115,12 @@ Http::post('/v1/account/tokens/magic-url')
|
||||
->inject('dbForProject')
|
||||
->inject('locale')
|
||||
->inject('queueForEvents')
|
||||
->inject('queueForMails')
|
||||
->inject('publisherForMails')
|
||||
->inject('plan')
|
||||
->inject('proofForPassword')
|
||||
->inject('platform')
|
||||
->inject('authorization')
|
||||
->action(function (string $userId, string $email, string $url, bool $phrase, Request $request, Response $response, Document $user, Document $project, Database $dbForProject, Locale $locale, Event $queueForEvents, Mail $queueForMails, array $plan, ProofsPassword $proofForPassword, array $platform, Authorization $authorization) {
|
||||
->action(function (string $userId, string $email, string $url, bool $phrase, Request $request, Response $response, Document $user, Document $project, Database $dbForProject, Locale $locale, Event $queueForEvents, MailPublisher $publisherForMails, array $plan, ProofsPassword $proofForPassword, array $platform, Authorization $authorization) {
|
||||
if (empty(System::getEnv('_APP_SMTP_HOST'))) {
|
||||
throw new Exception(Exception::GENERAL_SMTP_DISABLED, 'SMTP disabled');
|
||||
}
|
||||
@@ -2304,6 +2306,7 @@ Http::post('/v1/account/tokens/magic-url')
|
||||
$senderName = System::getEnv('_APP_SYSTEM_EMAIL_NAME', APP_NAME . ' Server');
|
||||
$replyToEmail = '';
|
||||
$replyToName = '';
|
||||
$smtpConfig = [];
|
||||
|
||||
if ($smtpEnabled) {
|
||||
if (!empty($smtp['senderEmail'])) {
|
||||
@@ -2321,13 +2324,6 @@ Http::post('/v1/account/tokens/magic-url')
|
||||
$replyToName = $smtp['replyToName'];
|
||||
}
|
||||
|
||||
$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'];
|
||||
@@ -2348,11 +2344,17 @@ Http::post('/v1/account/tokens/magic-url')
|
||||
$subject = $customTemplate['subject'] ?? $subject;
|
||||
}
|
||||
|
||||
$queueForMails
|
||||
->setSmtpReplyToEmail($replyToEmail)
|
||||
->setSmtpReplyToName($replyToName)
|
||||
->setSmtpSenderEmail($senderEmail)
|
||||
->setSmtpSenderName($senderName);
|
||||
$smtpConfig = [
|
||||
'host' => $smtp['host'] ?? '',
|
||||
'port' => $smtp['port'] ?? '',
|
||||
'username' => $smtp['username'] ?? '',
|
||||
'password' => $smtp['password'] ?? '',
|
||||
'secure' => $smtp['secure'] ?? '',
|
||||
'replyToEmail' => $replyToEmail,
|
||||
'replyToName' => $replyToName,
|
||||
'senderEmail' => $senderEmail,
|
||||
'senderName' => $senderName,
|
||||
];
|
||||
}
|
||||
|
||||
$projectName = $project->getAttribute('name');
|
||||
@@ -2374,18 +2376,17 @@ Http::post('/v1/account/tokens/magic-url')
|
||||
'team' => '',
|
||||
];
|
||||
|
||||
$queueForMails
|
||||
->setSubject($subject)
|
||||
->setPreview($preview)
|
||||
->setBody($body)
|
||||
->appendVariables($emailVariables)
|
||||
->setRecipient($email);
|
||||
|
||||
if ($project->getId() === 'console') {
|
||||
$queueForMails->setSenderName($platform['emailSenderName']);
|
||||
}
|
||||
|
||||
$queueForMails->trigger();
|
||||
$publisherForMails->enqueue(new MailMessage(
|
||||
project: $project,
|
||||
recipient: $email,
|
||||
subject: $subject,
|
||||
body: $body,
|
||||
preview: $preview,
|
||||
smtp: $smtpConfig,
|
||||
variables: $emailVariables,
|
||||
customMailOptions: $project->getId() === 'console' ? ['senderName' => $platform['emailSenderName']] : [],
|
||||
platform: $platform,
|
||||
));
|
||||
|
||||
$token->setAttribute('secret', $tokenSecret);
|
||||
|
||||
@@ -2436,12 +2437,12 @@ Http::post('/v1/account/tokens/email')
|
||||
->inject('dbForProject')
|
||||
->inject('locale')
|
||||
->inject('queueForEvents')
|
||||
->inject('queueForMails')
|
||||
->inject('publisherForMails')
|
||||
->inject('plan')
|
||||
->inject('proofForPassword')
|
||||
->inject('proofForCode')
|
||||
->inject('authorization')
|
||||
->action(function (string $userId, string $email, bool $phrase, Request $request, Response $response, User $user, Document $project, array $platform, Database $dbForProject, Locale $locale, Event $queueForEvents, Mail $queueForMails, array $plan, ProofsPassword $proofForPassword, ProofsCode $proofForCode, Authorization $authorization) {
|
||||
->action(function (string $userId, string $email, bool $phrase, Request $request, Response $response, User $user, Document $project, array $platform, Database $dbForProject, Locale $locale, Event $queueForEvents, MailPublisher $publisherForMails, array $plan, ProofsPassword $proofForPassword, ProofsCode $proofForCode, Authorization $authorization) {
|
||||
if (empty(System::getEnv('_APP_SMTP_HOST'))) {
|
||||
throw new Exception(Exception::GENERAL_SMTP_DISABLED, 'SMTP disabled');
|
||||
}
|
||||
@@ -2633,6 +2634,7 @@ Http::post('/v1/account/tokens/email')
|
||||
$senderName = System::getEnv('_APP_SYSTEM_EMAIL_NAME', APP_NAME . ' Server');
|
||||
$replyToEmail = '';
|
||||
$replyToName = '';
|
||||
$smtpConfig = [];
|
||||
|
||||
if ($smtpEnabled) {
|
||||
if (!empty($smtp['senderEmail'])) {
|
||||
@@ -2650,13 +2652,6 @@ Http::post('/v1/account/tokens/email')
|
||||
$replyToName = $smtp['replyToName'];
|
||||
}
|
||||
|
||||
$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'];
|
||||
@@ -2677,11 +2672,17 @@ Http::post('/v1/account/tokens/email')
|
||||
$subject = $customTemplate['subject'] ?? $subject;
|
||||
}
|
||||
|
||||
$queueForMails
|
||||
->setSmtpReplyToEmail($replyToEmail)
|
||||
->setSmtpReplyToName($replyToName)
|
||||
->setSmtpSenderEmail($senderEmail)
|
||||
->setSmtpSenderName($senderName);
|
||||
$smtpConfig = [
|
||||
'host' => $smtp['host'] ?? '',
|
||||
'port' => $smtp['port'] ?? '',
|
||||
'username' => $smtp['username'] ?? '',
|
||||
'password' => $smtp['password'] ?? '',
|
||||
'secure' => $smtp['secure'] ?? '',
|
||||
'replyToEmail' => $replyToEmail,
|
||||
'replyToName' => $replyToName,
|
||||
'senderEmail' => $senderEmail,
|
||||
'senderName' => $senderName,
|
||||
];
|
||||
}
|
||||
|
||||
$projectName = $project->getAttribute('name');
|
||||
@@ -2717,20 +2718,18 @@ Http::post('/v1/account/tokens/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();
|
||||
$publisherForMails->enqueue(new MailMessage(
|
||||
project: $project,
|
||||
recipient: $email,
|
||||
subject: $subject,
|
||||
bodyTemplate: $bodyTemplate,
|
||||
body: $body,
|
||||
preview: $preview,
|
||||
smtp: $smtpConfig,
|
||||
variables: $emailVariables,
|
||||
customMailOptions: $smtpBaseTemplate === APP_BRANDED_EMAIL_BASE_TEMPLATE ? ['senderName' => $platform['emailSenderName']] : [],
|
||||
platform: $platform,
|
||||
));
|
||||
|
||||
$token->setAttribute('secret', $tokenSecret);
|
||||
|
||||
@@ -2880,7 +2879,7 @@ Http::post('/v1/account/tokens/phone')
|
||||
->inject('platform')
|
||||
->inject('dbForProject')
|
||||
->inject('queueForEvents')
|
||||
->inject('queueForMessaging')
|
||||
->inject('publisherForMessaging')
|
||||
->inject('locale')
|
||||
->inject('timelimit')
|
||||
->inject('usage')
|
||||
@@ -2888,7 +2887,7 @@ Http::post('/v1/account/tokens/phone')
|
||||
->inject('store')
|
||||
->inject('proofForCode')
|
||||
->inject('authorization')
|
||||
->action(function (string $userId, string $phone, Request $request, Response $response, User $user, Document $project, array $platform, Database $dbForProject, Event $queueForEvents, Messaging $queueForMessaging, Locale $locale, callable $timelimit, Context $usage, array $plan, Store $store, ProofsCode $proofForCode, Authorization $authorization) {
|
||||
->action(function (string $userId, string $phone, Request $request, Response $response, User $user, Document $project, array $platform, Database $dbForProject, Event $queueForEvents, MessagingPublisher $publisherForMessaging, Locale $locale, callable $timelimit, Context $usage, array $plan, Store $store, ProofsCode $proofForCode, Authorization $authorization) {
|
||||
if (empty(System::getEnv('_APP_SMS_PROVIDER'))) {
|
||||
throw new Exception(Exception::GENERAL_PHONE_DISABLED, 'Phone provider not configured');
|
||||
}
|
||||
@@ -3021,11 +3020,13 @@ Http::post('/v1/account/tokens/phone')
|
||||
],
|
||||
]);
|
||||
|
||||
$queueForMessaging
|
||||
->setType(MESSAGE_SEND_TYPE_INTERNAL)
|
||||
->setMessage($messageDoc)
|
||||
->setRecipients([$phone])
|
||||
->setProviderType(MESSAGE_TYPE_SMS);
|
||||
$publisherForMessaging->enqueue(new MessagingMessage(
|
||||
type: MESSAGE_SEND_TYPE_INTERNAL,
|
||||
project: $project,
|
||||
message: $messageDoc,
|
||||
recipients: [$phone],
|
||||
providerType: MESSAGE_TYPE_SMS,
|
||||
));
|
||||
|
||||
$helper = PhoneNumberUtil::getInstance();
|
||||
try {
|
||||
@@ -3681,11 +3682,11 @@ Http::post('/v1/account/recovery')
|
||||
->inject('project')
|
||||
->inject('platform')
|
||||
->inject('locale')
|
||||
->inject('queueForMails')
|
||||
->inject('publisherForMails')
|
||||
->inject('queueForEvents')
|
||||
->inject('proofForToken')
|
||||
->inject('authorization')
|
||||
->action(function (string $email, string $url, Request $request, Response $response, User $user, Database $dbForProject, Document $project, array $platform, Locale $locale, Mail $queueForMails, Event $queueForEvents, ProofsToken $proofForToken, Authorization $authorization) {
|
||||
->action(function (string $email, string $url, Request $request, Response $response, User $user, Database $dbForProject, Document $project, array $platform, Locale $locale, MailPublisher $publisherForMails, Event $queueForEvents, ProofsToken $proofForToken, Authorization $authorization) {
|
||||
|
||||
if (empty(System::getEnv('_APP_SMTP_HOST'))) {
|
||||
throw new Exception(Exception::GENERAL_SMTP_DISABLED, 'SMTP Disabled');
|
||||
@@ -3768,6 +3769,7 @@ Http::post('/v1/account/recovery')
|
||||
$senderName = System::getEnv('_APP_SYSTEM_EMAIL_NAME', APP_NAME . ' Server');
|
||||
$replyToEmail = '';
|
||||
$replyToName = '';
|
||||
$smtpConfig = [];
|
||||
|
||||
if ($smtpEnabled) {
|
||||
if (!empty($smtp['senderEmail'])) {
|
||||
@@ -3785,13 +3787,6 @@ Http::post('/v1/account/recovery')
|
||||
$replyToName = $smtp['replyToName'];
|
||||
}
|
||||
|
||||
$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'];
|
||||
@@ -3812,11 +3807,17 @@ Http::post('/v1/account/recovery')
|
||||
$subject = $customTemplate['subject'] ?? $subject;
|
||||
}
|
||||
|
||||
$queueForMails
|
||||
->setSmtpReplyToEmail($replyToEmail)
|
||||
->setSmtpReplyToName($replyToName)
|
||||
->setSmtpSenderEmail($senderEmail)
|
||||
->setSmtpSenderName($senderName);
|
||||
$smtpConfig = [
|
||||
'host' => $smtp['host'] ?? '',
|
||||
'port' => $smtp['port'] ?? '',
|
||||
'username' => $smtp['username'] ?? '',
|
||||
'password' => $smtp['password'] ?? '',
|
||||
'secure' => $smtp['secure'] ?? '',
|
||||
'replyToEmail' => $replyToEmail,
|
||||
'replyToName' => $replyToName,
|
||||
'senderEmail' => $senderEmail,
|
||||
'senderName' => $senderName,
|
||||
];
|
||||
}
|
||||
|
||||
$emailVariables = [
|
||||
@@ -3829,19 +3830,18 @@ Http::post('/v1/account/recovery')
|
||||
'team' => ''
|
||||
];
|
||||
|
||||
$queueForMails
|
||||
->setRecipient($profile->getAttribute('email', ''))
|
||||
->setName($profile->getAttribute('name', ''))
|
||||
->setBody($body)
|
||||
->appendVariables($emailVariables)
|
||||
->setSubject($subject)
|
||||
->setPreview($preview);
|
||||
|
||||
if ($project->getId() === 'console') {
|
||||
$queueForMails->setSenderName($platform['emailSenderName']);
|
||||
}
|
||||
|
||||
$queueForMails->trigger();
|
||||
$publisherForMails->enqueue(new MailMessage(
|
||||
project: $project,
|
||||
recipient: $profile->getAttribute('email', ''),
|
||||
name: $profile->getAttribute('name', ''),
|
||||
subject: $subject,
|
||||
body: $body,
|
||||
preview: $preview,
|
||||
smtp: $smtpConfig,
|
||||
variables: $emailVariables,
|
||||
customMailOptions: $project->getId() === 'console' ? ['senderName' => $platform['emailSenderName']] : [],
|
||||
platform: $platform,
|
||||
));
|
||||
|
||||
$recovery->setAttribute('secret', $secret);
|
||||
|
||||
@@ -4009,10 +4009,10 @@ Http::post('/v1/account/verifications/email')
|
||||
->inject('dbForProject')
|
||||
->inject('locale')
|
||||
->inject('queueForEvents')
|
||||
->inject('queueForMails')
|
||||
->inject('publisherForMails')
|
||||
->inject('proofForToken')
|
||||
->inject('authorization')
|
||||
->action(function (string $url, Request $request, Response $response, Document $project, array $platform, User $user, Database $dbForProject, Locale $locale, Event $queueForEvents, Mail $queueForMails, ProofsToken $proofForToken, Authorization $authorization) {
|
||||
->action(function (string $url, Request $request, Response $response, Document $project, array $platform, User $user, Database $dbForProject, Locale $locale, Event $queueForEvents, MailPublisher $publisherForMails, ProofsToken $proofForToken, Authorization $authorization) {
|
||||
|
||||
if (empty(System::getEnv('_APP_SMTP_HOST'))) {
|
||||
throw new Exception(Exception::GENERAL_SMTP_DISABLED, 'SMTP Disabled');
|
||||
@@ -4099,6 +4099,7 @@ Http::post('/v1/account/verifications/email')
|
||||
$senderName = System::getEnv('_APP_SYSTEM_EMAIL_NAME', APP_NAME . ' Server');
|
||||
$replyToEmail = '';
|
||||
$replyToName = '';
|
||||
$smtpConfig = [];
|
||||
|
||||
if ($smtpEnabled) {
|
||||
if (!empty($smtp['senderEmail'])) {
|
||||
@@ -4116,13 +4117,6 @@ Http::post('/v1/account/verifications/email')
|
||||
$replyToName = $smtp['replyToName'];
|
||||
}
|
||||
|
||||
$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'];
|
||||
@@ -4143,11 +4137,17 @@ Http::post('/v1/account/verifications/email')
|
||||
$subject = $customTemplate['subject'] ?? $subject;
|
||||
}
|
||||
|
||||
$queueForMails
|
||||
->setSmtpReplyToEmail($replyToEmail)
|
||||
->setSmtpReplyToName($replyToName)
|
||||
->setSmtpSenderEmail($senderEmail)
|
||||
->setSmtpSenderName($senderName);
|
||||
$smtpConfig = [
|
||||
'host' => $smtp['host'] ?? '',
|
||||
'port' => $smtp['port'] ?? '',
|
||||
'username' => $smtp['username'] ?? '',
|
||||
'password' => $smtp['password'] ?? '',
|
||||
'secure' => $smtp['secure'] ?? '',
|
||||
'replyToEmail' => $replyToEmail,
|
||||
'replyToName' => $replyToName,
|
||||
'senderEmail' => $senderEmail,
|
||||
'senderName' => $senderName,
|
||||
];
|
||||
}
|
||||
|
||||
$emailVariables = [
|
||||
@@ -4174,20 +4174,19 @@ Http::post('/v1/account/verifications/email')
|
||||
]);
|
||||
}
|
||||
|
||||
$queueForMails
|
||||
->setSubject($subject)
|
||||
->setPreview($preview)
|
||||
->setBody($body)
|
||||
->setBodyTemplate($bodyTemplate)
|
||||
->appendVariables($emailVariables)
|
||||
->setRecipient($user->getAttribute('email'))
|
||||
->setName($user->getAttribute('name') ?? '');
|
||||
|
||||
if ($project->getId() === 'console') {
|
||||
$queueForMails->setSenderName($platform['emailSenderName']);
|
||||
}
|
||||
|
||||
$queueForMails->trigger();
|
||||
$publisherForMails->enqueue(new MailMessage(
|
||||
project: $project,
|
||||
recipient: $user->getAttribute('email'),
|
||||
name: $user->getAttribute('name') ?? '',
|
||||
subject: $subject,
|
||||
bodyTemplate: $bodyTemplate,
|
||||
body: $body,
|
||||
preview: $preview,
|
||||
smtp: $smtpConfig,
|
||||
variables: $emailVariables,
|
||||
customMailOptions: $project->getId() === 'console' ? ['senderName' => $platform['emailSenderName']] : [],
|
||||
platform: $platform,
|
||||
));
|
||||
|
||||
$verification->setAttribute('secret', $verificationSecret);
|
||||
|
||||
@@ -4321,7 +4320,7 @@ Http::post('/v1/account/verifications/phone')
|
||||
->inject('user')
|
||||
->inject('dbForProject')
|
||||
->inject('queueForEvents')
|
||||
->inject('queueForMessaging')
|
||||
->inject('publisherForMessaging')
|
||||
->inject('project')
|
||||
->inject('locale')
|
||||
->inject('timelimit')
|
||||
@@ -4329,7 +4328,7 @@ Http::post('/v1/account/verifications/phone')
|
||||
->inject('plan')
|
||||
->inject('proofForCode')
|
||||
->inject('authorization')
|
||||
->action(function (Request $request, Response $response, User $user, Database $dbForProject, Event $queueForEvents, Messaging $queueForMessaging, Document $project, Locale $locale, callable $timelimit, Context $usage, array $plan, ProofsCode $proofForCode, Authorization $authorization) {
|
||||
->action(function (Request $request, Response $response, User $user, Database $dbForProject, Event $queueForEvents, MessagingPublisher $publisherForMessaging, Document $project, Locale $locale, callable $timelimit, Context $usage, array $plan, ProofsCode $proofForCode, Authorization $authorization) {
|
||||
if (empty(System::getEnv('_APP_SMS_PROVIDER'))) {
|
||||
throw new Exception(Exception::GENERAL_PHONE_DISABLED, 'Phone provider not configured');
|
||||
}
|
||||
@@ -4398,11 +4397,13 @@ Http::post('/v1/account/verifications/phone')
|
||||
],
|
||||
]);
|
||||
|
||||
$queueForMessaging
|
||||
->setType(MESSAGE_SEND_TYPE_INTERNAL)
|
||||
->setMessage($messageDoc)
|
||||
->setRecipients([$user->getAttribute('phone')])
|
||||
->setProviderType(MESSAGE_TYPE_SMS);
|
||||
$publisherForMessaging->enqueue(new MessagingMessage(
|
||||
type: MESSAGE_SEND_TYPE_INTERNAL,
|
||||
project: $project,
|
||||
message: $messageDoc,
|
||||
recipients: [$user->getAttribute('phone')],
|
||||
providerType: MESSAGE_TYPE_SMS,
|
||||
));
|
||||
|
||||
$helper = PhoneNumberUtil::getInstance();
|
||||
try {
|
||||
|
||||
@@ -5,7 +5,8 @@ use Appwrite\Auth\Validator\Phone;
|
||||
use Appwrite\Detector\Detector;
|
||||
use Appwrite\Event\Delete;
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Event\Messaging;
|
||||
use Appwrite\Event\Message\Messaging as MessagingMessage;
|
||||
use Appwrite\Event\Publisher\Messaging as MessagingPublisher;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Messaging\Status as MessageStatus;
|
||||
use Appwrite\Permission;
|
||||
@@ -3187,9 +3188,9 @@ Http::post('/v1/messaging/messages/email')
|
||||
->inject('dbForProject')
|
||||
->inject('dbForPlatform')
|
||||
->inject('project')
|
||||
->inject('queueForMessaging')
|
||||
->inject('publisherForMessaging')
|
||||
->inject('response')
|
||||
->action(function (string $messageId, string $subject, string $content, ?array $topics, ?array $users, ?array $targets, ?array $cc, ?array $bcc, ?array $attachments, bool $draft, bool $html, ?string $scheduledAt, Event $queueForEvents, Database $dbForProject, Database $dbForPlatform, Document $project, Messaging $queueForMessaging, Response $response) {
|
||||
->action(function (string $messageId, string $subject, string $content, ?array $topics, ?array $users, ?array $targets, ?array $cc, ?array $bcc, ?array $attachments, bool $draft, bool $html, ?string $scheduledAt, Event $queueForEvents, Database $dbForProject, Database $dbForPlatform, Document $project, MessagingPublisher $publisherForMessaging, Response $response) {
|
||||
$messageId = $messageId == 'unique()'
|
||||
? ID::unique()
|
||||
: $messageId;
|
||||
@@ -3274,9 +3275,11 @@ Http::post('/v1/messaging/messages/email')
|
||||
|
||||
switch ($status) {
|
||||
case MessageStatus::PROCESSING:
|
||||
$queueForMessaging
|
||||
->setType(MESSAGE_SEND_TYPE_EXTERNAL)
|
||||
->setMessageId($message->getId());
|
||||
$publisherForMessaging->enqueue(new MessagingMessage(
|
||||
type: MESSAGE_SEND_TYPE_EXTERNAL,
|
||||
project: $project,
|
||||
messageId: $message->getId(),
|
||||
));
|
||||
break;
|
||||
case MessageStatus::SCHEDULED:
|
||||
$schedule = $dbForPlatform->createDocument('schedules', new Document([
|
||||
@@ -3362,9 +3365,9 @@ Http::post('/v1/messaging/messages/sms')
|
||||
->inject('dbForProject')
|
||||
->inject('dbForPlatform')
|
||||
->inject('project')
|
||||
->inject('queueForMessaging')
|
||||
->inject('publisherForMessaging')
|
||||
->inject('response')
|
||||
->action(function (string $messageId, string $content, ?array $topics, ?array $users, ?array $targets, bool $draft, ?string $scheduledAt, Event $queueForEvents, Database $dbForProject, Database $dbForPlatform, Document $project, Messaging $queueForMessaging, Response $response) {
|
||||
->action(function (string $messageId, string $content, ?array $topics, ?array $users, ?array $targets, bool $draft, ?string $scheduledAt, Event $queueForEvents, Database $dbForProject, Database $dbForPlatform, Document $project, MessagingPublisher $publisherForMessaging, Response $response) {
|
||||
$messageId = $messageId == 'unique()'
|
||||
? ID::unique()
|
||||
: $messageId;
|
||||
@@ -3418,9 +3421,11 @@ Http::post('/v1/messaging/messages/sms')
|
||||
|
||||
switch ($status) {
|
||||
case MessageStatus::PROCESSING:
|
||||
$queueForMessaging
|
||||
->setType(MESSAGE_SEND_TYPE_EXTERNAL)
|
||||
->setMessageId($message->getId());
|
||||
$publisherForMessaging->enqueue(new MessagingMessage(
|
||||
type: MESSAGE_SEND_TYPE_EXTERNAL,
|
||||
project: $project,
|
||||
messageId: $message->getId(),
|
||||
));
|
||||
break;
|
||||
case MessageStatus::SCHEDULED:
|
||||
$schedule = $dbForPlatform->createDocument('schedules', new Document([
|
||||
@@ -3498,10 +3503,10 @@ Http::post('/v1/messaging/messages/push')
|
||||
->inject('dbForProject')
|
||||
->inject('dbForPlatform')
|
||||
->inject('project')
|
||||
->inject('queueForMessaging')
|
||||
->inject('publisherForMessaging')
|
||||
->inject('response')
|
||||
->inject('platform')
|
||||
->action(function (string $messageId, string $title, string $body, ?array $topics, ?array $users, ?array $targets, ?array $data, string $action, string $image, string $icon, string $sound, string $color, string $tag, int $badge, bool $draft, ?string $scheduledAt, bool $contentAvailable, bool $critical, string $priority, Event $queueForEvents, Database $dbForProject, Database $dbForPlatform, Document $project, Messaging $queueForMessaging, Response $response, array $platform) {
|
||||
->action(function (string $messageId, string $title, string $body, ?array $topics, ?array $users, ?array $targets, ?array $data, string $action, string $image, string $icon, string $sound, string $color, string $tag, int $badge, bool $draft, ?string $scheduledAt, bool $contentAvailable, bool $critical, string $priority, Event $queueForEvents, Database $dbForProject, Database $dbForPlatform, Document $project, MessagingPublisher $publisherForMessaging, Response $response, array $platform) {
|
||||
$messageId = $messageId == 'unique()'
|
||||
? ID::unique()
|
||||
: $messageId;
|
||||
@@ -3638,9 +3643,11 @@ Http::post('/v1/messaging/messages/push')
|
||||
|
||||
switch ($status) {
|
||||
case MessageStatus::PROCESSING:
|
||||
$queueForMessaging
|
||||
->setType(MESSAGE_SEND_TYPE_EXTERNAL)
|
||||
->setMessageId($message->getId());
|
||||
$publisherForMessaging->enqueue(new MessagingMessage(
|
||||
type: MESSAGE_SEND_TYPE_EXTERNAL,
|
||||
project: $project,
|
||||
messageId: $message->getId(),
|
||||
));
|
||||
break;
|
||||
case MessageStatus::SCHEDULED:
|
||||
$schedule = $dbForPlatform->createDocument('schedules', new Document([
|
||||
@@ -3983,9 +3990,9 @@ Http::patch('/v1/messaging/messages/email/:messageId')
|
||||
->inject('dbForProject')
|
||||
->inject('dbForPlatform')
|
||||
->inject('project')
|
||||
->inject('queueForMessaging')
|
||||
->inject('publisherForMessaging')
|
||||
->inject('response')
|
||||
->action(function (string $messageId, ?array $topics, ?array $users, ?array $targets, ?string $subject, ?string $content, ?bool $draft, ?bool $html, ?array $cc, ?array $bcc, ?string $scheduledAt, ?array $attachments, Event $queueForEvents, Database $dbForProject, Database $dbForPlatform, Document $project, Messaging $queueForMessaging, Response $response) {
|
||||
->action(function (string $messageId, ?array $topics, ?array $users, ?array $targets, ?string $subject, ?string $content, ?bool $draft, ?bool $html, ?array $cc, ?array $bcc, ?string $scheduledAt, ?array $attachments, Event $queueForEvents, Database $dbForProject, Database $dbForPlatform, Document $project, MessagingPublisher $publisherForMessaging, Response $response) {
|
||||
$message = $dbForProject->getDocument('messages', $messageId);
|
||||
|
||||
if ($message->isEmpty()) {
|
||||
@@ -4141,9 +4148,11 @@ Http::patch('/v1/messaging/messages/email/:messageId')
|
||||
$message = $dbForProject->updateDocument('messages', $message->getId(), $message);
|
||||
|
||||
if ($status === MessageStatus::PROCESSING) {
|
||||
$queueForMessaging
|
||||
->setType(MESSAGE_SEND_TYPE_EXTERNAL)
|
||||
->setMessageId($message->getId());
|
||||
$publisherForMessaging->enqueue(new MessagingMessage(
|
||||
type: MESSAGE_SEND_TYPE_EXTERNAL,
|
||||
project: $project,
|
||||
messageId: $message->getId(),
|
||||
));
|
||||
}
|
||||
|
||||
$queueForEvents
|
||||
@@ -4205,9 +4214,9 @@ Http::patch('/v1/messaging/messages/sms/:messageId')
|
||||
->inject('dbForProject')
|
||||
->inject('dbForPlatform')
|
||||
->inject('project')
|
||||
->inject('queueForMessaging')
|
||||
->inject('publisherForMessaging')
|
||||
->inject('response')
|
||||
->action(function (string $messageId, ?array $topics, ?array $users, ?array $targets, ?string $content, ?bool $draft, ?string $scheduledAt, Event $queueForEvents, Database $dbForProject, Database $dbForPlatform, Document $project, Messaging $queueForMessaging, Response $response) {
|
||||
->action(function (string $messageId, ?array $topics, ?array $users, ?array $targets, ?string $content, ?bool $draft, ?string $scheduledAt, Event $queueForEvents, Database $dbForProject, Database $dbForPlatform, Document $project, MessagingPublisher $publisherForMessaging, Response $response) {
|
||||
$message = $dbForProject->getDocument('messages', $messageId);
|
||||
|
||||
if ($message->isEmpty()) {
|
||||
@@ -4323,9 +4332,11 @@ Http::patch('/v1/messaging/messages/sms/:messageId')
|
||||
$message = $dbForProject->updateDocument('messages', $message->getId(), $message);
|
||||
|
||||
if ($status === MessageStatus::PROCESSING) {
|
||||
$queueForMessaging
|
||||
->setType(MESSAGE_SEND_TYPE_EXTERNAL)
|
||||
->setMessageId($message->getId());
|
||||
$publisherForMessaging->enqueue(new MessagingMessage(
|
||||
type: MESSAGE_SEND_TYPE_EXTERNAL,
|
||||
project: $project,
|
||||
messageId: $message->getId(),
|
||||
));
|
||||
}
|
||||
|
||||
$queueForEvents
|
||||
@@ -4379,10 +4390,10 @@ Http::patch('/v1/messaging/messages/push/:messageId')
|
||||
->inject('dbForProject')
|
||||
->inject('dbForPlatform')
|
||||
->inject('project')
|
||||
->inject('queueForMessaging')
|
||||
->inject('publisherForMessaging')
|
||||
->inject('response')
|
||||
->inject('platform')
|
||||
->action(function (string $messageId, ?array $topics, ?array $users, ?array $targets, ?string $title, ?string $body, ?array $data, ?string $action, ?string $image, ?string $icon, ?string $sound, ?string $color, ?string $tag, ?int $badge, ?bool $draft, ?string $scheduledAt, ?bool $contentAvailable, ?bool $critical, ?string $priority, Event $queueForEvents, Database $dbForProject, Database $dbForPlatform, Document $project, Messaging $queueForMessaging, Response $response, array $platform) {
|
||||
->action(function (string $messageId, ?array $topics, ?array $users, ?array $targets, ?string $title, ?string $body, ?array $data, ?string $action, ?string $image, ?string $icon, ?string $sound, ?string $color, ?string $tag, ?int $badge, ?bool $draft, ?string $scheduledAt, ?bool $contentAvailable, ?bool $critical, ?string $priority, Event $queueForEvents, Database $dbForProject, Database $dbForPlatform, Document $project, MessagingPublisher $publisherForMessaging, Response $response, array $platform) {
|
||||
$message = $dbForProject->getDocument('messages', $messageId);
|
||||
|
||||
if ($message->isEmpty()) {
|
||||
@@ -4584,9 +4595,11 @@ Http::patch('/v1/messaging/messages/push/:messageId')
|
||||
$message = $dbForProject->updateDocument('messages', $message->getId(), $message);
|
||||
|
||||
if ($status === MessageStatus::PROCESSING) {
|
||||
$queueForMessaging
|
||||
->setType(MESSAGE_SEND_TYPE_EXTERNAL)
|
||||
->setMessageId($message->getId());
|
||||
$publisherForMessaging->enqueue(new MessagingMessage(
|
||||
type: MESSAGE_SEND_TYPE_EXTERNAL,
|
||||
project: $project,
|
||||
messageId: $message->getId(),
|
||||
));
|
||||
}
|
||||
|
||||
$queueForEvents
|
||||
|
||||
@@ -1274,7 +1274,7 @@ Http::error()
|
||||
if (!$publish && $project->getId() !== 'console') {
|
||||
$errorUser = new DBUser();
|
||||
try {
|
||||
$resolvedUser = $utopia->getResource('user');
|
||||
$resolvedUser = $utopia->context()->get('user');
|
||||
if ($resolvedUser instanceof DBUser) {
|
||||
$errorUser = $resolvedUser;
|
||||
}
|
||||
@@ -1293,7 +1293,7 @@ Http::error()
|
||||
if ($logger && $publish) {
|
||||
try {
|
||||
/** @var Utopia\Database\Document $user */
|
||||
$user = $utopia->getResource('user');
|
||||
$user = $utopia->context()->get('user');
|
||||
} catch (\Throwable) {
|
||||
// All good, user is optional information for logger
|
||||
}
|
||||
@@ -1494,7 +1494,7 @@ Http::error()
|
||||
// the cors resource (which depends on rule -> DB) would cascade.
|
||||
// Uses override:true to avoid duplicate headers if init() already set them.
|
||||
try {
|
||||
$cors = $utopia->getResource('cors');
|
||||
$cors = $utopia->context()->get('cors');
|
||||
foreach ($cors->headers($request->getOrigin()) as $name => $value) {
|
||||
$response
|
||||
->removeHeader($name)
|
||||
|
||||
@@ -8,10 +8,8 @@ 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;
|
||||
@@ -486,13 +484,11 @@ Http::init()
|
||||
->inject('project')
|
||||
->inject('user')
|
||||
->inject('queueForEvents')
|
||||
->inject('queueForMessaging')
|
||||
->inject('auditContext')
|
||||
->inject('queueForDeletes')
|
||||
->inject('queueForDatabase')
|
||||
->inject('usage')
|
||||
->inject('queueForFunctions')
|
||||
->inject('queueForMails')
|
||||
->inject('dbForProject')
|
||||
->inject('timelimit')
|
||||
->inject('resourceToken')
|
||||
@@ -504,7 +500,7 @@ Http::init()
|
||||
->inject('platform')
|
||||
->inject('authorization')
|
||||
->inject('cacheControlForStorage')
|
||||
->action(function (Http $utopia, Request $request, Response $response, Document $project, User $user, Event $queueForEvents, Messaging $queueForMessaging, AuditContext $auditContext, Delete $queueForDeletes, EventDatabase $queueForDatabase, 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, callable $cacheControlForStorage) {
|
||||
->action(function (Http $utopia, Request $request, Response $response, Document $project, User $user, Event $queueForEvents, AuditContext $auditContext, Delete $queueForDeletes, EventDatabase $queueForDatabase, Context $usage, Func $queueForFunctions, Database $dbForProject, callable $timelimit, Document $resourceToken, string $mode, ?Key $apiKey, array $plan, Document $devKey, Telemetry $telemetry, array $platform, Authorization $authorization, callable $cacheControlForStorage) {
|
||||
|
||||
$response->setUser($user);
|
||||
$request->setUser($user);
|
||||
@@ -617,13 +613,10 @@ Http::init()
|
||||
/* Auto-set projects */
|
||||
$queueForDeletes->setProject($project);
|
||||
$queueForDatabase->setProject($project);
|
||||
$queueForMessaging->setProject($project);
|
||||
$queueForFunctions->setProject($project);
|
||||
$queueForMails->setProject($project);
|
||||
|
||||
/* Auto-set platforms */
|
||||
$queueForFunctions->setPlatform($platform);
|
||||
$queueForMails->setPlatform($platform);
|
||||
|
||||
$useCache = $route->getLabel('cache', false);
|
||||
$storageCacheOperationsCounter = $telemetry->createCounter('storage.cache.operations.load');
|
||||
@@ -815,7 +808,6 @@ Http::shutdown()
|
||||
->inject('publisherForUsage')
|
||||
->inject('queueForDeletes')
|
||||
->inject('queueForDatabase')
|
||||
->inject('queueForMessaging')
|
||||
->inject('queueForFunctions')
|
||||
->inject('queueForWebhooks')
|
||||
->inject('queueForRealtime')
|
||||
@@ -826,7 +818,7 @@ Http::shutdown()
|
||||
->inject('bus')
|
||||
->inject('apiKey')
|
||||
->inject('mode')
|
||||
->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, 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, 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();
|
||||
|
||||
@@ -975,10 +967,6 @@ Http::shutdown()
|
||||
$queueForDatabase->trigger();
|
||||
}
|
||||
|
||||
if (! empty($queueForMessaging->getType())) {
|
||||
$queueForMessaging->trigger();
|
||||
}
|
||||
|
||||
// Cache label
|
||||
$useCache = $route->getLabel('cache', false);
|
||||
if ($useCache) {
|
||||
|
||||
+31
-39
@@ -3,7 +3,7 @@
|
||||
require_once __DIR__ . '/init.php';
|
||||
require_once __DIR__ . '/init/span.php';
|
||||
|
||||
$registerRequestResources = require __DIR__ . '/init/resources/request.php';
|
||||
$setRequestContext = require __DIR__ . '/init/resources/request.php';
|
||||
|
||||
use Appwrite\Utopia\Request;
|
||||
use Appwrite\Utopia\Response;
|
||||
@@ -26,6 +26,7 @@ use Utopia\Database\Helpers\ID;
|
||||
use Utopia\Database\Helpers\Permission;
|
||||
use Utopia\Database\Helpers\Role;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\DI\Container;
|
||||
use Utopia\Http\Adapter\Swoole\Server;
|
||||
use Utopia\Http\Files;
|
||||
use Utopia\Http\Http;
|
||||
@@ -57,7 +58,7 @@ $container->set('pools', function ($register) {
|
||||
$payloadSize = 12 * (1024 * 1024); // 12MB - adding slight buffer for headers and other data that might be sent with the payload - update later with valid testing
|
||||
$totalWorkers = intval(System::getEnv('_APP_CPU_NUM', swoole_cpu_num())) * intval(System::getEnv('_APP_WORKER_PER_CORE', 6));
|
||||
|
||||
$swooleAdapter = new Server(
|
||||
$swoole = new Server(
|
||||
host: "0.0.0.0",
|
||||
port: System::getEnv('PORT', 80),
|
||||
settings: [
|
||||
@@ -69,10 +70,10 @@ $swooleAdapter = new Server(
|
||||
Constant::OPTION_OUTPUT_BUFFER_SIZE => $payloadSize,
|
||||
Constant::OPTION_TASK_WORKER_NUM => 1, // required for the task to fetch domains background
|
||||
],
|
||||
container: $container,
|
||||
resources: $container,
|
||||
);
|
||||
|
||||
$http = $swooleAdapter->getServer();
|
||||
$http = $swoole->getServer();
|
||||
|
||||
/**
|
||||
* Assigns HTTP requests to worker threads by analyzing its payload/content.
|
||||
@@ -190,13 +191,11 @@ $http->on(Constant::EVENT_AFTER_RELOAD, function ($server) {
|
||||
Console::success('Reload completed...');
|
||||
});
|
||||
|
||||
$container->set('bus', function ($register) use ($swooleAdapter) {
|
||||
return $register->get('bus')->setResolver(fn (string $name) => $swooleAdapter->getContainer()->get($name));
|
||||
}, ['register']);
|
||||
$container->set('bus', fn ($register) => $register->get('bus')->setResolver(fn (string $name) => $swoole->context()->get($name)), ['register']);
|
||||
|
||||
include __DIR__ . '/controllers/general.php';
|
||||
|
||||
function createDatabase(Http $app, string $resourceKey, string $dbName, array $collections, mixed $pools, ?callable $extraSetup = null): void
|
||||
function createDatabase(Container $resources, string $resourceKey, string $dbName, array $collections, mixed $pools, ?callable $extraSetup = null): void
|
||||
{
|
||||
$max = 15;
|
||||
$sleep = 2;
|
||||
@@ -205,7 +204,7 @@ function createDatabase(Http $app, string $resourceKey, string $dbName, array $c
|
||||
while (true) {
|
||||
try {
|
||||
$attempts++;
|
||||
$resource = $app->getResource($resourceKey);
|
||||
$resource = $resources->get($resourceKey);
|
||||
/* @var $database Database */
|
||||
$database = is_callable($resource) ? $resource() : $resource;
|
||||
break; // exit loop on success
|
||||
@@ -288,23 +287,21 @@ function createDatabase(Http $app, string $resourceKey, string $dbName, array $c
|
||||
Span::current()?->finish();
|
||||
}
|
||||
|
||||
$http->on(Constant::EVENT_START, function ($http) use ($payloadSize, $totalWorkers, $swooleAdapter) {
|
||||
$app = new Http($swooleAdapter, 'UTC');
|
||||
|
||||
$http->on(Constant::EVENT_START, function ($http) use ($payloadSize, $totalWorkers, $container) {
|
||||
/** @var \Utopia\Pools\Group $pools */
|
||||
$pools = $app->getResource('pools');
|
||||
$pools = $container->get('pools');
|
||||
|
||||
go(function () use ($app, $pools) {
|
||||
go(function () use ($container, $pools) {
|
||||
|
||||
/** @var array $collections */
|
||||
$collections = Config::getParam('collections', []);
|
||||
|
||||
// create logs database first, `getLogsDB` is a callable.
|
||||
createDatabase($app, 'getLogsDB', 'logs', $collections['logs'], $pools);
|
||||
createDatabase($container, 'getLogsDB', 'logs', $collections['logs'], $pools);
|
||||
|
||||
// create appwrite database, `dbForPlatform` is a direct access call.
|
||||
createDatabase($app, 'dbForPlatform', 'appwrite', $collections['console'], $pools, function (Database $dbForPlatform) use ($collections, $app) {
|
||||
$authorization = $app->getResource('authorization');
|
||||
createDatabase($container, 'dbForPlatform', 'appwrite', $collections['console'], $pools, function (Database $dbForPlatform) use ($collections, $container) {
|
||||
$authorization = $container->get('authorization');
|
||||
|
||||
if ($dbForPlatform->getCollection(AuditAdapterSQL::COLLECTION)->isEmpty()) {
|
||||
$adapter = new AdapterDatabase($dbForPlatform);
|
||||
@@ -416,7 +413,7 @@ $http->on(Constant::EVENT_START, function ($http) use ($payloadSize, $totalWorke
|
||||
$documentsSharedTables = \explode(',', System::getEnv('_APP_DATABASE_DOCUMENTSDB_SHARED_TABLES', ''));
|
||||
$vectorSharedTables = \explode(',', System::getEnv('_APP_DATABASE_VECTORSDB_SHARED_TABLES', ''));
|
||||
|
||||
$cache = $app->getResource('cache');
|
||||
$cache = $container->get('cache');
|
||||
|
||||
// All shared tables pools that need project metadata collections
|
||||
$allSharedTables = \array_values(\array_unique(\array_filter([
|
||||
@@ -502,7 +499,7 @@ $http->on(Constant::EVENT_START, function ($http) use ($payloadSize, $totalWorke
|
||||
});
|
||||
});
|
||||
|
||||
$swooleAdapter->onRequest(function ($utopiaRequest, $utopiaResponse) use ($files, $swooleAdapter, $registerRequestResources) {
|
||||
$swoole->onRequest(function ($utopiaRequest, $utopiaResponse) use ($files, $swoole, $setRequestContext) {
|
||||
Span::init('http.request');
|
||||
|
||||
$request = new Request($utopiaRequest->getSwooleRequest());
|
||||
@@ -522,21 +519,18 @@ $swooleAdapter->onRequest(function ($utopiaRequest, $utopiaResponse) use ($files
|
||||
return;
|
||||
}
|
||||
|
||||
$requestContainer = $swooleAdapter->getContainer();
|
||||
$requestContainer->set('container', fn () => $requestContainer);
|
||||
$requestContainer->set('request', fn () => $request);
|
||||
$requestContainer->set('response', fn () => $response);
|
||||
$app = new Http($swoole, 'UTC');
|
||||
$app->context()->set('request', fn () => $request);
|
||||
$app->context()->set('response', fn () => $response);
|
||||
$app->context()->set('utopia', fn () => $app);
|
||||
|
||||
$app = new Http($swooleAdapter, 'UTC');
|
||||
$requestContainer->set('utopia', fn () => $app);
|
||||
|
||||
$registerRequestResources($requestContainer);
|
||||
$setRequestContext($app->context());
|
||||
|
||||
$app->setCompression(System::getEnv('_APP_COMPRESSION_ENABLED', 'enabled') === 'enabled');
|
||||
$app->setCompressionMinSize(intval(System::getEnv('_APP_COMPRESSION_MIN_SIZE_BYTES', '1024'))); // 1KB
|
||||
|
||||
try {
|
||||
$authorization = $app->getResource('authorization');
|
||||
$authorization = $app->context()->get('authorization');
|
||||
|
||||
$request->setAuthorization($authorization);
|
||||
$response->setAuthorization($authorization);
|
||||
@@ -552,18 +546,18 @@ $swooleAdapter->onRequest(function ($utopiaRequest, $utopiaResponse) use ($files
|
||||
|
||||
$version = System::getEnv('_APP_VERSION', 'UNKNOWN');
|
||||
|
||||
$logger = $app->getResource("logger");
|
||||
$logger = $app->context()->get("logger");
|
||||
if ($logger) {
|
||||
try {
|
||||
/** @var Utopia\Database\Document $user */
|
||||
$user = $app->getResource('user');
|
||||
$user = $app->context()->get('user');
|
||||
} catch (\Throwable $_th) {
|
||||
// All good, user is optional information for logger
|
||||
}
|
||||
|
||||
$route = $app->getRoute();
|
||||
|
||||
$log = $app->getResource("log");
|
||||
$log = $app->context()->get("log");
|
||||
|
||||
if (isset($user) && !$user->isEmpty()) {
|
||||
$log->setUser(new User($user->getId()));
|
||||
@@ -642,18 +636,16 @@ $swooleAdapter->onRequest(function ($utopiaRequest, $utopiaResponse) use ($files
|
||||
});
|
||||
|
||||
// Fetch domains every `DOMAIN_SYNC_TIMER` seconds and update in the memory
|
||||
$http->on(Constant::EVENT_TASK, function () use ($swooleAdapter) {
|
||||
$http->on(Constant::EVENT_TASK, function () use ($container) {
|
||||
$lastSyncUpdate = null;
|
||||
|
||||
$app = new Http($swooleAdapter, 'UTC');
|
||||
|
||||
/** @var Utopia\Database\Database $dbForPlatform */
|
||||
$dbForPlatform = $app->getResource('dbForPlatform');
|
||||
$dbForPlatform = $container->get('dbForPlatform');
|
||||
|
||||
/** @var \Swoole\Table $riskyDomains */
|
||||
$riskyDomains = $app->getResource('riskyDomains');
|
||||
$riskyDomains = $container->get('riskyDomains');
|
||||
|
||||
Timer::tick(DOMAIN_SYNC_TIMER * 1000, function () use ($dbForPlatform, $riskyDomains, &$lastSyncUpdate, $app) {
|
||||
Timer::tick(DOMAIN_SYNC_TIMER * 1000, function () use ($dbForPlatform, $riskyDomains, &$lastSyncUpdate, $container) {
|
||||
try {
|
||||
$time = DateTime::now();
|
||||
$limit = 1000;
|
||||
@@ -670,7 +662,7 @@ $http->on(Constant::EVENT_TASK, function () use ($swooleAdapter) {
|
||||
}
|
||||
$results = [];
|
||||
try {
|
||||
$authorization = $app->getResource('authorization');
|
||||
$authorization = $container->get('authorization');
|
||||
$results = $authorization->skip(fn () => $dbForPlatform->find('rules', $queries));
|
||||
} catch (Throwable $th) {
|
||||
Console::error('rules ' . $th->getMessage());
|
||||
@@ -720,4 +712,4 @@ $http->on(Constant::EVENT_TASK, function () use ($swooleAdapter) {
|
||||
});
|
||||
});
|
||||
|
||||
$swooleAdapter->start();
|
||||
$swoole->start();
|
||||
|
||||
@@ -5,6 +5,8 @@ use Appwrite\Event\Publisher\Audit as AuditPublisher;
|
||||
use Appwrite\Event\Publisher\Build as BuildPublisher;
|
||||
use Appwrite\Event\Publisher\Certificate as CertificatePublisher;
|
||||
use Appwrite\Event\Publisher\Execution as ExecutionPublisher;
|
||||
use Appwrite\Event\Publisher\Mail as MailPublisher;
|
||||
use Appwrite\Event\Publisher\Messaging as MessagingPublisher;
|
||||
use Appwrite\Event\Publisher\Migration as MigrationPublisher;
|
||||
use Appwrite\Event\Publisher\Screenshot as ScreenshotPublisher;
|
||||
use Appwrite\Event\Publisher\StatsResources as StatsResourcesPublisher;
|
||||
@@ -118,6 +120,14 @@ $container->set('publisherForBuilds', fn (Publisher $publisher) => new BuildPubl
|
||||
$publisher,
|
||||
new Queue(System::getEnv('_APP_BUILDS_QUEUE_NAME', Event::BUILDS_QUEUE_NAME))
|
||||
), ['publisher']);
|
||||
$container->set('publisherForMails', fn (Publisher $publisher) => new MailPublisher(
|
||||
$publisher,
|
||||
new Queue(System::getEnv('_APP_MAILS_QUEUE_NAME', Event::MAILS_QUEUE_NAME))
|
||||
), ['publisher']);
|
||||
$container->set('publisherForMessaging', fn (Publisher $publisher) => new MessagingPublisher(
|
||||
$publisher,
|
||||
new Queue(System::getEnv('_APP_MESSAGING_QUEUE_NAME', Event::MESSAGING_QUEUE_NAME))
|
||||
), ['publisher']);
|
||||
|
||||
/**
|
||||
* Platform configuration
|
||||
|
||||
+74
-118
@@ -9,8 +9,6 @@ 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\Realtime;
|
||||
use Appwrite\Event\Webhook;
|
||||
use Appwrite\Extend\Exception;
|
||||
@@ -61,26 +59,18 @@ use Utopia\Validator\WhiteList;
|
||||
* These resources depend (directly or transitively) on request/response
|
||||
* and must be fresh for each HTTP request.
|
||||
*/
|
||||
return function (Container $container): void {
|
||||
$container->set('utopia:graphql', function ($utopia) {
|
||||
return $utopia;
|
||||
}, ['utopia']);
|
||||
return function (Container $context): void {
|
||||
$context->set('utopia:graphql', fn ($utopia) => $utopia, ['utopia']);
|
||||
|
||||
$container->set('log', fn () => new Log(), []);
|
||||
$context->set('log', fn () => new Log(), []);
|
||||
|
||||
$container->set('logger', function ($register) {
|
||||
return $register->get('logger');
|
||||
}, ['register']);
|
||||
$context->set('logger', fn ($register) => $register->get('logger'), ['register']);
|
||||
|
||||
$container->set('authorization', function () {
|
||||
return new Authorization();
|
||||
}, []);
|
||||
$context->set('authorization', fn () => new Authorization(), []);
|
||||
|
||||
$container->set('store', function (): Store {
|
||||
return new Store();
|
||||
}, []);
|
||||
$context->set('store', fn (): Store => new Store(), []);
|
||||
|
||||
$container->set('proofForPassword', function (): Password {
|
||||
$context->set('proofForPassword', function (): Password {
|
||||
$hash = new Argon2();
|
||||
$hash
|
||||
->setMemoryCost(7168)
|
||||
@@ -94,21 +84,21 @@ return function (Container $container): void {
|
||||
return $password;
|
||||
});
|
||||
|
||||
$container->set('proofForToken', function (): Token {
|
||||
$context->set('proofForToken', function (): Token {
|
||||
$token = new Token();
|
||||
$token->setHash(new Sha());
|
||||
|
||||
return $token;
|
||||
});
|
||||
|
||||
$container->set('proofForCode', function (): Code {
|
||||
$context->set('proofForCode', function (): Code {
|
||||
$code = new Code();
|
||||
$code->setHash(new Sha());
|
||||
|
||||
return $code;
|
||||
});
|
||||
|
||||
$container->set('locale', function () {
|
||||
$context->set('locale', function () {
|
||||
$locale = new Locale(System::getEnv('_APP_LOCALE', 'en'));
|
||||
$locale->setFallback(System::getEnv('_APP_LOCALE', 'en'));
|
||||
|
||||
@@ -116,38 +106,16 @@ return function (Container $container): void {
|
||||
});
|
||||
|
||||
// Per-request queue resources (stateful, accumulate event data during request)
|
||||
$container->set('queueForMessaging', function (Publisher $publisher) {
|
||||
return new Messaging($publisher);
|
||||
}, ['publisher']);
|
||||
$container->set('queueForMails', function (Publisher $publisher) {
|
||||
return new Mail($publisher);
|
||||
}, ['publisher']);
|
||||
$container->set('queueForDatabase', function (Publisher $publisher) {
|
||||
return new EventDatabase($publisher);
|
||||
}, ['publisher']);
|
||||
$container->set('queueForDeletes', function (Publisher $publisher) {
|
||||
return new Delete($publisher);
|
||||
}, ['publisher']);
|
||||
$container->set('queueForEvents', function (Publisher $publisher) {
|
||||
return new Event($publisher);
|
||||
}, ['publisher']);
|
||||
$container->set('queueForWebhooks', function (Publisher $publisher) {
|
||||
return new Webhook($publisher);
|
||||
}, ['publisher']);
|
||||
$container->set('queueForRealtime', function () {
|
||||
return new Realtime();
|
||||
}, []);
|
||||
$container->set('usage', function () {
|
||||
return new UsageContext();
|
||||
}, []);
|
||||
$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('dbForPlatform', function (Group $pools, Cache $cache, Authorization $authorization) {
|
||||
$context->set('queueForDatabase', fn (Publisher $publisher) => new EventDatabase($publisher), ['publisher']);
|
||||
$context->set('queueForDeletes', fn (Publisher $publisher) => new Delete($publisher), ['publisher']);
|
||||
$context->set('queueForEvents', fn (Publisher $publisher) => new Event($publisher), ['publisher']);
|
||||
$context->set('queueForWebhooks', fn (Publisher $publisher) => new Webhook($publisher), ['publisher']);
|
||||
$context->set('queueForRealtime', fn () => new Realtime(), []);
|
||||
$context->set('usage', fn () => new UsageContext(), []);
|
||||
$context->set('auditContext', fn () => new AuditContext(), []);
|
||||
$context->set('queueForFunctions', fn (Publisher $publisher) => new Func($publisher), ['publisher']);
|
||||
$context->set('eventProcessor', fn () => new EventProcessor(), []);
|
||||
$context->set('dbForPlatform', function (Group $pools, Cache $cache, Authorization $authorization) {
|
||||
$adapter = new DatabasePool($pools->get('console'));
|
||||
$database = new Database($adapter, $cache);
|
||||
|
||||
@@ -165,7 +133,7 @@ return function (Container $container): void {
|
||||
return $database;
|
||||
}, ['pools', 'cache', 'authorization']);
|
||||
|
||||
$container->set('getProjectDB', function (Group $pools, Database $dbForPlatform, Cache $cache, Authorization $authorization) {
|
||||
$context->set('getProjectDB', function (Group $pools, Database $dbForPlatform, Cache $cache, Authorization $authorization) {
|
||||
$adapters = [];
|
||||
|
||||
return function (Document $project) use ($pools, $dbForPlatform, $cache, $authorization, &$adapters) {
|
||||
@@ -222,7 +190,7 @@ return function (Container $container): void {
|
||||
};
|
||||
}, ['pools', 'dbForPlatform', 'cache', 'authorization']);
|
||||
|
||||
$container->set('getLogsDB', function (Group $pools, Cache $cache, Authorization $authorization) {
|
||||
$context->set('getLogsDB', function (Group $pools, Cache $cache, Authorization $authorization) {
|
||||
$adapter = null;
|
||||
|
||||
return function (?Document $project = null) use ($pools, $cache, $authorization, &$adapter) {
|
||||
@@ -254,7 +222,7 @@ return function (Container $container): void {
|
||||
/**
|
||||
* List of allowed request hostnames for the request.
|
||||
*/
|
||||
$container->set('allowedHostnames', function (array $platform, Document $project, Document $rule, Document $devKey, Request $request) {
|
||||
$context->set('allowedHostnames', function (array $platform, Document $project, Document $rule, Document $devKey, Request $request) {
|
||||
$allowed = [...($platform['hostnames'] ?? [])];
|
||||
|
||||
/* Add platform configured hostnames */
|
||||
@@ -298,7 +266,7 @@ return function (Container $container): void {
|
||||
/**
|
||||
* List of allowed request schemes for the request.
|
||||
*/
|
||||
$container->set('allowedSchemes', function (array $platform, Document $project) {
|
||||
$context->set('allowedSchemes', function (array $platform, Document $project) {
|
||||
$allowed = [...($platform['schemas'] ?? [])];
|
||||
|
||||
if (! $project->isEmpty() && $project->getId() !== 'console') {
|
||||
@@ -318,7 +286,7 @@ return function (Container $container): void {
|
||||
/**
|
||||
* Whether the request origin is verified against the request hostname.
|
||||
*/
|
||||
$container->set('domainVerification', function (Request $request) {
|
||||
$context->set('domainVerification', function (Request $request) {
|
||||
$origin = \parse_url($request->getOrigin($request->getReferer('')), PHP_URL_HOST);
|
||||
$selfDomain = new Domain($request->getHostname());
|
||||
$endDomain = new Domain((string) $origin);
|
||||
@@ -330,7 +298,7 @@ return function (Container $container): void {
|
||||
/**
|
||||
* Cookie domain for the current request.
|
||||
*/
|
||||
$container->set('cookieDomain', function (Request $request, Document $project) {
|
||||
$context->set('cookieDomain', function (Request $request, Document $project) {
|
||||
$localHosts = ['localhost', 'localhost:' . $request->getPort()];
|
||||
|
||||
$migrationHost = System::getEnv('_APP_MIGRATION_HOST');
|
||||
@@ -364,7 +332,7 @@ return function (Container $container): void {
|
||||
/**
|
||||
* Rule associated with a request origin.
|
||||
*/
|
||||
$container->set('rule', function (Request $request, Database $dbForPlatform, Document $project, Authorization $authorization) {
|
||||
$context->set('rule', function (Request $request, Database $dbForPlatform, Document $project, Authorization $authorization) {
|
||||
$domain = \parse_url($request->getOrigin(), PHP_URL_HOST);
|
||||
|
||||
if (empty($domain)) {
|
||||
@@ -414,7 +382,7 @@ return function (Container $container): void {
|
||||
/**
|
||||
* CORS service
|
||||
*/
|
||||
$container->set('cors', function (array $allowedHostnames) {
|
||||
$context->set('cors', function (array $allowedHostnames) {
|
||||
$corsConfig = Config::getParam('cors');
|
||||
|
||||
return new Cors(
|
||||
@@ -426,23 +394,23 @@ return function (Container $container): void {
|
||||
);
|
||||
}, ['allowedHostnames']);
|
||||
|
||||
$container->set('originValidator', function (Document $devKey, array $allowedHostnames, array $allowedSchemes) {
|
||||
if (! $devKey->isEmpty()) {
|
||||
return new URL();
|
||||
}
|
||||
$context->set(
|
||||
'originValidator',
|
||||
fn (Document $devKey, array $allowedHostnames, array $allowedSchemes) => $devKey->isEmpty()
|
||||
? new Origin($allowedHostnames, $allowedSchemes)
|
||||
: new URL(),
|
||||
['devKey', 'allowedHostnames', 'allowedSchemes']
|
||||
);
|
||||
|
||||
return new Origin($allowedHostnames, $allowedSchemes);
|
||||
}, ['devKey', 'allowedHostnames', 'allowedSchemes']);
|
||||
$context->set(
|
||||
'redirectValidator',
|
||||
fn (Document $devKey, array $allowedHostnames, array $allowedSchemes) => $devKey->isEmpty()
|
||||
? new Redirect($allowedHostnames, $allowedSchemes)
|
||||
: new URL(),
|
||||
['devKey', 'allowedHostnames', 'allowedSchemes']
|
||||
);
|
||||
|
||||
$container->set('redirectValidator', function (Document $devKey, array $allowedHostnames, array $allowedSchemes) {
|
||||
if (! $devKey->isEmpty()) {
|
||||
return new URL();
|
||||
}
|
||||
|
||||
return new Redirect($allowedHostnames, $allowedSchemes);
|
||||
}, ['devKey', 'allowedHostnames', 'allowedSchemes']);
|
||||
|
||||
$container->set('user', function (string $mode, Document $project, Document $console, Request $request, Response $response, Database $dbForProject, Database $dbForPlatform, Store $store, Token $proofForToken, $authorization) {
|
||||
$context->set('user', function (string $mode, Document $project, Document $console, Request $request, Response $response, Database $dbForProject, Database $dbForPlatform, Store $store, Token $proofForToken, $authorization) {
|
||||
/**
|
||||
* Handles user authentication and session validation.
|
||||
*
|
||||
@@ -613,7 +581,7 @@ return function (Container $container): void {
|
||||
return $user;
|
||||
}, ['mode', 'project', 'console', 'request', 'response', 'dbForProject', 'dbForPlatform', 'store', 'proofForToken', 'authorization']);
|
||||
|
||||
$container->set('project', function ($dbForPlatform, $request, $console, $authorization, Http $utopia) {
|
||||
$context->set('project', function ($dbForPlatform, $request, $console, $authorization, Http $utopia) {
|
||||
/** @var Appwrite\Utopia\Request $request */
|
||||
/** @var Utopia\Database\Database $dbForPlatform */
|
||||
/** @var Utopia\Database\Document $console */
|
||||
@@ -646,7 +614,7 @@ return function (Container $container): void {
|
||||
return $project;
|
||||
}, ['dbForPlatform', 'request', 'console', 'authorization', 'utopia']);
|
||||
|
||||
$container->set('session', function (User $user, Store $store, Token $proofForToken) {
|
||||
$context->set('session', function (User $user, Store $store, Token $proofForToken) {
|
||||
if ($user->isEmpty()) {
|
||||
return;
|
||||
}
|
||||
@@ -667,7 +635,7 @@ return function (Container $container): void {
|
||||
return;
|
||||
}, ['user', 'store', 'proofForToken']);
|
||||
|
||||
$container->set('dbForProject', function (Group $pools, Database $dbForPlatform, Cache $cache, Document $project, Response $response, Publisher $publisher, Publisher $publisherFunctions, Publisher $publisherWebhooks, Event $queueForEvents, Func $queueForFunctions, Webhook $queueForWebhooks, Realtime $queueForRealtime, UsageContext $usage, Authorization $authorization, Request $request) {
|
||||
$context->set('dbForProject', function (Group $pools, Database $dbForPlatform, Cache $cache, Document $project, Response $response, Publisher $publisher, Publisher $publisherFunctions, Publisher $publisherWebhooks, Event $queueForEvents, Func $queueForFunctions, Webhook $queueForWebhooks, Realtime $queueForRealtime, UsageContext $usage, Authorization $authorization, Request $request) {
|
||||
if ($project->isEmpty() || $project->getId() === 'console') {
|
||||
return $dbForPlatform;
|
||||
}
|
||||
@@ -943,7 +911,7 @@ return function (Container $container): void {
|
||||
return $database;
|
||||
}, ['pools', 'dbForPlatform', 'cache', 'project', 'response', 'publisher', 'publisherFunctions', 'publisherWebhooks', 'queueForEvents', 'queueForFunctions', 'queueForWebhooks', 'queueForRealtime', 'usage', 'authorization', 'request']);
|
||||
|
||||
$container->set('schema', function ($utopia, $dbForProject, $authorization) {
|
||||
$context->set('schema', function ($utopia, $dbForProject, $authorization) {
|
||||
|
||||
$complexity = function (int $complexity, array $args) {
|
||||
$queries = Query::parseQueries($args['queries'] ?? []);
|
||||
@@ -1030,13 +998,9 @@ return function (Container $container): void {
|
||||
);
|
||||
}, ['utopia', 'dbForProject', 'authorization']);
|
||||
|
||||
$container->set('audit', function ($dbForProject) {
|
||||
$adapter = new AdapterDatabase($dbForProject);
|
||||
$context->set('audit', fn ($dbForProject) => new Audit(new AdapterDatabase($dbForProject)), ['dbForProject']);
|
||||
|
||||
return new Audit($adapter);
|
||||
}, ['dbForProject']);
|
||||
|
||||
$container->set('mode', function ($request, Document $project) {
|
||||
$context->set('mode', function ($request, Document $project) {
|
||||
/** @var Appwrite\Utopia\Request $request */
|
||||
|
||||
/**
|
||||
@@ -1054,7 +1018,7 @@ return function (Container $container): void {
|
||||
return $mode;
|
||||
}, ['request', 'project']);
|
||||
|
||||
$container->set('requestTimestamp', function ($request) {
|
||||
$context->set('requestTimestamp', function ($request) {
|
||||
// TODO: Move this to the Request class itself
|
||||
$timestampHeader = $request->getHeader('x-appwrite-timestamp');
|
||||
$requestTimestamp = null;
|
||||
@@ -1069,7 +1033,7 @@ return function (Container $container): void {
|
||||
return $requestTimestamp;
|
||||
}, ['request']);
|
||||
|
||||
$container->set('devKey', function (Request $request, Document $project, array $servers, Database $dbForPlatform, Authorization $authorization) {
|
||||
$context->set('devKey', function (Request $request, Document $project, array $servers, Database $dbForPlatform, Authorization $authorization) {
|
||||
$devKey = $request->getHeader('x-appwrite-dev-key', $request->getParam('devKey', ''));
|
||||
|
||||
// Check if given key match project's development keys
|
||||
@@ -1118,7 +1082,7 @@ return function (Container $container): void {
|
||||
return $key;
|
||||
}, ['request', 'project', 'servers', 'dbForPlatform', 'authorization']);
|
||||
|
||||
$container->set('team', function (Document $project, Database $dbForPlatform, Http $utopia, Request $request, Authorization $authorization) {
|
||||
$context->set('team', function (Document $project, Database $dbForPlatform, Http $utopia, Request $request, Authorization $authorization) {
|
||||
$teamInternalId = '';
|
||||
if ($project->getId() !== 'console') {
|
||||
$teamInternalId = $project->getAttribute('teamInternalId', '');
|
||||
@@ -1161,7 +1125,7 @@ return function (Container $container): void {
|
||||
return $team;
|
||||
}, ['project', 'dbForPlatform', 'utopia', 'request', 'authorization']);
|
||||
|
||||
$container->set('previewHostname', function (Request $request, ?Key $apiKey) {
|
||||
$context->set('previewHostname', function (Request $request, ?Key $apiKey) {
|
||||
$allowed = false;
|
||||
|
||||
if (Http::isDevelopment()) {
|
||||
@@ -1180,7 +1144,7 @@ return function (Container $container): void {
|
||||
return '';
|
||||
}, ['request', 'apiKey']);
|
||||
|
||||
$container->set('apiKey', function (Request $request, Document $project, Document $team, Document $user): ?Key {
|
||||
$context->set('apiKey', function (Request $request, Document $project, Document $team, Document $user): ?Key {
|
||||
$key = $request->getHeader('x-appwrite-key');
|
||||
|
||||
if (empty($key)) {
|
||||
@@ -1214,7 +1178,7 @@ return function (Container $container): void {
|
||||
return $key;
|
||||
}, ['request', 'project', 'team', 'user']);
|
||||
|
||||
$container->set('resourceToken', function ($project, $dbForProject, $request, Authorization $authorization) {
|
||||
$context->set('resourceToken', function ($project, $dbForProject, $request, Authorization $authorization) {
|
||||
$tokenJWT = $request->getParam('token');
|
||||
|
||||
if (! empty($tokenJWT) && ! $project->isEmpty()) { // JWT authentication
|
||||
@@ -1281,7 +1245,7 @@ return function (Container $container): void {
|
||||
return new Document([]);
|
||||
}, ['project', 'dbForProject', 'request', 'authorization']);
|
||||
|
||||
$container->set('getDatabasesDB', function (Group $pools, Cache $cache, Document $project, Request $request, UsageContext $usage, Authorization $authorization) {
|
||||
$context->set('getDatabasesDB', function (Group $pools, Cache $cache, Document $project, Request $request, UsageContext $usage, Authorization $authorization) {
|
||||
|
||||
return function (Document $database) use ($pools, $cache, $project, $request, $usage, $authorization): Database {
|
||||
$databaseDSN = $database->getAttribute('database', $project->getAttribute('database', ''));
|
||||
@@ -1443,35 +1407,27 @@ return function (Container $container): void {
|
||||
|
||||
}, ['pools', 'cache', 'project', 'request', 'usage', 'authorization']);
|
||||
|
||||
$container->set('transactionState', function (Database $dbForProject, Authorization $authorization, callable $getDatabasesDB) {
|
||||
return new TransactionState($dbForProject, $authorization, $getDatabasesDB);
|
||||
}, ['dbForProject', 'authorization', 'getDatabasesDB']);
|
||||
$context->set(
|
||||
'transactionState',
|
||||
fn (Database $dbForProject, Authorization $authorization, callable $getDatabasesDB) => new TransactionState($dbForProject, $authorization, $getDatabasesDB),
|
||||
['dbForProject', 'authorization', 'getDatabasesDB']
|
||||
);
|
||||
|
||||
$container->set('executionsRetentionCount', function (Document $project, array $plan) {
|
||||
if ($project->getId() === 'console' || empty($plan)) {
|
||||
return 0;
|
||||
}
|
||||
$context->set(
|
||||
'executionsRetentionCount',
|
||||
fn (Document $project, array $plan) => ($project->getId() === 'console' || empty($plan))
|
||||
? 0
|
||||
: (int) ($plan['executionsRetentionCount'] ?? 100),
|
||||
['project', 'plan']
|
||||
);
|
||||
|
||||
return (int) ($plan['executionsRetentionCount'] ?? 100);
|
||||
}, ['project', 'plan']);
|
||||
$context->set('deviceForFiles', fn ($project, Telemetry $telemetry) => new Device\Telemetry($telemetry, getDevice(APP_STORAGE_UPLOADS . '/app-' . $project->getId())), ['project', 'telemetry']);
|
||||
$context->set('deviceForSites', fn ($project, Telemetry $telemetry) => new Device\Telemetry($telemetry, getDevice(APP_STORAGE_SITES . '/app-' . $project->getId())), ['project', 'telemetry']);
|
||||
$context->set('deviceForMigrations', fn ($project, Telemetry $telemetry) => new Device\Telemetry($telemetry, getDevice(APP_STORAGE_IMPORTS . '/app-' . $project->getId())), ['project', 'telemetry']);
|
||||
$context->set('deviceForFunctions', fn ($project, Telemetry $telemetry) => new Device\Telemetry($telemetry, getDevice(APP_STORAGE_FUNCTIONS . '/app-' . $project->getId())), ['project', 'telemetry']);
|
||||
$context->set('deviceForBuilds', fn ($project, Telemetry $telemetry) => new Device\Telemetry($telemetry, getDevice(APP_STORAGE_BUILDS . '/app-' . $project->getId())), ['project', 'telemetry']);
|
||||
|
||||
$container->set('deviceForFiles', function ($project, Telemetry $telemetry) {
|
||||
return new Device\Telemetry($telemetry, getDevice(APP_STORAGE_UPLOADS . '/app-' . $project->getId()));
|
||||
}, ['project', 'telemetry']);
|
||||
$container->set('deviceForSites', function ($project, Telemetry $telemetry) {
|
||||
return new Device\Telemetry($telemetry, getDevice(APP_STORAGE_SITES . '/app-' . $project->getId()));
|
||||
}, ['project', 'telemetry']);
|
||||
$container->set('deviceForMigrations', function ($project, Telemetry $telemetry) {
|
||||
return new Device\Telemetry($telemetry, getDevice(APP_STORAGE_IMPORTS . '/app-' . $project->getId()));
|
||||
}, ['project', 'telemetry']);
|
||||
$container->set('deviceForFunctions', function ($project, Telemetry $telemetry) {
|
||||
return new Device\Telemetry($telemetry, getDevice(APP_STORAGE_FUNCTIONS . '/app-' . $project->getId()));
|
||||
}, ['project', 'telemetry']);
|
||||
$container->set('deviceForBuilds', function ($project, Telemetry $telemetry) {
|
||||
return new Device\Telemetry($telemetry, getDevice(APP_STORAGE_BUILDS . '/app-' . $project->getId()));
|
||||
}, ['project', 'telemetry']);
|
||||
|
||||
$container->set('embeddingAgent', function ($register) {
|
||||
$context->set('embeddingAgent', function ($register) {
|
||||
$adapter = new Ollama();
|
||||
$adapter->setEndpoint(System::getEnv('_APP_EMBEDDING_ENDPOINT', 'http://ollama:11434/api/embed'));
|
||||
$adapter->setTimeout((int) System::getEnv('_APP_EMBEDDING_TIMEOUT', '30000'));
|
||||
|
||||
@@ -4,8 +4,6 @@ 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\Realtime;
|
||||
use Appwrite\Event\Webhook;
|
||||
use Appwrite\Usage\Context;
|
||||
@@ -333,14 +331,6 @@ return function (Container $container): void {
|
||||
return new EventDatabase($publisher);
|
||||
}, ['publisher']);
|
||||
|
||||
$container->set('queueForMessaging', function (Publisher $publisher) {
|
||||
return new Messaging($publisher);
|
||||
}, ['publisher']);
|
||||
|
||||
$container->set('queueForMails', function (Publisher $publisher) {
|
||||
return new Mail($publisher);
|
||||
}, ['publisher']);
|
||||
|
||||
$container->set('queueForDeletes', function (Publisher $publisher) {
|
||||
return new Delete($publisher);
|
||||
}, ['publisher']);
|
||||
|
||||
@@ -881,6 +881,13 @@ $hostPath = rtrim($this->getParam('hostPath', ''), '/');
|
||||
- _APP_ENV
|
||||
- _APP_WORKER_PER_CORE
|
||||
- _APP_OPENSSL_KEY_V1
|
||||
- _APP_OPTIONS_FORCE_HTTPS
|
||||
- _APP_DOMAIN
|
||||
- _APP_CONSOLE_DOMAIN
|
||||
- _APP_DOMAIN_FUNCTIONS
|
||||
- _APP_DOMAIN_SITES
|
||||
- _APP_MIGRATION_HOST
|
||||
- _APP_CONSOLE_SCHEMA
|
||||
- _APP_REDIS_HOST
|
||||
- _APP_REDIS_PORT
|
||||
- _APP_REDIS_USER
|
||||
@@ -909,6 +916,13 @@ $hostPath = rtrim($this->getParam('hostPath', ''), '/');
|
||||
- _APP_ENV
|
||||
- _APP_WORKER_PER_CORE
|
||||
- _APP_OPENSSL_KEY_V1
|
||||
- _APP_OPTIONS_FORCE_HTTPS
|
||||
- _APP_DOMAIN
|
||||
- _APP_CONSOLE_DOMAIN
|
||||
- _APP_DOMAIN_FUNCTIONS
|
||||
- _APP_DOMAIN_SITES
|
||||
- _APP_MIGRATION_HOST
|
||||
- _APP_CONSOLE_SCHEMA
|
||||
- _APP_REDIS_HOST
|
||||
- _APP_REDIS_PORT
|
||||
- _APP_REDIS_USER
|
||||
|
||||
+5
-5
@@ -51,7 +51,7 @@
|
||||
"ext-sockets": "*",
|
||||
"appwrite/php-runtimes": "0.20.*",
|
||||
"appwrite/php-clamav": "2.0.*",
|
||||
"utopia-php/abuse": "1.2.*",
|
||||
"utopia-php/abuse": "1.3.*",
|
||||
"utopia-php/agents": "1.2.*",
|
||||
"utopia-php/analytics": "0.15.*",
|
||||
"utopia-php/audit": "2.2.*",
|
||||
@@ -67,15 +67,15 @@
|
||||
"utopia-php/emails": "0.6.*",
|
||||
"utopia-php/dns": "1.6.*",
|
||||
"utopia-php/dsn": "0.2.1",
|
||||
"utopia-php/http": "0.34.*",
|
||||
"utopia-php/fetch": "0.5.*",
|
||||
"utopia-php/http": "^2.0@RC",
|
||||
"utopia-php/fetch": "^1.1",
|
||||
"utopia-php/validators": "0.2.*",
|
||||
"utopia-php/image": "0.8.*",
|
||||
"utopia-php/locale": "0.8.*",
|
||||
"utopia-php/logger": "0.6.*",
|
||||
"utopia-php/logger": "0.8.*",
|
||||
"utopia-php/messaging": "0.22.*",
|
||||
"utopia-php/migration": "1.*",
|
||||
"utopia-php/platform": "0.13.*",
|
||||
"utopia-php/platform": "^1.0@RC",
|
||||
"utopia-php/pools": "1.*",
|
||||
"utopia-php/span": "1.1.*",
|
||||
"utopia-php/preloader": "0.2.*",
|
||||
|
||||
Generated
+102
-115
@@ -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": "ec2ad489c60f0102f0dfab223b6d1fe4",
|
||||
"content-hash": "9db08148f3a8f53bd972eb7b3a835b3b",
|
||||
"packages": [
|
||||
{
|
||||
"name": "adhocore/jwt",
|
||||
@@ -69,25 +69,25 @@
|
||||
},
|
||||
{
|
||||
"name": "appwrite/appwrite",
|
||||
"version": "19.1.0",
|
||||
"version": "23.1.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/appwrite/sdk-for-php.git",
|
||||
"reference": "8738e812062f899c85b2598eef43d6a247f08a56"
|
||||
"reference": "2f275921f10ceb7cff99f2d463f7328b296234fa"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/appwrite/sdk-for-php/zipball/8738e812062f899c85b2598eef43d6a247f08a56",
|
||||
"reference": "8738e812062f899c85b2598eef43d6a247f08a56",
|
||||
"url": "https://api.github.com/repos/appwrite/sdk-for-php/zipball/2f275921f10ceb7cff99f2d463f7328b296234fa",
|
||||
"reference": "2f275921f10ceb7cff99f2d463f7328b296234fa",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-curl": "*",
|
||||
"ext-json": "*",
|
||||
"php": ">=7.1.0"
|
||||
"php": ">=8.2.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"mockery/mockery": "^1.6.12",
|
||||
"mockery/mockery": "1.6.12",
|
||||
"phpunit/phpunit": "^10"
|
||||
},
|
||||
"type": "library",
|
||||
@@ -100,14 +100,14 @@
|
||||
"license": [
|
||||
"BSD-3-Clause"
|
||||
],
|
||||
"description": "Appwrite is an open-source self-hosted backend server that abstract and simplify complex and repetitive development tasks behind a very simple REST API",
|
||||
"description": "Appwrite is an open-source self-hosted backend server that abstracts and simplifies complex and repetitive development tasks behind a very simple REST API",
|
||||
"support": {
|
||||
"email": "team@appwrite.io",
|
||||
"issues": "https://github.com/appwrite/sdk-for-php/issues",
|
||||
"source": "https://github.com/appwrite/sdk-for-php/tree/19.1.0",
|
||||
"source": "https://github.com/appwrite/sdk-for-php/tree/23.1.0",
|
||||
"url": "https://appwrite.io/support"
|
||||
},
|
||||
"time": "2025-12-18T08:07:43+00:00"
|
||||
"time": "2026-05-08T13:44:58+00:00"
|
||||
},
|
||||
{
|
||||
"name": "appwrite/php-clamav",
|
||||
@@ -3359,24 +3359,24 @@
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/abuse",
|
||||
"version": "1.2.3",
|
||||
"version": "1.3.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/utopia-php/abuse.git",
|
||||
"reference": "53f4274939353522ba331f55bcff6e6011ffc56c"
|
||||
"reference": "5d7efbe5c6b0cf7d06003114fd86e24ba785582f"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/utopia-php/abuse/zipball/53f4274939353522ba331f55bcff6e6011ffc56c",
|
||||
"reference": "53f4274939353522ba331f55bcff6e6011ffc56c",
|
||||
"url": "https://api.github.com/repos/utopia-php/abuse/zipball/5d7efbe5c6b0cf7d06003114fd86e24ba785582f",
|
||||
"reference": "5d7efbe5c6b0cf7d06003114fd86e24ba785582f",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"appwrite/appwrite": "19.*",
|
||||
"appwrite/appwrite": "23.*",
|
||||
"ext-curl": "*",
|
||||
"ext-pdo": "*",
|
||||
"ext-redis": "*",
|
||||
"php": ">=8.0",
|
||||
"php": ">=8.2",
|
||||
"utopia-php/database": "5.*"
|
||||
},
|
||||
"require-dev": {
|
||||
@@ -3405,27 +3405,27 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/utopia-php/abuse/issues",
|
||||
"source": "https://github.com/utopia-php/abuse/tree/1.2.3"
|
||||
"source": "https://github.com/utopia-php/abuse/tree/1.3.0"
|
||||
},
|
||||
"time": "2026-04-29T11:19:08+00:00"
|
||||
"time": "2026-05-11T08:07:02+00:00"
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/agents",
|
||||
"version": "1.2.1",
|
||||
"version": "1.2.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/utopia-php/agents.git",
|
||||
"reference": "052227953678a30ecc4b5467401fcb0b2386471e"
|
||||
"reference": "0703f4cae02261e09a1bf0d39a4b1ce649cae634"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/utopia-php/agents/zipball/052227953678a30ecc4b5467401fcb0b2386471e",
|
||||
"reference": "052227953678a30ecc4b5467401fcb0b2386471e",
|
||||
"url": "https://api.github.com/repos/utopia-php/agents/zipball/0703f4cae02261e09a1bf0d39a4b1ce649cae634",
|
||||
"reference": "0703f4cae02261e09a1bf0d39a4b1ce649cae634",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=8.3",
|
||||
"utopia-php/fetch": "0.5.*"
|
||||
"utopia-php/fetch": "^1.1.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"laravel/pint": "^1.18",
|
||||
@@ -3458,9 +3458,9 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/utopia-php/agents/issues",
|
||||
"source": "https://github.com/utopia-php/agents/tree/1.2.1"
|
||||
"source": "https://github.com/utopia-php/agents/tree/1.2.2"
|
||||
},
|
||||
"time": "2026-02-24T06:03:55+00:00"
|
||||
"time": "2026-05-08T10:38:23+00:00"
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/analytics",
|
||||
@@ -3510,22 +3510,22 @@
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/audit",
|
||||
"version": "2.2.2",
|
||||
"version": "2.2.3",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/utopia-php/audit.git",
|
||||
"reference": "90886c202e7983999e6b6a8201004d5ab61d4b57"
|
||||
"reference": "95e9961fa286d2fdb6bf3eaa198f21d51bf58d9c"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/utopia-php/audit/zipball/90886c202e7983999e6b6a8201004d5ab61d4b57",
|
||||
"reference": "90886c202e7983999e6b6a8201004d5ab61d4b57",
|
||||
"url": "https://api.github.com/repos/utopia-php/audit/zipball/95e9961fa286d2fdb6bf3eaa198f21d51bf58d9c",
|
||||
"reference": "95e9961fa286d2fdb6bf3eaa198f21d51bf58d9c",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=8.0",
|
||||
"utopia-php/database": "5.*",
|
||||
"utopia-php/fetch": "0.5.*",
|
||||
"utopia-php/fetch": "^1.1",
|
||||
"utopia-php/validators": "0.2.*"
|
||||
},
|
||||
"require-dev": {
|
||||
@@ -3553,9 +3553,9 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/utopia-php/audit/issues",
|
||||
"source": "https://github.com/utopia-php/audit/tree/2.2.2"
|
||||
"source": "https://github.com/utopia-php/audit/tree/2.2.3"
|
||||
},
|
||||
"time": "2026-05-04T06:48:58+00:00"
|
||||
"time": "2026-05-08T10:38:23+00:00"
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/auth",
|
||||
@@ -3614,16 +3614,16 @@
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/cache",
|
||||
"version": "1.0.1",
|
||||
"version": "1.0.3",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/utopia-php/cache.git",
|
||||
"reference": "05ceba981436a4022553f7aaa2a05fa049d0f71c"
|
||||
"reference": "ef52a04e8bfa314c621e3d3326ffcf50db3dfdfa"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/utopia-php/cache/zipball/05ceba981436a4022553f7aaa2a05fa049d0f71c",
|
||||
"reference": "05ceba981436a4022553f7aaa2a05fa049d0f71c",
|
||||
"url": "https://api.github.com/repos/utopia-php/cache/zipball/ef52a04e8bfa314c621e3d3326ffcf50db3dfdfa",
|
||||
"reference": "ef52a04e8bfa314c621e3d3326ffcf50db3dfdfa",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -3660,9 +3660,9 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/utopia-php/cache/issues",
|
||||
"source": "https://github.com/utopia-php/cache/tree/1.0.1"
|
||||
"source": "https://github.com/utopia-php/cache/tree/1.0.3"
|
||||
},
|
||||
"time": "2026-03-12T03:39:09+00:00"
|
||||
"time": "2026-05-11T11:02:13+00:00"
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/cli",
|
||||
@@ -4180,22 +4180,21 @@
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/emails",
|
||||
"version": "0.6.9",
|
||||
"version": "0.6.10",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/utopia-php/emails.git",
|
||||
"reference": "3a59fb392a03a88f5497e5fdb0ea84a252a4dfdf"
|
||||
"reference": "2e397754ce68c2ba918564b9f31d9923c0a90429"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/utopia-php/emails/zipball/3a59fb392a03a88f5497e5fdb0ea84a252a4dfdf",
|
||||
"reference": "3a59fb392a03a88f5497e5fdb0ea84a252a4dfdf",
|
||||
"url": "https://api.github.com/repos/utopia-php/emails/zipball/2e397754ce68c2ba918564b9f31d9923c0a90429",
|
||||
"reference": "2e397754ce68c2ba918564b9f31d9923c0a90429",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=8.0",
|
||||
"utopia-php/domains": "^1.0",
|
||||
"utopia-php/fetch": "^0.5",
|
||||
"utopia-php/validators": "0.*"
|
||||
},
|
||||
"require-dev": {
|
||||
@@ -4203,7 +4202,8 @@
|
||||
"phpstan/phpstan": "^1.10",
|
||||
"phpunit/phpunit": "^9.3",
|
||||
"utopia-php/cli": "^0.22",
|
||||
"utopia-php/console": "0.*"
|
||||
"utopia-php/console": "0.*",
|
||||
"utopia-php/fetch": "^1.1"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
@@ -4235,22 +4235,22 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/utopia-php/emails/issues",
|
||||
"source": "https://github.com/utopia-php/emails/tree/0.6.9"
|
||||
"source": "https://github.com/utopia-php/emails/tree/0.6.10"
|
||||
},
|
||||
"time": "2026-03-14T13:52:56+00:00"
|
||||
"time": "2026-05-08T10:16:22+00:00"
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/fetch",
|
||||
"version": "0.5.1",
|
||||
"version": "1.1.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/utopia-php/fetch.git",
|
||||
"reference": "a96a010e1c273f3888765449687baf58cbc61fcd"
|
||||
"reference": "64f2b3a789480f1deb102ce684dac4217d8e98d5"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/utopia-php/fetch/zipball/a96a010e1c273f3888765449687baf58cbc61fcd",
|
||||
"reference": "a96a010e1c273f3888765449687baf58cbc61fcd",
|
||||
"url": "https://api.github.com/repos/utopia-php/fetch/zipball/64f2b3a789480f1deb102ce684dac4217d8e98d5",
|
||||
"reference": "64f2b3a789480f1deb102ce684dac4217d8e98d5",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -4259,7 +4259,8 @@
|
||||
"require-dev": {
|
||||
"laravel/pint": "^1.5.0",
|
||||
"phpstan/phpstan": "^1.10",
|
||||
"phpunit/phpunit": "^9.5"
|
||||
"phpunit/phpunit": "^9.5",
|
||||
"swoole/ide-helper": "^6.0"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
@@ -4274,22 +4275,22 @@
|
||||
"description": "A simple library that provides an interface for making HTTP Requests.",
|
||||
"support": {
|
||||
"issues": "https://github.com/utopia-php/fetch/issues",
|
||||
"source": "https://github.com/utopia-php/fetch/tree/0.5.1"
|
||||
"source": "https://github.com/utopia-php/fetch/tree/1.1.2"
|
||||
},
|
||||
"time": "2025-12-18T16:25:10+00:00"
|
||||
"time": "2026-04-29T11:19:19+00:00"
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/http",
|
||||
"version": "0.34.25",
|
||||
"version": "2.0.0-rc1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/utopia-php/http.git",
|
||||
"reference": "76be330d4197bae680eb4ccc29c573456fe91904"
|
||||
"reference": "3e3b431d443844c6bf810120dee735f45880856f"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/utopia-php/http/zipball/76be330d4197bae680eb4ccc29c573456fe91904",
|
||||
"reference": "76be330d4197bae680eb4ccc29c573456fe91904",
|
||||
"url": "https://api.github.com/repos/utopia-php/http/zipball/3e3b431d443844c6bf810120dee735f45880856f",
|
||||
"reference": "3e3b431d443844c6bf810120dee735f45880856f",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -4330,9 +4331,9 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/utopia-php/http/issues",
|
||||
"source": "https://github.com/utopia-php/http/tree/0.34.25"
|
||||
"source": "https://github.com/utopia-php/http/tree/2.0.0-rc1"
|
||||
},
|
||||
"time": "2026-05-05T04:39:15+00:00"
|
||||
"time": "2026-05-05T15:00:03+00:00"
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/image",
|
||||
@@ -4434,20 +4435,21 @@
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/logger",
|
||||
"version": "0.6.2",
|
||||
"version": "0.8.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/utopia-php/logger.git",
|
||||
"reference": "25b5bd2ad8bb51292f76332faa7034644fd0941d"
|
||||
"reference": "132236c42222cd614cb882938a48f8729ef3118b"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/utopia-php/logger/zipball/25b5bd2ad8bb51292f76332faa7034644fd0941d",
|
||||
"reference": "25b5bd2ad8bb51292f76332faa7034644fd0941d",
|
||||
"url": "https://api.github.com/repos/utopia-php/logger/zipball/132236c42222cd614cb882938a48f8729ef3118b",
|
||||
"reference": "132236c42222cd614cb882938a48f8729ef3118b",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=8.0"
|
||||
"php": ">=8.1",
|
||||
"utopia-php/fetch": "^1.1"
|
||||
},
|
||||
"require-dev": {
|
||||
"laravel/pint": "1.2.*",
|
||||
@@ -4482,9 +4484,9 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/utopia-php/logger/issues",
|
||||
"source": "https://github.com/utopia-php/logger/tree/0.6.2"
|
||||
"source": "https://github.com/utopia-php/logger/tree/0.8.0"
|
||||
},
|
||||
"time": "2024-10-14T16:02:49+00:00"
|
||||
"time": "2026-05-05T06:04:27+00:00"
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/messaging",
|
||||
@@ -4539,24 +4541,24 @@
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/migration",
|
||||
"version": "1.10.1",
|
||||
"version": "1.11.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/utopia-php/migration.git",
|
||||
"reference": "759d6d61b327313cbeeeb4ea0c3e2459164b4827"
|
||||
"reference": "0fca44f40ad07bf2d56e9396afa6fa6d9b098ef1"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/utopia-php/migration/zipball/759d6d61b327313cbeeeb4ea0c3e2459164b4827",
|
||||
"reference": "759d6d61b327313cbeeeb4ea0c3e2459164b4827",
|
||||
"url": "https://api.github.com/repos/utopia-php/migration/zipball/0fca44f40ad07bf2d56e9396afa6fa6d9b098ef1",
|
||||
"reference": "0fca44f40ad07bf2d56e9396afa6fa6d9b098ef1",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"appwrite/appwrite": "19.*",
|
||||
"appwrite/appwrite": "23.*",
|
||||
"ext-curl": "*",
|
||||
"ext-openssl": "*",
|
||||
"halaxa/json-machine": "^1.2",
|
||||
"php": ">=8.1",
|
||||
"php": ">=8.2",
|
||||
"utopia-php/database": "5.*",
|
||||
"utopia-php/dsn": "0.2.*",
|
||||
"utopia-php/storage": "2.*"
|
||||
@@ -4574,25 +4576,7 @@
|
||||
"Utopia\\Migration\\": "src/Migration"
|
||||
}
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"Utopia\\Tests\\": "tests/Migration"
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"test": [
|
||||
"./vendor/bin/phpunit"
|
||||
],
|
||||
"lint": [
|
||||
"./vendor/bin/pint --test"
|
||||
],
|
||||
"format": [
|
||||
"./vendor/bin/pint"
|
||||
],
|
||||
"check": [
|
||||
"./vendor/bin/phpstan analyse --level 3 src tests --memory-limit 2G"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
@@ -4605,10 +4589,10 @@
|
||||
"utopia"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/utopia-php/migration/tree/1.10.1",
|
||||
"issues": "https://github.com/utopia-php/migration/issues"
|
||||
"issues": "https://github.com/utopia-php/migration/issues",
|
||||
"source": "https://github.com/utopia-php/migration/tree/1.11.0"
|
||||
},
|
||||
"time": "2026-05-07T07:23:57+00:00"
|
||||
"time": "2026-05-11T08:13:06+00:00"
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/mongo",
|
||||
@@ -4673,16 +4657,16 @@
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/platform",
|
||||
"version": "0.13.2",
|
||||
"version": "1.0.0-rc1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/utopia-php/platform.git",
|
||||
"reference": "a20cb8b20a1e4c9886309c2d033a0292ba0937b9"
|
||||
"reference": "36c0a8b2f3d96ca056d724701a302a127111e933"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/utopia-php/platform/zipball/a20cb8b20a1e4c9886309c2d033a0292ba0937b9",
|
||||
"reference": "a20cb8b20a1e4c9886309c2d033a0292ba0937b9",
|
||||
"url": "https://api.github.com/repos/utopia-php/platform/zipball/36c0a8b2f3d96ca056d724701a302a127111e933",
|
||||
"reference": "36c0a8b2f3d96ca056d724701a302a127111e933",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -4690,7 +4674,7 @@
|
||||
"ext-redis": "*",
|
||||
"php": ">=8.3",
|
||||
"utopia-php/cli": "0.23.3",
|
||||
"utopia-php/http": "0.34.25",
|
||||
"utopia-php/http": "^2.0@RC",
|
||||
"utopia-php/queue": "0.18.2",
|
||||
"utopia-php/servers": "0.4.0"
|
||||
},
|
||||
@@ -4718,9 +4702,9 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/utopia-php/platform/issues",
|
||||
"source": "https://github.com/utopia-php/platform/tree/0.13.2"
|
||||
"source": "https://github.com/utopia-php/platform/tree/1.0.0-rc1"
|
||||
},
|
||||
"time": "2026-05-05T06:00:26+00:00"
|
||||
"time": "2026-05-05T15:09:27+00:00"
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/pools",
|
||||
@@ -5254,23 +5238,23 @@
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/vcs",
|
||||
"version": "3.2.0",
|
||||
"version": "3.2.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/utopia-php/vcs.git",
|
||||
"reference": "44a84ab52b42fc12f812b4d7331286b519d39db3"
|
||||
"reference": "03ccd12b75d67d29094eb760b468fddde4b6b5e5"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/utopia-php/vcs/zipball/44a84ab52b42fc12f812b4d7331286b519d39db3",
|
||||
"reference": "44a84ab52b42fc12f812b4d7331286b519d39db3",
|
||||
"url": "https://api.github.com/repos/utopia-php/vcs/zipball/03ccd12b75d67d29094eb760b468fddde4b6b5e5",
|
||||
"reference": "03ccd12b75d67d29094eb760b468fddde4b6b5e5",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"adhocore/jwt": "^1.1",
|
||||
"php": ">=8.0",
|
||||
"utopia-php/cache": "1.0.*",
|
||||
"utopia-php/fetch": "0.5.*"
|
||||
"utopia-php/fetch": "^1.1"
|
||||
},
|
||||
"require-dev": {
|
||||
"laravel/pint": "1.*.*",
|
||||
@@ -5297,9 +5281,9 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/utopia-php/vcs/issues",
|
||||
"source": "https://github.com/utopia-php/vcs/tree/3.2.0"
|
||||
"source": "https://github.com/utopia-php/vcs/tree/3.2.1"
|
||||
},
|
||||
"time": "2026-04-08T16:00:31+00:00"
|
||||
"time": "2026-05-08T10:13:53+00:00"
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/websocket",
|
||||
@@ -5492,16 +5476,16 @@
|
||||
"packages-dev": [
|
||||
{
|
||||
"name": "appwrite/sdk-generator",
|
||||
"version": "1.27.5",
|
||||
"version": "1.28.4",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/appwrite/sdk-generator.git",
|
||||
"reference": "9faa38b48d422f3da764a719712905c83b3922cb"
|
||||
"reference": "38de925e8c9e7f0f720d45187be54a291aaf696b"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/9faa38b48d422f3da764a719712905c83b3922cb",
|
||||
"reference": "9faa38b48d422f3da764a719712905c83b3922cb",
|
||||
"url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/38de925e8c9e7f0f720d45187be54a291aaf696b",
|
||||
"reference": "38de925e8c9e7f0f720d45187be54a291aaf696b",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -5537,9 +5521,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.27.5"
|
||||
"source": "https://github.com/appwrite/sdk-generator/tree/1.28.4"
|
||||
},
|
||||
"time": "2026-05-05T12:09:40+00:00"
|
||||
"time": "2026-05-11T13:55:49+00:00"
|
||||
},
|
||||
{
|
||||
"name": "brianium/paratest",
|
||||
@@ -8471,7 +8455,10 @@
|
||||
],
|
||||
"aliases": [],
|
||||
"minimum-stability": "dev",
|
||||
"stability-flags": {},
|
||||
"stability-flags": {
|
||||
"utopia-php/http": 5,
|
||||
"utopia-php/platform": 5
|
||||
},
|
||||
"prefer-stable": true,
|
||||
"prefer-lowest": false,
|
||||
"platform": {
|
||||
|
||||
+15
-1
@@ -1114,6 +1114,13 @@ services:
|
||||
- _APP_WORKER_PER_CORE
|
||||
- _APP_POOL_ADAPTER
|
||||
- _APP_OPENSSL_KEY_V1
|
||||
- _APP_OPTIONS_FORCE_HTTPS
|
||||
- _APP_DOMAIN
|
||||
- _APP_CONSOLE_DOMAIN
|
||||
- _APP_DOMAIN_FUNCTIONS
|
||||
- _APP_DOMAIN_SITES
|
||||
- _APP_MIGRATION_HOST
|
||||
- _APP_CONSOLE_SCHEMA
|
||||
- _APP_REDIS_HOST
|
||||
- _APP_REDIS_PORT
|
||||
- _APP_REDIS_USER
|
||||
@@ -1145,6 +1152,13 @@ services:
|
||||
- _APP_WORKER_PER_CORE
|
||||
- _APP_POOL_ADAPTER
|
||||
- _APP_OPENSSL_KEY_V1
|
||||
- _APP_OPTIONS_FORCE_HTTPS
|
||||
- _APP_DOMAIN
|
||||
- _APP_CONSOLE_DOMAIN
|
||||
- _APP_DOMAIN_FUNCTIONS
|
||||
- _APP_DOMAIN_SITES
|
||||
- _APP_MIGRATION_HOST
|
||||
- _APP_CONSOLE_SCHEMA
|
||||
- _APP_REDIS_HOST
|
||||
- _APP_REDIS_PORT
|
||||
- _APP_REDIS_USER
|
||||
@@ -1478,4 +1492,4 @@ volumes:
|
||||
appwrite-sites:
|
||||
appwrite-builds:
|
||||
appwrite-config:
|
||||
appwrite-models:
|
||||
appwrite-models:
|
||||
|
||||
@@ -55,7 +55,7 @@ class Google extends OAuth2
|
||||
'state' => \json_encode($this->state),
|
||||
'response_type' => 'code',
|
||||
'access_type' => 'offline',
|
||||
'prompt' => 'consent'
|
||||
'prompt' => $this->getPrompt()
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -72,7 +72,7 @@ class Google extends OAuth2
|
||||
'https://oauth2.googleapis.com/token?' . \http_build_query([
|
||||
'code' => $code,
|
||||
'client_id' => $this->appID,
|
||||
'client_secret' => $this->appSecret,
|
||||
'client_secret' => $this->getClientSecret(),
|
||||
'redirect_uri' => $this->callback,
|
||||
'scope' => null,
|
||||
'grant_type' => 'authorization_code'
|
||||
@@ -95,7 +95,7 @@ class Google extends OAuth2
|
||||
'https://oauth2.googleapis.com/token?' . \http_build_query([
|
||||
'refresh_token' => $refreshToken,
|
||||
'client_id' => $this->appID,
|
||||
'client_secret' => $this->appSecret,
|
||||
'client_secret' => $this->getClientSecret(),
|
||||
'grant_type' => 'refresh_token'
|
||||
])
|
||||
), true);
|
||||
@@ -177,4 +177,54 @@ class Google extends OAuth2
|
||||
|
||||
return $this->user;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the Client Secret from the JSON stored in appSecret
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function getClientSecret(): string
|
||||
{
|
||||
$secret = $this->getAppSecret();
|
||||
|
||||
return $secret['clientSecret'] ?? $this->appSecret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the prompt values from the JSON stored in appSecret
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function getPrompt(): string
|
||||
{
|
||||
$secret = $this->getAppSecret();
|
||||
$prompt = $secret['prompt'] ?? [];
|
||||
|
||||
if (empty($prompt)) {
|
||||
$prompt = ['consent'];
|
||||
}
|
||||
|
||||
return \implode(' ', $prompt);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode the JSON stored in appSecret.
|
||||
* Falls back to treating the raw string as the client secret for backwards compatibility.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getAppSecret(): array
|
||||
{
|
||||
try {
|
||||
$secret = \json_decode($this->appSecret, true, 512, JSON_THROW_ON_ERROR);
|
||||
} catch (\Throwable $th) {
|
||||
return ['clientSecret' => $this->appSecret];
|
||||
}
|
||||
|
||||
if (!\is_array($secret)) {
|
||||
return ['clientSecret' => $this->appSecret];
|
||||
}
|
||||
|
||||
return $secret;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,14 +4,14 @@ namespace Appwrite\Bus\Listeners;
|
||||
|
||||
use Appwrite\Auth\MFA\Type;
|
||||
use Appwrite\Bus\Events\SessionCreated;
|
||||
use Appwrite\Event\Mail;
|
||||
use Appwrite\Event\Message\Mail as MailMessage;
|
||||
use Appwrite\Event\Publisher\Mail as MailPublisher;
|
||||
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;
|
||||
|
||||
@@ -31,14 +31,14 @@ class Mails extends Listener
|
||||
{
|
||||
$this
|
||||
->desc('Sends session alert emails')
|
||||
->inject('publisher')
|
||||
->inject('publisherForMails')
|
||||
->inject('locale')
|
||||
->inject('platform')
|
||||
->inject('dbForProject')
|
||||
->callback($this->handle(...));
|
||||
}
|
||||
|
||||
public function handle(SessionCreated $event, Publisher $publisher, Locale $locale, array $platform, Database $dbForProject): void
|
||||
public function handle(SessionCreated $event, MailPublisher $publisherForMails, Locale $locale, array $platform, Database $dbForProject): void
|
||||
{
|
||||
$project = new Document($event->project);
|
||||
|
||||
@@ -124,34 +124,32 @@ class Mails extends Listener
|
||||
];
|
||||
}
|
||||
|
||||
$queueForMails = new Mail($publisher);
|
||||
|
||||
$smtpConfig = [];
|
||||
if ($smtp['enabled'] ?? false) {
|
||||
$queueForMails
|
||||
->setSmtpHost($smtp['host'] ?? '')
|
||||
->setSmtpPort($smtp['port'] ?? '')
|
||||
->setSmtpUsername($smtp['username'] ?? '')
|
||||
->setSmtpPassword($smtp['password'] ?? '')
|
||||
->setSmtpSecure($smtp['secure'] ?? '')
|
||||
->setSmtpReplyToEmail($customTemplate['replyToEmail'] ?? $customTemplate['replyTo'] ?? $smtp['replyToEmail'] ?? $smtp['replyTo'] ?? '') // Includes backwards compatibility
|
||||
->setSmtpReplyToName($customTemplate['replyToName'] ?? $smtp['replyToName'] ?? '')
|
||||
->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'));
|
||||
$smtpConfig = [
|
||||
'host' => $smtp['host'] ?? '',
|
||||
'port' => $smtp['port'] ?? '',
|
||||
'username' => $smtp['username'] ?? '',
|
||||
'password' => $smtp['password'] ?? '',
|
||||
'secure' => $smtp['secure'] ?? '',
|
||||
'replyToEmail' => $customTemplate['replyToEmail'] ?? $customTemplate['replyTo'] ?? $smtp['replyToEmail'] ?? $smtp['replyTo'] ?? '', // Includes backwards compatibility
|
||||
'replyToName' => $customTemplate['replyToName'] ?? $smtp['replyToName'] ?? '',
|
||||
'senderEmail' => $customTemplate['senderEmail'] ?? $smtp['senderEmail'] ?? System::getEnv('_APP_SYSTEM_EMAIL_ADDRESS', APP_EMAIL_TEAM),
|
||||
'senderName' => $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();
|
||||
$publisherForMails->enqueue(new MailMessage(
|
||||
project: $project,
|
||||
recipient: $event->user['email'],
|
||||
subject: $subject,
|
||||
bodyTemplate: __DIR__ . '/../../../../app/config/locale/templates/' . $smtpBaseTemplate . '.tpl',
|
||||
body: $body,
|
||||
preview: $preview,
|
||||
smtp: $smtpConfig,
|
||||
variables: $emailVariables,
|
||||
customMailOptions: $isBranded ? ['senderName' => $platform['emailSenderName']] : [],
|
||||
platform: $platform,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,576 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Event;
|
||||
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\Queue\Publisher;
|
||||
use Utopia\System\System;
|
||||
|
||||
class Mail extends Event
|
||||
{
|
||||
protected string $recipient = '';
|
||||
protected string $name = '';
|
||||
protected string $subject = '';
|
||||
protected string $body = '';
|
||||
protected string $preview = '';
|
||||
protected array $smtp = [];
|
||||
protected array $variables = [];
|
||||
protected string $bodyTemplate = '';
|
||||
protected array $attachment = [];
|
||||
|
||||
protected array $customMailOptions = [];
|
||||
|
||||
public function __construct(protected Publisher $publisher)
|
||||
{
|
||||
parent::__construct($publisher);
|
||||
|
||||
$this
|
||||
->setQueue(System::getEnv('_APP_MAILS_QUEUE_NAME', Event::MAILS_QUEUE_NAME))
|
||||
->setClass(System::getEnv('_APP_MAILS_CLASS_NAME', Event::MAILS_CLASS_NAME));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets subject for the mail event.
|
||||
*
|
||||
* @param string $subject
|
||||
* @return self
|
||||
*/
|
||||
public function setSubject(string $subject): self
|
||||
{
|
||||
$this->subject = $subject;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns subject for the mail event.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getSubject(): string
|
||||
{
|
||||
return $this->subject;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets recipient for the mail event.
|
||||
*
|
||||
* @param string $recipient
|
||||
* @return self
|
||||
*/
|
||||
public function setRecipient(string $recipient): self
|
||||
{
|
||||
$this->recipient = $recipient;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns set recipient for mail event.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getRecipient(): string
|
||||
{
|
||||
return $this->recipient;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets body for the mail event.
|
||||
*
|
||||
* @param string $body
|
||||
* @return self
|
||||
*/
|
||||
public function setBody(string $body): self
|
||||
{
|
||||
$this->body = $body;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns body for the mail event.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getBody(): string
|
||||
{
|
||||
return $this->body;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets preview for the mail event.
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function setPreview(string $preview): self
|
||||
{
|
||||
$this->preview = $preview;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns preview for the mail event.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getPreview(): string
|
||||
{
|
||||
return $this->preview;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets name for the mail event.
|
||||
*
|
||||
* @param string $name
|
||||
* @return self
|
||||
*/
|
||||
public function setName(string $name): self
|
||||
{
|
||||
$this->name = $name;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns set name for the mail event.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getName(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets bodyTemplate for the mail event.
|
||||
*
|
||||
* @param string $bodyTemplate
|
||||
* @return self
|
||||
*/
|
||||
public function setBodyTemplate(string $bodyTemplate): self
|
||||
{
|
||||
$this->bodyTemplate = $bodyTemplate;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns subject for the mail event.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getBodyTemplate(): string
|
||||
{
|
||||
return $this->bodyTemplate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set SMTP Host
|
||||
*
|
||||
* @param string $host
|
||||
* @return self
|
||||
*/
|
||||
public function setSmtpHost(string $host): self
|
||||
{
|
||||
$this->smtp['host'] = $host;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set SMTP port
|
||||
*
|
||||
* @param int $port
|
||||
* @return self
|
||||
*/
|
||||
public function setSmtpPort(int $port): self
|
||||
{
|
||||
$this->smtp['port'] = $port;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set SMTP username
|
||||
*
|
||||
* @param string $username
|
||||
* @return self
|
||||
*/
|
||||
public function setSmtpUsername(string $username): self
|
||||
{
|
||||
$this->smtp['username'] = $username;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set SMTP password
|
||||
*
|
||||
* @param string $password
|
||||
* @return self
|
||||
*/
|
||||
public function setSmtpPassword(string $password): self
|
||||
{
|
||||
$this->smtp['password'] = $password;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set SMTP secure
|
||||
*
|
||||
* @param string $secure
|
||||
* @return self
|
||||
*/
|
||||
public function setSmtpSecure(string $secure): self
|
||||
{
|
||||
$this->smtp['secure'] = $secure;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set SMTP sender email
|
||||
*
|
||||
* @param string $senderEmail
|
||||
* @return self
|
||||
*/
|
||||
public function setSmtpSenderEmail(string $senderEmail): self
|
||||
{
|
||||
$this->smtp['senderEmail'] = $senderEmail;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set SMTP sender name
|
||||
*
|
||||
* @param string $senderName
|
||||
* @return self
|
||||
*/
|
||||
public function setSmtpSenderName(string $senderName): self
|
||||
{
|
||||
$this->smtp['senderName'] = $senderName;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set SMTP reply-to email
|
||||
*
|
||||
* @param string $email
|
||||
* @return self
|
||||
*/
|
||||
public function setSmtpReplyToEmail(string $email): self
|
||||
{
|
||||
$this->smtp['replyToEmail'] = $email;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set SMTP reply-to name
|
||||
*
|
||||
* @param string $name
|
||||
* @return self
|
||||
*/
|
||||
public function setSmtpReplyToName(string $name): self
|
||||
{
|
||||
$this->smtp['replyToName'] = $name;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get SMTP
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getSmtpHost(): string
|
||||
{
|
||||
return $this->smtp['host'] ?? '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get SMTP port
|
||||
*
|
||||
* @return integer
|
||||
*/
|
||||
public function getSmtpPort(): int
|
||||
{
|
||||
return $this->smtp['port'] ?? 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get SMTP username
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getSmtpUsername(): string
|
||||
{
|
||||
return $this->smtp['username'] ?? '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get SMTP password
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getSmtpPassword(): string
|
||||
{
|
||||
return $this->smtp['password'] ?? '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get SMTP secure
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getSmtpSecure(): string
|
||||
{
|
||||
return $this->smtp['secure'] ?? '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get SMTP sender email
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getSmtpSenderEmail(): string
|
||||
{
|
||||
return $this->smtp['senderEmail'] ?? '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get SMTP sender name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getSmtpSenderName(): string
|
||||
{
|
||||
return $this->smtp['senderName'] ?? '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get SMTP reply-to email
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getSmtpReplyToEmail(): string
|
||||
{
|
||||
return $this->smtp['replyToEmail'] ?? '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get SMTP reply-to name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getSmtpReplyToName(): string
|
||||
{
|
||||
return $this->smtp['replyToName'] ?? '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Email Variables
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getVariables(): array
|
||||
{
|
||||
return $this->variables;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set Email Variables
|
||||
*
|
||||
* @param array $variables
|
||||
* @return self
|
||||
*/
|
||||
public function setVariables(array $variables): self
|
||||
{
|
||||
$this->variables = $variables;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Append variables to the email event.
|
||||
*
|
||||
* @param array $variables
|
||||
* @return self
|
||||
*/
|
||||
public function appendVariables(array $variables): self
|
||||
{
|
||||
$this->variables = \array_merge($this->variables, $variables);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set attachment
|
||||
* @param string $content
|
||||
* @param string $filename
|
||||
* @param string $encoding
|
||||
* @param string $type
|
||||
* @return self
|
||||
*/
|
||||
public function setAttachment(string $content, string $filename, string $encoding = 'base64', string $type = 'plain/text')
|
||||
{
|
||||
$this->attachment = [
|
||||
'content' => base64_encode($content),
|
||||
'filename' => $filename,
|
||||
'encoding' => $encoding,
|
||||
'type' => $type,
|
||||
];
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get attachment
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getAttachment(): array
|
||||
{
|
||||
return $this->attachment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset attachment
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function resetAttachment(): self
|
||||
{
|
||||
$this->attachment = [];
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set sender email
|
||||
*
|
||||
* @param string $email
|
||||
* @return self
|
||||
*/
|
||||
public function setSenderEmail(string $email): self
|
||||
{
|
||||
$this->customMailOptions['senderEmail'] = $email;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get sender email
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getSenderEmail(): string
|
||||
{
|
||||
return $this->customMailOptions['senderEmail'] ?? '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Set sender name
|
||||
*
|
||||
* @param string $name
|
||||
* @return self
|
||||
*/
|
||||
public function setSenderName(string $name): self
|
||||
{
|
||||
$this->customMailOptions['senderName'] = $name;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get sender name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getSenderName(): string
|
||||
{
|
||||
return $this->customMailOptions['senderName'] ?? '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Set reply-to email
|
||||
*
|
||||
* @param string $email
|
||||
* @return self
|
||||
*/
|
||||
public function setReplyToEmail(string $email): self
|
||||
{
|
||||
$this->customMailOptions['replyToEmail'] = $email;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get reply-to email
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getReplyToEmail(): string
|
||||
{
|
||||
return $this->customMailOptions['replyToEmail'] ?? '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Set reply-to name
|
||||
*
|
||||
* @param string $name
|
||||
* @return self
|
||||
*/
|
||||
public function setReplyToName(string $name): self
|
||||
{
|
||||
$this->customMailOptions['replyToName'] = $name;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get reply-to name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getReplyToName(): string
|
||||
{
|
||||
return $this->customMailOptions['replyToName'] ?? '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function reset(): self
|
||||
{
|
||||
$this->project = null;
|
||||
$this->recipient = '';
|
||||
$this->name = '';
|
||||
$this->subject = '';
|
||||
$this->body = '';
|
||||
$this->variables = [];
|
||||
$this->bodyTemplate = '';
|
||||
$this->attachment = [];
|
||||
$this->customMailOptions = [];
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the payload for the event
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function preparePayload(): array
|
||||
{
|
||||
$platform = $this->platform;
|
||||
if (empty($platform)) {
|
||||
$platform = Config::getParam('platform', []);
|
||||
}
|
||||
|
||||
return [
|
||||
'project' => $this->project,
|
||||
'recipient' => $this->recipient,
|
||||
'name' => $this->name,
|
||||
'subject' => $this->subject,
|
||||
'bodyTemplate' => $this->bodyTemplate,
|
||||
'body' => $this->body,
|
||||
'preview' => $this->preview,
|
||||
'smtp' => $this->smtp,
|
||||
'variables' => $this->variables,
|
||||
'attachment' => $this->attachment,
|
||||
'customMailOptions' => $this->customMailOptions,
|
||||
'events' => Event::generateEvents($this->getEvent(), $this->getParams()),
|
||||
'platform' => $platform,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Event\Message;
|
||||
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\Database\Document;
|
||||
|
||||
final class Mail extends Base
|
||||
{
|
||||
public function __construct(
|
||||
public readonly ?Document $project = null,
|
||||
public readonly string $recipient = '',
|
||||
public readonly string $name = '',
|
||||
public readonly string $subject = '',
|
||||
public readonly string $bodyTemplate = '',
|
||||
public readonly string $body = '',
|
||||
public readonly string $preview = '',
|
||||
public readonly array $smtp = [],
|
||||
public readonly array $variables = [],
|
||||
public readonly array $attachment = [],
|
||||
public readonly array $customMailOptions = [],
|
||||
public readonly array $events = [],
|
||||
public readonly array $platform = [],
|
||||
) {
|
||||
}
|
||||
|
||||
public function toArray(): array
|
||||
{
|
||||
$platform = !empty($this->platform) ? $this->platform : Config::getParam('platform', []);
|
||||
|
||||
return [
|
||||
'project' => $this->project?->getArrayCopy(),
|
||||
'recipient' => $this->recipient,
|
||||
'name' => $this->name,
|
||||
'subject' => $this->subject,
|
||||
'bodyTemplate' => $this->bodyTemplate,
|
||||
'body' => $this->body,
|
||||
'preview' => $this->preview,
|
||||
'smtp' => $this->smtp,
|
||||
'variables' => $this->variables,
|
||||
'attachment' => $this->attachment,
|
||||
'customMailOptions' => $this->customMailOptions,
|
||||
'events' => $this->events,
|
||||
'platform' => $platform,
|
||||
];
|
||||
}
|
||||
|
||||
public static function fromArray(array $data): static
|
||||
{
|
||||
return new self(
|
||||
project: !empty($data['project']) ? new Document($data['project']) : null,
|
||||
recipient: $data['recipient'] ?? '',
|
||||
name: $data['name'] ?? '',
|
||||
subject: $data['subject'] ?? '',
|
||||
bodyTemplate: $data['bodyTemplate'] ?? '',
|
||||
body: $data['body'] ?? '',
|
||||
preview: $data['preview'] ?? '',
|
||||
smtp: $data['smtp'] ?? [],
|
||||
variables: $data['variables'] ?? [],
|
||||
attachment: $data['attachment'] ?? [],
|
||||
customMailOptions: $data['customMailOptions'] ?? [],
|
||||
events: $data['events'] ?? [],
|
||||
platform: $data['platform'] ?? [],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Event\Message;
|
||||
|
||||
use Utopia\Database\Document;
|
||||
|
||||
final class Messaging extends Base
|
||||
{
|
||||
public function __construct(
|
||||
public readonly string $type,
|
||||
public readonly Document $project,
|
||||
public readonly ?Document $user = null,
|
||||
public readonly ?string $messageId = null,
|
||||
public readonly ?Document $message = null,
|
||||
public readonly ?array $recipients = null,
|
||||
public readonly ?string $providerType = null,
|
||||
) {
|
||||
}
|
||||
|
||||
public function toArray(): array
|
||||
{
|
||||
return [
|
||||
'type' => $this->type,
|
||||
'project' => $this->project->getArrayCopy(),
|
||||
'user' => $this->user?->getArrayCopy(),
|
||||
'messageId' => $this->messageId,
|
||||
'message' => $this->message?->getArrayCopy(),
|
||||
'recipients' => $this->recipients,
|
||||
'providerType' => $this->providerType,
|
||||
];
|
||||
}
|
||||
|
||||
public static function fromArray(array $data): static
|
||||
{
|
||||
return new self(
|
||||
type: $data['type'] ?? '',
|
||||
project: new Document($data['project'] ?? []),
|
||||
user: !empty($data['user']) ? new Document($data['user']) : null,
|
||||
messageId: $data['messageId'] ?? null,
|
||||
message: !empty($data['message']) ? new Document($data['message']) : null,
|
||||
recipients: $data['recipients'] ?? null,
|
||||
providerType: $data['providerType'] ?? null,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,182 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Event;
|
||||
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Queue\Publisher;
|
||||
use Utopia\System\System;
|
||||
|
||||
class Messaging extends Event
|
||||
{
|
||||
protected string $type = '';
|
||||
protected ?string $messageId = null;
|
||||
protected ?Document $message = null;
|
||||
protected ?array $recipients = null;
|
||||
protected ?string $scheduledAt = null;
|
||||
protected ?string $providerType = null;
|
||||
|
||||
public function __construct(protected Publisher $publisher)
|
||||
{
|
||||
parent::__construct($publisher);
|
||||
|
||||
$this
|
||||
->setQueue(System::getEnv('_APP_MESSAGING_QUEUE_NAME', Event::MESSAGING_QUEUE_NAME))
|
||||
->setClass(System::getEnv('_APP_MESSAGING_CLASS_NAME', Event::MESSAGING_CLASS_NAME));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets type for the build event.
|
||||
*
|
||||
* @param string $type Can be `MESSAGE_SEND_TYPE_INTERNAL` or `MESSAGE_SEND_TYPE_EXTERNAL`.
|
||||
* @return self
|
||||
*/
|
||||
public function setType(string $type): self
|
||||
{
|
||||
$this->type = $type;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns set type for the function event.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getType(): string
|
||||
{
|
||||
return $this->type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets recipient for the messaging event.
|
||||
*
|
||||
* @param string[] $recipients
|
||||
* @return self
|
||||
*/
|
||||
public function setRecipients(array $recipients): self
|
||||
{
|
||||
$this->recipients = $recipients;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns set recipient for messaging event.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public function getRecipient(): array
|
||||
{
|
||||
return $this->recipients;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets message document for the messaging event.
|
||||
*
|
||||
* @param Document $message
|
||||
* @return self
|
||||
*/
|
||||
public function setMessage(Document $message): self
|
||||
{
|
||||
$this->message = $message;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns message document for the messaging event.
|
||||
*
|
||||
* @return Document
|
||||
*/
|
||||
public function getMessage(): Document
|
||||
{
|
||||
return $this->message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets message ID for the messaging event.
|
||||
*
|
||||
* @param string $messageId
|
||||
* @return self
|
||||
*/
|
||||
public function setMessageId(string $messageId): self
|
||||
{
|
||||
$this->messageId = $messageId;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns set message ID for the messaging event.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getMessageId(): string
|
||||
{
|
||||
return $this->messageId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets provider type for the messaging event.
|
||||
*
|
||||
* @param string $providerType
|
||||
* @return self
|
||||
*/
|
||||
public function setProviderType(string $providerType): self
|
||||
{
|
||||
$this->providerType = $providerType;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns set provider type for the messaging event.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getProviderType(): string
|
||||
{
|
||||
return $this->providerType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets Scheduled delivery time for the messaging event.
|
||||
*
|
||||
* @param string $scheduledAt
|
||||
* @return self
|
||||
*/
|
||||
public function setScheduledAt(string $scheduledAt): self
|
||||
{
|
||||
$this->scheduledAt = $scheduledAt;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns set Delivery Time for the messaging event.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getScheduledAt(): string
|
||||
{
|
||||
return $this->scheduledAt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the payload for the event
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function preparePayload(): array
|
||||
{
|
||||
return [
|
||||
'type' => $this->type,
|
||||
'project' => $this->project,
|
||||
'user' => $this->user,
|
||||
'messageId' => $this->messageId,
|
||||
'message' => $this->message,
|
||||
'recipients' => $this->recipients,
|
||||
'providerType' => $this->providerType,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Event\Publisher;
|
||||
|
||||
use Appwrite\Event\Message\Mail as MailMessage;
|
||||
use Utopia\Queue\Publisher;
|
||||
use Utopia\Queue\Queue;
|
||||
|
||||
readonly class Mail extends Base
|
||||
{
|
||||
public function __construct(
|
||||
Publisher $publisher,
|
||||
protected Queue $queue
|
||||
) {
|
||||
parent::__construct($publisher);
|
||||
}
|
||||
|
||||
public function enqueue(MailMessage $message, ?Queue $queue = null): string|bool
|
||||
{
|
||||
return $this->publish($queue ?? $this->queue, $message);
|
||||
}
|
||||
|
||||
public function getSize(bool $failed = false, ?Queue $queue = null): int
|
||||
{
|
||||
return $this->getQueueSize($queue ?? $this->queue, $failed);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Event\Publisher;
|
||||
|
||||
use Appwrite\Event\Message\Messaging as MessagingMessage;
|
||||
use Utopia\Queue\Publisher;
|
||||
use Utopia\Queue\Queue;
|
||||
|
||||
readonly class Messaging extends Base
|
||||
{
|
||||
public function __construct(
|
||||
Publisher $publisher,
|
||||
protected Queue $queue
|
||||
) {
|
||||
parent::__construct($publisher);
|
||||
}
|
||||
|
||||
public function enqueue(MessagingMessage $message, ?Queue $queue = null): string|bool
|
||||
{
|
||||
return $this->publish($queue ?? $this->queue, $message);
|
||||
}
|
||||
|
||||
public function getSize(bool $failed = false, ?Queue $queue = null): int
|
||||
{
|
||||
return $this->getQueueSize($queue ?? $this->queue, $failed);
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,6 @@ 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;
|
||||
@@ -53,22 +52,6 @@ class Resolvers
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
@@ -95,9 +78,9 @@ class Resolvers
|
||||
?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');
|
||||
$utopia = $utopia->context()->get('utopia:graphql');
|
||||
$request = $utopia->context()->get('request');
|
||||
$response = $utopia->context()->get('response');
|
||||
|
||||
self::resolve(
|
||||
$utopia,
|
||||
@@ -167,9 +150,9 @@ class Resolvers
|
||||
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');
|
||||
$utopia = $utopia->context()->get('utopia:graphql');
|
||||
$request = $utopia->context()->get('request');
|
||||
$response = $utopia->context()->get('response');
|
||||
|
||||
self::resolve(
|
||||
$utopia,
|
||||
@@ -203,9 +186,9 @@ class Resolvers
|
||||
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');
|
||||
$utopia = $utopia->context()->get('utopia:graphql');
|
||||
$request = $utopia->context()->get('request');
|
||||
$response = $utopia->context()->get('response');
|
||||
|
||||
$beforeResolve = function ($payload) {
|
||||
return $payload['documents'];
|
||||
@@ -245,9 +228,9 @@ class Resolvers
|
||||
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');
|
||||
$utopia = $utopia->context()->get('utopia:graphql');
|
||||
$request = $utopia->context()->get('request');
|
||||
$response = $utopia->context()->get('response');
|
||||
|
||||
self::resolve(
|
||||
$utopia,
|
||||
@@ -282,9 +265,9 @@ class Resolvers
|
||||
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');
|
||||
$utopia = $utopia->context()->get('utopia:graphql');
|
||||
$request = $utopia->context()->get('request');
|
||||
$response = $utopia->context()->get('response');
|
||||
|
||||
self::resolve(
|
||||
$utopia,
|
||||
@@ -317,9 +300,9 @@ class Resolvers
|
||||
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');
|
||||
$utopia = $utopia->context()->get('utopia:graphql');
|
||||
$request = $utopia->context()->get('request');
|
||||
$response = $utopia->context()->get('response');
|
||||
|
||||
self::resolve(
|
||||
$utopia,
|
||||
@@ -373,10 +356,9 @@ class Resolvers
|
||||
}
|
||||
|
||||
/** @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 = clone $utopia->context()->get('response');
|
||||
$utopia->context()->set('request', static fn () => $request);
|
||||
$utopia->context()->set('response', static fn () => $resolverResponse);
|
||||
$resolverResponse->setContentType(Response::CONTENT_TYPE_NULL);
|
||||
$resolverResponse->setSent(false);
|
||||
|
||||
|
||||
@@ -84,7 +84,7 @@ class Schema
|
||||
protected static function api(Http $utopia, callable $complexity): array
|
||||
{
|
||||
Mapper::init($utopia
|
||||
->getResource('response')
|
||||
->context()->get('response')
|
||||
->getModels());
|
||||
|
||||
$queries = [];
|
||||
|
||||
@@ -254,7 +254,7 @@ class Mapper
|
||||
array $injections
|
||||
): Type {
|
||||
$validator = \is_callable($validator)
|
||||
? \call_user_func_array($validator, $utopia->getResources($injections))
|
||||
? \call_user_func_array($validator, \array_map($utopia->context()->get(...), $injections))
|
||||
: $validator;
|
||||
|
||||
$isNullable = $validator instanceof Nullable;
|
||||
|
||||
@@ -154,7 +154,7 @@ class Server
|
||||
|
||||
$nativeServer = $adapter->getNativeServer();
|
||||
|
||||
$container = $adapter->getContainer();
|
||||
$container = $adapter->resources();
|
||||
$container->set('installerState', fn () => $state);
|
||||
$container->set('installerConfig', fn () => $config);
|
||||
$container->set('installerPaths', fn () => $paths);
|
||||
|
||||
@@ -5,8 +5,10 @@ namespace Appwrite\Platform\Modules\Account\Http\Account\MFA\Challenges;
|
||||
use Appwrite\Auth\MFA\Type;
|
||||
use Appwrite\Detector\Detector;
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Event\Mail;
|
||||
use Appwrite\Event\Messaging;
|
||||
use Appwrite\Event\Message\Mail as MailMessage;
|
||||
use Appwrite\Event\Message\Messaging as MessagingMessage;
|
||||
use Appwrite\Event\Publisher\Mail as MailPublisher;
|
||||
use Appwrite\Event\Publisher\Messaging as MessagingPublisher;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\ContentType;
|
||||
@@ -101,8 +103,8 @@ class Create extends Action
|
||||
->inject('platform')
|
||||
->inject('request')
|
||||
->inject('queueForEvents')
|
||||
->inject('queueForMessaging')
|
||||
->inject('queueForMails')
|
||||
->inject('publisherForMessaging')
|
||||
->inject('publisherForMails')
|
||||
->inject('timelimit')
|
||||
->inject('usage')
|
||||
->inject('plan')
|
||||
@@ -121,8 +123,8 @@ class Create extends Action
|
||||
array $platform,
|
||||
Request $request,
|
||||
Event $queueForEvents,
|
||||
Messaging $queueForMessaging,
|
||||
Mail $queueForMails,
|
||||
MessagingPublisher $publisherForMessaging,
|
||||
MailPublisher $publisherForMails,
|
||||
callable $timelimit,
|
||||
Context $usage,
|
||||
array $plan,
|
||||
@@ -180,16 +182,18 @@ class Create extends Action
|
||||
$message = $message->render();
|
||||
|
||||
$phone = $user->getAttribute('phone');
|
||||
$queueForMessaging
|
||||
->setType(MESSAGE_SEND_TYPE_INTERNAL)
|
||||
->setMessage(new Document([
|
||||
$publisherForMessaging->enqueue(new MessagingMessage(
|
||||
type: MESSAGE_SEND_TYPE_INTERNAL,
|
||||
project: $project,
|
||||
message: new Document([
|
||||
'$id' => $challenge->getId(),
|
||||
'data' => [
|
||||
'content' => $code,
|
||||
],
|
||||
]))
|
||||
->setRecipients([$phone])
|
||||
->setProviderType(MESSAGE_TYPE_SMS);
|
||||
]),
|
||||
recipients: [$phone],
|
||||
providerType: MESSAGE_TYPE_SMS,
|
||||
));
|
||||
|
||||
$helper = PhoneNumberUtil::getInstance();
|
||||
try {
|
||||
@@ -252,6 +256,7 @@ class Create extends Action
|
||||
$senderName = System::getEnv('_APP_SYSTEM_EMAIL_NAME', APP_NAME . ' Server');
|
||||
$replyToEmail = '';
|
||||
$replyToName = '';
|
||||
$smtpConfig = [];
|
||||
|
||||
if ($smtpEnabled) {
|
||||
if (!empty($smtp['senderEmail'])) {
|
||||
@@ -269,13 +274,6 @@ class Create extends Action
|
||||
$replyToName = $smtp['replyToName'];
|
||||
}
|
||||
|
||||
$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'];
|
||||
@@ -296,11 +294,17 @@ class Create extends Action
|
||||
$subject = $customTemplate['subject'] ?? $subject;
|
||||
}
|
||||
|
||||
$queueForMails
|
||||
->setSmtpReplyToEmail($replyToEmail)
|
||||
->setSmtpReplyToName($replyToName)
|
||||
->setSmtpSenderEmail($senderEmail)
|
||||
->setSmtpSenderName($senderName);
|
||||
$smtpConfig = [
|
||||
'host' => $smtp['host'] ?? '',
|
||||
'port' => $smtp['port'] ?? '',
|
||||
'username' => $smtp['username'] ?? '',
|
||||
'password' => $smtp['password'] ?? '',
|
||||
'secure' => $smtp['secure'] ?? '',
|
||||
'replyToEmail' => $replyToEmail,
|
||||
'replyToName' => $replyToName,
|
||||
'senderEmail' => $senderEmail,
|
||||
'senderName' => $senderName,
|
||||
];
|
||||
}
|
||||
|
||||
$emailVariables = [
|
||||
@@ -327,20 +331,18 @@ class Create extends Action
|
||||
]);
|
||||
}
|
||||
|
||||
$queueForMails
|
||||
->setSubject($subject)
|
||||
->setPreview($preview)
|
||||
->setBody($body)
|
||||
->setBodyTemplate($bodyTemplate)
|
||||
->appendVariables($emailVariables)
|
||||
->setRecipient($user->getAttribute('email'));
|
||||
|
||||
// since this is console project, set email sender name!
|
||||
if ($smtpBaseTemplate === APP_BRANDED_EMAIL_BASE_TEMPLATE) {
|
||||
$queueForMails->setSenderName($platform['emailSenderName']);
|
||||
}
|
||||
|
||||
$queueForMails->trigger();
|
||||
$publisherForMails->enqueue(new MailMessage(
|
||||
project: $project,
|
||||
recipient: $user->getAttribute('email'),
|
||||
subject: $subject,
|
||||
bodyTemplate: $bodyTemplate,
|
||||
body: $body,
|
||||
preview: $preview,
|
||||
smtp: $smtpConfig,
|
||||
variables: $emailVariables,
|
||||
customMailOptions: $smtpBaseTemplate === APP_BRANDED_EMAIL_BASE_TEMPLATE ? ['senderName' => $platform['emailSenderName']] : [],
|
||||
platform: $platform,
|
||||
));
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
@@ -287,7 +287,29 @@ class Update extends Base
|
||||
}
|
||||
|
||||
// Inform scheduler if function is still active
|
||||
$schedule = $dbForPlatform->getDocument('schedules', $function->getAttribute('scheduleId'));
|
||||
$schedule = $authorization->skip(fn () => $dbForPlatform->getDocument('schedules', $function->getAttribute('scheduleId')));
|
||||
|
||||
// Re-create schedule if missing
|
||||
if ($schedule->isEmpty()) {
|
||||
$schedule = $authorization->skip(
|
||||
fn () => $dbForPlatform->createDocument('schedules', new Document([
|
||||
'region' => $project->getAttribute('region'),
|
||||
'resourceType' => SCHEDULE_RESOURCE_TYPE_FUNCTION,
|
||||
'resourceId' => $function->getId(),
|
||||
'resourceInternalId' => $function->getSequence(),
|
||||
'resourceUpdatedAt' => DateTime::now(),
|
||||
'projectId' => $project->getId(),
|
||||
'schedule' => $function->getAttribute('schedule'),
|
||||
'active' => false,
|
||||
]))
|
||||
);
|
||||
|
||||
$function = $dbForProject->updateDocument('functions', $function->getId(), new Document([
|
||||
'scheduleId' => $schedule->getId(),
|
||||
'scheduleInternalId' => $schedule->getSequence(),
|
||||
]));
|
||||
}
|
||||
|
||||
$schedule
|
||||
->setAttribute('resourceUpdatedAt', DateTime::now())
|
||||
->setAttribute('schedule', $function->getAttribute('schedule'))
|
||||
|
||||
@@ -109,9 +109,7 @@ class Screenshots extends Action
|
||||
throw new \Exception("Rule for deployment not found");
|
||||
}
|
||||
|
||||
$client = new FetchClient();
|
||||
$client->setTimeout(\intval($site->getAttribute('timeout', '15')) * 1000);
|
||||
$client->addHeader('content-type', FetchClient::CONTENT_TYPE_APPLICATION_JSON);
|
||||
$timeout = \intval($site->getAttribute('timeout', '15')) * 1000;
|
||||
|
||||
$bucket = $dbForPlatform->getDocument('buckets', 'screenshots');
|
||||
|
||||
@@ -162,8 +160,8 @@ class Screenshots extends Action
|
||||
]);
|
||||
|
||||
$screenshotError = null;
|
||||
$screenshots = batch(\array_map(function ($key) use ($configs, $apiKey, $site, $client, &$screenshotError) {
|
||||
return function () use ($key, $configs, $apiKey, $site, $client, &$screenshotError) {
|
||||
$screenshots = batch(\array_map(function ($key) use ($configs, $apiKey, $site, $timeout, &$screenshotError) {
|
||||
return function () use ($key, $configs, $apiKey, $site, $timeout, &$screenshotError) {
|
||||
try {
|
||||
$config = $configs[$key];
|
||||
|
||||
@@ -179,6 +177,10 @@ class Screenshots extends Action
|
||||
}
|
||||
|
||||
$browserEndpoint = System::getEnv('_APP_BROWSER_HOST', 'http://appwrite-browser:3000/v1');
|
||||
$client = new FetchClient();
|
||||
$client->setTimeout($timeout);
|
||||
$client->addHeader('content-type', FetchClient::CONTENT_TYPE_APPLICATION_JSON);
|
||||
|
||||
$fetchResponse = $client->fetch(
|
||||
url: $browserEndpoint . '/screenshots',
|
||||
method: 'POST',
|
||||
|
||||
@@ -6,11 +6,11 @@ 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\Publisher\Audit;
|
||||
use Appwrite\Event\Publisher\Build as BuildPublisher;
|
||||
use Appwrite\Event\Publisher\Certificate;
|
||||
use Appwrite\Event\Publisher\Mail as MailPublisher;
|
||||
use Appwrite\Event\Publisher\Messaging as MessagingPublisher;
|
||||
use Appwrite\Event\Publisher\Migration as MigrationPublisher;
|
||||
use Appwrite\Event\Publisher\Screenshot;
|
||||
use Appwrite\Event\Publisher\StatsResources as StatsResourcesPublisher;
|
||||
@@ -77,14 +77,14 @@ class Get extends Base
|
||||
->inject('queueForDatabase')
|
||||
->inject('queueForDeletes')
|
||||
->inject('publisherForAudits')
|
||||
->inject('queueForMails')
|
||||
->inject('publisherForMails')
|
||||
->inject('queueForFunctions')
|
||||
->inject('publisherForStatsResources')
|
||||
->inject('publisherForUsage')
|
||||
->inject('queueForWebhooks')
|
||||
->inject('publisherForCertificates')
|
||||
->inject('publisherForBuilds')
|
||||
->inject('queueForMessaging')
|
||||
->inject('publisherForMessaging')
|
||||
->inject('publisherForMigrations')
|
||||
->inject('publisherForScreenshots')
|
||||
->callback($this->action(...));
|
||||
@@ -97,14 +97,14 @@ class Get extends Base
|
||||
Database $queueForDatabase,
|
||||
Delete $queueForDeletes,
|
||||
Audit $publisherForAudits,
|
||||
Mail $queueForMails,
|
||||
MailPublisher $publisherForMails,
|
||||
Func $queueForFunctions,
|
||||
StatsResourcesPublisher $publisherForStatsResources,
|
||||
UsagePublisher $publisherForUsage,
|
||||
Webhook $queueForWebhooks,
|
||||
Certificate $publisherForCertificates,
|
||||
BuildPublisher $publisherForBuilds,
|
||||
Messaging $queueForMessaging,
|
||||
MessagingPublisher $publisherForMessaging,
|
||||
MigrationPublisher $publisherForMigrations,
|
||||
Screenshot $publisherForScreenshots,
|
||||
): void {
|
||||
@@ -114,7 +114,7 @@ class Get extends Base
|
||||
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) => $publisherForAudits,
|
||||
System::getEnv('_APP_MAILS_QUEUE_NAME', Event::MAILS_QUEUE_NAME) => $queueForMails,
|
||||
System::getEnv('_APP_MAILS_QUEUE_NAME', Event::MAILS_QUEUE_NAME) => $publisherForMails,
|
||||
System::getEnv('_APP_FUNCTIONS_QUEUE_NAME', Event::FUNCTIONS_QUEUE_NAME) => $queueForFunctions,
|
||||
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,
|
||||
@@ -122,7 +122,7 @@ class Get extends Base
|
||||
System::getEnv('_APP_CERTIFICATES_QUEUE_NAME', Event::CERTIFICATES_QUEUE_NAME) => $publisherForCertificates,
|
||||
System::getEnv('_APP_BUILDS_QUEUE_NAME', Event::BUILDS_QUEUE_NAME) => $publisherForBuilds,
|
||||
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_MESSAGING_QUEUE_NAME', Event::MESSAGING_QUEUE_NAME) => $publisherForMessaging,
|
||||
System::getEnv('_APP_MIGRATIONS_QUEUE_NAME', Event::MIGRATIONS_QUEUE_NAME) => $publisherForMigrations,
|
||||
default => throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Unknown queue name: ' . $name),
|
||||
};
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
namespace Appwrite\Platform\Modules\Health\Http\Health\Queue\Mails;
|
||||
|
||||
use Appwrite\Event\Mail;
|
||||
use Appwrite\Event\Publisher\Mail as MailPublisher;
|
||||
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('queueForMails')
|
||||
->inject('publisherForMails')
|
||||
->inject('response')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(int|string $threshold, Mail $queueForMails, Response $response): void
|
||||
public function action(int|string $threshold, MailPublisher $publisherForMails, Response $response): void
|
||||
{
|
||||
$threshold = (int) $threshold;
|
||||
|
||||
$size = $queueForMails->getSize();
|
||||
$size = $publisherForMails->getSize();
|
||||
|
||||
$this->assertQueueThreshold($size, $threshold);
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
namespace Appwrite\Platform\Modules\Health\Http\Health\Queue\Messaging;
|
||||
|
||||
use Appwrite\Event\Messaging;
|
||||
use Appwrite\Event\Publisher\Messaging as MessagingPublisher;
|
||||
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('queueForMessaging')
|
||||
->inject('publisherForMessaging')
|
||||
->inject('response')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(int|string $threshold, Messaging $queueForMessaging, Response $response): void
|
||||
public function action(int|string $threshold, MessagingPublisher $publisherForMessaging, Response $response): void
|
||||
{
|
||||
$threshold = (int) $threshold;
|
||||
|
||||
$size = $queueForMessaging->getSize();
|
||||
$size = $publisherForMessaging->getSize();
|
||||
|
||||
$this->assertQueueThreshold($size, $threshold);
|
||||
|
||||
|
||||
@@ -33,7 +33,6 @@ class Delete extends Action
|
||||
->desc('Delete project')
|
||||
->groups(['api', 'project'])
|
||||
->label('scope', 'project.write')
|
||||
->label('event', 'project.delete')
|
||||
->label('audits.event', 'project.delete')
|
||||
->label('audits.resource', 'project/{project.$id}')
|
||||
->label('sdk', new Method(
|
||||
|
||||
@@ -3,8 +3,22 @@
|
||||
namespace Appwrite\Platform\Modules\Project\Http\Project\OAuth2\Google;
|
||||
|
||||
use Appwrite\Auth\OAuth2\Google;
|
||||
use Appwrite\Event\Event as QueueEvent;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Platform\Action;
|
||||
use Appwrite\Platform\Modules\Project\Http\Project\OAuth2\Base;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Validator\ArrayList;
|
||||
use Utopia\Validator\Boolean;
|
||||
use Utopia\Validator\Nullable;
|
||||
use Utopia\Validator\Text;
|
||||
use Utopia\Validator\WhiteList;
|
||||
|
||||
class Update extends Base
|
||||
{
|
||||
@@ -52,4 +66,118 @@ class Update extends Base
|
||||
{
|
||||
return 'GOCSPX-2k8gsR0000000000000000VNahJj';
|
||||
}
|
||||
|
||||
public static function getParameters(): array
|
||||
{
|
||||
return \array_merge(parent::getParameters(), [
|
||||
[
|
||||
'$id' => 'prompt',
|
||||
'name' => 'Prompt',
|
||||
'example' => '["consent"]',
|
||||
'hint' => '',
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$providerId = static::getProviderId();
|
||||
$providerLabel = static::getProviderLabel();
|
||||
|
||||
$this
|
||||
->setHttpMethod(Action::HTTP_REQUEST_METHOD_PATCH)
|
||||
->setHttpPath('/v1/project/oauth2/' . $providerId)
|
||||
->desc('Update project OAuth2 ' . $providerLabel)
|
||||
->groups(['api', 'project'])
|
||||
->label('scope', 'oauth2.write')
|
||||
->label('event', 'oauth2.[providerId].update')
|
||||
->label('audits.event', 'project.oauth2.[providerId].update')
|
||||
->label('audits.resource', 'project.oauth2/{response.$id}')
|
||||
->label('sdk', new Method(
|
||||
namespace: 'project',
|
||||
group: 'oauth2',
|
||||
name: static::getProviderSDKMethod(),
|
||||
description: 'Update the project OAuth2 ' . $providerLabel . ' configuration.',
|
||||
auth: [AuthType::ADMIN, AuthType::KEY],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
model: static::getResponseModel(),
|
||||
)
|
||||
],
|
||||
))
|
||||
->param(static::getClientIdParamName(), null, new Nullable(new Text(256, 0)), static::getClientIdDescription(), optional: true)
|
||||
->param(static::getClientSecretParamName(), null, new Nullable(new Text(512, 0)), static::getClientSecretDescription(), optional: true)
|
||||
->param('prompt', null, new Nullable(new ArrayList(new WhiteList(['none', 'consent', 'select_account'], true), 3)), 'Array of Google OAuth2 prompt values. If "none" is included, it must be the only element. "none" means: don\'t display any authentication or consent screens. Must not be specified with other values. "consent" means: prompt the user for consent. "select_account" means: prompt the user to select an account.', optional: true)
|
||||
->param('enabled', null, new Nullable(new Boolean()), 'OAuth2 sign-in method status. Set to true to enable new session creation. Setting to true will trigger end-to-end credentials validation, and will throw if the credentials are invalid.', true)
|
||||
->inject('response')
|
||||
->inject('dbForPlatform')
|
||||
->inject('project')
|
||||
->inject('authorization')
|
||||
->inject('queueForEvents')
|
||||
->callback($this->handle(...));
|
||||
}
|
||||
|
||||
public function buildReadResponse(Document $project): Document
|
||||
{
|
||||
$providerId = static::getProviderId();
|
||||
$oAuthProviders = $project->getAttribute('oAuthProviders', []);
|
||||
$decoded = $this->decodeStoredSecret($project);
|
||||
|
||||
return new Document([
|
||||
'$id' => $providerId,
|
||||
'enabled' => $oAuthProviders[$providerId . 'Enabled'] ?? false,
|
||||
static::getClientIdParamName() => $oAuthProviders[$providerId . 'Appid'] ?? '',
|
||||
static::getClientSecretParamName() => '',
|
||||
'prompt' => $decoded['prompt'] ?? ['consent'],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom callback used instead of the parent's `action()` because Google
|
||||
* takes an additional optional `prompt` parameter. The method is named
|
||||
* differently to avoid an LSP-incompatible override of Base::action().
|
||||
*/
|
||||
public function handle(
|
||||
?string $clientId,
|
||||
?string $clientSecret,
|
||||
?array $prompt,
|
||||
?bool $enabled,
|
||||
Response $response,
|
||||
Database $dbForPlatform,
|
||||
Document $project,
|
||||
Authorization $authorization,
|
||||
QueueEvent $queueForEvents
|
||||
): void {
|
||||
$providerId = static::getProviderId();
|
||||
$queueForEvents->setParam('providerId', $providerId);
|
||||
|
||||
if ($prompt !== null) {
|
||||
if (empty($prompt)) {
|
||||
throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'Prompt array cannot be empty.');
|
||||
}
|
||||
|
||||
if (\in_array('none', $prompt) && \count($prompt) > 1) {
|
||||
throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'When "none" is used as a prompt value, it must be the only element in the array.');
|
||||
}
|
||||
}
|
||||
|
||||
$storedRaw = $project->getAttribute('oAuthProviders', [])[$providerId . 'Secret'] ?? '';
|
||||
$existing = $this->decodeStoredSecret($project);
|
||||
|
||||
// Backwards compatibility: secrets stored before the prompt feature
|
||||
// were saved as plain strings. Treat the raw value as clientSecret.
|
||||
if (!empty($storedRaw) && empty($existing)) {
|
||||
$existing = ['clientSecret' => $storedRaw];
|
||||
}
|
||||
|
||||
$encodedSecret = \json_encode([
|
||||
'clientSecret' => $clientSecret ?? ($existing['clientSecret'] ?? ''),
|
||||
'prompt' => $prompt ?? ($existing['prompt'] ?? ['consent']),
|
||||
]);
|
||||
|
||||
$project = $this->persistCredentials($project, $dbForPlatform, $authorization, $clientId, $encodedSecret, $enabled);
|
||||
|
||||
$response->dynamic($this->buildReadResponse($project), static::getResponseModel());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
|
||||
namespace Appwrite\Platform\Modules\Project\Http\Project\SMTP\Tests;
|
||||
|
||||
use Appwrite\Event\Mail;
|
||||
use Appwrite\Event\Message\Mail as MailMessage;
|
||||
use Appwrite\Event\Publisher\Mail as MailPublisher;
|
||||
use Appwrite\Extend\Exception as Exception;
|
||||
use Appwrite\Platform\Action;
|
||||
use Appwrite\SDK\AuthType;
|
||||
@@ -67,7 +68,7 @@ class Create extends Action
|
||||
->param('secure', '', new WhiteList(['tls', 'ssl'], true), 'Does SMTP server use secure connection', optional: true, deprecated: true) // Backwards compatibility
|
||||
->inject('response')
|
||||
->inject('project')
|
||||
->inject('queueForMails')
|
||||
->inject('publisherForMails')
|
||||
->inject('plan')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
@@ -87,7 +88,7 @@ class Create extends Action
|
||||
string $paramSecure, // Backwards compatibility
|
||||
Response $response,
|
||||
Document $project,
|
||||
Mail $queueForMails,
|
||||
MailPublisher $publisherForMails,
|
||||
array $plan
|
||||
): void {
|
||||
// Backwards compatibility: use inline params if provided, otherwise fall back to project SMTP config.
|
||||
@@ -153,23 +154,24 @@ class Create extends Action
|
||||
->setParam('{{privacyUrl}}', $plan['privacyUrl'] ?? APP_EMAIL_PRIVACY_URL);
|
||||
|
||||
foreach ($emails as $email) {
|
||||
$queueForMails
|
||||
->setSmtpHost($host)
|
||||
->setSmtpPort($port)
|
||||
->setSmtpUsername($username)
|
||||
->setSmtpPassword($password)
|
||||
->setSmtpSecure($secure)
|
||||
->setSmtpReplyToEmail($replyToEmail)
|
||||
->setSmtpReplyToName($replyToName)
|
||||
->setSmtpSenderEmail($senderEmail)
|
||||
->setSmtpSenderName($senderName)
|
||||
->setRecipient($email)
|
||||
->setName('')
|
||||
->setBodyTemplate(APP_CE_CONFIG_DIR . '/locale/templates/email-base-styled.tpl')
|
||||
->setBody($template->render())
|
||||
->setVariables([])
|
||||
->setSubject($subject)
|
||||
->trigger();
|
||||
$publisherForMails->enqueue(new MailMessage(
|
||||
project: $project,
|
||||
recipient: $email,
|
||||
subject: $subject,
|
||||
bodyTemplate: APP_CE_CONFIG_DIR . '/locale/templates/email-base-styled.tpl',
|
||||
body: $template->render(),
|
||||
smtp: [
|
||||
'host' => $host,
|
||||
'port' => $port,
|
||||
'username' => $username,
|
||||
'password' => $password,
|
||||
'secure' => $secure,
|
||||
'replyToEmail' => $replyToEmail,
|
||||
'replyToName' => $replyToName,
|
||||
'senderEmail' => $senderEmail,
|
||||
'senderName' => $senderName,
|
||||
],
|
||||
));
|
||||
}
|
||||
|
||||
$response->noContent();
|
||||
|
||||
@@ -245,28 +245,43 @@ class Get extends Action
|
||||
throw new Exception(Exception::STORAGE_FILE_TYPE_UNSUPPORTED, $e->getMessage());
|
||||
}
|
||||
|
||||
$image->crop((int) $width, (int) $height, $gravity);
|
||||
if ($width > 0 || $height > 0 || $gravity !== Image::GRAVITY_CENTER) {
|
||||
Span::add('storage.transform.crop.width', $width);
|
||||
Span::add('storage.transform.crop.height', $height);
|
||||
Span::add('storage.transform.crop.gravity', $gravity);
|
||||
$image->crop($width, $height, $gravity);
|
||||
}
|
||||
|
||||
if (!empty($opacity)) {
|
||||
if ($opacity !== 1.0) {
|
||||
Span::add('storage.transform.opacity', $opacity);
|
||||
$image->setOpacity($opacity);
|
||||
}
|
||||
|
||||
if (!empty($background)) {
|
||||
Span::add('storage.transform.background', $background);
|
||||
$image->setBackground('#' . $background);
|
||||
}
|
||||
|
||||
if (!empty($borderWidth)) {
|
||||
if ($borderWidth > 0) {
|
||||
Span::add('storage.transform.border.width', $borderWidth);
|
||||
Span::add('storage.transform.border.color', $borderColor);
|
||||
$image->setBorder($borderWidth, '#' . $borderColor);
|
||||
}
|
||||
|
||||
if (!empty($borderRadius)) {
|
||||
if ($borderRadius > 0) {
|
||||
Span::add('storage.transform.borderRadius', $borderRadius);
|
||||
$image->setBorderRadius($borderRadius);
|
||||
}
|
||||
|
||||
if (!empty($rotation)) {
|
||||
if ($rotation !== 0) {
|
||||
Span::add('storage.transform.rotation', $rotation);
|
||||
$image->setRotation(($rotation + 360) % 360);
|
||||
}
|
||||
|
||||
if ($quality !== -1) {
|
||||
Span::add('storage.transform.quality', $quality);
|
||||
}
|
||||
|
||||
$data = $image->output($output, $quality);
|
||||
|
||||
$renderingTime = \microtime(true) - $startTime - $downloadTime - $decryptionTime - $decompressionTime;
|
||||
|
||||
@@ -4,8 +4,10 @@ namespace Appwrite\Platform\Modules\Teams\Http\Memberships;
|
||||
|
||||
use Appwrite\Auth\Validator\Phone;
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Event\Mail;
|
||||
use Appwrite\Event\Messaging;
|
||||
use Appwrite\Event\Message\Mail as MailMessage;
|
||||
use Appwrite\Event\Message\Messaging as MessagingMessage;
|
||||
use Appwrite\Event\Publisher\Mail as MailPublisher;
|
||||
use Appwrite\Event\Publisher\Messaging as MessagingPublisher;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Platform\Action;
|
||||
use Appwrite\SDK\AuthType;
|
||||
@@ -87,18 +89,19 @@ class Create extends Action
|
||||
->inject('dbForProject')
|
||||
->inject('authorization')
|
||||
->inject('locale')
|
||||
->inject('queueForMails')
|
||||
->inject('queueForMessaging')
|
||||
->inject('publisherForMails')
|
||||
->inject('publisherForMessaging')
|
||||
->inject('queueForEvents')
|
||||
->inject('timelimit')
|
||||
->inject('usage')
|
||||
->inject('plan')
|
||||
->inject('platform')
|
||||
->inject('proofForPassword')
|
||||
->inject('proofForToken')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(string $teamId, string $email, string $userId, string $phone, array $roles, string $url, string $name, Response $response, Document $project, User $user, Database $dbForProject, Authorization $authorization, Locale $locale, Mail $queueForMails, Messaging $queueForMessaging, Event $queueForEvents, callable $timelimit, Context $usage, array $plan, Password $proofForPassword, Token $proofForToken)
|
||||
public function action(string $teamId, string $email, string $userId, string $phone, array $roles, string $url, string $name, Response $response, Document $project, User $user, Database $dbForProject, Authorization $authorization, Locale $locale, MailPublisher $publisherForMails, MessagingPublisher $publisherForMessaging, Event $queueForEvents, callable $timelimit, Context $usage, array $plan, array $platform, Password $proofForPassword, Token $proofForToken)
|
||||
{
|
||||
$isAppUser = $user->isApp($authorization->getRoles());
|
||||
$isPrivilegedUser = $user->isPrivileged($authorization->getRoles());
|
||||
@@ -345,6 +348,7 @@ class Create extends Action
|
||||
$senderName = System::getEnv('_APP_SYSTEM_EMAIL_NAME', APP_NAME . ' Server');
|
||||
$replyToEmail = '';
|
||||
$replyToName = '';
|
||||
$smtpConfig = [];
|
||||
|
||||
if ($smtpEnabled) {
|
||||
if (! empty($smtp['senderEmail'])) {
|
||||
@@ -362,13 +366,6 @@ class Create extends Action
|
||||
$replyToName = $smtp['replyToName'];
|
||||
}
|
||||
|
||||
$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'];
|
||||
@@ -389,11 +386,17 @@ class Create extends Action
|
||||
$subject = $customTemplate['subject'] ?? $subject;
|
||||
}
|
||||
|
||||
$queueForMails
|
||||
->setSmtpReplyToEmail($replyToEmail)
|
||||
->setSmtpReplyToName($replyToName)
|
||||
->setSmtpSenderEmail($senderEmail)
|
||||
->setSmtpSenderName($senderName);
|
||||
$smtpConfig = [
|
||||
'host' => $smtp['host'] ?? '',
|
||||
'port' => $smtp['port'] ?? '',
|
||||
'username' => $smtp['username'] ?? '',
|
||||
'password' => $smtp['password'] ?? '',
|
||||
'secure' => $smtp['secure'] ?? '',
|
||||
'replyToEmail' => $replyToEmail,
|
||||
'replyToName' => $replyToName,
|
||||
'senderEmail' => $senderEmail,
|
||||
'senderName' => $senderName,
|
||||
];
|
||||
}
|
||||
|
||||
$emailVariables = [
|
||||
@@ -406,14 +409,17 @@ class Create extends Action
|
||||
'project' => $projectName,
|
||||
];
|
||||
|
||||
$queueForMails
|
||||
->setSubject($subject)
|
||||
->setBody($body)
|
||||
->setPreview($preview)
|
||||
->setRecipient($invitee->getAttribute('email'))
|
||||
->setName($invitee->getAttribute('name', ''))
|
||||
->appendVariables($emailVariables)
|
||||
->trigger();
|
||||
$publisherForMails->enqueue(new MailMessage(
|
||||
project: $project,
|
||||
recipient: $invitee->getAttribute('email'),
|
||||
name: $invitee->getAttribute('name', ''),
|
||||
subject: $subject,
|
||||
body: $body,
|
||||
preview: $preview,
|
||||
smtp: $smtpConfig,
|
||||
variables: $emailVariables,
|
||||
platform: $platform,
|
||||
));
|
||||
} elseif (! empty($phone)) {
|
||||
if (empty(System::getEnv('_APP_SMS_PROVIDER'))) {
|
||||
throw new Exception(Exception::GENERAL_PHONE_DISABLED, 'Phone provider not configured');
|
||||
@@ -431,11 +437,13 @@ class Create extends Action
|
||||
],
|
||||
]);
|
||||
|
||||
$queueForMessaging
|
||||
->setType(MESSAGE_SEND_TYPE_INTERNAL)
|
||||
->setMessage($messageDoc)
|
||||
->setRecipients([$phone])
|
||||
->setProviderType('SMS');
|
||||
$publisherForMessaging->enqueue(new MessagingMessage(
|
||||
type: MESSAGE_SEND_TYPE_INTERNAL,
|
||||
project: $project,
|
||||
message: $messageDoc,
|
||||
recipients: [$phone],
|
||||
providerType: 'SMS',
|
||||
));
|
||||
|
||||
$helper = PhoneNumberUtil::getInstance();
|
||||
try {
|
||||
|
||||
@@ -7,6 +7,7 @@ use Appwrite\SDK\Language\Android;
|
||||
use Appwrite\SDK\Language\Apple;
|
||||
use Appwrite\SDK\Language\ClaudePlugin;
|
||||
use Appwrite\SDK\Language\CLI;
|
||||
use Appwrite\SDK\Language\CodexPlugin;
|
||||
use Appwrite\SDK\Language\CursorPlugin;
|
||||
use Appwrite\SDK\Language\Dart;
|
||||
use Appwrite\SDK\Language\Deno;
|
||||
@@ -455,6 +456,9 @@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
case 'claude-plugin':
|
||||
$config = new ClaudePlugin();
|
||||
break;
|
||||
case 'codex-plugin':
|
||||
$config = new CodexPlugin();
|
||||
break;
|
||||
default:
|
||||
throw new \Exception('Language "' . $language['key'] . '" not supported');
|
||||
}
|
||||
|
||||
@@ -2,14 +2,20 @@
|
||||
|
||||
namespace Appwrite\Platform\Tasks;
|
||||
|
||||
use Appwrite\Event\Messaging;
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Event\Message\Messaging as MessagingMessage;
|
||||
use Appwrite\Event\Publisher\Messaging as MessagingPublisher;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Queue\Queue;
|
||||
use Utopia\System\System;
|
||||
|
||||
class ScheduleMessages extends ScheduleBase
|
||||
{
|
||||
public const UPDATE_TIMER = 3; // seconds
|
||||
public const ENQUEUE_TIMER = 4; // seconds
|
||||
|
||||
private ?MessagingPublisher $publisherForMessaging = null;
|
||||
|
||||
public static function getName(): string
|
||||
{
|
||||
return 'schedule-messages';
|
||||
@@ -27,6 +33,11 @@ class ScheduleMessages extends ScheduleBase
|
||||
|
||||
protected function enqueueResources(Database $dbForPlatform, callable $getProjectDB): void
|
||||
{
|
||||
$publisherForMessaging = $this->publisherForMessaging ??= new MessagingPublisher(
|
||||
$this->publisherMessaging,
|
||||
new Queue(System::getEnv('_APP_MESSAGING_QUEUE_NAME', Event::MESSAGING_QUEUE_NAME))
|
||||
);
|
||||
|
||||
foreach ($this->schedules as $schedule) {
|
||||
if (!$schedule['active']) {
|
||||
continue;
|
||||
@@ -39,16 +50,14 @@ class ScheduleMessages extends ScheduleBase
|
||||
continue;
|
||||
}
|
||||
|
||||
\go(function () use ($schedule, $scheduledAt, $dbForPlatform) {
|
||||
$queueForMessaging = new Messaging($this->publisherMessaging);
|
||||
|
||||
\go(function () use ($schedule, $scheduledAt, $dbForPlatform, $publisherForMessaging) {
|
||||
$this->updateProjectAccess($schedule['project'], $dbForPlatform);
|
||||
|
||||
$queueForMessaging
|
||||
->setType(MESSAGE_SEND_TYPE_EXTERNAL)
|
||||
->setMessageId($schedule['resourceId'])
|
||||
->setProject($schedule['project'])
|
||||
->trigger();
|
||||
$publisherForMessaging->enqueue(new MessagingMessage(
|
||||
type: MESSAGE_SEND_TYPE_EXTERNAL,
|
||||
project: $schedule['project'],
|
||||
messageId: $schedule['resourceId'],
|
||||
));
|
||||
|
||||
$dbForPlatform->deleteDocument(
|
||||
'schedules',
|
||||
|
||||
@@ -5,8 +5,9 @@ namespace Appwrite\Platform\Workers;
|
||||
use Appwrite\Certificates\Adapter as CertificatesAdapter;
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Event\Func;
|
||||
use Appwrite\Event\Mail;
|
||||
use Appwrite\Event\Message\Mail as MailMessage;
|
||||
use Appwrite\Event\Publisher\Certificate;
|
||||
use Appwrite\Event\Publisher\Mail as MailPublisher;
|
||||
use Appwrite\Event\Realtime;
|
||||
use Appwrite\Event\Webhook;
|
||||
use Appwrite\Extend\Exception as AppwriteException;
|
||||
@@ -50,7 +51,7 @@ class Certificates extends Action
|
||||
->desc('Certificates worker')
|
||||
->inject('message')
|
||||
->inject('dbForPlatform')
|
||||
->inject('queueForMails')
|
||||
->inject('publisherForMails')
|
||||
->inject('queueForEvents')
|
||||
->inject('queueForWebhooks')
|
||||
->inject('queueForFunctions')
|
||||
@@ -66,7 +67,7 @@ class Certificates extends Action
|
||||
/**
|
||||
* @param Message $message
|
||||
* @param Database $dbForPlatform
|
||||
* @param Mail $queueForMails
|
||||
* @param MailPublisher $publisherForMails
|
||||
* @param Event $queueForEvents
|
||||
* @param Webhook $queueForWebhooks
|
||||
* @param Func $queueForFunctions
|
||||
@@ -83,7 +84,7 @@ class Certificates extends Action
|
||||
public function action(
|
||||
Message $message,
|
||||
Database $dbForPlatform,
|
||||
Mail $queueForMails,
|
||||
MailPublisher $publisherForMails,
|
||||
Event $queueForEvents,
|
||||
Webhook $queueForWebhooks,
|
||||
Func $queueForFunctions,
|
||||
@@ -116,7 +117,7 @@ class Certificates extends Action
|
||||
break;
|
||||
|
||||
case \Appwrite\Event\Certificate::ACTION_GENERATION:
|
||||
$this->handleCertificateGenerationAction($domain, $domainType, $dbForPlatform, $queueForMails, $queueForEvents, $queueForWebhooks, $queueForFunctions, $queueForRealtime, $log, $certificates, $authorization, $skipRenewCheck, $plan, $validationDomain);
|
||||
$this->handleCertificateGenerationAction($domain, $domainType, $dbForPlatform, $publisherForMails, $queueForEvents, $queueForWebhooks, $queueForFunctions, $queueForRealtime, $log, $certificates, $authorization, $skipRenewCheck, $plan, $validationDomain);
|
||||
break;
|
||||
|
||||
default:
|
||||
@@ -209,7 +210,7 @@ class Certificates extends Action
|
||||
* @param Domain $domain
|
||||
* @param ?string $domainType
|
||||
* @param Database $dbForPlatform
|
||||
* @param Mail $queueForMails
|
||||
* @param MailPublisher $publisherForMails
|
||||
* @param Event $queueForEvents
|
||||
* @param Webhook $queueForWebhooks
|
||||
* @param Func $queueForFunctions
|
||||
@@ -233,7 +234,7 @@ class Certificates extends Action
|
||||
Domain $domain,
|
||||
?string $domainType,
|
||||
Database $dbForPlatform,
|
||||
Mail $queueForMails,
|
||||
MailPublisher $publisherForMails,
|
||||
Event $queueForEvents,
|
||||
Webhook $queueForWebhooks,
|
||||
Func $queueForFunctions,
|
||||
@@ -358,7 +359,7 @@ class Certificates extends Action
|
||||
$rule->setAttribute('status', RULE_STATUS_CERTIFICATE_GENERATION_FAILED);
|
||||
|
||||
// Send email to security email
|
||||
$this->notifyError($domain->get(), $e->getMessage(), $attempts, $queueForMails, $plan);
|
||||
$this->notifyError($domain->get(), $e->getMessage(), $attempts, $publisherForMails, $plan);
|
||||
|
||||
throw $e;
|
||||
} finally {
|
||||
@@ -524,12 +525,12 @@ class Certificates extends Action
|
||||
* @param string $domain Domain that caused the error
|
||||
* @param string $errorMessage Verbose error message
|
||||
* @param int $attempt How many times it failed already
|
||||
* @param Mail $queueForMails
|
||||
* @param MailPublisher $publisherForMails
|
||||
* @param array $plan
|
||||
* @return void
|
||||
* @throws Exception
|
||||
*/
|
||||
private function notifyError(string $domain, string $errorMessage, int $attempt, Mail $queueForMails, array $plan): void
|
||||
private function notifyError(string $domain, string $errorMessage, int $attempt, MailPublisher $publisherForMails, array $plan): void
|
||||
{
|
||||
// Log error into console
|
||||
Console::warning('Cannot renew domain (' . $domain . ') on attempt no. ' . $attempt . ' certificate: ' . $errorMessage);
|
||||
@@ -560,14 +561,14 @@ class Certificates extends Action
|
||||
$subject = $locale->getText("emails.certificate.subject");
|
||||
$preview = $locale->getText("emails.certificate.preview");
|
||||
|
||||
$queueForMails
|
||||
->setSubject($subject)
|
||||
->setPreview($preview)
|
||||
->setBody($body)
|
||||
->setName('Appwrite Administrator')
|
||||
->setBodyTemplate(__DIR__ . '/../../../../app/config/locale/templates/email-base-styled.tpl')
|
||||
->setVariables($emailVariables)
|
||||
->setRecipient(System::getEnv('_APP_EMAIL_CERTIFICATES', System::getEnv('_APP_SYSTEM_SECURITY_EMAIL_ADDRESS')))
|
||||
->trigger();
|
||||
$publisherForMails->enqueue(new MailMessage(
|
||||
recipient: System::getEnv('_APP_EMAIL_CERTIFICATES', System::getEnv('_APP_SYSTEM_SECURITY_EMAIL_ADDRESS')),
|
||||
name: 'Appwrite Administrator',
|
||||
subject: $subject,
|
||||
bodyTemplate: __DIR__ . '/../../../../app/config/locale/templates/email-base-styled.tpl',
|
||||
body: $body,
|
||||
preview: $preview,
|
||||
variables: $emailVariables,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,9 +3,10 @@
|
||||
namespace Appwrite\Platform\Workers;
|
||||
|
||||
use Ahc\Jwt\JWT;
|
||||
use Appwrite\Event\Mail;
|
||||
use Appwrite\Event\Message\Mail as MailMessage;
|
||||
use Appwrite\Event\Message\Migration;
|
||||
use Appwrite\Event\Message\Usage as UsageMessage;
|
||||
use Appwrite\Event\Publisher\Mail as MailPublisher;
|
||||
use Appwrite\Event\Publisher\Usage as UsagePublisher;
|
||||
use Appwrite\Event\Realtime;
|
||||
use Appwrite\Extend\Exception;
|
||||
@@ -102,7 +103,7 @@ class Migrations extends Action
|
||||
->inject('queueForRealtime')
|
||||
->inject('deviceForMigrations')
|
||||
->inject('deviceForFiles')
|
||||
->inject('queueForMails')
|
||||
->inject('publisherForMails')
|
||||
->inject('usage')
|
||||
->inject('publisherForUsage')
|
||||
->inject('plan')
|
||||
@@ -124,7 +125,7 @@ class Migrations extends Action
|
||||
Realtime $queueForRealtime,
|
||||
Device $deviceForMigrations,
|
||||
Device $deviceForFiles,
|
||||
Mail $queueForMails,
|
||||
MailPublisher $publisherForMails,
|
||||
Context $usage,
|
||||
UsagePublisher $publisherForUsage,
|
||||
array $plan,
|
||||
@@ -163,7 +164,7 @@ class Migrations extends Action
|
||||
$this->processMigration(
|
||||
$migration,
|
||||
$queueForRealtime,
|
||||
$queueForMails,
|
||||
$publisherForMails,
|
||||
$usage,
|
||||
$publisherForUsage,
|
||||
$platform,
|
||||
@@ -293,6 +294,7 @@ class Migrations extends Action
|
||||
$this->getDatabasesDB,
|
||||
Config::getParam('collections', [])['databases']['collections'],
|
||||
OnDuplicate::tryFrom($options['onDuplicate'] ?? '') ?? OnDuplicate::Fail,
|
||||
$this->resolveDestinationDatabaseDsn(...),
|
||||
),
|
||||
DestinationCSV::getName() => new DestinationCSV(
|
||||
$this->deviceForFiles,
|
||||
@@ -316,6 +318,19 @@ class Migrations extends Action
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Legacy / tablesdb databases route to the destination project's DSN (same as a fresh
|
||||
* Databases create), while documentsdb / vectorsdb keep the source DSN — the dedicated-DB
|
||||
* backfill that would re-point them is not run during migrations.
|
||||
*/
|
||||
private function resolveDestinationDatabaseDsn(ResourceDatabase $resource): string
|
||||
{
|
||||
return match ($resource->getType()) {
|
||||
DATABASE_TYPE_DOCUMENTSDB, DATABASE_TYPE_VECTORSDB => (string) $resource->getDatabase(),
|
||||
default => (string) $this->project->getAttribute('database', ''),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws AuthorizationException
|
||||
* @throws Structure
|
||||
@@ -426,7 +441,7 @@ class Migrations extends Action
|
||||
protected function processMigration(
|
||||
Document $migration,
|
||||
Realtime $queueForRealtime,
|
||||
Mail $queueForMails,
|
||||
MailPublisher $publisherForMails,
|
||||
Context $usage,
|
||||
UsagePublisher $publisherForUsage,
|
||||
array $platform,
|
||||
@@ -630,7 +645,7 @@ class Migrations extends Action
|
||||
}
|
||||
$destination_type = $migration->getAttribute('destination');
|
||||
if ($destination_type === DestinationCSV::getName() || $destination_type === DestinationJSON::getName()) {
|
||||
$this->handleDataExportComplete($project, $migration, $queueForMails, $queueForRealtime, $platform, $authorization);
|
||||
$this->handleDataExportComplete($project, $migration, $publisherForMails, $queueForRealtime, $platform, $authorization);
|
||||
}
|
||||
} finally {
|
||||
$source?->cleanup();
|
||||
@@ -657,7 +672,7 @@ class Migrations extends Action
|
||||
*
|
||||
* @param Document $project
|
||||
* @param Document $migration
|
||||
* @param Mail $queueForMails
|
||||
* @param MailPublisher $publisherForMails
|
||||
* @param Realtime $queueForRealtime
|
||||
* @param array $platform
|
||||
* @param Authorization $authorization
|
||||
@@ -666,7 +681,7 @@ class Migrations extends Action
|
||||
protected function handleDataExportComplete(
|
||||
Document $project,
|
||||
Document $migration,
|
||||
Mail $queueForMails,
|
||||
MailPublisher $publisherForMails,
|
||||
Realtime $queueForRealtime,
|
||||
array $platform,
|
||||
Authorization $authorization,
|
||||
@@ -718,7 +733,7 @@ class Migrations extends Action
|
||||
project: $project,
|
||||
user: $user,
|
||||
options: $options,
|
||||
queueForMails: $queueForMails,
|
||||
publisherForMails: $publisherForMails,
|
||||
platform: $platform,
|
||||
exportType: $migration->getAttribute('destination') === DestinationJSON::getName() ? 'JSON' : 'CSV',
|
||||
sizeMB: $sizeMB
|
||||
@@ -781,7 +796,7 @@ class Migrations extends Action
|
||||
project: $project,
|
||||
user: $user,
|
||||
options: $options,
|
||||
queueForMails: $queueForMails,
|
||||
publisherForMails: $publisherForMails,
|
||||
platform: $platform,
|
||||
exportType: $migration->getAttribute('destination') === DestinationJSON::getName() ? 'JSON' : 'CSV',
|
||||
downloadUrl: $downloadUrl
|
||||
@@ -795,7 +810,7 @@ class Migrations extends Action
|
||||
* @param Document $project
|
||||
* @param Document $user The user who triggered the operation
|
||||
* @param array $options Migration options
|
||||
* @param Mail $queueForMails
|
||||
* @param MailPublisher $publisherForMails
|
||||
* @param array $platform
|
||||
* @param string $downloadUrl Download URL for successful exports
|
||||
* @param float $sizeMB File size in MB for failed exports
|
||||
@@ -807,7 +822,7 @@ class Migrations extends Action
|
||||
Document $project,
|
||||
Document $user,
|
||||
array $options,
|
||||
Mail $queueForMails,
|
||||
MailPublisher $publisherForMails,
|
||||
array $platform,
|
||||
string $exportType = 'CSV',
|
||||
string $downloadUrl = '',
|
||||
@@ -877,17 +892,18 @@ class Migrations extends Action
|
||||
'type' => $exportType,
|
||||
];
|
||||
|
||||
$queueForMails
|
||||
->setProject($project)
|
||||
->setSubject($subject)
|
||||
->setPreview($preview)
|
||||
->setBody($emailBody)
|
||||
->setBodyTemplate(__DIR__ . '/../../../../app/config/locale/templates/email-base-styled.tpl')
|
||||
->setVariables($emailVariables)
|
||||
->setName($user->getAttribute('name', $user->getAttribute('email')))
|
||||
->setRecipient($user->getAttribute('email'))
|
||||
->setSenderName($platform['emailSenderName'])
|
||||
->trigger();
|
||||
$publisherForMails->enqueue(new MailMessage(
|
||||
project: $project,
|
||||
recipient: $user->getAttribute('email'),
|
||||
name: $user->getAttribute('name', $user->getAttribute('email')),
|
||||
subject: $subject,
|
||||
bodyTemplate: __DIR__ . '/../../../../app/config/locale/templates/email-base-styled.tpl',
|
||||
body: $emailBody,
|
||||
preview: $preview,
|
||||
variables: $emailVariables,
|
||||
customMailOptions: ['senderName' => $platform['emailSenderName']],
|
||||
platform: $platform,
|
||||
));
|
||||
|
||||
Console::info("CSV export {$emailType} notification email sent to " . $user->getAttribute('email'));
|
||||
}
|
||||
|
||||
@@ -2,8 +2,9 @@
|
||||
|
||||
namespace Appwrite\Platform\Workers;
|
||||
|
||||
use Appwrite\Event\Mail;
|
||||
use Appwrite\Event\Message\Mail as MailMessage;
|
||||
use Appwrite\Event\Message\Usage as UsageMessage;
|
||||
use Appwrite\Event\Publisher\Mail as MailPublisher;
|
||||
use Appwrite\Event\Publisher\Usage as UsagePublisher;
|
||||
use Appwrite\Template\Template;
|
||||
use Appwrite\Usage\Context as UsageContext;
|
||||
@@ -36,7 +37,7 @@ class Webhooks extends Action
|
||||
->inject('message')
|
||||
->inject('project')
|
||||
->inject('dbForPlatform')
|
||||
->inject('queueForMails')
|
||||
->inject('publisherForMails')
|
||||
->inject('publisherForUsage')
|
||||
->inject('log')
|
||||
->inject('plan')
|
||||
@@ -47,14 +48,14 @@ class Webhooks extends Action
|
||||
* @param Message $message
|
||||
* @param Document $project
|
||||
* @param Database $dbForPlatform
|
||||
* @param Mail $queueForMails
|
||||
* @param MailPublisher $publisherForMails
|
||||
* @param UsagePublisher $publisherForUsage
|
||||
* @param Log $log
|
||||
* @param array $plan
|
||||
* @return void
|
||||
* @throws Exception
|
||||
*/
|
||||
public function action(Message $message, Document $project, Database $dbForPlatform, Mail $queueForMails, UsagePublisher $publisherForUsage, Log $log, array $plan): void
|
||||
public function action(Message $message, Document $project, Database $dbForPlatform, MailPublisher $publisherForMails, UsagePublisher $publisherForUsage, Log $log, array $plan): void
|
||||
{
|
||||
$this->errors = [];
|
||||
$payload = $message->getPayload();
|
||||
@@ -73,7 +74,7 @@ class Webhooks extends Action
|
||||
|
||||
foreach ($project->getAttribute('webhooks', []) as $webhook) {
|
||||
if (array_intersect($webhook->getAttribute('events', []), $events)) {
|
||||
$this->execute($events, $webhookPayload, $webhook, $user, $project, $dbForPlatform, $queueForMails, $publisherForUsage, $plan);
|
||||
$this->execute($events, $webhookPayload, $webhook, $user, $project, $dbForPlatform, $publisherForMails, $publisherForUsage, $plan);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,11 +90,11 @@ class Webhooks extends Action
|
||||
* @param Document $user
|
||||
* @param Document $project
|
||||
* @param Database $dbForPlatform
|
||||
* @param Mail $queueForMails
|
||||
* @param MailPublisher $publisherForMails
|
||||
* @param array $plan
|
||||
* @return void
|
||||
*/
|
||||
private function execute(array $events, string $payload, Document $webhook, Document $user, Document $project, Database $dbForPlatform, Mail $queueForMails, UsagePublisher $publisherForUsage, array $plan): void
|
||||
private function execute(array $events, string $payload, Document $webhook, Document $user, Document $project, Database $dbForPlatform, MailPublisher $publisherForMails, UsagePublisher $publisherForUsage, array $plan): void
|
||||
{
|
||||
if ($webhook->getAttribute('enabled') !== true) {
|
||||
return;
|
||||
@@ -171,7 +172,7 @@ class Webhooks extends Action
|
||||
if ($attempts >= \intval(System::getEnv('_APP_WEBHOOK_MAX_FAILED_ATTEMPTS', '10'))) {
|
||||
$webhook->setAttribute('enabled', false);
|
||||
$updatePayload['enabled'] = false;
|
||||
$this->sendEmailAlert($attempts, $statusCode, $webhook, $project, $dbForPlatform, $queueForMails, $plan);
|
||||
$this->sendEmailAlert($attempts, $statusCode, $webhook, $project, $dbForPlatform, $publisherForMails, $plan);
|
||||
}
|
||||
|
||||
$dbForPlatform->updateDocument('webhooks', $webhook->getId(), new Document($updatePayload));
|
||||
@@ -203,11 +204,11 @@ class Webhooks extends Action
|
||||
* @param Document $webhook
|
||||
* @param Document $project
|
||||
* @param Database $dbForPlatform
|
||||
* @param Mail $queueForMails
|
||||
* @param MailPublisher $publisherForMails
|
||||
* @param array $plan
|
||||
* @return void
|
||||
*/
|
||||
public function sendEmailAlert(int $attempts, mixed $statusCode, Document $webhook, Document $project, Database $dbForPlatform, Mail $queueForMails, array $plan): void
|
||||
public function sendEmailAlert(int $attempts, mixed $statusCode, Document $webhook, Document $project, Database $dbForPlatform, MailPublisher $publisherForMails, array $plan): void
|
||||
{
|
||||
$memberships = $dbForPlatform->find('memberships', [
|
||||
Query::equal('teamInternalId', [$project->getAttribute('teamInternalId')]),
|
||||
@@ -251,18 +252,16 @@ class Webhooks extends Action
|
||||
->setParam('{{message}}', $template->render())
|
||||
->setParam('{{year}}', date("Y"));
|
||||
|
||||
$queueForMails
|
||||
->setProject($project)
|
||||
->setSubject($subject)
|
||||
->setPreview($preview)
|
||||
->setBody($body->render());
|
||||
|
||||
foreach ($users as $user) {
|
||||
$queueForMails
|
||||
->setVariables(['user' => $user->getAttribute('name', '')])
|
||||
->setName($user->getAttribute('name', ''))
|
||||
->setRecipient($user->getAttribute('email'))
|
||||
->trigger();
|
||||
$publisherForMails->enqueue(new MailMessage(
|
||||
project: $project,
|
||||
recipient: $user->getAttribute('email'),
|
||||
name: $user->getAttribute('name', ''),
|
||||
subject: $subject,
|
||||
body: $body->render(),
|
||||
preview: $preview,
|
||||
variables: ['user' => $user->getAttribute('name', '')],
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ use Utopia\DI\Container;
|
||||
|
||||
class Swoole extends Promise
|
||||
{
|
||||
private const REQUEST_CONTAINER_CONTEXT_KEY = '__utopia_http_request_container';
|
||||
private const REQUEST_CONTAINER_CONTEXT_KEY = '__utopia__';
|
||||
|
||||
public function __construct(?callable $executor = null)
|
||||
{
|
||||
|
||||
@@ -524,6 +524,7 @@ class OpenAPI3 extends Format
|
||||
case \Appwrite\Utopia\Database\Validator\Queries\Identities::class:
|
||||
case \Appwrite\Utopia\Database\Validator\Queries\Indexes::class:
|
||||
case \Appwrite\Utopia\Database\Validator\Queries\Installations::class:
|
||||
case \Appwrite\Utopia\Database\Validator\Queries\Branches::class:
|
||||
case \Appwrite\Utopia\Database\Validator\Queries\Memberships::class:
|
||||
case \Appwrite\Utopia\Database\Validator\Queries\Messages::class:
|
||||
case \Appwrite\Utopia\Database\Validator\Queries\Migrations::class:
|
||||
@@ -755,7 +756,18 @@ class OpenAPI3 extends Format
|
||||
$node['schema']['default'] = $param['default'];
|
||||
}
|
||||
|
||||
if (false !== \strpos($url, ':' . $name)) { // Param is in URL path
|
||||
$pathAliases = [$name, ...($param['aliases'] ?? [])];
|
||||
$pathAliasMap = \array_flip($pathAliases);
|
||||
$isPathParam = false;
|
||||
|
||||
foreach (\explode('/', $url) as $segment) {
|
||||
if ($segment !== '' && $segment[0] === ':' && isset($pathAliasMap[\substr($segment, 1)])) {
|
||||
$isPathParam = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ($isPathParam) { // Param is in URL path (directly or through alias)
|
||||
$node['in'] = 'path';
|
||||
$temp['parameters'][] = $node;
|
||||
} elseif ($route->getMethod() == 'GET') { // Param is in query
|
||||
@@ -796,7 +808,14 @@ class OpenAPI3 extends Format
|
||||
}
|
||||
}
|
||||
|
||||
$url = \str_replace(':' . $name, '{' . $name . '}', $url);
|
||||
$segments = \explode('/', $url);
|
||||
foreach ($segments as &$segment) {
|
||||
if ($segment !== '' && $segment[0] === ':' && isset($pathAliasMap[\substr($segment, 1)])) {
|
||||
$segment = '{' . $name . '}';
|
||||
}
|
||||
}
|
||||
unset($segment);
|
||||
$url = \implode('/', $segments);
|
||||
}
|
||||
|
||||
if (!empty($bodyRequired)) {
|
||||
|
||||
@@ -511,6 +511,7 @@ class Swagger2 extends Format
|
||||
case \Utopia\Database\Validator\Queries::class:
|
||||
case \Utopia\Database\Validator\Queries\Document::class:
|
||||
case \Utopia\Database\Validator\Queries\Documents::class:
|
||||
case \Appwrite\Utopia\Database\Validator\Queries\Branches::class:
|
||||
case \Appwrite\Utopia\Database\Validator\Queries\Columns::class:
|
||||
case \Appwrite\Utopia\Database\Validator\Queries\Tables::class:
|
||||
$node['type'] = 'array';
|
||||
@@ -722,7 +723,18 @@ class Swagger2 extends Format
|
||||
$node['default'] = $param['default'];
|
||||
}
|
||||
|
||||
if (\str_contains($url, ':' . $name)) { // Param is in URL path
|
||||
$pathAliases = [$name, ...($param['aliases'] ?? [])];
|
||||
$pathAliasMap = \array_flip($pathAliases);
|
||||
$isPathParam = false;
|
||||
|
||||
foreach (\explode('/', $url) as $segment) {
|
||||
if ($segment !== '' && $segment[0] === ':' && isset($pathAliasMap[\substr($segment, 1)])) {
|
||||
$isPathParam = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ($isPathParam) { // Param is in URL path (directly or through alias)
|
||||
$node['in'] = 'path';
|
||||
$temp['parameters'][] = $node;
|
||||
} elseif ($route->getMethod() == 'GET') { // Param is in query
|
||||
@@ -767,7 +779,14 @@ class Swagger2 extends Format
|
||||
}
|
||||
}
|
||||
|
||||
$url = \str_replace(':' . $name, '{' . $name . '}', $url);
|
||||
$segments = \explode('/', $url);
|
||||
foreach ($segments as &$segment) {
|
||||
if ($segment !== '' && $segment[0] === ':' && isset($pathAliasMap[\substr($segment, 1)])) {
|
||||
$segment = '{' . $name . '}';
|
||||
}
|
||||
}
|
||||
unset($segment);
|
||||
$url = \implode('/', $segments);
|
||||
}
|
||||
|
||||
if (!empty($bodyRequired)) {
|
||||
|
||||
@@ -25,6 +25,20 @@ class OAuth2Google extends OAuth2Base
|
||||
return 'GOCSPX-2k8gsR0000000000000000VNahJj';
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->addRule('prompt', [
|
||||
'type' => self::TYPE_ENUM,
|
||||
'description' => 'Google OAuth2 prompt values.',
|
||||
'default' => ['consent'],
|
||||
'example' => ['consent'],
|
||||
'array' => true,
|
||||
'enum' => ['none', 'consent', 'select_account'],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Name
|
||||
*
|
||||
|
||||
@@ -50,6 +50,8 @@ class Comment
|
||||
|
||||
protected string $statePrefix = '[appwrite]: #';
|
||||
|
||||
protected ?string $tip = null;
|
||||
|
||||
/**
|
||||
* @var mixed[] $builds
|
||||
*/
|
||||
@@ -81,7 +83,14 @@ class Comment
|
||||
|
||||
public function generateComment(): string
|
||||
{
|
||||
$json = \json_encode($this->builds);
|
||||
if ($this->tip === null) {
|
||||
$this->tip = $this->tips[\array_rand($this->tips)];
|
||||
}
|
||||
|
||||
$json = \json_encode([
|
||||
'builds' => $this->builds,
|
||||
'tip' => $this->tip,
|
||||
]);
|
||||
|
||||
$text = $this->statePrefix . \base64_encode($json) . "\n\n";
|
||||
|
||||
@@ -226,8 +235,7 @@ class Comment
|
||||
$i++;
|
||||
}
|
||||
|
||||
$tip = $this->tips[array_rand($this->tips)];
|
||||
$text .= "\n<br>\n\n> [!TIP]\n> $tip\n\n";
|
||||
$text .= "\n<br>\n\n> [!TIP]\n> {$this->tip}\n\n";
|
||||
|
||||
return $text;
|
||||
}
|
||||
@@ -252,8 +260,15 @@ class Comment
|
||||
|
||||
$json = \base64_decode($state);
|
||||
|
||||
$builds = \json_decode($json, true);
|
||||
$this->builds = \is_array($builds) ? $builds : [];
|
||||
$data = \json_decode($json, true);
|
||||
|
||||
if (\is_array($data) && \array_key_exists('builds', $data)) {
|
||||
$this->builds = \is_array($data['builds']) ? $data['builds'] : [];
|
||||
$this->tip = $data['tip'] ?? null;
|
||||
} else {
|
||||
// Backward compatibility with old state format (builds array only)
|
||||
$this->builds = \is_array($data) ? $data : [];
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
@@ -10,7 +10,6 @@ use Tests\E2E\Scopes\SideConsole;
|
||||
use Utopia\Database\Helpers\ID;
|
||||
use Utopia\Database\Helpers\Permission;
|
||||
use Utopia\Database\Helpers\Role;
|
||||
use Utopia\Database\Query;
|
||||
|
||||
class DatabasesConsoleClientTest extends Scope
|
||||
{
|
||||
@@ -258,55 +257,4 @@ class DatabasesConsoleClientTest extends Scope
|
||||
$this->assertIsArray($response['body']['documents']);
|
||||
}
|
||||
|
||||
#[Depends('testCreateCollection')]
|
||||
public function testGetCollectionLogs(array $data)
|
||||
{
|
||||
$databaseId = $data['databaseId'];
|
||||
/**
|
||||
* Test for SUCCESS
|
||||
*/
|
||||
$logs = $this->client->call(Client::METHOD_GET, '/vectorsdb/' . $databaseId . '/collections/' . $data['moviesId'] . '/logs', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()));
|
||||
|
||||
$this->assertEquals(200, $logs['headers']['status-code']);
|
||||
$this->assertIsArray($logs['body']['logs']);
|
||||
$this->assertIsNumeric($logs['body']['total']);
|
||||
|
||||
$logs = $this->client->call(Client::METHOD_GET, '/vectorsdb/' . $databaseId . '/collections/' . $data['moviesId'] . '/logs', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'queries' => [Query::limit(1)->toString()]
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $logs['headers']['status-code']);
|
||||
$this->assertIsArray($logs['body']['logs']);
|
||||
$this->assertLessThanOrEqual(1, count($logs['body']['logs']));
|
||||
$this->assertIsNumeric($logs['body']['total']);
|
||||
|
||||
$logs = $this->client->call(Client::METHOD_GET, '/vectorsdb/' . $databaseId . '/collections/' . $data['moviesId'] . '/logs', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'queries' => [Query::offset(1)->toString()]
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $logs['headers']['status-code']);
|
||||
$this->assertIsArray($logs['body']['logs']);
|
||||
$this->assertIsNumeric($logs['body']['total']);
|
||||
|
||||
$logs = $this->client->call(Client::METHOD_GET, '/vectorsdb/' . $databaseId . '/collections/' . $data['moviesId'] . '/logs', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'queries' => [Query::offset(1)->toString(), Query::limit(1)->toString()]
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $logs['headers']['status-code']);
|
||||
$this->assertIsArray($logs['body']['logs']);
|
||||
$this->assertLessThanOrEqual(1, count($logs['body']['logs']));
|
||||
$this->assertIsNumeric($logs['body']['total']);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,6 @@ use Tests\E2E\Scopes\SideConsole;
|
||||
use Utopia\Database\Helpers\ID;
|
||||
use Utopia\Database\Helpers\Permission;
|
||||
use Utopia\Database\Helpers\Role;
|
||||
use Utopia\Database\Query;
|
||||
|
||||
class VectorsDBConsoleClientTest extends Scope
|
||||
{
|
||||
@@ -258,55 +257,4 @@ class VectorsDBConsoleClientTest extends Scope
|
||||
$this->assertIsArray($response['body']['documents']);
|
||||
}
|
||||
|
||||
#[Depends('testCreateCollection')]
|
||||
public function testGetCollectionLogs(array $data)
|
||||
{
|
||||
$databaseId = $data['databaseId'];
|
||||
/**
|
||||
* Test for SUCCESS
|
||||
*/
|
||||
$logs = $this->client->call(Client::METHOD_GET, '/vectorsdb/' . $databaseId . '/collections/' . $data['moviesId'] . '/logs', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()));
|
||||
|
||||
$this->assertEquals(200, $logs['headers']['status-code']);
|
||||
$this->assertIsArray($logs['body']['logs']);
|
||||
$this->assertIsNumeric($logs['body']['total']);
|
||||
|
||||
$logs = $this->client->call(Client::METHOD_GET, '/vectorsdb/' . $databaseId . '/collections/' . $data['moviesId'] . '/logs', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'queries' => [Query::limit(1)->toString()]
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $logs['headers']['status-code']);
|
||||
$this->assertIsArray($logs['body']['logs']);
|
||||
$this->assertLessThanOrEqual(1, count($logs['body']['logs']));
|
||||
$this->assertIsNumeric($logs['body']['total']);
|
||||
|
||||
$logs = $this->client->call(Client::METHOD_GET, '/vectorsdb/' . $databaseId . '/collections/' . $data['moviesId'] . '/logs', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'queries' => [Query::offset(1)->toString()]
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $logs['headers']['status-code']);
|
||||
$this->assertIsArray($logs['body']['logs']);
|
||||
$this->assertIsNumeric($logs['body']['total']);
|
||||
|
||||
$logs = $this->client->call(Client::METHOD_GET, '/vectorsdb/' . $databaseId . '/collections/' . $data['moviesId'] . '/logs', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'queries' => [Query::offset(1)->toString(), Query::limit(1)->toString()]
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $logs['headers']['status-code']);
|
||||
$this->assertIsArray($logs['body']['logs']);
|
||||
$this->assertLessThanOrEqual(1, count($logs['body']['logs']));
|
||||
$this->assertIsNumeric($logs['body']['total']);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ namespace Tests\E2E\Services\GraphQL;
|
||||
|
||||
use CURLFile;
|
||||
use Utopia\Console;
|
||||
use Utopia\Image\Image;
|
||||
|
||||
trait Base
|
||||
{
|
||||
@@ -516,6 +517,21 @@ trait Base
|
||||
}
|
||||
';
|
||||
|
||||
protected function assertFilePreviewResponse(array $file): void
|
||||
{
|
||||
$this->assertEquals(200, $file['headers']['status-code']);
|
||||
$this->assertEquals('image/png', $file['headers']['content-type']);
|
||||
$this->assertNotEmpty($file['body']);
|
||||
|
||||
$image = new Image($file['body']);
|
||||
$dimensions = \getimagesizefromstring($file['body']);
|
||||
|
||||
$this->assertNotEmpty($image->output('png'));
|
||||
$this->assertIsArray($dimensions);
|
||||
$this->assertEquals(100, $dimensions[0]);
|
||||
$this->assertEquals(100, $dimensions[1]);
|
||||
}
|
||||
|
||||
public function getQuery(string $name): string
|
||||
{
|
||||
switch ($name) {
|
||||
@@ -2388,8 +2404,8 @@ trait Base
|
||||
}
|
||||
}';
|
||||
case self::GET_FILE_PREVIEW:
|
||||
return 'query getFilePreview($bucketId: String!, $fileId: String!) {
|
||||
storageGetFilePreview(bucketId: $bucketId, fileId: $fileId) {
|
||||
return 'query getFilePreview($bucketId: String!, $fileId: String!, $width: Int, $height: Int) {
|
||||
storageGetFilePreview(bucketId: $bucketId, fileId: $fileId, width: $width, height: $height) {
|
||||
status
|
||||
}
|
||||
}';
|
||||
|
||||
@@ -200,7 +200,7 @@ class StorageClientTest extends Scope
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $gqlPayload);
|
||||
|
||||
$this->assertEquals(46719, \strlen($file['body']));
|
||||
$this->assertFilePreviewResponse($file);
|
||||
|
||||
return $file;
|
||||
}
|
||||
|
||||
@@ -262,7 +262,7 @@ class StorageServerTest extends Scope
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $gqlPayload);
|
||||
|
||||
$this->assertEquals(46719, \strlen($file['body']));
|
||||
$this->assertFilePreviewResponse($file);
|
||||
|
||||
return $file;
|
||||
}
|
||||
|
||||
@@ -2564,6 +2564,159 @@ trait OAuth2Base
|
||||
]);
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Update Google (clientId + clientSecret + optional prompt)
|
||||
// =========================================================================
|
||||
|
||||
/**
|
||||
* Default prompt MUST run before any other Google test that sets a custom
|
||||
* prompt value. The global resetProjectOAuth2() only clears Amazon state,
|
||||
* so Google state leaks across tests in the same class. Running this first
|
||||
* guarantees the stored JSON blob has no pre-existing "prompt" key.
|
||||
*/
|
||||
public function testUpdateOAuth2GoogleDefaultPrompt(): void
|
||||
{
|
||||
// When prompt is omitted and nothing is stored, the default is ['consent'].
|
||||
$response = $this->updateOAuth2('google', [
|
||||
'clientId' => 'google-default-client',
|
||||
'clientSecret' => 'google-default-secret',
|
||||
'enabled' => false,
|
||||
]);
|
||||
|
||||
$this->assertSame(200, $response['headers']['status-code']);
|
||||
$this->assertSame(['consent'], $response['body']['prompt']);
|
||||
|
||||
// Cleanup
|
||||
$this->updateOAuth2('google', [
|
||||
'clientId' => '',
|
||||
'clientSecret' => '',
|
||||
'enabled' => false,
|
||||
]);
|
||||
}
|
||||
|
||||
public function testUpdateOAuth2Google(): void
|
||||
{
|
||||
$response = $this->updateOAuth2('google', [
|
||||
'clientId' => '120000000095-92ifjb00000000000000000000g7ijfb.apps.googleusercontent.com',
|
||||
'clientSecret' => 'GOCSPX-2k8gsR0000000000000000VNahJj',
|
||||
'prompt' => ['select_account'],
|
||||
'enabled' => false,
|
||||
]);
|
||||
|
||||
$this->assertSame(200, $response['headers']['status-code']);
|
||||
$this->assertSame('google', $response['body']['$id']);
|
||||
$this->assertSame('120000000095-92ifjb00000000000000000000g7ijfb.apps.googleusercontent.com', $response['body']['clientId']);
|
||||
$this->assertSame(['select_account'], $response['body']['prompt']);
|
||||
|
||||
// Cleanup
|
||||
$this->updateOAuth2('google', [
|
||||
'clientId' => '',
|
||||
'clientSecret' => '',
|
||||
'enabled' => false,
|
||||
]);
|
||||
}
|
||||
|
||||
public function testUpdateOAuth2GooglePartialPreservesPrompt(): void
|
||||
{
|
||||
// Seed clientSecret + prompt.
|
||||
$this->updateOAuth2('google', [
|
||||
'clientId' => 'google-seed-client',
|
||||
'clientSecret' => 'google-seed-secret',
|
||||
'prompt' => ['consent', 'select_account'],
|
||||
'enabled' => false,
|
||||
]);
|
||||
|
||||
// Update only clientId.
|
||||
$response = $this->updateOAuth2('google', [
|
||||
'clientId' => 'google-rotated-client',
|
||||
]);
|
||||
|
||||
$this->assertSame(200, $response['headers']['status-code']);
|
||||
$this->assertSame('google-rotated-client', $response['body']['clientId']);
|
||||
$this->assertSame(['consent', 'select_account'], $response['body']['prompt']);
|
||||
|
||||
// Cleanup
|
||||
$this->updateOAuth2('google', [
|
||||
'clientId' => '',
|
||||
'clientSecret' => '',
|
||||
'enabled' => false,
|
||||
]);
|
||||
}
|
||||
|
||||
public function testUpdateOAuth2GooglePromptNoneAloneRejected(): void
|
||||
{
|
||||
$response = $this->updateOAuth2('google', [
|
||||
'clientId' => 'whatever',
|
||||
'clientSecret' => 'whatever',
|
||||
'prompt' => ['none', 'consent'],
|
||||
'enabled' => false,
|
||||
]);
|
||||
|
||||
$this->assertSame(400, $response['headers']['status-code']);
|
||||
$this->assertSame('general_argument_invalid', $response['body']['type']);
|
||||
}
|
||||
|
||||
public function testUpdateOAuth2GooglePromptEmptyArrayRejected(): void
|
||||
{
|
||||
$response = $this->updateOAuth2('google', [
|
||||
'clientId' => 'whatever',
|
||||
'clientSecret' => 'whatever',
|
||||
'prompt' => [],
|
||||
'enabled' => false,
|
||||
]);
|
||||
|
||||
$this->assertSame(400, $response['headers']['status-code']);
|
||||
$this->assertSame('general_argument_invalid', $response['body']['type']);
|
||||
}
|
||||
|
||||
public function testUpdateOAuth2GooglePromptNoneAloneAccepted(): void
|
||||
{
|
||||
$response = $this->updateOAuth2('google', [
|
||||
'clientId' => '120000000095-92ifjb00000000000000000000g7ijfb.apps.googleusercontent.com',
|
||||
'clientSecret' => 'GOCSPX-2k8gsR0000000000000000VNahJj',
|
||||
'prompt' => ['none'],
|
||||
'enabled' => false,
|
||||
]);
|
||||
|
||||
$this->assertSame(200, $response['headers']['status-code']);
|
||||
$this->assertSame(['none'], $response['body']['prompt']);
|
||||
|
||||
// Cleanup
|
||||
$this->updateOAuth2('google', [
|
||||
'clientId' => '',
|
||||
'clientSecret' => '',
|
||||
'enabled' => false,
|
||||
]);
|
||||
}
|
||||
|
||||
public function testUpdateOAuth2GoogleEnableAndReadBack(): void
|
||||
{
|
||||
$update = $this->updateOAuth2('google', [
|
||||
'clientId' => 'google-enable-client',
|
||||
'clientSecret' => 'google-enable-secret',
|
||||
'prompt' => ['select_account'],
|
||||
'enabled' => true,
|
||||
]);
|
||||
|
||||
$this->assertSame(200, $update['headers']['status-code']);
|
||||
$this->assertTrue($update['body']['enabled']);
|
||||
|
||||
// GET must hide clientSecret while keeping clientId and prompt.
|
||||
$get = $this->getOAuth2Provider('google');
|
||||
$this->assertSame(200, $get['headers']['status-code']);
|
||||
$this->assertTrue($get['body']['enabled']);
|
||||
$this->assertSame('google-enable-client', $get['body']['clientId']);
|
||||
$this->assertSame(['select_account'], $get['body']['prompt']);
|
||||
$this->assertSame('', $get['body']['clientSecret']);
|
||||
|
||||
// Cleanup
|
||||
$this->updateOAuth2('google', [
|
||||
'clientId' => '',
|
||||
'clientSecret' => '',
|
||||
'enabled' => false,
|
||||
]);
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Smoke test: every plain (clientId + clientSecret) provider
|
||||
//
|
||||
|
||||
@@ -2633,6 +2633,7 @@ class SitesCustomServerTest extends Scope
|
||||
|
||||
// Poll for execution logs to be written (async)
|
||||
// Filter by requestPath to avoid picking up screenshot worker executions
|
||||
// Wait for both the execution entry AND its logs field to be populated
|
||||
$logs = null;
|
||||
$timeout = 120;
|
||||
$start = \time();
|
||||
@@ -2642,12 +2643,13 @@ class SitesCustomServerTest extends Scope
|
||||
Query::equal('requestPath', ['/logs-inline'])->toString(),
|
||||
Query::limit(1)->toString(),
|
||||
]);
|
||||
if (!empty($logs['body']['executions'])) {
|
||||
if (!empty($logs['body']['executions']) && !empty($logs['body']['executions'][0]['logs'])) {
|
||||
break;
|
||||
}
|
||||
\usleep(500000);
|
||||
}
|
||||
$this->assertNotEmpty($logs['body']['executions'], 'Execution logs were not available within timeout');
|
||||
$this->assertNotNull($logs['body']['executions'][0]['logs'], 'Execution logs content was not populated within timeout');
|
||||
$this->assertEquals(200, $logs['headers']['status-code']);
|
||||
$this->assertStringContainsString($deploymentId, $logs['body']['executions'][0]['deploymentId']);
|
||||
$this->assertStringContainsString("GET", $logs['body']['executions'][0]['requestMethod']);
|
||||
@@ -2681,11 +2683,21 @@ class SitesCustomServerTest extends Scope
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertStringContainsString("Action logs printed.", $response['body']);
|
||||
|
||||
$logs = $this->listLogs($siteId, [
|
||||
Query::orderDesc('$createdAt')->toString(),
|
||||
Query::equal('requestPath', ['/logs-action'])->toString(),
|
||||
Query::limit(1)->toString(),
|
||||
]);
|
||||
$logs = null;
|
||||
$start = \time();
|
||||
while (\time() - $start < $timeout) {
|
||||
$logs = $this->listLogs($siteId, [
|
||||
Query::orderDesc('$createdAt')->toString(),
|
||||
Query::equal('requestPath', ['/logs-action'])->toString(),
|
||||
Query::limit(1)->toString(),
|
||||
]);
|
||||
if (!empty($logs['body']['executions']) && !empty($logs['body']['executions'][0]['logs'])) {
|
||||
break;
|
||||
}
|
||||
\usleep(500000);
|
||||
}
|
||||
$this->assertNotEmpty($logs['body']['executions'], 'Action execution logs were not available within timeout');
|
||||
$this->assertNotNull($logs['body']['executions'][0]['logs'], 'Action execution logs content was not populated within timeout');
|
||||
$this->assertEquals(200, $logs['headers']['status-code']);
|
||||
$this->assertStringContainsString($deploymentId, $logs['body']['executions'][0]['deploymentId']);
|
||||
$this->assertStringContainsString("GET", $logs['body']['executions'][0]['requestMethod']);
|
||||
|
||||
@@ -0,0 +1,154 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Unit\Vcs;
|
||||
|
||||
use Appwrite\Vcs\Comment;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Utopia\Database\Document;
|
||||
|
||||
class CommentTest extends TestCase
|
||||
{
|
||||
public function testTipIsPreservedAcrossMultipleGenerations(): void
|
||||
{
|
||||
$comment = new Comment(['consoleHostname' => 'localhost']);
|
||||
$comment->addBuild(
|
||||
new Document(['$id' => 'project1', 'name' => 'Test Project', 'region' => 'default']),
|
||||
new Document(['$id' => 'func1', 'name' => 'Test Function']),
|
||||
'function',
|
||||
'ready',
|
||||
'dep1',
|
||||
['type' => 'logs'],
|
||||
''
|
||||
);
|
||||
|
||||
$first = $comment->generateComment();
|
||||
$firstTip = $this->extractTip($first);
|
||||
|
||||
$this->assertNotNull($firstTip);
|
||||
$this->assertNotEmpty($firstTip);
|
||||
|
||||
$second = $comment->generateComment();
|
||||
$secondTip = $this->extractTip($second);
|
||||
|
||||
$this->assertEquals($firstTip, $secondTip);
|
||||
}
|
||||
|
||||
public function testTipIsRestoredFromParsedComment(): void
|
||||
{
|
||||
$comment = new Comment(['consoleHostname' => 'localhost']);
|
||||
$comment->addBuild(
|
||||
new Document(['$id' => 'project1', 'name' => 'Test Project', 'region' => 'default']),
|
||||
new Document(['$id' => 'func1', 'name' => 'Test Function']),
|
||||
'function',
|
||||
'ready',
|
||||
'dep1',
|
||||
['type' => 'logs'],
|
||||
''
|
||||
);
|
||||
|
||||
$original = $comment->generateComment();
|
||||
$originalTip = $this->extractTip($original);
|
||||
|
||||
$parsed = new Comment(['consoleHostname' => 'localhost']);
|
||||
$parsed->parseComment($original);
|
||||
$parsed->addBuild(
|
||||
new Document(['$id' => 'project1', 'name' => 'Test Project', 'region' => 'default']),
|
||||
new Document(['$id' => 'func2', 'name' => 'Another Function']),
|
||||
'function',
|
||||
'building',
|
||||
'dep2',
|
||||
['type' => 'logs'],
|
||||
''
|
||||
);
|
||||
|
||||
$regenerated = $parsed->generateComment();
|
||||
$regeneratedTip = $this->extractTip($regenerated);
|
||||
|
||||
$this->assertEquals($originalTip, $regeneratedTip);
|
||||
}
|
||||
|
||||
public function testBackwardCompatibilityWithOldStateFormat(): void
|
||||
{
|
||||
$oldBuilds = [
|
||||
'project1_func1' => [
|
||||
'projectName' => 'Test Project',
|
||||
'projectId' => 'project1',
|
||||
'region' => 'default',
|
||||
'resourceName' => 'Test Function',
|
||||
'resourceId' => 'func1',
|
||||
'resourceType' => 'function',
|
||||
'buildStatus' => 'ready',
|
||||
'deploymentId' => 'dep1',
|
||||
'action' => ['type' => 'logs'],
|
||||
'previewUrl' => '',
|
||||
],
|
||||
];
|
||||
|
||||
$oldState = '[appwrite]: #' . \base64_encode(\json_encode($oldBuilds)) . "\n\n";
|
||||
$oldState .= "> [!TIP]\n> Old tip that should be ignored\n\n";
|
||||
|
||||
$comment = new Comment(['consoleHostname' => 'localhost']);
|
||||
$comment->parseComment($oldState);
|
||||
|
||||
$new = $comment->generateComment();
|
||||
$newTip = $this->extractTip($new);
|
||||
|
||||
$this->assertNotNull($newTip);
|
||||
$this->assertNotEquals('Old tip that should be ignored', $newTip);
|
||||
$this->assertContains($newTip, $this->getTips());
|
||||
}
|
||||
|
||||
public function testParseOldStateFormatWithOnlyBuilds(): void
|
||||
{
|
||||
$oldBuilds = [
|
||||
'project1_func1' => [
|
||||
'projectName' => 'Test Project',
|
||||
'projectId' => 'project1',
|
||||
'region' => 'default',
|
||||
'resourceName' => 'Test Function',
|
||||
'resourceId' => 'func1',
|
||||
'resourceType' => 'function',
|
||||
'buildStatus' => 'ready',
|
||||
'deploymentId' => 'dep1',
|
||||
'action' => ['type' => 'logs'],
|
||||
'previewUrl' => '',
|
||||
],
|
||||
];
|
||||
|
||||
$state = '[appwrite]: #' . \base64_encode(\json_encode($oldBuilds)) . "\n\n";
|
||||
|
||||
$comment = new Comment(['consoleHostname' => 'localhost']);
|
||||
$comment->parseComment($state);
|
||||
|
||||
$this->assertEquals(false, $comment->isEmpty());
|
||||
|
||||
$first = $comment->generateComment();
|
||||
$firstTip = $this->extractTip($first);
|
||||
|
||||
$this->assertNotNull($firstTip);
|
||||
$this->assertNotEmpty($firstTip);
|
||||
$this->assertContains($firstTip, $this->getTips());
|
||||
|
||||
$second = $comment->generateComment();
|
||||
$secondTip = $this->extractTip($second);
|
||||
|
||||
$this->assertEquals($firstTip, $secondTip);
|
||||
}
|
||||
|
||||
private function extractTip(string $comment): ?string
|
||||
{
|
||||
if (\preg_match('/> \[!TIP\]\n> (.+)/', $comment, $matches)) {
|
||||
return $matches[1];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private function getTips(): array
|
||||
{
|
||||
$reflection = new \ReflectionClass(Comment::class);
|
||||
$property = $reflection->getProperty('tips');
|
||||
|
||||
return $property->getValue(new Comment(['consoleHostname' => 'localhost']));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user