mirror of
https://github.com/appwrite/appwrite.git
synced 2026-05-26 13:51:13 +00:00
Compare commits
441
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c6a8e5710e | ||
|
|
4efe1e6890 | ||
|
|
c7caf8d23d | ||
|
|
a4d889145b | ||
|
|
fe1a2f6364 | ||
|
|
3f0d1d2a27 | ||
|
|
08b18f0774 | ||
|
|
6f1841681f | ||
|
|
29d2a99e92 | ||
|
|
17bae22b24 | ||
|
|
6bc5d0dd06 | ||
|
|
7f97d91f66 | ||
|
|
6266dfb7ab | ||
|
|
88648b78f0 | ||
|
|
e7f720b8e7 | ||
|
|
8685266e97 | ||
|
|
e19ca24d5d | ||
|
|
ee97d4ef4d | ||
|
|
764fb0d156 | ||
|
|
a659b0b187 | ||
|
|
22be6514fb | ||
|
|
7e4645ffbd | ||
|
|
4b801273c0 | ||
|
|
d6ca57d008 | ||
|
|
03555fa365 | ||
|
|
b07046eaba | ||
|
|
9cbd5be707 | ||
|
|
e9aaea19a4 | ||
|
|
3fc5fd8fcf | ||
|
|
5f63100f4c | ||
|
|
39aeecc58c | ||
|
|
ce31a4f336 | ||
|
|
5f1e537e53 | ||
|
|
27370b6acb | ||
|
|
f1abdde9cf | ||
|
|
8436fb0175 | ||
|
|
6a4dcd76a7 | ||
|
|
e831b3e952 | ||
|
|
4dec4f2ff2 | ||
|
|
aa4c6b6e8f | ||
|
|
2628cc31fc | ||
|
|
e3768ce8ee | ||
|
|
dfc5f1c8dc | ||
|
|
e375009a49 | ||
|
|
cf1623bc91 | ||
|
|
e364082577 | ||
|
|
9951ccdca3 | ||
|
|
035cbacbfb | ||
|
|
690c72b789 | ||
|
|
dd0c8af160 | ||
|
|
933fffd763 | ||
|
|
1538927486 | ||
|
|
49bf24d388 | ||
|
|
f62d765bbf | ||
|
|
268dd50106 | ||
|
|
4eb956b820 | ||
|
|
01a6d6e4d6 | ||
|
|
c1e902c79c | ||
|
|
9bcb4d7ca7 | ||
|
|
f77205295b | ||
|
|
781df9d4cf | ||
|
|
238b4e447d | ||
|
|
349ac6bbc1 | ||
|
|
dca75b059b | ||
|
|
fd145e988a | ||
|
|
c6ac0f28e4 | ||
|
|
52d7ebd41e | ||
|
|
41ff2ed48e | ||
|
|
436064e7cc | ||
|
|
a3ae9f39ec | ||
|
|
19ab27bf57 | ||
|
|
9ad625da36 | ||
|
|
26e8792b63 | ||
|
|
c2df9409d6 | ||
|
|
fd6146888b | ||
|
|
91227249b8 | ||
|
|
4400eedbae | ||
|
|
cbcf86e362 | ||
|
|
83dc606c14 | ||
|
|
e69b9eb428 | ||
|
|
8cfc28f08c | ||
|
|
5473be5756 | ||
|
|
a3fca62818 | ||
|
|
d397ab9b40 | ||
|
|
e59e726d38 | ||
|
|
8272aa6fa2 | ||
|
|
43b9bf8152 | ||
|
|
7312f4bfe0 | ||
|
|
a83c01db65 | ||
|
|
d50dcad24a | ||
|
|
5df3280131 | ||
|
|
151ef8ab7c | ||
|
|
515696dd36 | ||
|
|
227e47f121 | ||
|
|
940fa701da | ||
|
|
ff76f3efa0 | ||
|
|
2ef2d4d498 | ||
|
|
0d19894331 | ||
|
|
1d9acf5f29 | ||
|
|
d3994b436d | ||
|
|
cc51f42430 | ||
|
|
8cf0438a98 | ||
|
|
b2ebfa9bb9 | ||
|
|
ed14acfc75 | ||
|
|
90df8efe0a | ||
|
|
c1aeaf9bb9 | ||
|
|
2b8d035545 | ||
|
|
f7b09c090b | ||
|
|
d9e4318ab9 | ||
|
|
4523ad5e1f | ||
|
|
a67930a18a | ||
|
|
24900ee08e | ||
|
|
aa3a63c87b | ||
|
|
586eb3ca19 | ||
|
|
8c903ab687 | ||
|
|
c5b8535a7f | ||
|
|
cf101b6a8e | ||
|
|
218cc13b20 | ||
|
|
282b5b2a6c | ||
|
|
948c702ad4 | ||
|
|
c28f476462 | ||
|
|
3078cc5941 | ||
|
|
74a4ae19ee | ||
|
|
3b3a04877f | ||
|
|
a4a57bc64d | ||
|
|
fd91490ae9 | ||
|
|
81d78ceb96 | ||
|
|
9233fe5b42 | ||
|
|
3c1e07fc92 | ||
|
|
27534133bb | ||
|
|
52b8e1b94d | ||
|
|
4d3c60b36c | ||
|
|
8d93922a52 | ||
|
|
89d8c9285a | ||
|
|
ffe06180bc | ||
|
|
349db8d370 | ||
|
|
246ff8900b | ||
|
|
6f28f2e4f7 | ||
|
|
42144ab9a0 | ||
|
|
836a89714d | ||
|
|
f1d40a8e92 | ||
|
|
05eead1857 | ||
|
|
2cd422f4c2 | ||
|
|
b756a38252 | ||
|
|
4f676aac15 | ||
|
|
7a711b7295 | ||
|
|
03e62dde94 | ||
|
|
b81b818c47 | ||
|
|
95789b5aec | ||
|
|
20c0c8081c | ||
|
|
254606c4f9 | ||
|
|
7cef1202f1 | ||
|
|
ff298cb206 | ||
|
|
9263ea3661 | ||
|
|
8aa09cfcc1 | ||
|
|
ab208a01b6 | ||
|
|
80fffa32b8 | ||
|
|
664c24173e | ||
|
|
5b3af53bdd | ||
|
|
b2809c911d | ||
|
|
febe332050 | ||
|
|
d18de7d1e8 | ||
|
|
4e39506bca | ||
|
|
1b945bdeea | ||
|
|
5c9f360744 | ||
|
|
4a44af7308 | ||
|
|
37921a5694 | ||
|
|
27bf229524 | ||
|
|
e873f02b94 | ||
|
|
5750155591 | ||
|
|
d2dcd82efa | ||
|
|
cb80f0e9c8 | ||
|
|
6a2ddb221c | ||
|
|
6428b73966 | ||
|
|
7b718e7dcc | ||
|
|
15422dc7ca | ||
|
|
0e7c50c181 | ||
|
|
8781942633 | ||
|
|
a614afe5c7 | ||
|
|
cf80efbc3c | ||
|
|
ec1fad20dd | ||
|
|
2c2e8bcabd | ||
|
|
b7bf14a18a | ||
|
|
0e157efabe | ||
|
|
69735b383b | ||
|
|
b627a7d6ff | ||
|
|
14381f2268 | ||
|
|
97d6656687 | ||
|
|
e6f3061103 | ||
|
|
9acfb2a679 | ||
|
|
c5123529ee | ||
|
|
a0c27afec8 | ||
|
|
5a0bb57db2 | ||
|
|
d422b7abdc | ||
|
|
34078ad9af | ||
|
|
2d94704948 | ||
|
|
cd54e91784 | ||
|
|
79f536445e | ||
|
|
11839c6dcc | ||
|
|
a9eb9d47a5 | ||
|
|
246f487687 | ||
|
|
88e7fdbd9e | ||
|
|
aada60bdb1 | ||
|
|
f43e55620b | ||
|
|
bf3ac31157 | ||
|
|
e9b026647a | ||
|
|
e93a2c7101 | ||
|
|
8d8a0e883e | ||
|
|
75587e629e | ||
|
|
ad88b827e6 | ||
|
|
4d1dabac66 | ||
|
|
a8670ee8fb | ||
|
|
5b109f2f8d | ||
|
|
3e35b888a7 | ||
|
|
bcd2bfe5b2 | ||
|
|
d7e29a4dae | ||
|
|
3596662fec | ||
|
|
f40c7d415c | ||
|
|
176d59e7c9 | ||
|
|
bb25a36cf5 | ||
|
|
f37e091b77 | ||
|
|
18380a679d | ||
|
|
2a82604a2b | ||
|
|
b618bf1353 | ||
|
|
0e1cabe47a | ||
|
|
54693d9417 | ||
|
|
2cbd8bbb47 | ||
|
|
12b2acd7f7 | ||
|
|
3684fee2a2 | ||
|
|
aaa870e656 | ||
|
|
94274381ff | ||
|
|
3703da188e | ||
|
|
d9446fa892 | ||
|
|
8a4a6d83d4 | ||
|
|
b524b9f934 | ||
|
|
275a6fe078 | ||
|
|
8b4c90e603 | ||
|
|
a4acd000c4 | ||
|
|
f7e5e825f2 | ||
|
|
7dd85f9143 | ||
|
|
7da3591d2b | ||
|
|
faf13c502b | ||
|
|
43da03ef63 | ||
|
|
5e4de3931a | ||
|
|
2b7ed2aa24 | ||
|
|
5123956790 | ||
|
|
28d0348ef5 | ||
|
|
ad0b2ebb06 | ||
|
|
a3542ed7fa | ||
|
|
0aa8d402ae | ||
|
|
0466c82a42 | ||
|
|
995f844254 | ||
|
|
18a26809af | ||
|
|
42d3240d05 | ||
|
|
52c072489b | ||
|
|
602ceb55ef | ||
|
|
f09aec7651 | ||
|
|
f818f16ebe | ||
|
|
20162a69be | ||
|
|
f1e0ef2991 | ||
|
|
a1f321d92e | ||
|
|
4e2bd9c307 | ||
|
|
e799589d11 | ||
|
|
efadf1d74b | ||
|
|
7f6482a7d2 | ||
|
|
1435e2c526 | ||
|
|
84f18341ed | ||
|
|
2ea66a0d8b | ||
|
|
2d5289c383 | ||
|
|
06068c0406 | ||
|
|
5827f2ece6 | ||
|
|
62de39b858 | ||
|
|
c1cab2e41a | ||
|
|
95dd44115d | ||
|
|
cad35c7d5c | ||
|
|
9f65c6ce41 | ||
|
|
28621678dd | ||
|
|
4b98579adf | ||
|
|
6b7fb918cc | ||
|
|
ffeb27e5a3 | ||
|
|
46e86ce854 | ||
|
|
5767ab8ac7 | ||
|
|
5002afde09 | ||
|
|
c13a0d1010 | ||
|
|
f526fc8577 | ||
|
|
63333a0cbf | ||
|
|
0048d3f911 | ||
|
|
31adbe6189 | ||
|
|
a1811570b3 | ||
|
|
90f5f9b30b | ||
|
|
408a12322a | ||
|
|
97477686fd | ||
|
|
9de9e5bdf0 | ||
|
|
de361573f2 | ||
|
|
917ce627bb | ||
|
|
a8f33ab964 | ||
|
|
0f67fd7618 | ||
|
|
ec8d48327c | ||
|
|
ce07c6ffd5 | ||
|
|
6679d02a35 | ||
|
|
63e226c761 | ||
|
|
6290a664e9 | ||
|
|
ac80ba270b | ||
|
|
f9c18a6462 | ||
|
|
d2ea6ef2ca | ||
|
|
1946ee86b2 | ||
|
|
5eabeeea20 | ||
|
|
d043b4740e | ||
|
|
6eb84c8235 | ||
|
|
61cee5892a | ||
|
|
a77558807c | ||
|
|
f455bb7a58 | ||
|
|
bdf6c660d9 | ||
|
|
b0a2ef271a | ||
|
|
5c7e08fdff | ||
|
|
fd83090215 | ||
|
|
2ed8247fba | ||
|
|
b2b6fb6b69 | ||
|
|
4822164e32 | ||
|
|
42542aec61 | ||
|
|
6e19db130e | ||
|
|
ec0f7cf683 | ||
|
|
ced5b88b57 | ||
|
|
d58929b621 | ||
|
|
29092b277b | ||
|
|
9927e4bd8e | ||
|
|
0e0e40f991 | ||
|
|
3db776e2e5 | ||
|
|
61ca4e3969 | ||
|
|
40c2e1a1fa | ||
|
|
429454ca0e | ||
|
|
8cef822e8f | ||
|
|
6dae78a206 | ||
|
|
18ae81bbb0 | ||
|
|
e03218d8fa | ||
|
|
4f030619da | ||
|
|
e267dcf9ab | ||
|
|
9c1cbecda4 | ||
|
|
8597d48e41 | ||
|
|
971858078c | ||
|
|
88bbb965f8 | ||
|
|
5510bc4f6a | ||
|
|
d802c57cb9 | ||
|
|
32dc870eaf | ||
|
|
2c06e4185f | ||
|
|
869c35416d | ||
|
|
e6c3acb21c | ||
|
|
e7405b4393 | ||
|
|
3eabca1d7a | ||
|
|
ec984de510 | ||
|
|
9d6be1eef4 | ||
|
|
06a05311a4 | ||
|
|
58c248f4f2 | ||
|
|
59ee0901c9 | ||
|
|
2d3c1086ae | ||
|
|
e624040e57 | ||
|
|
496b91480b | ||
|
|
b08f3bdc52 | ||
|
|
9c646c2893 | ||
|
|
77942af4ea | ||
|
|
41b2962e64 | ||
|
|
25ae2e7314 | ||
|
|
8d378720b0 | ||
|
|
34f782d986 | ||
|
|
587a039493 | ||
|
|
93ee8f45ea | ||
|
|
5157da870f | ||
|
|
6fabfe08ce | ||
|
|
1eb7bf7619 | ||
|
|
a17f9ce989 | ||
|
|
053d25c64e | ||
|
|
8ca59f22c9 | ||
|
|
63f04f893d | ||
|
|
aa5543ac0a | ||
|
|
60e51548e8 | ||
|
|
e802102c8f | ||
|
|
87f29c6889 | ||
|
|
a70d241da0 | ||
|
|
e17cdc2bd4 | ||
|
|
a4f174ea7e | ||
|
|
175fcb51a8 | ||
|
|
772358ff51 | ||
|
|
abad809a91 | ||
|
|
076e64886e | ||
|
|
88107ee7b3 | ||
|
|
db720fbb22 | ||
|
|
d92580470d | ||
|
|
fc714de11d | ||
|
|
92e8334169 | ||
|
|
8716497d1f | ||
|
|
d8486612de | ||
|
|
1225aa4861 | ||
|
|
e8006dce6e | ||
|
|
894d771a7c | ||
|
|
4e928ee08b | ||
|
|
4da9873b83 | ||
|
|
082110bba8 | ||
|
|
7167fe2fb4 | ||
|
|
d1c3ee0290 | ||
|
|
6bd3821804 | ||
|
|
1d32faff4f | ||
|
|
e67016584f | ||
|
|
a4b6d9a635 | ||
|
|
1f27769cc9 | ||
|
|
0d2a4da347 | ||
|
|
ee34e2b587 | ||
|
|
52dbf14d79 | ||
|
|
70f42a0dff | ||
|
|
2ef62b309e | ||
|
|
a480d8ed73 | ||
|
|
4fea92c9cb | ||
|
|
05add106c1 | ||
|
|
0e3a017d50 | ||
|
|
d28cce761d | ||
|
|
abb96f650b | ||
|
|
1785a78fa1 | ||
|
|
3f06b5cd1a | ||
|
|
95d5d1bdd9 | ||
|
|
cd6a6956d6 | ||
|
|
7e18e6f8c5 | ||
|
|
968b1c0861 | ||
|
|
2ce39768c3 | ||
|
|
69bc935517 | ||
|
|
26ff78d98a | ||
|
|
0935c60f3c | ||
|
|
54fa813bc6 | ||
|
|
f9d0cb7daf | ||
|
|
c5d5946fd2 | ||
|
|
af41c64135 | ||
|
|
63afcad23d | ||
|
|
eb76c31fdd | ||
|
|
51fd96f4bf | ||
|
|
14351c7338 | ||
|
|
01c6cc86ce | ||
|
|
c6f16cde4a | ||
|
|
8412790436 | ||
|
|
fdca14e659 | ||
|
|
e3b1eead55 | ||
|
|
11fc170d72 | ||
|
|
467b2a9281 | ||
|
|
3b2c8a4d36 |
@@ -442,7 +442,8 @@ jobs:
|
||||
VCS,
|
||||
Messaging,
|
||||
Migrations,
|
||||
Project
|
||||
Project,
|
||||
Presences
|
||||
]
|
||||
include:
|
||||
- service: Databases
|
||||
|
||||
+2
-56
@@ -2,17 +2,10 @@
|
||||
|
||||
require_once __DIR__ . '/init.php';
|
||||
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Event\Publisher\Certificate as CertificatePublisher;
|
||||
use Appwrite\Event\Publisher\Delete as DeletePublisher;
|
||||
use Appwrite\Event\Publisher\Func as FunctionPublisher;
|
||||
use Appwrite\Event\Publisher\StatsResources as StatsResourcesPublisher;
|
||||
use Appwrite\Event\Publisher\Usage as UsagePublisher;
|
||||
use Appwrite\Platform\Appwrite;
|
||||
use Appwrite\Runtimes\Runtimes;
|
||||
use Appwrite\Usage\Context as UsageContext;
|
||||
use Appwrite\Utopia\Database\Documents\User;
|
||||
use Executor\Executor;
|
||||
use Swoole\Runtime;
|
||||
use Swoole\Timer;
|
||||
use Utopia\Cache\Adapter\Pool as CachePool;
|
||||
@@ -26,17 +19,12 @@ use Utopia\Database\Adapter\Pool as DatabasePool;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\DI\Container;
|
||||
use Utopia\DSN\DSN;
|
||||
use Utopia\Logger\Log;
|
||||
use Utopia\Platform\Service;
|
||||
use Utopia\Pools\Group;
|
||||
use Utopia\Queue\Broker\Pool as BrokerPool;
|
||||
use Utopia\Queue\Publisher;
|
||||
use Utopia\Queue\Queue;
|
||||
use Utopia\Registry\Registry;
|
||||
use Utopia\System\System;
|
||||
use Utopia\Telemetry\Adapter\None as NoTelemetry;
|
||||
|
||||
use function Swoole\Coroutine\run;
|
||||
|
||||
@@ -47,6 +35,7 @@ Config::setParam('runtimes', (new Runtimes('v5'))->getAll(supported: false));
|
||||
require_once __DIR__ . '/controllers/general.php';
|
||||
|
||||
global $register;
|
||||
global $container;
|
||||
|
||||
$platform = new Appwrite();
|
||||
$args = $_SERVER['argv'] ?? [];
|
||||
@@ -58,7 +47,6 @@ if (! isset($args[0])) {
|
||||
}
|
||||
|
||||
$taskName = $args[0];
|
||||
$container = new Container();
|
||||
$cli = new CLI(new Generic(), $_SERVER['argv'] ?? [], $container);
|
||||
|
||||
$platform->setCli($cli);
|
||||
@@ -131,10 +119,6 @@ $container->set('dbForPlatform', function ($pools, $cache, $authorization) {
|
||||
return $dbForPlatform;
|
||||
}, ['pools', 'cache', 'authorization']);
|
||||
|
||||
$container->set('console', function () {
|
||||
return new Document(Config::getParam('console'));
|
||||
}, []);
|
||||
|
||||
$container->set(
|
||||
'isResourceBlocked',
|
||||
fn () => fn (Document $project, string $resourceType, ?string $resourceId) => false,
|
||||
@@ -251,44 +235,10 @@ $container->set('getLogsDB', function (Group $pools, Cache $cache, Authorization
|
||||
return $database;
|
||||
};
|
||||
}, ['pools', 'cache', 'authorization']);
|
||||
$container->set('publisher', function (Group $pools) {
|
||||
return new BrokerPool(publisher: $pools->get('publisher'));
|
||||
}, ['pools']);
|
||||
$container->set('publisherDatabases', function (BrokerPool $publisher) {
|
||||
return $publisher;
|
||||
}, ['publisher']);
|
||||
$container->set('publisherFunctions', function (BrokerPool $publisher) {
|
||||
return $publisher;
|
||||
}, ['publisher']);
|
||||
$container->set('publisherMigrations', function (BrokerPool $publisher) {
|
||||
return $publisher;
|
||||
}, ['publisher']);
|
||||
$container->set('publisherMessaging', function (BrokerPool $publisher) {
|
||||
return $publisher;
|
||||
}, ['publisher']);
|
||||
|
||||
$container->set('usage', function () {
|
||||
return new UsageContext();
|
||||
}, []);
|
||||
$container->set('publisherForUsage', fn (Publisher $publisher) => new UsagePublisher(
|
||||
$publisher,
|
||||
new Queue(System::getEnv('_APP_STATS_USAGE_QUEUE_NAME', Event::STATS_USAGE_QUEUE_NAME))
|
||||
), ['publisher']);
|
||||
$container->set('publisherForCertificates', fn (Publisher $publisher) => new CertificatePublisher(
|
||||
$publisher,
|
||||
new Queue(System::getEnv('_APP_CERTIFICATES_QUEUE_NAME', Event::CERTIFICATES_QUEUE_NAME))
|
||||
), ['publisher']);
|
||||
$container->set('publisherForStatsResources', fn (Publisher $publisher) => new StatsResourcesPublisher(
|
||||
$publisher,
|
||||
new Queue(System::getEnv('_APP_STATS_RESOURCES_QUEUE_NAME', Event::STATS_RESOURCES_QUEUE_NAME))
|
||||
), ['publisher']);
|
||||
$container->set('publisherForFunctions', fn (Publisher $publisher) => new FunctionPublisher(
|
||||
$publisher,
|
||||
new Queue(System::getEnv('_APP_FUNCTIONS_QUEUE_NAME', Event::FUNCTIONS_QUEUE_NAME), 'utopia-queue', Event::FUNCTIONS_QUEUE_TTL)
|
||||
), ['publisher']);
|
||||
$container->set('publisherForDeletes', fn (Publisher $publisher) => new DeletePublisher(
|
||||
$publisher,
|
||||
new Queue(System::getEnv('_APP_DELETE_QUEUE_NAME', Event::DELETE_QUEUE_NAME))
|
||||
), ['publisher']);
|
||||
$container->set('logError', function (Registry $register) {
|
||||
return function (Throwable $error, string $namespace, string $action) use ($register) {
|
||||
Console::error('[Error] Timestamp: ' . date('c', time()));
|
||||
@@ -341,14 +291,10 @@ $container->set('logError', function (Registry $register) {
|
||||
};
|
||||
}, ['register']);
|
||||
|
||||
$container->set('executor', fn () => new Executor(), []);
|
||||
|
||||
$container->set('bus', function (Registry $register) use ($container) {
|
||||
return $register->get('bus')->setResolver(fn (string $name) => $container->get($name));
|
||||
}, ['register']);
|
||||
|
||||
$container->set('telemetry', fn () => new NoTelemetry(), []);
|
||||
|
||||
$exitCode = 0;
|
||||
|
||||
$cli
|
||||
|
||||
@@ -841,6 +841,28 @@ return [
|
||||
'array' => true,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('providerBranches'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 128,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => [],
|
||||
'array' => true,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('providerPaths'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 128,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => [],
|
||||
'array' => true,
|
||||
'filters' => [],
|
||||
],
|
||||
],
|
||||
'indexes' => [
|
||||
[
|
||||
@@ -1320,6 +1342,28 @@ return [
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('providerBranches'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 128,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => [],
|
||||
'array' => true,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('providerPaths'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 128,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => [],
|
||||
'array' => true,
|
||||
'filters' => [],
|
||||
],
|
||||
],
|
||||
'indexes' => [
|
||||
[
|
||||
@@ -2754,4 +2798,146 @@ return [
|
||||
],
|
||||
],
|
||||
],
|
||||
|
||||
// Naming it presenceLogs as later it might be only be used as a presence events table only and not for the actual presence
|
||||
'presenceLogs' => [
|
||||
'$collection' => ID::custom(Database::METADATA),
|
||||
'$id' => ID::custom('presenceLogs'),
|
||||
'name' => 'Presence Logs',
|
||||
'attributes' => [
|
||||
[
|
||||
'$id' => ID::custom('userInternalId'),
|
||||
'type' => Database::VAR_ID,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => true,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('userId'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('expiresAt'),
|
||||
'type' => Database::VAR_DATETIME,
|
||||
'format' => '',
|
||||
'size' => 0,
|
||||
'signed' => false,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => ['datetime'],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('status'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('source'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => true,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('hostname'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('metadata'),
|
||||
'type' => Database::VAR_TEXT,
|
||||
'format' => '',
|
||||
'size' => 65535,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => new \stdClass(),
|
||||
'array' => false,
|
||||
'filters' => ['json'],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('permissionsHash'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 32,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
],
|
||||
'indexes' => [
|
||||
[
|
||||
'$id' => ID::custom('_unique_userId'),
|
||||
'type' => Database::INDEX_UNIQUE,
|
||||
'attributes' => ['userId'],
|
||||
'lengths' => [Database::LENGTH_KEY],
|
||||
'orders' => [Database::ORDER_ASC]
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('_key_userInternal'),
|
||||
'type' => Database::INDEX_KEY,
|
||||
'attributes' => ['userInternalId'],
|
||||
'orders' => [Database::ORDER_ASC]
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('_key_expiresAt'),
|
||||
'type' => Database::INDEX_KEY,
|
||||
'attributes' => ['expiresAt'],
|
||||
'lengths' => [],
|
||||
'orders' => [Database::ORDER_ASC]
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('_key_status'),
|
||||
'type' => Database::INDEX_KEY,
|
||||
'attributes' => ['status'],
|
||||
'lengths' => [Database::LENGTH_KEY],
|
||||
'orders' => [Database::ORDER_ASC]
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('_key_source'),
|
||||
'type' => Database::INDEX_KEY,
|
||||
'attributes' => ['source'],
|
||||
'lengths' => [Database::LENGTH_KEY],
|
||||
'orders' => [Database::ORDER_ASC]
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('_key_source_status'),
|
||||
'type' => Database::INDEX_KEY,
|
||||
'attributes' => ['source', 'status']
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('_key_permissionsHash'),
|
||||
'type' => Database::INDEX_KEY,
|
||||
'attributes' => ['permissionsHash']
|
||||
]
|
||||
]
|
||||
]
|
||||
];
|
||||
|
||||
@@ -22,6 +22,7 @@ return [
|
||||
'X-Appwrite-Locale',
|
||||
'X-Appwrite-Mode',
|
||||
'X-Appwrite-JWT',
|
||||
'X-Appwrite-Organization',
|
||||
'X-Appwrite-Response-Format',
|
||||
'X-Appwrite-Timeout',
|
||||
'X-Appwrite-ID',
|
||||
|
||||
@@ -725,6 +725,18 @@ return [
|
||||
'code' => 404,
|
||||
],
|
||||
|
||||
/** Presence */
|
||||
Exception::PRESENCE_NOT_FOUND => [
|
||||
'name' => Exception::PRESENCE_NOT_FOUND,
|
||||
'description' => 'Presence with the requested ID could not be found.',
|
||||
'code' => 404,
|
||||
],
|
||||
Exception::PRESENCE_ALREADY_EXISTS => [
|
||||
'name' => Exception::PRESENCE_ALREADY_EXISTS,
|
||||
'description' => 'Presence with the requested ID \'%s\' already exists. Try again with a different ID or use ID.unique() to generate a unique ID.',
|
||||
'code' => 409,
|
||||
],
|
||||
|
||||
/** Databases */
|
||||
Exception::DATABASE_NOT_FOUND => [
|
||||
'name' => Exception::DATABASE_NOT_FOUND,
|
||||
|
||||
+70
-14
@@ -14,7 +14,11 @@ return [
|
||||
'name' => 'Analog',
|
||||
'screenshotSleep' => 3000,
|
||||
'buildRuntime' => 'node-22',
|
||||
'runtimes' => $templateRuntimes['NODE'],
|
||||
'runtimes' => array_merge(
|
||||
$templateRuntimes['NODE'],
|
||||
$templateRuntimes['BUN'],
|
||||
$templateRuntimes['DENO']
|
||||
),
|
||||
'bundleCommand' => 'bash /usr/local/server/helpers/analog/bundle.sh',
|
||||
'envCommand' => 'source /usr/local/server/helpers/analog/env.sh',
|
||||
'adapters' => [
|
||||
@@ -40,7 +44,11 @@ return [
|
||||
'name' => 'Angular',
|
||||
'screenshotSleep' => 3000,
|
||||
'buildRuntime' => 'node-22',
|
||||
'runtimes' => $templateRuntimes['NODE'],
|
||||
'runtimes' => array_merge(
|
||||
$templateRuntimes['NODE'],
|
||||
$templateRuntimes['BUN'],
|
||||
$templateRuntimes['DENO']
|
||||
),
|
||||
'bundleCommand' => 'bash /usr/local/server/helpers/angular/bundle.sh',
|
||||
'envCommand' => 'source /usr/local/server/helpers/angular/env.sh',
|
||||
'adapters' => [
|
||||
@@ -66,7 +74,11 @@ return [
|
||||
'name' => 'Next.js',
|
||||
'screenshotSleep' => 3000,
|
||||
'buildRuntime' => 'node-22',
|
||||
'runtimes' => $templateRuntimes['NODE'],
|
||||
'runtimes' => array_merge(
|
||||
$templateRuntimes['NODE'],
|
||||
$templateRuntimes['BUN'],
|
||||
$templateRuntimes['DENO']
|
||||
),
|
||||
'bundleCommand' => 'bash /usr/local/server/helpers/next-js/bundle.sh',
|
||||
'envCommand' => 'source /usr/local/server/helpers/next-js/env.sh',
|
||||
'adapters' => [
|
||||
@@ -91,7 +103,11 @@ return [
|
||||
'name' => 'React',
|
||||
'screenshotSleep' => 3000,
|
||||
'buildRuntime' => 'node-22',
|
||||
'runtimes' => $templateRuntimes['NODE'],
|
||||
'runtimes' => array_merge(
|
||||
$templateRuntimes['NODE'],
|
||||
$templateRuntimes['BUN'],
|
||||
$templateRuntimes['DENO']
|
||||
),
|
||||
'adapters' => [
|
||||
'static' => [
|
||||
'key' => 'static',
|
||||
@@ -108,7 +124,11 @@ return [
|
||||
'name' => 'Nuxt',
|
||||
'screenshotSleep' => 3000,
|
||||
'buildRuntime' => 'node-22',
|
||||
'runtimes' => $templateRuntimes['NODE'],
|
||||
'runtimes' => array_merge(
|
||||
$templateRuntimes['NODE'],
|
||||
$templateRuntimes['BUN'],
|
||||
$templateRuntimes['DENO']
|
||||
),
|
||||
'bundleCommand' => 'bash /usr/local/server/helpers/nuxt/bundle.sh',
|
||||
'envCommand' => 'source /usr/local/server/helpers/nuxt/env.sh',
|
||||
'adapters' => [
|
||||
@@ -133,7 +153,11 @@ return [
|
||||
'name' => 'Vue.js',
|
||||
'screenshotSleep' => 5000,
|
||||
'buildRuntime' => 'node-22',
|
||||
'runtimes' => $templateRuntimes['NODE'],
|
||||
'runtimes' => array_merge(
|
||||
$templateRuntimes['NODE'],
|
||||
$templateRuntimes['BUN'],
|
||||
$templateRuntimes['DENO']
|
||||
),
|
||||
'adapters' => [
|
||||
'static' => [
|
||||
'key' => 'static',
|
||||
@@ -150,7 +174,11 @@ return [
|
||||
'name' => 'SvelteKit',
|
||||
'screenshotSleep' => 3000,
|
||||
'buildRuntime' => 'node-22',
|
||||
'runtimes' => $templateRuntimes['NODE'],
|
||||
'runtimes' => array_merge(
|
||||
$templateRuntimes['NODE'],
|
||||
$templateRuntimes['BUN'],
|
||||
$templateRuntimes['DENO']
|
||||
),
|
||||
'bundleCommand' => 'bash /usr/local/server/helpers/sveltekit/bundle.sh',
|
||||
'envCommand' => 'source /usr/local/server/helpers/sveltekit/env.sh',
|
||||
'adapters' => [
|
||||
@@ -175,7 +203,11 @@ return [
|
||||
'name' => 'Astro',
|
||||
'screenshotSleep' => 3000,
|
||||
'buildRuntime' => 'node-22',
|
||||
'runtimes' => $templateRuntimes['NODE'],
|
||||
'runtimes' => array_merge(
|
||||
$templateRuntimes['NODE'],
|
||||
$templateRuntimes['BUN'],
|
||||
$templateRuntimes['DENO']
|
||||
),
|
||||
'bundleCommand' => 'bash /usr/local/server/helpers/astro/bundle.sh',
|
||||
'envCommand' => 'source /usr/local/server/helpers/astro/env.sh',
|
||||
'adapters' => [
|
||||
@@ -200,7 +232,11 @@ return [
|
||||
'name' => 'TanStack Start',
|
||||
'screenshotSleep' => 3000,
|
||||
'buildRuntime' => 'node-22',
|
||||
'runtimes' => $templateRuntimes['NODE'],
|
||||
'runtimes' => array_merge(
|
||||
$templateRuntimes['NODE'],
|
||||
$templateRuntimes['BUN'],
|
||||
$templateRuntimes['DENO']
|
||||
),
|
||||
'bundleCommand' => 'bash /usr/local/server/helpers/tanstack-start/bundle.sh',
|
||||
'envCommand' => 'source /usr/local/server/helpers/tanstack-start/env.sh',
|
||||
'adapters' => [
|
||||
@@ -225,7 +261,11 @@ return [
|
||||
'name' => 'Remix',
|
||||
'screenshotSleep' => 3000,
|
||||
'buildRuntime' => 'node-22',
|
||||
'runtimes' => $templateRuntimes['NODE'],
|
||||
'runtimes' => array_merge(
|
||||
$templateRuntimes['NODE'],
|
||||
$templateRuntimes['BUN'],
|
||||
$templateRuntimes['DENO']
|
||||
),
|
||||
'bundleCommand' => 'bash /usr/local/server/helpers/remix/bundle.sh',
|
||||
'envCommand' => 'source /usr/local/server/helpers/remix/env.sh',
|
||||
'adapters' => [
|
||||
@@ -250,7 +290,11 @@ return [
|
||||
'name' => 'Lynx',
|
||||
'screenshotSleep' => 5000,
|
||||
'buildRuntime' => 'node-22',
|
||||
'runtimes' => $templateRuntimes['NODE'],
|
||||
'runtimes' => array_merge(
|
||||
$templateRuntimes['NODE'],
|
||||
$templateRuntimes['BUN'],
|
||||
$templateRuntimes['DENO']
|
||||
),
|
||||
'adapters' => [
|
||||
'static' => [
|
||||
'key' => 'static',
|
||||
@@ -284,7 +328,11 @@ return [
|
||||
'name' => 'React Native',
|
||||
'screenshotSleep' => 3000,
|
||||
'buildRuntime' => 'node-22',
|
||||
'runtimes' => $templateRuntimes['NODE'],
|
||||
'runtimes' => array_merge(
|
||||
$templateRuntimes['NODE'],
|
||||
$templateRuntimes['BUN'],
|
||||
$templateRuntimes['DENO']
|
||||
),
|
||||
'adapters' => [
|
||||
'static' => [
|
||||
'key' => 'static',
|
||||
@@ -301,7 +349,11 @@ return [
|
||||
'name' => 'Vite',
|
||||
'screenshotSleep' => 3000,
|
||||
'buildRuntime' => 'node-22',
|
||||
'runtimes' => $templateRuntimes['NODE'],
|
||||
'runtimes' => array_merge(
|
||||
$templateRuntimes['NODE'],
|
||||
$templateRuntimes['BUN'],
|
||||
$templateRuntimes['DENO']
|
||||
),
|
||||
'adapters' => [
|
||||
'static' => [
|
||||
'key' => 'static',
|
||||
@@ -317,7 +369,11 @@ return [
|
||||
'name' => 'Other',
|
||||
'screenshotSleep' => 3000,
|
||||
'buildRuntime' => 'node-22',
|
||||
'runtimes' => $templateRuntimes['NODE'],
|
||||
'runtimes' => array_merge(
|
||||
$templateRuntimes['NODE'],
|
||||
$templateRuntimes['BUN'],
|
||||
$templateRuntimes['DENO']
|
||||
),
|
||||
'adapters' => [
|
||||
'static' => [
|
||||
'key' => 'static',
|
||||
|
||||
@@ -12,6 +12,8 @@ $member = [
|
||||
'account',
|
||||
'teams.read',
|
||||
'teams.write',
|
||||
'presences.read',
|
||||
'presences.write',
|
||||
'documents.read',
|
||||
'documents.write',
|
||||
'rows.read',
|
||||
@@ -47,6 +49,8 @@ $admins = [
|
||||
'buckets.write',
|
||||
'users.read',
|
||||
'users.write',
|
||||
'presences.read',
|
||||
'presences.write',
|
||||
'databases.read',
|
||||
'databases.write',
|
||||
'collections.read',
|
||||
@@ -146,7 +150,7 @@ return [
|
||||
'label' => 'Owner',
|
||||
'scopes' => \array_merge($member, $admins),
|
||||
],
|
||||
User::ROLE_APPS => [
|
||||
User::ROLE_KEYS => [
|
||||
'label' => 'Applications',
|
||||
'scopes' => ['global', 'health.read', 'graphql'],
|
||||
],
|
||||
|
||||
@@ -4,17 +4,23 @@
|
||||
|
||||
return [
|
||||
"projects.read" => [
|
||||
"description" => 'Access to read organization\'s projects',
|
||||
"description" => 'Access to read organization projects',
|
||||
"category" => "Projects",
|
||||
],
|
||||
"projects.write" => [
|
||||
"description" =>
|
||||
"Access to create, update, and delete projects in organization",
|
||||
"Access to create, update, and delete organization projects",
|
||||
"category" => "Projects",
|
||||
],
|
||||
"devKeys.read" => [
|
||||
"description" => 'Access to read project\'s development keys',
|
||||
"category" => "Other",
|
||||
"deprecated" => true,
|
||||
],
|
||||
"devKeys.write" => [
|
||||
"description" =>
|
||||
"Access to create, update, and delete project\'s development keys",
|
||||
"category" => "Other",
|
||||
"deprecated" => true,
|
||||
],
|
||||
];
|
||||
|
||||
@@ -379,4 +379,12 @@ return [
|
||||
'description' => 'Access to delete reports under Advisor service.',
|
||||
'category' => 'Advisor',
|
||||
],
|
||||
'presences.read' => [
|
||||
'description' => 'Access to read your project\'s presences',
|
||||
'category' => 'Presences',
|
||||
],
|
||||
'presences.write' => [
|
||||
'description' => 'Access to create, update, and delete your project\'s presences',
|
||||
'category' => 'Presences',
|
||||
],
|
||||
];
|
||||
|
||||
@@ -67,6 +67,7 @@ use Utopia\Emails\Email;
|
||||
use Utopia\Emails\Validator\Email as EmailValidator;
|
||||
use Utopia\Http\Http;
|
||||
use Utopia\Locale\Locale;
|
||||
use Utopia\Platform\Enum;
|
||||
use Utopia\Storage\Validator\FileName;
|
||||
use Utopia\System\System;
|
||||
use Utopia\Validator;
|
||||
@@ -1243,7 +1244,7 @@ Http::get('/v1/account/sessions/oauth2/:provider')
|
||||
))
|
||||
->label('abuse-limit', 50)
|
||||
->label('abuse-key', 'ip:{ip}')
|
||||
->param('provider', '', new WhiteList(\array_keys(Config::getParam('oAuthProviders')), true), 'OAuth2 Provider. Currently, supported providers are: ' . \implode(', ', \array_keys(\array_filter(Config::getParam('oAuthProviders'), fn ($node) => (!$node['mock'])))) . '.')
|
||||
->param('provider', '', new WhiteList(\array_keys(Config::getParam('oAuthProviders')), true), 'OAuth2 Provider. Currently, supported providers are: ' . \implode(', ', \array_keys(\array_filter(Config::getParam('oAuthProviders'), fn ($node) => (!$node['mock'])))) . '.', enum: new Enum(name: 'OAuthProvider', exclude: ['mock', 'mock-unverified']))
|
||||
->param('success', '', fn ($redirectValidator) => $redirectValidator, 'URL to redirect back to your app after a successful login attempt. Only URLs from hostnames in your project\'s platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.', true, ['redirectValidator'])
|
||||
->param('failure', '', fn ($redirectValidator) => $redirectValidator, 'URL to redirect back to your app after a failed login attempt. Only URLs from hostnames in your project\'s platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.', true, ['redirectValidator'])
|
||||
->param('scopes', [], new ArrayList(new Text(APP_LIMIT_ARRAY_ELEMENT_SIZE), APP_LIMIT_ARRAY_PARAMS_SIZE), 'A list of custom OAuth2 scopes. Check each provider internal docs for a list of supported scopes. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' scopes are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long.', true)
|
||||
@@ -2009,7 +2010,7 @@ Http::get('/v1/account/tokens/oauth2/:provider')
|
||||
))
|
||||
->label('abuse-limit', 50)
|
||||
->label('abuse-key', 'ip:{ip}')
|
||||
->param('provider', '', new WhiteList(\array_keys(Config::getParam('oAuthProviders')), true), 'OAuth2 Provider. Currently, supported providers are: ' . \implode(', ', \array_keys(\array_filter(Config::getParam('oAuthProviders'), fn ($node) => (!$node['mock'])))) . '.')
|
||||
->param('provider', '', new WhiteList(\array_keys(Config::getParam('oAuthProviders')), true), 'OAuth2 Provider. Currently, supported providers are: ' . \implode(', ', \array_keys(\array_filter(Config::getParam('oAuthProviders'), fn ($node) => (!$node['mock'])))) . '.', enum: new Enum(name: 'OAuthProvider', exclude: ['mock', 'mock-unverified']))
|
||||
->param('success', '', fn ($redirectValidator) => $redirectValidator, 'URL to redirect back to your app after a successful login attempt. Only URLs from hostnames in your project\'s platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.', true, ['redirectValidator'])
|
||||
->param('failure', '', fn ($redirectValidator) => $redirectValidator, 'URL to redirect back to your app after a failed login attempt. Only URLs from hostnames in your project\'s platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.', true, ['redirectValidator'])
|
||||
->param('scopes', [], new ArrayList(new Text(APP_LIMIT_ARRAY_ELEMENT_SIZE), APP_LIMIT_ARRAY_PARAMS_SIZE), 'A list of custom OAuth2 scopes. Check each provider internal docs for a list of supported scopes. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' scopes are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long.', true)
|
||||
|
||||
@@ -39,7 +39,7 @@ Http::init()
|
||||
if (
|
||||
array_key_exists('graphql', $project->getAttribute('apis', []))
|
||||
&& !$project->getAttribute('apis', [])['graphql']
|
||||
&& !($user->isPrivileged($authorization->getRoles()) || $user->isApp($authorization->getRoles()))
|
||||
&& !($user->isPrivileged($authorization->getRoles()) || $user->isKey($authorization->getRoles()))
|
||||
) {
|
||||
throw new AppwriteException(AppwriteException::GENERAL_API_DISABLED);
|
||||
}
|
||||
|
||||
@@ -47,6 +47,7 @@ use Utopia\Database\Validator\UID;
|
||||
use Utopia\Emails\Validator\Email;
|
||||
use Utopia\Http\Http;
|
||||
use Utopia\Locale\Locale;
|
||||
use Utopia\Platform\Enum;
|
||||
use Utopia\System\System;
|
||||
use Utopia\Validator\ArrayList;
|
||||
use Utopia\Validator\Boolean;
|
||||
@@ -364,7 +365,7 @@ Http::post('/v1/messaging/providers/smtp')
|
||||
->param('port', 587, new Range(1, 65535), 'The default SMTP server port.', true)
|
||||
->param('username', '', new Text(0), 'Authentication username.', true)
|
||||
->param('password', '', new Text(0), 'Authentication password.', true)
|
||||
->param('encryption', '', new WhiteList(['none', 'ssl', 'tls']), 'Encryption type. Can be omitted, \'ssl\', or \'tls\'', true)
|
||||
->param('encryption', '', new WhiteList(['none', 'ssl', 'tls']), 'Encryption type. Can be omitted, \'ssl\', or \'tls\'', true, enum: new Enum(name: 'SmtpEncryption'))
|
||||
->param('autoTLS', true, new Boolean(), 'Enable SMTP AutoTLS feature.', true)
|
||||
->param('mailer', '', new Text(0), 'The value to use for the X-Mailer header.', true)
|
||||
->param('fromName', '', new Text(128, 0), 'Sender Name.', true)
|
||||
@@ -1602,7 +1603,7 @@ Http::patch('/v1/messaging/providers/smtp/:providerId')
|
||||
->param('port', null, new Nullable(new Range(1, 65535)), 'SMTP port.', true)
|
||||
->param('username', '', new Text(0), 'Authentication username.', true)
|
||||
->param('password', '', new Text(0), 'Authentication password.', true)
|
||||
->param('encryption', '', new WhiteList(['none', 'ssl', 'tls']), 'Encryption type. Can be \'ssl\' or \'tls\'', true)
|
||||
->param('encryption', '', new WhiteList(['none', 'ssl', 'tls']), 'Encryption type. Can be \'ssl\' or \'tls\'', true, enum: new Enum(name: 'SmtpEncryption'))
|
||||
->param('autoTLS', null, new Nullable(new Boolean()), 'Enable SMTP AutoTLS feature.', true)
|
||||
->param('mailer', '', new Text(0), 'The value to use for the X-Mailer header.', true)
|
||||
->param('fromName', '', new Text(128), 'Sender Name.', true)
|
||||
@@ -3501,7 +3502,7 @@ Http::post('/v1/messaging/messages/push')
|
||||
->param('scheduledAt', null, new Nullable(new DatetimeValidator(requireDateInFuture: true)), 'Scheduled delivery time for message in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. DateTime value must be in future.', true)
|
||||
->param('contentAvailable', false, new Boolean(), 'If set to true, the notification will be delivered in the background. Available only for iOS Platform.', true)
|
||||
->param('critical', false, new Boolean(), 'If set to true, the notification will be marked as critical. This requires the app to have the critical notification entitlement. Available only for iOS Platform.', true)
|
||||
->param('priority', 'high', new WhiteList(['normal', 'high']), 'Set the notification priority. "normal" will consider device state and may not deliver notifications immediately. "high" will always attempt to immediately deliver the notification.', true)
|
||||
->param('priority', 'high', new WhiteList(['normal', 'high']), 'Set the notification priority. "normal" will consider device state and may not deliver notifications immediately. "high" will always attempt to immediately deliver the notification.', true, enum: new Enum(name: 'MessagePriority'))
|
||||
->inject('queueForEvents')
|
||||
->inject('dbForProject')
|
||||
->inject('dbForPlatform')
|
||||
@@ -4388,7 +4389,7 @@ Http::patch('/v1/messaging/messages/push/:messageId')
|
||||
->param('scheduledAt', null, new Nullable(new DatetimeValidator(requireDateInFuture: true)), 'Scheduled delivery time for message in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. DateTime value must be in future.', true)
|
||||
->param('contentAvailable', null, new Nullable(new Boolean()), 'If set to true, the notification will be delivered in the background. Available only for iOS Platform.', true)
|
||||
->param('critical', null, new Nullable(new Boolean()), 'If set to true, the notification will be marked as critical. This requires the app to have the critical notification entitlement. Available only for iOS Platform.', true)
|
||||
->param('priority', null, new Nullable(new WhiteList(['normal', 'high'])), 'Set the notification priority. "normal" will consider device battery state and may send notifications later. "high" will always attempt to immediately deliver the notification.', true)
|
||||
->param('priority', null, new Nullable(new WhiteList(['normal', 'high'])), 'Set the notification priority. "normal" will consider device battery state and may send notifications later. "high" will always attempt to immediately deliver the notification.', true, enum: new Enum(name: 'MessagePriority'))
|
||||
->inject('queueForEvents')
|
||||
->inject('dbForProject')
|
||||
->inject('dbForPlatform')
|
||||
|
||||
@@ -10,6 +10,7 @@ use Utopia\Database\Query;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Database\Validator\Datetime as DateTimeValidator;
|
||||
use Utopia\Http\Http;
|
||||
use Utopia\Platform\Enum;
|
||||
use Utopia\Validator\WhiteList;
|
||||
|
||||
Http::get('/v1/project/usage')
|
||||
@@ -31,7 +32,13 @@ Http::get('/v1/project/usage')
|
||||
))
|
||||
->param('startDate', '', new DateTimeValidator(), 'Starting date for the usage')
|
||||
->param('endDate', '', new DateTimeValidator(), 'End date for the usage')
|
||||
->param('period', '1d', new WhiteList(['1h', '1d']), 'Period used', true)
|
||||
->param('period', '1d', new WhiteList(['1h', '1d']), 'Period used', true, enum: new Enum(
|
||||
name: 'ProjectUsageRange',
|
||||
map: [
|
||||
'1h' => 'OneHour',
|
||||
'1d' => 'OneDay',
|
||||
]
|
||||
))
|
||||
->inject('response')
|
||||
->inject('project')
|
||||
->inject('dbForProject')
|
||||
|
||||
@@ -63,6 +63,7 @@ use Utopia\Emails\Email;
|
||||
use Utopia\Emails\Validator\Email as EmailValidator;
|
||||
use Utopia\Http\Http;
|
||||
use Utopia\Locale\Locale;
|
||||
use Utopia\Platform\Enum;
|
||||
use Utopia\System\System;
|
||||
use Utopia\Validator\ArrayList;
|
||||
use Utopia\Validator\Assoc;
|
||||
@@ -432,7 +433,7 @@ Http::post('/v1/users/sha')
|
||||
->param('userId', '', fn (Database $dbForProject) => new CustomId(false, $dbForProject->getAdapter()->getMaxUIDLength()), 'User ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.', false, ['dbForProject'])
|
||||
->param('email', '', new EmailValidator(), 'User email.')
|
||||
->param('password', '', new Password(), 'User password hashed using SHA.')
|
||||
->param('passwordVersion', '', new WhiteList(['sha1', 'sha224', 'sha256', 'sha384', 'sha512/224', 'sha512/256', 'sha512', 'sha3-224', 'sha3-256', 'sha3-384', 'sha3-512']), "Optional SHA version used to hash password. Allowed values are: 'sha1', 'sha224', 'sha256', 'sha384', 'sha512/224', 'sha512/256', 'sha512', 'sha3-224', 'sha3-256', 'sha3-384', 'sha3-512'", true)
|
||||
->param('passwordVersion', '', new WhiteList(['sha1', 'sha224', 'sha256', 'sha384', 'sha512/224', 'sha512/256', 'sha512', 'sha3-224', 'sha3-256', 'sha3-384', 'sha3-512']), "Optional SHA version used to hash password. Allowed values are: 'sha1', 'sha224', 'sha256', 'sha384', 'sha512/224', 'sha512/256', 'sha512', 'sha3-224', 'sha3-256', 'sha3-384', 'sha3-512'", true, enum: new Enum(name: 'PasswordHash'))
|
||||
->param('name', '', new Text(128), 'User name. Max length: 128 chars.', true)
|
||||
->inject('response')
|
||||
->inject('project')
|
||||
@@ -606,7 +607,7 @@ Http::post('/v1/users/:userId/targets')
|
||||
))
|
||||
->param('targetId', '', fn (Database $dbForProject) => new CustomId(false, $dbForProject->getAdapter()->getMaxUIDLength()), 'Target ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.', false, ['dbForProject'])
|
||||
->param('userId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'User ID.', false, ['dbForProject'])
|
||||
->param('providerType', '', new WhiteList([MESSAGE_TYPE_EMAIL, MESSAGE_TYPE_SMS, MESSAGE_TYPE_PUSH]), 'The target provider type. Can be one of the following: `email`, `sms` or `push`.')
|
||||
->param('providerType', '', new WhiteList([MESSAGE_TYPE_EMAIL, MESSAGE_TYPE_SMS, MESSAGE_TYPE_PUSH]), 'The target provider type. Can be one of the following: `email`, `sms` or `push`.', enum: new Enum(name: 'MessagingProviderType'))
|
||||
->param('identifier', '', new Text(Database::LENGTH_KEY), 'The target identifier (token, email, phone etc.)')
|
||||
->param('providerId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Provider ID. Message will be sent to this target from the specified provider ID. If no provider ID is set the first setup provider will be used.', true, ['dbForProject'])
|
||||
->param('name', '', new Text(128), 'Target name. Max length: 128 chars. For example: My Awesome App Galaxy S23.', true)
|
||||
@@ -732,6 +733,13 @@ Http::get('/v1/users')
|
||||
$cursor->setValue($cursorDocument);
|
||||
}
|
||||
|
||||
$skipFilters = ['subQueryAuthenticators', 'subQuerySessions', 'subQueryTokens', 'subQueryChallenges', 'subQueryMemberships'];
|
||||
|
||||
$selects = Query::getByType($queries, [Query::TYPE_SELECT]);
|
||||
if (empty($selects)) {
|
||||
$skipFilters[] = 'subQueryTargets';
|
||||
}
|
||||
|
||||
$users = [];
|
||||
$total = 0;
|
||||
|
||||
@@ -744,7 +752,32 @@ Http::get('/v1/users')
|
||||
} catch (QueryException $e) {
|
||||
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
|
||||
}
|
||||
}, ['subQueryAuthenticators', 'subQuerySessions', 'subQueryTokens', 'subQueryChallenges', 'subQueryMemberships']);
|
||||
}, $skipFilters);
|
||||
|
||||
if (empty($selects) && !empty($users)) {
|
||||
$sequences = [];
|
||||
foreach ($users as $user) {
|
||||
$sequences[] = $user->getSequence();
|
||||
}
|
||||
|
||||
try {
|
||||
$targets = $dbForProject->getAuthorization()->skip(fn () => $dbForProject->find('targets', [
|
||||
Query::equal('userInternalId', $sequences),
|
||||
Query::limit(PHP_INT_MAX),
|
||||
]));
|
||||
} catch (QueryException $e) {
|
||||
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
|
||||
}
|
||||
|
||||
$targetsByUser = [];
|
||||
foreach ($targets as $target) {
|
||||
$targetsByUser[$target->getAttribute('userInternalId')][] = $target;
|
||||
}
|
||||
|
||||
foreach ($users as $user) {
|
||||
$user->setAttribute('targets', $targetsByUser[$user->getSequence()] ?? []);
|
||||
}
|
||||
}
|
||||
|
||||
$response->dynamic(new Document([
|
||||
'users' => $users,
|
||||
@@ -2286,7 +2319,7 @@ Http::delete('/v1/users/:userId/mfa/authenticators/:type')
|
||||
)
|
||||
])
|
||||
->param('userId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'User ID.', false, ['dbForProject'])
|
||||
->param('type', null, new WhiteList([Type::TOTP]), 'Type of authenticator.')
|
||||
->param('type', null, new WhiteList([Type::TOTP]), 'Type of authenticator.', enum: new Enum(name: 'AuthenticatorType'))
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('queueForEvents')
|
||||
@@ -2799,7 +2832,7 @@ Http::get('/v1/users/usage')
|
||||
)
|
||||
]
|
||||
))
|
||||
->param('range', '30d', new WhiteList(['24h', '30d', '90d'], true), 'Date range.', true)
|
||||
->param('range', '30d', new WhiteList(['24h', '30d', '90d'], true), 'Date range.', true, enum: new Enum(name: 'UsageRange'))
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('authorization')
|
||||
|
||||
+10
-33
@@ -184,31 +184,7 @@ function router(Http $utopia, Database $dbForPlatform, callable $getProjectDB, S
|
||||
/** @var Database $dbForProject */
|
||||
$dbForProject = $getProjectDB($project);
|
||||
|
||||
if (!empty($rule->getAttribute('deploymentId', ''))) {
|
||||
$deployment = $authorization->skip(fn () => $dbForProject->getDocument('deployments', $rule->getAttribute('deploymentId')));
|
||||
} else {
|
||||
// 1.6.x DB schema compatibility
|
||||
// TODO: Make sure deploymentId is never empty, and remove this code
|
||||
|
||||
// Check if site or function; should never be site, but better safe than sorry
|
||||
// Attempts to use attribute from both schemas (1.6 and 1.7)
|
||||
$resourceType = $rule->getAttribute('deploymentResourceType', $rule->getAttribute('resourceType', ''));
|
||||
|
||||
// ID of site or function
|
||||
$resourceId = $rule->getAttribute('deploymentResourceId', '');
|
||||
|
||||
// Document of site or function
|
||||
$resource = $resourceType === 'function' ?
|
||||
$authorization->skip(fn () => $dbForProject->getDocument('functions', $resourceId)) :
|
||||
$authorization->skip(fn () => $dbForProject->getDocument('sites', $resourceId));
|
||||
|
||||
// ID of active deployments
|
||||
// Attempts to use attribute from both schemas (1.6 and 1.7)
|
||||
$activeDeploymentId = $resource->getAttribute('deploymentId', $resource->getAttribute('deployment', ''));
|
||||
|
||||
// Get deployment document, as intended originally
|
||||
$deployment = $authorization->skip(fn () => $dbForProject->getDocument('deployments', $activeDeploymentId));
|
||||
}
|
||||
$deployment = $authorization->skip(fn () => $dbForProject->getDocument('deployments', $rule->getAttribute('deploymentId')));
|
||||
|
||||
if ($deployment->isEmpty()) {
|
||||
$resourceType = $rule->getAttribute('deploymentResourceType', '');
|
||||
@@ -859,7 +835,8 @@ Http::init()
|
||||
->inject('authorization')
|
||||
->inject('publisherForDeletes')
|
||||
->inject('executionsRetentionCount')
|
||||
->action(function (Http $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Log $log, Document $project, Database $dbForPlatform, callable $getProjectDB, Locale $locale, array $localeCodes, Reader $geodb, Event $queueForEvents, Bus $bus, Executor $executor, array $platform, callable $isResourceBlocked, string $previewHostname, Document $devKey, ?Key $apiKey, Cors $cors, Authorization $authorization, DeletePublisher $publisherForDeletes, int $executionsRetentionCount) {
|
||||
->inject('params')
|
||||
->action(function (Http $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Log $log, Document $project, Database $dbForPlatform, callable $getProjectDB, Locale $locale, array $localeCodes, Reader $geodb, Event $queueForEvents, Bus $bus, Executor $executor, array $platform, callable $isResourceBlocked, string $previewHostname, Document $devKey, ?Key $apiKey, Cors $cors, Authorization $authorization, DeletePublisher $publisherForDeletes, int $executionsRetentionCount, array $params) {
|
||||
/*
|
||||
* Appwrite Router
|
||||
*/
|
||||
@@ -868,14 +845,14 @@ Http::init()
|
||||
// Only run Router when external domain
|
||||
if (!\in_array($hostname, $platformHostnames) || !empty($previewHostname)) {
|
||||
if (router($utopia, $dbForPlatform, $getProjectDB, $swooleRequest, $request, $response, $log, $queueForEvents, $bus, $executor, $geodb, $isResourceBlocked, $platform, $previewHostname, $authorization, $apiKey, $publisherForDeletes, $executionsRetentionCount)) {
|
||||
$utopia->getRoute()?->label('router', true);
|
||||
$utopia->match($request)?->route->label('router', true);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Request format
|
||||
*/
|
||||
$route = $utopia->getRoute();
|
||||
$route = $utopia->match($request)?->route;
|
||||
$request->setRoute($route);
|
||||
|
||||
if ($route === null) {
|
||||
@@ -900,7 +877,7 @@ Http::init()
|
||||
}
|
||||
if (version_compare($requestFormat, '1.8.0', '<')) {
|
||||
$dbForProject = $getProjectDB($project);
|
||||
$request->addFilter(new RequestV20($dbForProject, $route->getPathValues($request)));
|
||||
$request->addFilter(new RequestV20($dbForProject, $params));
|
||||
}
|
||||
if (version_compare($requestFormat, '1.9.0', '<')) {
|
||||
$request->addFilter(new RequestV21());
|
||||
@@ -1178,7 +1155,7 @@ Http::options()
|
||||
// Only run Router when external domain
|
||||
if (!in_array($request->getHostname(), $platformHostnames) || !empty($previewHostname)) {
|
||||
if (router($utopia, $dbForPlatform, $getProjectDB, $swooleRequest, $request, $response, $log, $queueForEvents, $bus, $executor, $geodb, $isResourceBlocked, $platform, $previewHostname, $authorization, $apiKey, $publisherForDeletes, $executionsRetentionCount)) {
|
||||
$utopia->getRoute()?->label('router', true);
|
||||
$utopia->match($request)?->route->label('router', true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1213,7 +1190,7 @@ Http::error()
|
||||
->inject('authorization')
|
||||
->action(function (Throwable $error, Http $utopia, Request $request, Response $response, Document $project, ?Logger $logger, Log $log, Bus $bus, Document $devKey, Authorization $authorization) {
|
||||
$version = System::getEnv('_APP_VERSION', 'UNKNOWN');
|
||||
$route = $utopia->getRoute();
|
||||
$route = $utopia->match($request)?->route;
|
||||
$class = \get_class($error);
|
||||
$code = $error->getCode();
|
||||
$message = $error->getMessage();
|
||||
@@ -1579,7 +1556,7 @@ Http::get('/robots.txt')
|
||||
$response->text($template->render(false));
|
||||
} else {
|
||||
if (router($utopia, $dbForPlatform, $getProjectDB, $swooleRequest, $request, $response, $log, $queueForEvents, $bus, $executor, $geodb, $isResourceBlocked, $platform, $previewHostname, $authorization, $apiKey, $publisherForDeletes, $executionsRetentionCount)) {
|
||||
$utopia->getRoute()?->label('router', true);
|
||||
$utopia->match($request)?->route->label('router', true);
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -1613,7 +1590,7 @@ Http::get('/humans.txt')
|
||||
$response->text($template->render(false));
|
||||
} else {
|
||||
if (router($utopia, $dbForPlatform, $getProjectDB, $swooleRequest, $request, $response, $log, $queueForEvents, $bus, $executor, $geodb, $isResourceBlocked, $platform, $previewHostname, $authorization, $apiKey, $publisherForDeletes, $executionsRetentionCount)) {
|
||||
$utopia->getRoute()?->label('router', true);
|
||||
$utopia->match($request)?->route->label('router', true);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -13,6 +13,7 @@ use Utopia\Database\Helpers\Permission;
|
||||
use Utopia\Database\Helpers\Role;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Http\Http;
|
||||
use Utopia\Http\Route;
|
||||
use Utopia\Locale\Locale;
|
||||
use Utopia\System\System;
|
||||
use Utopia\Validator\Text;
|
||||
@@ -283,13 +284,11 @@ Http::get('/v1/mock/github/callback')
|
||||
|
||||
Http::shutdown()
|
||||
->groups(['mock'])
|
||||
->inject('utopia')
|
||||
->inject('response')
|
||||
->inject('request')
|
||||
->action(function (Http $utopia, Response $response, Request $request) {
|
||||
->inject('route')
|
||||
->action(function (Response $response, Route $route) {
|
||||
|
||||
$result = [];
|
||||
$route = $utopia->getRoute();
|
||||
$path = APP_STORAGE_CACHE . '/tests.json';
|
||||
$tests = (\file_exists($path)) ? \json_decode(\file_get_contents($path), true) : [];
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@ use Appwrite\Auth\Key;
|
||||
use Appwrite\Auth\MFA\Type\TOTP;
|
||||
use Appwrite\Bus\Events\RequestCompleted;
|
||||
use Appwrite\Event\Context\Audit as AuditContext;
|
||||
use Appwrite\Event\Database as EventDatabase;
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Event\Message\Audit as AuditMessage;
|
||||
use Appwrite\Event\Message\Func as FunctionMessage;
|
||||
@@ -38,6 +37,7 @@ use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Database\Validator\Authorization\Input;
|
||||
use Utopia\Database\Validator\Roles;
|
||||
use Utopia\Http\Http;
|
||||
use Utopia\Http\Route;
|
||||
use Utopia\Span\Span;
|
||||
use Utopia\System\System;
|
||||
use Utopia\Telemetry\Adapter as Telemetry;
|
||||
@@ -86,7 +86,7 @@ $parseLabel = function (string $label, array $responsePayload, array $requestPar
|
||||
|
||||
Http::init()
|
||||
->groups(['api'])
|
||||
->inject('utopia')
|
||||
->inject('route')
|
||||
->inject('request')
|
||||
->inject('dbForPlatform')
|
||||
->inject('dbForProject')
|
||||
@@ -99,11 +99,7 @@ Http::init()
|
||||
->inject('team')
|
||||
->inject('apiKey')
|
||||
->inject('authorization')
|
||||
->action(function (Http $utopia, Request $request, Database $dbForPlatform, Database $dbForProject, AuditContext $auditContext, Document $project, User $user, ?Document $session, array $servers, string $mode, Document $team, ?Key $apiKey, Authorization $authorization) {
|
||||
$route = $utopia->getRoute();
|
||||
if ($route === null) {
|
||||
throw new AppwriteException(AppwriteException::GENERAL_ROUTE_NOT_FOUND);
|
||||
}
|
||||
->action(function (Route $route, Request $request, Database $dbForPlatform, Database $dbForProject, AuditContext $auditContext, Document $project, User $user, ?Document $session, array $servers, string $mode, Document $team, ?Key $apiKey, Authorization $authorization) {
|
||||
|
||||
/**
|
||||
* Handle user authentication and session validation.
|
||||
@@ -179,8 +175,8 @@ Http::init()
|
||||
$role = $apiKey->getRole();
|
||||
$scopes = $apiKey->getScopes();
|
||||
|
||||
// Handle special app role case
|
||||
if ($apiKey->getRole() === User::ROLE_APPS) {
|
||||
// Handle special key role case
|
||||
if ($apiKey->getRole() === User::ROLE_KEYS) {
|
||||
// Disable authorization checks for project API keys
|
||||
// Dynamic supported for backwards compatibility
|
||||
if (($apiKey->getType() === API_KEY_STANDARD || $apiKey->getType() === API_KEY_EPHEMERAL || $apiKey->getType() === 'dynamic') && $apiKey->getProjectId() === $project->getId()) {
|
||||
@@ -190,7 +186,7 @@ Http::init()
|
||||
$user = new User([
|
||||
'$id' => '',
|
||||
'status' => true,
|
||||
'type' => ACTIVITY_TYPE_KEY_PROJECT,
|
||||
'type' => ACTOR_TYPE_KEY_PROJECT,
|
||||
'email' => 'app.' . $project->getId() . '@service.' . $request->getHostname(),
|
||||
'password' => '',
|
||||
'name' => $apiKey->getName(),
|
||||
@@ -262,9 +258,9 @@ Http::init()
|
||||
|
||||
$userClone = clone $user;
|
||||
$userClone->setAttribute('type', match ($apiKey->getType()) {
|
||||
API_KEY_STANDARD => ACTIVITY_TYPE_KEY_PROJECT,
|
||||
API_KEY_ACCOUNT => ACTIVITY_TYPE_KEY_ACCOUNT,
|
||||
default => ACTIVITY_TYPE_KEY_ORGANIZATION,
|
||||
API_KEY_STANDARD => ACTOR_TYPE_KEY_PROJECT,
|
||||
API_KEY_ACCOUNT => ACTOR_TYPE_KEY_ACCOUNT,
|
||||
default => ACTOR_TYPE_KEY_ORGANIZATION,
|
||||
});
|
||||
$auditContext->user = $userClone;
|
||||
}
|
||||
@@ -429,7 +425,7 @@ Http::init()
|
||||
if (
|
||||
array_key_exists($namespace, $project->getAttribute('services', []))
|
||||
&& ! $project->getAttribute('services', [])[$namespace]
|
||||
&& ! ($user->isPrivileged($authorization->getRoles()) || $user->isApp($authorization->getRoles()))
|
||||
&& ! ($user->isPrivileged($authorization->getRoles()) || $user->isKey($authorization->getRoles()))
|
||||
) {
|
||||
throw new Exception(Exception::GENERAL_SERVICE_DISABLED);
|
||||
}
|
||||
@@ -439,7 +435,7 @@ Http::init()
|
||||
if (
|
||||
array_key_exists('rest', $project->getAttribute('apis', []))
|
||||
&& ! $project->getAttribute('apis', [])['rest']
|
||||
&& ! ($user->isPrivileged($authorization->getRoles()) || $user->isApp($authorization->getRoles()))
|
||||
&& ! ($user->isPrivileged($authorization->getRoles()) || $user->isKey($authorization->getRoles()))
|
||||
) {
|
||||
throw new AppwriteException(AppwriteException::GENERAL_API_DISABLED);
|
||||
}
|
||||
@@ -478,7 +474,7 @@ Http::init()
|
||||
|
||||
Http::init()
|
||||
->groups(['api'])
|
||||
->inject('utopia')
|
||||
->inject('route')
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
->inject('project')
|
||||
@@ -486,21 +482,16 @@ Http::init()
|
||||
->inject('timelimit')
|
||||
->inject('devKey')
|
||||
->inject('authorization')
|
||||
->action(function (Http $utopia, Request $request, Response $response, Document $project, User $user, callable $timelimit, Document $devKey, Authorization $authorization) {
|
||||
->action(function (Route $route, Request $request, Response $response, Document $project, User $user, callable $timelimit, Document $devKey, Authorization $authorization) {
|
||||
$response->setUser($user);
|
||||
$request->setUser($user);
|
||||
|
||||
$roles = $authorization->getRoles();
|
||||
$shouldCheckAbuse = System::getEnv('_APP_OPTIONS_ABUSE', 'enabled') !== 'disabled'
|
||||
&& ! $user->isApp($roles)
|
||||
&& ! $user->isKey($roles)
|
||||
&& ! $user->isPrivileged($roles)
|
||||
&& $devKey->isEmpty();
|
||||
|
||||
$route = $utopia->getRoute();
|
||||
if ($route === null) {
|
||||
throw new AppwriteException(AppwriteException::GENERAL_ROUTE_NOT_FOUND);
|
||||
}
|
||||
|
||||
$abuseKeyLabel = $route->getLabel('abuse-key', 'url:{url},ip:{ip}');
|
||||
$abuseKeyLabel = (! is_array($abuseKeyLabel)) ? [$abuseKeyLabel] : $abuseKeyLabel;
|
||||
$closestLimit = null;
|
||||
@@ -557,14 +548,13 @@ Http::init()
|
||||
|
||||
Http::init()
|
||||
->groups(['api'])
|
||||
->inject('utopia')
|
||||
->inject('route')
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
->inject('project')
|
||||
->inject('user')
|
||||
->inject('queueForEvents')
|
||||
->inject('auditContext')
|
||||
->inject('queueForDatabase')
|
||||
->inject('usage')
|
||||
->inject('publisherForFunctions')
|
||||
->inject('dbForProject')
|
||||
@@ -576,17 +566,12 @@ Http::init()
|
||||
->inject('platform')
|
||||
->inject('authorization')
|
||||
->inject('cacheControlForStorage')
|
||||
->action(function (Http $utopia, Request $request, Response $response, Document $project, User $user, Event $queueForEvents, AuditContext $auditContext, EventDatabase $queueForDatabase, Context $usage, FunctionPublisher $publisherForFunctions, Database $dbForProject, Document $resourceToken, string $mode, ?Key $apiKey, array $plan, Telemetry $telemetry, array $platform, Authorization $authorization, callable $cacheControlForStorage) {
|
||||
->action(function (Route $route, Request $request, Response $response, Document $project, User $user, Event $queueForEvents, AuditContext $auditContext, Context $usage, FunctionPublisher $publisherForFunctions, Database $dbForProject, Document $resourceToken, string $mode, ?Key $apiKey, array $plan, Telemetry $telemetry, array $platform, Authorization $authorization, callable $cacheControlForStorage) {
|
||||
|
||||
$response->setUser($user);
|
||||
$request->setUser($user);
|
||||
|
||||
$route = $utopia->getRoute();
|
||||
if ($route === null) {
|
||||
throw new AppwriteException(AppwriteException::GENERAL_ROUTE_NOT_FOUND);
|
||||
}
|
||||
|
||||
$path = $route->getMatchedPath();
|
||||
$path = $route->getPath();
|
||||
$databaseType = match (true) {
|
||||
str_contains($path, '/documentsdb') => DATABASE_TYPE_DOCUMENTSDB,
|
||||
str_contains($path, '/vectorsdb') => DATABASE_TYPE_VECTORSDB,
|
||||
@@ -617,20 +602,16 @@ Http::init()
|
||||
$userClone = clone $user;
|
||||
// $user doesn't support `type` and can cause unintended effects.
|
||||
if (empty($user->getAttribute('type'))) {
|
||||
$userClone->setAttribute('type', $mode === APP_MODE_ADMIN ? ACTIVITY_TYPE_ADMIN : ACTIVITY_TYPE_USER);
|
||||
$userClone->setAttribute('type', $mode === APP_MODE_ADMIN ? ACTOR_TYPE_ADMIN : ACTOR_TYPE_USER);
|
||||
}
|
||||
$auditContext->user = $userClone;
|
||||
}
|
||||
|
||||
/* Auto-set projects */
|
||||
$queueForDatabase->setProject($project);
|
||||
|
||||
$useCache = $route->getLabel('cache', false);
|
||||
$storageCacheOperationsCounter = $telemetry->createCounter('storage.cache.operations.load');
|
||||
if ($useCache) {
|
||||
$route = $utopia->match($request);
|
||||
$roles = $authorization->getRoles();
|
||||
$isAppUser = $user->isApp($roles);
|
||||
$isAppUser = $user->isKey($roles);
|
||||
$isImageTransformation = $route->getPath() === '/v1/storage/buckets/:bucketId/files/:fileId/preview';
|
||||
$isDisabled = isset($plan['imageTransformations']) && $plan['imageTransformations'] === -1 && ! $user->isPrivileged($roles);
|
||||
|
||||
@@ -766,12 +747,11 @@ Http::init()
|
||||
*/
|
||||
Http::shutdown()
|
||||
->groups(['session'])
|
||||
->inject('utopia')
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
->inject('project')
|
||||
->inject('dbForProject')
|
||||
->action(function (Http $utopia, Request $request, Response $response, Document $project, Database $dbForProject) {
|
||||
->action(function (Request $request, Response $response, Document $project, Database $dbForProject) {
|
||||
$sessionLimit = $project->getAttribute('auths', [])['maxSessions'] ?? 0;
|
||||
|
||||
if ($sessionLimit === 0) {
|
||||
@@ -805,7 +785,7 @@ Http::shutdown()
|
||||
|
||||
Http::shutdown()
|
||||
->groups(['api'])
|
||||
->inject('utopia')
|
||||
->inject('route')
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
->inject('project')
|
||||
@@ -815,7 +795,6 @@ Http::shutdown()
|
||||
->inject('publisherForAudits')
|
||||
->inject('usage')
|
||||
->inject('publisherForUsage')
|
||||
->inject('queueForDatabase')
|
||||
->inject('publisherForFunctions')
|
||||
->inject('queueForWebhooks')
|
||||
->inject('queueForRealtime')
|
||||
@@ -826,7 +805,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, EventDatabase $queueForDatabase, FunctionPublisher $publisherForFunctions, Event $queueForWebhooks, Realtime $queueForRealtime, Database $dbForProject, Authorization $authorization, callable $timelimit, EventProcessor $eventProcessor, Bus $bus, ?Key $apiKey, string $mode) use ($parseLabel) {
|
||||
->action(function (Route $route, Request $request, Response $response, Document $project, User $user, Event $queueForEvents, AuditContext $auditContext, Audit $publisherForAudits, Context $usage, UsagePublisher $publisherForUsage, FunctionPublisher $publisherForFunctions, Event $queueForWebhooks, Realtime $queueForRealtime, Database $dbForProject, Authorization $authorization, callable $timelimit, EventProcessor $eventProcessor, Bus $bus, ?Key $apiKey, string $mode) use ($parseLabel) {
|
||||
|
||||
$responsePayload = $response->getPayload();
|
||||
|
||||
@@ -882,7 +861,6 @@ Http::shutdown()
|
||||
}
|
||||
}
|
||||
|
||||
$route = $utopia->getRoute();
|
||||
$requestParams = $route->getParamsValues();
|
||||
|
||||
/**
|
||||
@@ -935,7 +913,7 @@ Http::shutdown()
|
||||
$userClone = clone $user;
|
||||
// $user doesn't support `type` and can cause unintended effects.
|
||||
if (empty($user->getAttribute('type'))) {
|
||||
$userClone->setAttribute('type', $mode === APP_MODE_ADMIN ? ACTIVITY_TYPE_ADMIN : ACTIVITY_TYPE_USER);
|
||||
$userClone->setAttribute('type', $mode === APP_MODE_ADMIN ? ACTOR_TYPE_ADMIN : ACTOR_TYPE_USER);
|
||||
}
|
||||
$auditContext->user = $userClone;
|
||||
} elseif ($auditContext->user === null || $auditContext->user->isEmpty()) {
|
||||
@@ -950,7 +928,7 @@ Http::shutdown()
|
||||
$user = new User([
|
||||
'$id' => '',
|
||||
'status' => true,
|
||||
'type' => ACTIVITY_TYPE_GUEST,
|
||||
'type' => ACTOR_TYPE_GUEST,
|
||||
'email' => 'guest.' . $project->getId() . '@service.' . $request->getHostname(),
|
||||
'password' => '',
|
||||
'name' => 'Guest',
|
||||
@@ -973,10 +951,6 @@ Http::shutdown()
|
||||
$publisherForAudits->enqueue(AuditMessage::fromContext($auditContext));
|
||||
}
|
||||
|
||||
if (! empty($queueForDatabase->getType())) {
|
||||
$queueForDatabase->trigger();
|
||||
}
|
||||
|
||||
// Cache label
|
||||
$useCache = $route->getLabel('cache', false);
|
||||
if ($useCache) {
|
||||
|
||||
@@ -9,6 +9,7 @@ use Utopia\Database\DateTime;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Http\Http;
|
||||
use Utopia\Http\Route;
|
||||
use Utopia\System\System;
|
||||
|
||||
Http::init()
|
||||
@@ -32,13 +33,13 @@ Http::init()
|
||||
|
||||
Http::init()
|
||||
->groups(['auth'])
|
||||
->inject('utopia')
|
||||
->inject('route')
|
||||
->inject('request')
|
||||
->inject('project')
|
||||
->inject('geodb')
|
||||
->inject('user')
|
||||
->inject('authorization')
|
||||
->action(function (Http $utopia, Request $request, Document $project, Reader $geodb, User $user, Authorization $authorization) {
|
||||
->action(function (Route $route, Request $request, Document $project, Reader $geodb, User $user, Authorization $authorization) {
|
||||
$denylist = System::getEnv('_APP_CONSOLE_COUNTRIES_DENYLIST', '');
|
||||
if (!empty($denylist && $project->getId() === 'console')) {
|
||||
$countries = explode(',', $denylist);
|
||||
@@ -49,10 +50,8 @@ Http::init()
|
||||
}
|
||||
}
|
||||
|
||||
$route = $utopia->match($request);
|
||||
|
||||
$isPrivilegedUser = $user->isPrivileged($authorization->getRoles());
|
||||
$isAppUser = $user->isApp($authorization->getRoles());
|
||||
$isAppUser = $user->isKey($authorization->getRoles());
|
||||
|
||||
if ($isAppUser || $isPrivilegedUser) { // Skip limits for app and console devs
|
||||
return;
|
||||
|
||||
+2
-2
@@ -539,7 +539,7 @@ $swoole->onRequest(function ($utopiaRequest, $utopiaResponse) use ($files, $swoo
|
||||
|
||||
$app->run($request, $response);
|
||||
|
||||
$route = $app->getRoute();
|
||||
$route = $app->match($request)?->route;
|
||||
Span::add('http.path', $route?->getPath() ?? 'unknown');
|
||||
} catch (\Throwable $th) {
|
||||
Span::error($th);
|
||||
@@ -555,7 +555,7 @@ $swoole->onRequest(function ($utopiaRequest, $utopiaResponse) use ($files, $swoo
|
||||
// All good, user is optional information for logger
|
||||
}
|
||||
|
||||
$route = $app->getRoute();
|
||||
$route = $app->match($request)?->route;
|
||||
|
||||
$log = $app->context()->get("log");
|
||||
|
||||
|
||||
@@ -159,14 +159,14 @@ const SESSION_PROVIDER_TOKEN = 'token';
|
||||
const SESSION_PROVIDER_SERVER = 'server';
|
||||
|
||||
/**
|
||||
* Activity associated with user or the app.
|
||||
* Actor that performed the request (user, admin, guest, or API key).
|
||||
*/
|
||||
const ACTIVITY_TYPE_USER = 'user';
|
||||
const ACTIVITY_TYPE_ADMIN = 'admin';
|
||||
const ACTIVITY_TYPE_GUEST = 'guest';
|
||||
const ACTIVITY_TYPE_KEY_PROJECT = 'keyProject';
|
||||
const ACTIVITY_TYPE_KEY_ACCOUNT = 'keyAccount';
|
||||
const ACTIVITY_TYPE_KEY_ORGANIZATION = 'keyOrganization';
|
||||
const ACTOR_TYPE_USER = 'user';
|
||||
const ACTOR_TYPE_ADMIN = 'admin';
|
||||
const ACTOR_TYPE_GUEST = 'guest';
|
||||
const ACTOR_TYPE_KEY_PROJECT = 'keyProject';
|
||||
const ACTOR_TYPE_KEY_ACCOUNT = 'keyAccount';
|
||||
const ACTOR_TYPE_KEY_ORGANIZATION = 'keyOrganization';
|
||||
|
||||
/**
|
||||
* MFA
|
||||
@@ -394,6 +394,7 @@ const METRIC_NETWORK_OUTBOUND = 'network.outbound';
|
||||
const METRIC_MAU = 'users.mau';
|
||||
const METRIC_DAU = 'users.dau';
|
||||
const METRIC_WAU = 'users.wau';
|
||||
const METRIC_USERS_PRESENCE = 'users.presence';
|
||||
const METRIC_WEBHOOKS = 'webhooks';
|
||||
const METRIC_PLATFORMS = 'platforms';
|
||||
const METRIC_PROVIDERS = 'providers';
|
||||
|
||||
+6
-1
@@ -174,6 +174,7 @@ use Appwrite\Utopia\Response\Model\PolicySessionInvalidation;
|
||||
use Appwrite\Utopia\Response\Model\PolicySessionLimit;
|
||||
use Appwrite\Utopia\Response\Model\PolicyUserLimit;
|
||||
use Appwrite\Utopia\Response\Model\Preferences;
|
||||
use Appwrite\Utopia\Response\Model\Presence;
|
||||
use Appwrite\Utopia\Response\Model\Project;
|
||||
use Appwrite\Utopia\Response\Model\ProjectAuthMethod;
|
||||
use Appwrite\Utopia\Response\Model\ProjectProtocol;
|
||||
@@ -214,6 +215,7 @@ use Appwrite\Utopia\Response\Model\UsageDocumentsDB;
|
||||
use Appwrite\Utopia\Response\Model\UsageDocumentsDBs;
|
||||
use Appwrite\Utopia\Response\Model\UsageFunction;
|
||||
use Appwrite\Utopia\Response\Model\UsageFunctions;
|
||||
use Appwrite\Utopia\Response\Model\UsagePresence;
|
||||
use Appwrite\Utopia\Response\Model\UsageProject;
|
||||
use Appwrite\Utopia\Response\Model\UsageSite;
|
||||
use Appwrite\Utopia\Response\Model\UsageSites;
|
||||
@@ -237,6 +239,7 @@ Response::setModel(new ErrorDev());
|
||||
// Lists
|
||||
Response::setModel(new BaseList('Rows List', Response::MODEL_ROW_LIST, 'rows', Response::MODEL_ROW));
|
||||
Response::setModel(new BaseList('Documents List', Response::MODEL_DOCUMENT_LIST, 'documents', Response::MODEL_DOCUMENT));
|
||||
Response::setModel(new BaseList('Presences List', Response::MODEL_PRESENCE_LIST, 'presences', Response::MODEL_PRESENCE));
|
||||
Response::setModel(new BaseList('Tables List', Response::MODEL_TABLE_LIST, 'tables', Response::MODEL_TABLE));
|
||||
Response::setModel(new BaseList('Collections List', Response::MODEL_COLLECTION_LIST, 'collections', Response::MODEL_COLLECTION));
|
||||
Response::setModel(new BaseList('Databases List', Response::MODEL_DATABASE_LIST, 'databases', Response::MODEL_DATABASE));
|
||||
@@ -263,7 +266,7 @@ Response::setModel(new BaseList('Frameworks List', Response::MODEL_FRAMEWORK_LIS
|
||||
Response::setModel(new BaseList('Runtimes List', Response::MODEL_RUNTIME_LIST, 'runtimes', Response::MODEL_RUNTIME));
|
||||
Response::setModel(new BaseList('Deployments List', Response::MODEL_DEPLOYMENT_LIST, 'deployments', Response::MODEL_DEPLOYMENT));
|
||||
Response::setModel(new BaseList('Executions List', Response::MODEL_EXECUTION_LIST, 'executions', Response::MODEL_EXECUTION));
|
||||
Response::setModel(new BaseList('Projects List', Response::MODEL_PROJECT_LIST, 'projects', Response::MODEL_PROJECT, true, false));
|
||||
Response::setModel(new BaseList('Projects List', Response::MODEL_PROJECT_LIST, 'projects', Response::MODEL_PROJECT, true, true));
|
||||
Response::setModel(new BaseList('Webhooks List', Response::MODEL_WEBHOOK_LIST, 'webhooks', Response::MODEL_WEBHOOK, true, true));
|
||||
Response::setModel(new BaseList('API Keys List', Response::MODEL_KEY_LIST, 'keys', Response::MODEL_KEY, true, true));
|
||||
Response::setModel(new BaseList('Dev Keys List', Response::MODEL_DEV_KEY_LIST, 'devKeys', Response::MODEL_DEV_KEY, true, false));
|
||||
@@ -361,6 +364,7 @@ Response::setModel(new Index());
|
||||
Response::setModel(new ColumnIndex());
|
||||
Response::setModel(new Row());
|
||||
Response::setModel(new ModelDocument());
|
||||
Response::setModel(new Presence());
|
||||
Response::setModel(new Log());
|
||||
Response::setModel(new User());
|
||||
Response::setModel(new AlgoMd5());
|
||||
@@ -489,6 +493,7 @@ Response::setModel(new UsageDatabase());
|
||||
Response::setModel(new UsageTable());
|
||||
Response::setModel(new UsageCollection());
|
||||
Response::setModel(new UsageUsers());
|
||||
Response::setModel(new UsagePresence());
|
||||
Response::setModel(new UsageStorage());
|
||||
Response::setModel(new UsageBuckets());
|
||||
Response::setModel(new UsageFunctions());
|
||||
|
||||
+15
-1
@@ -240,6 +240,12 @@ $register->set('pools', function () {
|
||||
'multiple' => true,
|
||||
'schemes' => ['redis'],
|
||||
],
|
||||
'lock' => [
|
||||
'type' => 'lock',
|
||||
'dsns' => $fallbackForRedis,
|
||||
'multiple' => false,
|
||||
'schemes' => ['redis'],
|
||||
],
|
||||
];
|
||||
|
||||
$maxConnections = (int) System::getEnv('_APP_CONNECTIONS_MAX', 151);
|
||||
@@ -332,7 +338,13 @@ $register->set('pools', function () {
|
||||
|
||||
$poolAdapter = System::getEnv('_APP_POOL_ADAPTER', default: 'stack') === 'swoole' ? new SwoolePool() : new StackPool();
|
||||
|
||||
$pool = new Pool($poolAdapter, $name, $poolSize, function () use ($type, $resource, $dsn) {
|
||||
// PubSub workers hold one long-lived subscribed connection and also need
|
||||
// spare capacity for publishes from the same process.
|
||||
$connectionPoolSize = $type === 'pubsub'
|
||||
? max(2, $poolSize)
|
||||
: $poolSize;
|
||||
|
||||
$pool = new Pool($poolAdapter, $name, $connectionPoolSize, function () use ($type, $resource, $dsn) {
|
||||
// Get Adapter
|
||||
switch ($type) {
|
||||
case 'database':
|
||||
@@ -369,6 +381,8 @@ $register->set('pools', function () {
|
||||
}
|
||||
|
||||
return $adapter;
|
||||
case 'lock':
|
||||
return $resource();
|
||||
default:
|
||||
throw new Exception(Exception::GENERAL_SERVER_ERROR, "Server error: Missing adapter implementation.");
|
||||
}
|
||||
|
||||
+99
-81
@@ -4,6 +4,7 @@ use Appwrite\Event\Event;
|
||||
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\Database as DatabasePublisher;
|
||||
use Appwrite\Event\Publisher\Delete as DeletePublisher;
|
||||
use Appwrite\Event\Publisher\Execution as ExecutionPublisher;
|
||||
use Appwrite\Event\Publisher\Func as FunctionPublisher;
|
||||
@@ -28,6 +29,7 @@ use Utopia\Database\Document;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\DI\Container;
|
||||
use Utopia\DSN\DSN;
|
||||
use Utopia\Lock\Distributed;
|
||||
use Utopia\Pools\Group;
|
||||
use Utopia\Queue\Broker\Pool as BrokerPool;
|
||||
use Utopia\Queue\Publisher;
|
||||
@@ -51,6 +53,93 @@ global $register;
|
||||
global $container;
|
||||
$container = new Container();
|
||||
|
||||
$container->set('console', fn () => new Document(Config::getParam('console')), []);
|
||||
|
||||
$container->set('executor', fn () => new Executor(), []);
|
||||
|
||||
$container->set('telemetry', fn () => new NoTelemetry(), []);
|
||||
|
||||
$container->set('publisher', fn (Group $pools) => new BrokerPool(publisher: $pools->get('publisher')), ['pools']);
|
||||
|
||||
$container->set('publisherDatabases', fn (Publisher $publisher) => $publisher, ['publisher']);
|
||||
|
||||
$container->set('publisherFunctions', fn (Publisher $publisher) => $publisher, ['publisher']);
|
||||
|
||||
$container->set('publisherMigrations', fn (Publisher $publisher) => $publisher, ['publisher']);
|
||||
|
||||
$container->set('publisherMails', fn (Publisher $publisher) => $publisher, ['publisher']);
|
||||
|
||||
$container->set('publisherDeletes', fn (Publisher $publisher) => $publisher, ['publisher']);
|
||||
|
||||
$container->set('publisherMessaging', fn (Publisher $publisher) => $publisher, ['publisher']);
|
||||
|
||||
$container->set('publisherWebhooks', fn (Publisher $publisher) => $publisher, ['publisher']);
|
||||
|
||||
$container->set('publisherForAudits', fn (Publisher $publisher) => new AuditPublisher(
|
||||
$publisher,
|
||||
new Queue(System::getEnv('_APP_AUDITS_QUEUE_NAME', Event::AUDITS_QUEUE_NAME))
|
||||
), ['publisher']);
|
||||
|
||||
$container->set('publisherForCertificates', fn (Publisher $publisher) => new CertificatePublisher(
|
||||
$publisher,
|
||||
new Queue(System::getEnv('_APP_CERTIFICATES_QUEUE_NAME', Event::CERTIFICATES_QUEUE_NAME))
|
||||
), ['publisher']);
|
||||
|
||||
$container->set('publisherForScreenshots', fn (Publisher $publisher) => new ScreenshotPublisher(
|
||||
$publisher,
|
||||
new Queue(System::getEnv('_APP_SCREENSHOTS_QUEUE_NAME', Event::SCREENSHOTS_QUEUE_NAME))
|
||||
), ['publisher']);
|
||||
|
||||
$container->set('publisherForUsage', fn (Publisher $publisher) => new UsagePublisher(
|
||||
$publisher,
|
||||
new Queue(System::getEnv('_APP_STATS_USAGE_QUEUE_NAME', Event::STATS_USAGE_QUEUE_NAME))
|
||||
), ['publisher']);
|
||||
|
||||
$container->set('publisherForExecutions', fn (Publisher $publisher) => new ExecutionPublisher(
|
||||
$publisher,
|
||||
new Queue(System::getEnv('_APP_EXECUTIONS_QUEUE_NAME', Event::EXECUTIONS_QUEUE_NAME))
|
||||
), ['publisher']);
|
||||
|
||||
$container->set('publisherForFunctions', fn (Publisher $publisher) => new FunctionPublisher(
|
||||
$publisher,
|
||||
new Queue(System::getEnv('_APP_FUNCTIONS_QUEUE_NAME', Event::FUNCTIONS_QUEUE_NAME), 'utopia-queue', Event::FUNCTIONS_QUEUE_TTL)
|
||||
), ['publisher']);
|
||||
|
||||
$container->set('publisherForMigrations', fn (Publisher $publisher) => new MigrationPublisher(
|
||||
$publisher,
|
||||
new Queue(System::getEnv('_APP_MIGRATIONS_QUEUE_NAME', Event::MIGRATIONS_QUEUE_NAME))
|
||||
), ['publisher']);
|
||||
|
||||
$container->set('publisherForStatsResources', fn (Publisher $publisher) => new StatsResourcesPublisher(
|
||||
$publisher,
|
||||
new Queue(System::getEnv('_APP_STATS_RESOURCES_QUEUE_NAME', Event::STATS_RESOURCES_QUEUE_NAME))
|
||||
), ['publisher']);
|
||||
|
||||
$container->set('publisherForBuilds', fn (Publisher $publisher) => new BuildPublisher(
|
||||
$publisher,
|
||||
new Queue(System::getEnv('_APP_BUILDS_QUEUE_NAME', Event::BUILDS_QUEUE_NAME))
|
||||
), ['publisher']);
|
||||
|
||||
$container->set('publisherForDatabase', fn (Publisher $publisherDatabases) => new DatabasePublisher(
|
||||
$publisherDatabases,
|
||||
new Queue(System::getEnv('_APP_DATABASE_QUEUE_NAME', Event::DATABASE_QUEUE_NAME))
|
||||
), ['publisherDatabases']);
|
||||
|
||||
$container->set('publisherForDeletes', fn (Publisher $publisher) => new DeletePublisher(
|
||||
$publisher,
|
||||
new Queue(System::getEnv('_APP_DELETE_QUEUE_NAME', Event::DELETE_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']);
|
||||
|
||||
$container->set('logger', function ($register) {
|
||||
return $register->get('logger');
|
||||
}, ['register']);
|
||||
@@ -65,79 +154,6 @@ $container->set('localeCodes', function () {
|
||||
return array_map(fn ($locale) => $locale['code'], Config::getParam('locale-codes', []));
|
||||
});
|
||||
|
||||
// Queues - shared infrastructure (stateless pool wrappers)
|
||||
$container->set('publisher', function (Group $pools) {
|
||||
return new BrokerPool(publisher: $pools->get('publisher'));
|
||||
}, ['pools']);
|
||||
$container->set('publisherDatabases', function (Publisher $publisher) {
|
||||
return $publisher;
|
||||
}, ['publisher']);
|
||||
$container->set('publisherFunctions', function (Publisher $publisher) {
|
||||
return $publisher;
|
||||
}, ['publisher']);
|
||||
$container->set('publisherMigrations', function (Publisher $publisher) {
|
||||
return $publisher;
|
||||
}, ['publisher']);
|
||||
$container->set('publisherMails', function (Publisher $publisher) {
|
||||
return $publisher;
|
||||
}, ['publisher']);
|
||||
$container->set('publisherDeletes', function (Publisher $publisher) {
|
||||
return $publisher;
|
||||
}, ['publisher']);
|
||||
$container->set('publisherMessaging', function (Publisher $publisher) {
|
||||
return $publisher;
|
||||
}, ['publisher']);
|
||||
$container->set('publisherWebhooks', function (Publisher $publisher) {
|
||||
return $publisher;
|
||||
}, ['publisher']);
|
||||
$container->set('publisherForAudits', fn (Publisher $publisher) => new AuditPublisher(
|
||||
$publisher,
|
||||
new Queue(System::getEnv('_APP_AUDITS_QUEUE_NAME', Event::AUDITS_QUEUE_NAME))
|
||||
), ['publisher']);
|
||||
$container->set('publisherForCertificates', fn (Publisher $publisher) => new CertificatePublisher(
|
||||
$publisher,
|
||||
new Queue(System::getEnv('_APP_CERTIFICATES_QUEUE_NAME', Event::CERTIFICATES_QUEUE_NAME))
|
||||
), ['publisher']);
|
||||
$container->set('publisherForScreenshots', fn (Publisher $publisher) => new ScreenshotPublisher(
|
||||
$publisher,
|
||||
new Queue(System::getEnv('_APP_SCREENSHOTS_QUEUE_NAME', Event::SCREENSHOTS_QUEUE_NAME))
|
||||
), ['publisher']);
|
||||
$container->set('publisherForUsage', fn (Publisher $publisher) => new UsagePublisher(
|
||||
$publisher,
|
||||
new Queue(System::getEnv('_APP_STATS_USAGE_QUEUE_NAME', Event::STATS_USAGE_QUEUE_NAME))
|
||||
), ['publisher']);
|
||||
$container->set('publisherForExecutions', fn (Publisher $publisher) => new ExecutionPublisher(
|
||||
$publisher,
|
||||
new Queue(System::getEnv('_APP_EXECUTIONS_QUEUE_NAME', Event::EXECUTIONS_QUEUE_NAME))
|
||||
), ['publisher']);
|
||||
$container->set('publisherForFunctions', fn (Publisher $publisher) => new FunctionPublisher(
|
||||
$publisher,
|
||||
new Queue(System::getEnv('_APP_FUNCTIONS_QUEUE_NAME', Event::FUNCTIONS_QUEUE_NAME), 'utopia-queue', Event::FUNCTIONS_QUEUE_TTL)
|
||||
), ['publisher']);
|
||||
$container->set('publisherForMigrations', fn (Publisher $publisher) => new MigrationPublisher(
|
||||
$publisher,
|
||||
new Queue(System::getEnv('_APP_MIGRATIONS_QUEUE_NAME', Event::MIGRATIONS_QUEUE_NAME))
|
||||
), ['publisher']);
|
||||
$container->set('publisherForStatsResources', fn (Publisher $publisher) => new StatsResourcesPublisher(
|
||||
$publisher,
|
||||
new Queue(System::getEnv('_APP_STATS_RESOURCES_QUEUE_NAME', Event::STATS_RESOURCES_QUEUE_NAME))
|
||||
), ['publisher']);
|
||||
$container->set('publisherForBuilds', fn (Publisher $publisher) => new BuildPublisher(
|
||||
$publisher,
|
||||
new Queue(System::getEnv('_APP_BUILDS_QUEUE_NAME', Event::BUILDS_QUEUE_NAME))
|
||||
), ['publisher']);
|
||||
$container->set('publisherForDeletes', fn (Publisher $publisher) => new DeletePublisher(
|
||||
$publisher,
|
||||
new Queue(System::getEnv('_APP_DELETE_QUEUE_NAME', Event::DELETE_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
|
||||
@@ -146,10 +162,6 @@ $container->set('platform', function () {
|
||||
return Config::getParam('platform', []);
|
||||
}, []);
|
||||
|
||||
$container->set('console', function () {
|
||||
return new Document(Config::getParam('console'));
|
||||
}, []);
|
||||
|
||||
$container->set('authorization', function () {
|
||||
return new Authorization();
|
||||
}, []);
|
||||
@@ -208,8 +220,6 @@ $container->set('getLogsDB', function (Group $pools, Cache $cache, Authorization
|
||||
};
|
||||
}, ['pools', 'cache', 'authorization']);
|
||||
|
||||
$container->set('telemetry', fn () => new NoTelemetry());
|
||||
|
||||
$container->set('cache', function (Group $pools, Telemetry $telemetry) {
|
||||
$list = Config::getParam('pools-cache', []);
|
||||
$adapters = [];
|
||||
@@ -243,6 +253,16 @@ $container->set('redis', function () {
|
||||
return $redis;
|
||||
});
|
||||
|
||||
$container->set('locks', function (Group $pools) {
|
||||
return function (string $key, int $ttl, callable $callback, float $timeout = 0.0) use ($pools): mixed {
|
||||
return $pools->get('lock')->use(function (\Redis $redis) use ($key, $ttl, $callback, $timeout) {
|
||||
$lock = new Distributed($redis, $key, ttl: $ttl);
|
||||
|
||||
return $lock->withLock($callback, timeout: $timeout);
|
||||
});
|
||||
};
|
||||
}, ['pools']);
|
||||
|
||||
$container->set('timelimit', function (\Redis $redis) {
|
||||
return function (string $key, int $limit, int $time) use ($redis) {
|
||||
return new TimeLimitRedis($key, $limit, $time, $redis);
|
||||
@@ -400,5 +420,3 @@ $container->set(
|
||||
'isResourceBlocked',
|
||||
fn () => fn (Document $project, string $resourceType, ?string $resourceId) => false
|
||||
);
|
||||
|
||||
$container->set('executor', fn () => new Executor());
|
||||
|
||||
@@ -5,7 +5,6 @@ use Ahc\Jwt\JWTException;
|
||||
use Appwrite\Auth\Key;
|
||||
use Appwrite\Databases\TransactionState;
|
||||
use Appwrite\Event\Context\Audit as AuditContext;
|
||||
use Appwrite\Event\Database as EventDatabase;
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Event\Message\Func as FunctionMessage;
|
||||
use Appwrite\Event\Publisher\Func as FunctionPublisher;
|
||||
@@ -107,7 +106,6 @@ return function (Container $context): void {
|
||||
});
|
||||
|
||||
// Per-request queue resources (stateful, accumulate event data during request)
|
||||
$context->set('queueForDatabase', fn (Publisher $publisher) => new EventDatabase($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(), []);
|
||||
@@ -598,7 +596,7 @@ return function (Container $context): void {
|
||||
// These endpoints moved from /v1/projects/:projectId/<resource> to /v1/<resource>
|
||||
// When accessed via the old alias path, extract projectId from the URI
|
||||
$deprecatedProjectPathPrefix = '/v1/projects/';
|
||||
$route = $utopia->match($request);
|
||||
$route = $utopia->match($request)?->route;
|
||||
if (!empty($route)) {
|
||||
$isDeprecatedAlias = \str_starts_with($request->getURI(), $deprecatedProjectPathPrefix) &&
|
||||
!\str_starts_with($route->getPath(), $deprecatedProjectPathPrefix);
|
||||
@@ -1095,7 +1093,7 @@ return function (Container $context): void {
|
||||
if ($project->getId() !== 'console') {
|
||||
$teamInternalId = $project->getAttribute('teamInternalId', '');
|
||||
} else {
|
||||
$route = $utopia->match($request);
|
||||
$route = $utopia->match($request)?->route;
|
||||
$path = ! empty($route) ? $route->getPath() : $request->getURI();
|
||||
$orgHeader = $request->getHeader('x-appwrite-organization', '');
|
||||
if (str_starts_with($path, '/v1/projects/:projectId')) {
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
<?php
|
||||
|
||||
use Appwrite\Event\Database as EventDatabase;
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Event\Publisher\Func as FunctionPublisher;
|
||||
use Appwrite\Event\Realtime;
|
||||
use Appwrite\Event\Webhook;
|
||||
use Appwrite\Usage\Context;
|
||||
@@ -22,7 +20,6 @@ use Utopia\DSN\DSN;
|
||||
use Utopia\Logger\Log;
|
||||
use Utopia\Pools\Group;
|
||||
use Utopia\Queue\Publisher;
|
||||
use Utopia\Queue\Queue;
|
||||
use Utopia\Registry\Registry;
|
||||
use Utopia\Storage\Device\Telemetry as TelemetryDevice;
|
||||
use Utopia\System\System;
|
||||
@@ -327,10 +324,6 @@ return function (Container $container): void {
|
||||
return DateTime::addSeconds(new \DateTime(), -1 * (int) System::getEnv('_APP_MAINTENANCE_RETENTION_EXECUTION', 1209600)); // 14 days
|
||||
}, []);
|
||||
|
||||
$container->set('queueForDatabase', function (Publisher $publisher) {
|
||||
return new EventDatabase($publisher);
|
||||
}, ['publisher']);
|
||||
|
||||
$container->set('queueForEvents', function (Publisher $publisher) {
|
||||
return new Event($publisher);
|
||||
}, ['publisher']);
|
||||
@@ -339,10 +332,6 @@ return function (Container $container): void {
|
||||
return new Webhook($publisher);
|
||||
}, ['publisher']);
|
||||
|
||||
$container->set('publisherForFunctions', fn (Publisher $publisher) => new FunctionPublisher(
|
||||
$publisher,
|
||||
new Queue(System::getEnv('_APP_FUNCTIONS_QUEUE_NAME', Event::FUNCTIONS_QUEUE_NAME), 'utopia-queue', Event::FUNCTIONS_QUEUE_TTL)
|
||||
), ['publisher']);
|
||||
$container->set('queueForRealtime', function () {
|
||||
return new Realtime();
|
||||
}, []);
|
||||
|
||||
+299
-315
@@ -1,10 +1,20 @@
|
||||
<?php
|
||||
|
||||
use Appwrite\Event\Event as QueueEvent;
|
||||
use Appwrite\Event\Publisher\Usage as UsagePublisher;
|
||||
use Appwrite\Event\Realtime as QueueRealtime;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Extend\Exception as AppwriteException;
|
||||
use Appwrite\Messaging\Adapter\Realtime;
|
||||
use Appwrite\Network\Validator\Origin;
|
||||
use Appwrite\Presences\State as PresenceState;
|
||||
use Appwrite\PubSub\Adapter\Pool as PubSubPool;
|
||||
use Appwrite\Realtime\Message\Dispatcher as MessageDispatcher;
|
||||
use Appwrite\Realtime\Message\Handlers\Authentication as AuthenticationHandler;
|
||||
use Appwrite\Realtime\Message\Handlers\Ping as PingHandler;
|
||||
use Appwrite\Realtime\Message\Handlers\Presence as PresenceHandler;
|
||||
use Appwrite\Realtime\Message\Handlers\Subscribe as SubscribeHandler;
|
||||
use Appwrite\Realtime\Message\Handlers\Unsubscribe as UnsubscribeHandler;
|
||||
use Appwrite\Utopia\Database\Documents\User;
|
||||
use Appwrite\Utopia\Request;
|
||||
use Appwrite\Utopia\Response;
|
||||
@@ -16,9 +26,6 @@ use Swoole\Table;
|
||||
use Swoole\Timer;
|
||||
use Utopia\Abuse\Abuse;
|
||||
use Utopia\Abuse\Adapters\TimeLimit\Redis as TimeLimitRedis;
|
||||
use Utopia\Auth\Hashes\Sha;
|
||||
use Utopia\Auth\Proofs\Token;
|
||||
use Utopia\Auth\Store;
|
||||
use Utopia\Cache\Adapter\Pool as CachePool;
|
||||
use Utopia\Cache\Adapter\Sharding;
|
||||
use Utopia\Cache\Cache;
|
||||
@@ -28,7 +35,9 @@ use Utopia\Database\Adapter\Pool as DatabasePool;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\DateTime;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Exception\Authorization as AuthorizationException;
|
||||
use Utopia\Database\Exception\Query as QueryException;
|
||||
use Utopia\Database\Exception\Timeout as TimeoutException;
|
||||
use Utopia\Database\Helpers\ID;
|
||||
use Utopia\Database\Helpers\Role;
|
||||
use Utopia\Database\Query;
|
||||
@@ -37,6 +46,8 @@ use Utopia\DI\Container;
|
||||
use Utopia\DSN\DSN;
|
||||
use Utopia\Logger\Log;
|
||||
use Utopia\Pools\Group;
|
||||
use Utopia\Queue\Broker\Pool as BrokerPool;
|
||||
use Utopia\Queue\Queue;
|
||||
use Utopia\Registry\Registry;
|
||||
use Utopia\Span\Span;
|
||||
use Utopia\System\System;
|
||||
@@ -67,6 +78,38 @@ set_exception_handler(function (\Throwable $e) {
|
||||
));
|
||||
});
|
||||
|
||||
global $container;
|
||||
|
||||
if (!$container->has('pools')) {
|
||||
$container->set('pools', function ($register) {
|
||||
return $register->get('pools');
|
||||
}, ['register']);
|
||||
}
|
||||
|
||||
if (!$container->has('publisherForUsage')) {
|
||||
$container->set('publisherForUsage', function (Group $pools): UsagePublisher {
|
||||
$statsUsageConnection = System::getEnv('_APP_CONNECTIONS_QUEUE_STATS_USAGE', '');
|
||||
$publisherPoolName = 'publisher';
|
||||
|
||||
if (!empty($statsUsageConnection)) {
|
||||
try {
|
||||
$pools->get('publisher_' . $statsUsageConnection);
|
||||
$publisherPoolName = 'publisher_' . $statsUsageConnection;
|
||||
} catch (Throwable) {
|
||||
// Fallback to default publisher pool when custom one is unavailable.
|
||||
}
|
||||
}
|
||||
|
||||
return new UsagePublisher(
|
||||
new BrokerPool(publisher: $pools->get($publisherPoolName)),
|
||||
new Queue(System::getEnv(
|
||||
'_APP_STATS_USAGE_QUEUE_NAME',
|
||||
QueueEvent::STATS_USAGE_QUEUE_NAME
|
||||
))
|
||||
);
|
||||
}, ['pools']);
|
||||
}
|
||||
|
||||
// Allows overriding
|
||||
if (!function_exists('getConsoleDB')) {
|
||||
function getConsoleDB(): Database
|
||||
@@ -234,6 +277,7 @@ if (!function_exists('getRealtime')) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (!function_exists('getTelemetry')) {
|
||||
function getTelemetry(int $workerId): Utopia\Telemetry\Adapter
|
||||
{
|
||||
@@ -247,18 +291,58 @@ if (!function_exists('getTelemetry')) {
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('getQueueForEvents')) {
|
||||
function getQueueForEvents(): QueueEvent
|
||||
{
|
||||
$ctx = Coroutine::getContext();
|
||||
|
||||
if (!isset($ctx['queueForEvents'])) {
|
||||
global $register;
|
||||
/** @var Group $pools */
|
||||
$pools = $register->get('pools');
|
||||
$ctx['queueForEvents'] = new QueueEvent(new BrokerPool(
|
||||
publisher: $pools->get('publisher')
|
||||
));
|
||||
}
|
||||
|
||||
return $ctx['queueForEvents'];
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('getQueueForRealtime')) {
|
||||
function getQueueForRealtime(): QueueRealtime
|
||||
{
|
||||
$ctx = Coroutine::getContext();
|
||||
|
||||
if (!isset($ctx['queueForRealtime'])) {
|
||||
$ctx['queueForRealtime'] = new QueueRealtime();
|
||||
}
|
||||
|
||||
return $ctx['queueForRealtime'];
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('triggerStats')) {
|
||||
function triggerStats(array $event, string $projectId): void
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
global $container;
|
||||
$container->set('pools', function ($register) {
|
||||
return $register->get('pools');
|
||||
}, ['register']);
|
||||
if (!function_exists('checkForProjectUsage')) {
|
||||
function checkForProjectUsage(Document $project): void
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
$realtime = getRealtime();
|
||||
$presenceState = new PresenceState();
|
||||
|
||||
$messageDispatcher = (new MessageDispatcher())
|
||||
->addHandler(new PingHandler())
|
||||
->addHandler(new AuthenticationHandler())
|
||||
->addHandler(new SubscribeHandler())
|
||||
->addHandler(new UnsubscribeHandler())
|
||||
->addHandler(new PresenceHandler());
|
||||
|
||||
/**
|
||||
* Table for statistics across all workers.
|
||||
@@ -292,7 +376,16 @@ if (!function_exists('logError')) {
|
||||
|
||||
$logger = $register->get('realtimeLogger');
|
||||
|
||||
if ($logger && !$error instanceof Exception) {
|
||||
// Match HTTP semantics (app/controllers/general.php): AppwriteException uses its
|
||||
// configured publish flag; everything else publishes only for code 0 or >= 500.
|
||||
// Without this, expected client errors (e.g. Utopia DB Authorization) hit Sentry.
|
||||
if ($error instanceof AppwriteException) {
|
||||
$publish = $error->isPublishable();
|
||||
} else {
|
||||
$publish = $error->getCode() === 0 || $error->getCode() >= 500;
|
||||
}
|
||||
|
||||
if ($logger && $publish) {
|
||||
$version = System::getEnv('_APP_VERSION', 'UNKNOWN');
|
||||
|
||||
$log = new Log();
|
||||
@@ -612,6 +705,16 @@ $server->onWorkerStart(function (int $workerId) use ($server, $register, $stats,
|
||||
}
|
||||
}
|
||||
|
||||
// Strip deleted presences from in-memory connection state so onClose doesn't
|
||||
// re-fire delete events for rows already removed via HTTP DELETE.
|
||||
$deletedPresenceId = Realtime::extractDeletedPresenceId($event);
|
||||
if ($deletedPresenceId !== null) {
|
||||
$realtime->removePresenceFromConnections(
|
||||
(string) ($event['project'] ?? ''),
|
||||
$deletedPresenceId,
|
||||
);
|
||||
}
|
||||
|
||||
$receivers = $realtime->getSubscribers($event);
|
||||
|
||||
if (System::getEnv('_APP_ENV', 'production') === 'development' && !empty($receivers)) {
|
||||
@@ -755,7 +858,7 @@ $server->onOpen(function (int $connection, SwooleRequest $request) use ($server,
|
||||
$websocketEnabled = $apis['websocket'] ?? $apis['realtime'] ?? true;
|
||||
if (
|
||||
!$websocketEnabled
|
||||
&& !($user->isPrivileged($authorization->getRoles()) || $user->isApp($authorization->getRoles()))
|
||||
&& !($user->isPrivileged($authorization->getRoles()) || $user->isKey($authorization->getRoles()))
|
||||
) {
|
||||
throw new AppwriteException(AppwriteException::GENERAL_API_DISABLED);
|
||||
}
|
||||
@@ -898,6 +1001,16 @@ $server->onOpen(function (int $connection, SwooleRequest $request) use ($server,
|
||||
$success = true;
|
||||
|
||||
} catch (Throwable $th) {
|
||||
Span::error($th);
|
||||
|
||||
// Convert known Utopia DB exceptions to AppwriteException so isPublishable()
|
||||
// suppresses expected client errors (permission denied, query timeout) from Sentry.
|
||||
if ($th instanceof AuthorizationException) {
|
||||
$th = new AppwriteException(AppwriteException::USER_UNAUTHORIZED, previous: $th);
|
||||
} elseif ($th instanceof TimeoutException) {
|
||||
$th = new AppwriteException(AppwriteException::DATABASE_TIMEOUT, previous: $th);
|
||||
}
|
||||
|
||||
logError($th, 'realtime', project: $project, user: $logUser, authorization: $authorization);
|
||||
|
||||
// Handle SQL error code is 'HY000'
|
||||
@@ -933,7 +1046,6 @@ $server->onOpen(function (int $connection, SwooleRequest $request) use ($server,
|
||||
Console::error('[Error] Code: ' . $response['data']['code']);
|
||||
Console::error('[Error] Message: ' . $response['data']['message']);
|
||||
}
|
||||
Span::error($th);
|
||||
} finally {
|
||||
Span::add('realtime.success', $success);
|
||||
Span::add('realtime.response_code', $responseCode);
|
||||
@@ -951,15 +1063,12 @@ $server->onOpen(function (int $connection, SwooleRequest $request) use ($server,
|
||||
}
|
||||
});
|
||||
|
||||
$server->onMessage(function (int $connection, string $message) use ($server, $realtime, $containerId, $register) {
|
||||
$server->onMessage(function (int $connection, string $message) use ($container, $server, $realtime, $containerId, $register, $presenceState, $messageDispatcher) {
|
||||
$project = null;
|
||||
$authorization = null;
|
||||
$projectId = $realtime->connections[$connection]['projectId'] ?? null;
|
||||
$rawSize = \strlen($message);
|
||||
$messageType = 'invalid';
|
||||
$subscriptionDelta = 0;
|
||||
$subscriptionsRequested = 0;
|
||||
$subscriptionsRemoved = 0;
|
||||
$outboundBytes = 0;
|
||||
$responseCode = 200;
|
||||
$success = false;
|
||||
@@ -972,17 +1081,44 @@ $server->onMessage(function (int $connection, string $message) use ($server, $re
|
||||
try {
|
||||
$response = new Response(new SwooleResponse());
|
||||
|
||||
// Get authorization from connection (stored during onOpen)
|
||||
$authorization = $realtime->connections[$connection]['authorization'] ?? null;
|
||||
if ($authorization === null) {
|
||||
$authorization = new Authorization();
|
||||
// Build a fresh Authorization per message. The connection-scoped instance is shared
|
||||
// across coroutines, and `Authorization::skip()` toggles instance state — concurrent
|
||||
// messages on the same connection (e.g. `authentication` + `presence` sent back-to-back)
|
||||
// would interleave skip/restore and leak permission checks into supposedly-skipped lookups.
|
||||
$authorization = new Authorization();
|
||||
$connectionAuthorization = $realtime->connections[$connection]['authorization'] ?? null;
|
||||
if ($connectionAuthorization !== null) {
|
||||
foreach ($connectionAuthorization->getRoles() as $role) {
|
||||
$authorization->addRole($role);
|
||||
}
|
||||
}
|
||||
$connectionRoles = $realtime->connections[$connection]['roles'] ?? [];
|
||||
foreach ($connectionRoles as $role) {
|
||||
if ($authorization->hasRole($role)) {
|
||||
continue;
|
||||
}
|
||||
$authorization->addRole($role);
|
||||
}
|
||||
|
||||
$database = getConsoleDB();
|
||||
$database->setAuthorization($authorization);
|
||||
|
||||
if (!empty($projectId) && $projectId !== 'console') {
|
||||
$project = $authorization->skip(fn () => $database->getDocument('projects', $projectId));
|
||||
// Negative-cache race: if any prior code path queried projects:$projectId
|
||||
// before this project existed (e.g. a router probe during connection
|
||||
// setup), the Database's shared cache may hold an empty result. Try the
|
||||
// cached read first, and only purge/retry when the first lookup reports
|
||||
// not-found so the shared cache remains effective for normal traffic.
|
||||
try {
|
||||
$project = $authorization->skip(fn () => $database->getDocument('projects', $projectId));
|
||||
} catch (AppwriteException $e) {
|
||||
if ($e->getCode() !== 404) {
|
||||
throw $e;
|
||||
}
|
||||
|
||||
$database->purgeCachedDocument('projects', $projectId);
|
||||
$project = $authorization->skip(fn () => $database->getDocument('projects', $projectId));
|
||||
}
|
||||
|
||||
$database = getProjectDB($project);
|
||||
$database->setAuthorization($authorization);
|
||||
@@ -990,6 +1126,10 @@ $server->onMessage(function (int $connection, string $message) use ($server, $re
|
||||
$project = null;
|
||||
}
|
||||
|
||||
if ($project !== null) {
|
||||
checkForProjectUsage($project);
|
||||
}
|
||||
|
||||
/*
|
||||
* Abuse Check
|
||||
*
|
||||
@@ -1008,6 +1148,7 @@ $server->onMessage(function (int $connection, string $message) use ($server, $re
|
||||
}
|
||||
|
||||
// Record realtime inbound bytes for this project
|
||||
// not making this a part of the dispatcher as we need to get the inbound bytes as well even if we dont enter the dispatcher
|
||||
if ($project !== null && !$project->isEmpty()) {
|
||||
triggerStats([
|
||||
METRIC_REALTIME_INBOUND => $rawSize,
|
||||
@@ -1026,300 +1167,54 @@ $server->onMessage(function (int $connection, string $message) use ($server, $re
|
||||
throw new Exception(Exception::REALTIME_MESSAGE_FORMAT_INVALID, 'Message type is not valid.');
|
||||
}
|
||||
|
||||
// Ping does not require project context; other messages do (e.g. after unsubscribe during auth)
|
||||
if (empty($projectId) && ($message['type'] ?? '') !== 'ping') {
|
||||
throw new Exception(Exception::REALTIME_POLICY_VIOLATION, 'Missing project context. Reconnect to the project first.');
|
||||
// Child of the global container: per-message values like $connection and $project
|
||||
// live on this scope so concurrent message coroutines don't clobber each other,
|
||||
// while globally-registered services (pools, ...) remain reachable via the parent.
|
||||
$messageContainer = new Container($container);
|
||||
$messageContainer->set('connectionId', fn () => $connection);
|
||||
$messageContainer->set('server', fn () => $server);
|
||||
$messageContainer->set('realtime', fn () => $realtime);
|
||||
$messageContainer->set('register', fn () => $register);
|
||||
$messageContainer->set('response', fn () => $response);
|
||||
$messageContainer->set('presenceState', fn () => $presenceState);
|
||||
$messageContainer->set('database', fn () => $database);
|
||||
$messageContainer->set('authorization', fn () => $authorization);
|
||||
$messageContainer->set('project', fn () => $project);
|
||||
$messageContainer->set('projectId', fn () => $projectId);
|
||||
$messageContainer->set('queueForEvents', fn () => getQueueForEvents());
|
||||
$messageContainer->set('queueForRealtime', fn () => getQueueForRealtime());
|
||||
|
||||
$responsePayload = $messageDispatcher->dispatch($messageContainer, $message);
|
||||
|
||||
if ($responsePayload !== null) {
|
||||
$responseJson = json_encode($responsePayload);
|
||||
if ($responseJson === false) {
|
||||
throw new \RuntimeException(
|
||||
'Failed to encode realtime response payload: ' . json_last_error_msg()
|
||||
);
|
||||
}
|
||||
|
||||
$server->send([$connection], $responseJson);
|
||||
$bytes = \strlen($responseJson);
|
||||
$outboundBytes += $bytes;
|
||||
|
||||
if ($project !== null && !$project->isEmpty()) {
|
||||
triggerStats([METRIC_REALTIME_OUTBOUND => $bytes], $project->getId());
|
||||
}
|
||||
}
|
||||
|
||||
switch ($message['type']) {
|
||||
case 'ping':
|
||||
$pongPayloadJson = json_encode([
|
||||
'type' => 'pong'
|
||||
]);
|
||||
|
||||
$server->send([$connection], $pongPayloadJson);
|
||||
$outboundBytes += \strlen($pongPayloadJson);
|
||||
|
||||
if ($project !== null && !$project->isEmpty()) {
|
||||
$pongOutboundBytes = \strlen($pongPayloadJson);
|
||||
|
||||
if ($pongOutboundBytes > 0) {
|
||||
triggerStats([
|
||||
METRIC_REALTIME_OUTBOUND => $pongOutboundBytes,
|
||||
], $project->getId());
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
case 'authentication':
|
||||
if (!array_key_exists('session', $message['data'])) {
|
||||
throw new Exception(Exception::REALTIME_MESSAGE_FORMAT_INVALID, 'Payload is not valid.');
|
||||
}
|
||||
|
||||
$store = new Store();
|
||||
|
||||
$store->decode($message['data']['session']);
|
||||
|
||||
/** @var User $user */
|
||||
$user = $database->getDocument('users', $store->getProperty('id', ''));
|
||||
|
||||
/**
|
||||
* TODO:
|
||||
* Moving forward, we should try to use our dependency injection container
|
||||
* to inject the proof for token.
|
||||
* This way we will have one source of truth for the proof for token.
|
||||
*/
|
||||
$proofForToken = new Token();
|
||||
$proofForToken->setHash(new Sha());
|
||||
|
||||
if (
|
||||
empty($user->getId()) // Check a document has been found in the DB
|
||||
|| !$user->sessionVerify($store->getProperty('secret', ''), $proofForToken) // Validate user has valid login token
|
||||
) {
|
||||
// cookie not valid
|
||||
throw new Exception(Exception::REALTIME_MESSAGE_FORMAT_INVALID, 'Session is not valid.');
|
||||
}
|
||||
|
||||
$roles = $user->getRoles($database->getAuthorization());
|
||||
|
||||
$authorization = $realtime->connections[$connection]['authorization'] ?? null;
|
||||
$projectId = $realtime->connections[$connection]['projectId'] ?? null;
|
||||
// Capture the pre-auth userId so we can rebind any account channels
|
||||
// that were stored under it (e.g. guest who subscribed to `account`
|
||||
// and now authenticates). unsubscribe() below clears the connection
|
||||
// entry, so we must read it first.
|
||||
$previousUserId = $realtime->connections[$connection]['userId'] ?? '';
|
||||
|
||||
$subscriptionsBefore = \count($realtime->getSubscriptionMetadata($connection));
|
||||
$meta = $realtime->getSubscriptionMetadata($connection);
|
||||
|
||||
$realtime->unsubscribe($connection);
|
||||
|
||||
if (!empty($projectId)) {
|
||||
foreach ($meta as $subscriptionId => $subscription) {
|
||||
$queries = Query::parseQueries($subscription['queries'] ?? []);
|
||||
$channels = Realtime::rebindAccountChannels(
|
||||
$subscription['channels'] ?? [],
|
||||
$previousUserId,
|
||||
$user->getId()
|
||||
);
|
||||
|
||||
$realtime->subscribe(
|
||||
$projectId,
|
||||
$connection,
|
||||
$subscriptionId,
|
||||
$roles,
|
||||
$channels,
|
||||
$queries,
|
||||
$user->getId()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if ($authorization !== null) {
|
||||
$realtime->connections[$connection]['authorization'] = $authorization;
|
||||
}
|
||||
|
||||
$subscriptionsAfter = \count($realtime->getSubscriptionMetadata($connection));
|
||||
$subscriptionDelta = $subscriptionsAfter - $subscriptionsBefore;
|
||||
if ($subscriptionDelta !== 0) {
|
||||
$register->get('telemetry.workerSubscriptionCounter')->add($subscriptionDelta, $register->get('telemetry.workerAttributes'));
|
||||
}
|
||||
|
||||
$user = $response->output($user, Response::MODEL_ACCOUNT);
|
||||
|
||||
$authResponsePayloadJson = json_encode([
|
||||
'type' => 'response',
|
||||
'data' => [
|
||||
'to' => 'authentication',
|
||||
'success' => true,
|
||||
'user' => $user
|
||||
]
|
||||
]);
|
||||
|
||||
$server->send([$connection], $authResponsePayloadJson);
|
||||
$outboundBytes += \strlen($authResponsePayloadJson);
|
||||
|
||||
if ($project !== null && !$project->isEmpty()) {
|
||||
$authOutboundBytes = \strlen($authResponsePayloadJson);
|
||||
|
||||
if ($authOutboundBytes > 0) {
|
||||
triggerStats([
|
||||
METRIC_REALTIME_OUTBOUND => $authOutboundBytes,
|
||||
], $project->getId());
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case 'subscribe':
|
||||
/**
|
||||
* Message based upsertion of a subscription
|
||||
* If subscriptionId is given then it will match subId of the connection and update the subscription with channels and queries
|
||||
* If non-existing subid is given or not given a new subid will be generated
|
||||
* Similar to what we have now -> two subscribe() block with same channels and queries still two different subscriptions
|
||||
*
|
||||
* structure of the payload -> array of maps
|
||||
* 'data' : [subscriptionId:"" , channels:[] , queries:[]]
|
||||
*/
|
||||
if (!is_array($message['data']) || !array_is_list($message['data'])) {
|
||||
throw new Exception(Exception::REALTIME_MESSAGE_FORMAT_INVALID, 'Payload is not valid.');
|
||||
}
|
||||
|
||||
$roles = $realtime->connections[$connection]['roles'] ?? [Role::guests()->toString()];
|
||||
$userId = $realtime->connections[$connection]['userId'] ?? '';
|
||||
|
||||
// bulk validation + parsing before subscribing
|
||||
$parsedPayloads = [];
|
||||
$subscriptionsBefore = \count($realtime->getSubscriptionMetadata($connection));
|
||||
foreach ($message['data'] as $payload) {
|
||||
if (!\is_array($payload)) {
|
||||
throw new Exception(Exception::REALTIME_MESSAGE_FORMAT_INVALID, 'Each subscribe payload must be an object.');
|
||||
}
|
||||
if (!array_key_exists('channels', $payload)) {
|
||||
throw new Exception(Exception::REALTIME_MESSAGE_FORMAT_INVALID, 'channels is not present in payload.');
|
||||
}
|
||||
if (!is_array($payload['channels']) || !array_is_list($payload['channels'])) {
|
||||
throw new Exception(Exception::REALTIME_MESSAGE_FORMAT_INVALID, 'channels is not a valid array.');
|
||||
}
|
||||
// registering the queries if not present and check in the same payload later on
|
||||
if (!array_key_exists('queries', $payload)) {
|
||||
$payload['queries'] = [];
|
||||
}
|
||||
if (!is_array($payload['queries']) || !array_is_list($payload['queries'])) {
|
||||
throw new Exception(Exception::REALTIME_MESSAGE_FORMAT_INVALID, 'queries is not a valid array.');
|
||||
}
|
||||
|
||||
$subscriptionId = \array_key_exists('subscriptionId', $payload)
|
||||
? $payload['subscriptionId']
|
||||
: ID::unique();
|
||||
|
||||
try {
|
||||
$convertedQueries = Realtime::convertQueries($payload['queries']);
|
||||
} catch (QueryException $e) {
|
||||
throw new Exception(Exception::REALTIME_MESSAGE_FORMAT_INVALID, 'Invalid query: ' . $e->getMessage());
|
||||
}
|
||||
|
||||
$convertedChannels = \array_keys(Realtime::convertChannels($payload['channels'], $userId));
|
||||
|
||||
$parsedPayloads[] = [
|
||||
'subscriptionId' => $subscriptionId,
|
||||
'channels' => $payload['channels'],
|
||||
'convertedChannels' => $convertedChannels,
|
||||
'queries' => $convertedQueries,
|
||||
];
|
||||
}
|
||||
|
||||
foreach ($parsedPayloads as $parsedPayload) {
|
||||
$subscriptionId = $parsedPayload['subscriptionId'];
|
||||
$channels = $parsedPayload['convertedChannels'];
|
||||
$queries = $parsedPayload['queries'];
|
||||
$realtime->subscribe($projectId, $connection, $subscriptionId, $roles, $channels, $queries);
|
||||
}
|
||||
$subscriptionsAfter = \count($realtime->getSubscriptionMetadata($connection));
|
||||
$subscriptionDelta = $subscriptionsAfter - $subscriptionsBefore;
|
||||
$subscriptionsRequested = \count($parsedPayloads);
|
||||
if ($subscriptionDelta !== 0) {
|
||||
$register->get('telemetry.workerSubscriptionCounter')->add($subscriptionDelta, $register->get('telemetry.workerAttributes'));
|
||||
}
|
||||
|
||||
$responsePayload = json_encode([
|
||||
'type' => 'response',
|
||||
'data' => [
|
||||
'to' => 'subscribe',
|
||||
'success' => true,
|
||||
'subscriptions' => \array_map(function (array $parsedPayload) {
|
||||
return [
|
||||
'subscriptionId' => $parsedPayload['subscriptionId'],
|
||||
'channels' => $parsedPayload['convertedChannels'],
|
||||
'queries' => \array_map(fn ($q) => $q->toString(), $parsedPayload['queries']),
|
||||
];
|
||||
}, $parsedPayloads),
|
||||
]
|
||||
]);
|
||||
|
||||
$server->send([$connection], $responsePayload);
|
||||
$outboundBytes += \strlen($responsePayload);
|
||||
|
||||
if ($project !== null && !$project->isEmpty()) {
|
||||
$subscribeOutboundBytes = \strlen($responsePayload);
|
||||
|
||||
if ($subscribeOutboundBytes > 0) {
|
||||
triggerStats([
|
||||
METRIC_REALTIME_OUTBOUND => $subscribeOutboundBytes,
|
||||
], $project->getId());
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case 'unsubscribe':
|
||||
if (!\is_array($message['data']) || !\array_is_list($message['data'])) {
|
||||
throw new Exception(Exception::REALTIME_MESSAGE_FORMAT_INVALID, 'Payload is not valid.');
|
||||
}
|
||||
|
||||
$subscriptionsBefore = \count($realtime->getSubscriptionMetadata($connection));
|
||||
|
||||
// Validate every payload before executing any removal so an invalid entry
|
||||
// later in the batch does not leave earlier entries half-applied on the server.
|
||||
$validatedIds = [];
|
||||
foreach ($message['data'] as $payload) {
|
||||
if (
|
||||
!\is_array($payload)
|
||||
|| !\array_key_exists('subscriptionId', $payload)
|
||||
|| !\is_string($payload['subscriptionId'])
|
||||
|| $payload['subscriptionId'] === ''
|
||||
) {
|
||||
throw new Exception(Exception::REALTIME_MESSAGE_FORMAT_INVALID, 'Each unsubscribe payload must include a non-empty subscriptionId.');
|
||||
}
|
||||
$validatedIds[] = $payload['subscriptionId'];
|
||||
}
|
||||
|
||||
$unsubscribeResults = [];
|
||||
foreach ($validatedIds as $subscriptionId) {
|
||||
$wasRemoved = $realtime->unsubscribeSubscription($connection, $subscriptionId);
|
||||
$unsubscribeResults[] = [
|
||||
'subscriptionId' => $subscriptionId,
|
||||
'removed' => $wasRemoved,
|
||||
];
|
||||
}
|
||||
$subscriptionsAfter = \count($realtime->getSubscriptionMetadata($connection));
|
||||
$subscriptionDelta = $subscriptionsAfter - $subscriptionsBefore;
|
||||
$subscriptionsRequested = \count($validatedIds);
|
||||
$subscriptionsRemoved = \count(\array_filter($unsubscribeResults, fn (array $item) => $item['removed']));
|
||||
if ($subscriptionDelta !== 0) {
|
||||
$register->get('telemetry.workerSubscriptionCounter')->add($subscriptionDelta, $register->get('telemetry.workerAttributes'));
|
||||
}
|
||||
|
||||
$unsubscribeResponsePayload = json_encode([
|
||||
'type' => 'response',
|
||||
'data' => [
|
||||
'to' => 'unsubscribe',
|
||||
'success' => true,
|
||||
'subscriptions' => $unsubscribeResults,
|
||||
],
|
||||
]);
|
||||
|
||||
$server->send([$connection], $unsubscribeResponsePayload);
|
||||
$outboundBytes += \strlen($unsubscribeResponsePayload);
|
||||
|
||||
if ($project !== null && !$project->isEmpty()) {
|
||||
$unsubscribeOutboundBytes = \strlen($unsubscribeResponsePayload);
|
||||
|
||||
if ($unsubscribeOutboundBytes > 0) {
|
||||
triggerStats([
|
||||
METRIC_REALTIME_OUTBOUND => $unsubscribeOutboundBytes,
|
||||
], $project->getId());
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new Exception(Exception::REALTIME_MESSAGE_FORMAT_INVALID, 'Message type is not valid.');
|
||||
}
|
||||
$success = true;
|
||||
} catch (Throwable $th) {
|
||||
Span::error($th);
|
||||
|
||||
// Convert known Utopia DB exceptions to AppwriteException so isPublishable()
|
||||
// suppresses expected client errors (permission denied, query timeout) from Sentry.
|
||||
if ($th instanceof AuthorizationException) {
|
||||
$th = new AppwriteException(AppwriteException::USER_UNAUTHORIZED, previous: $th);
|
||||
} elseif ($th instanceof TimeoutException) {
|
||||
$th = new AppwriteException(AppwriteException::DATABASE_TIMEOUT, previous: $th);
|
||||
}
|
||||
|
||||
logError($th, 'realtimeMessage', project: $project, authorization: $authorization);
|
||||
$code = $th->getCode();
|
||||
if (!is_int($code)) {
|
||||
@@ -1349,14 +1244,9 @@ $server->onMessage(function (int $connection, string $message) use ($server, $re
|
||||
if ($th->getCode() === 1008) {
|
||||
$server->close($connection, $th->getCode());
|
||||
}
|
||||
Span::error($th);
|
||||
} finally {
|
||||
Span::add('realtime.success', $success);
|
||||
Span::add('realtime.response_code', $responseCode);
|
||||
Span::add('realtime.subscription_delta', $subscriptionDelta);
|
||||
Span::add('realtime.subscriptions_requested', $subscriptionsRequested);
|
||||
Span::add('realtime.subscriptions_removed', $subscriptionsRemoved);
|
||||
Span::add('realtime.subscribe.subscriptions_count', $subscriptionsRequested);
|
||||
Span::add('realtime.outbound_bytes', $outboundBytes);
|
||||
Span::add('project.id', $project?->getId() ?? $projectId);
|
||||
Span::add('user.id', $realtime->connections[$connection]['userId'] ?? null);
|
||||
@@ -1365,7 +1255,7 @@ $server->onMessage(function (int $connection, string $message) use ($server, $re
|
||||
}
|
||||
});
|
||||
|
||||
$server->onClose(function (int $connection) use ($realtime, $stats, $register) {
|
||||
$server->onClose(function (int $connection) use ($realtime, $stats, $register, $container, $presenceState) {
|
||||
$projectId = null;
|
||||
$userId = null;
|
||||
$subscriptionsBeforeClose = 0;
|
||||
@@ -1390,6 +1280,100 @@ $server->onClose(function (int $connection) use ($realtime, $stats, $register) {
|
||||
}
|
||||
|
||||
$projectId = $realtime->connections[$connection]['projectId'];
|
||||
/** @var array<string, Document> $presencesById */
|
||||
$presencesById = $realtime->connections[$connection]['presences'] ?? [];
|
||||
|
||||
if (
|
||||
!empty($presencesById)
|
||||
&& $projectId !== 'console'
|
||||
) {
|
||||
go(function () use ($presencesById, $projectId, $userId, $container, $presenceState): void {
|
||||
// Fresh span: the parent realtime.close span finishes before this coroutine
|
||||
Span::init('realtime.close.presenceCleanup');
|
||||
Span::add('realtime.projectId', $projectId);
|
||||
Span::add('realtime.presenceCount', \count($presencesById));
|
||||
|
||||
try {
|
||||
$dbForPlatform = getConsoleDB();
|
||||
$project = $dbForPlatform->getAuthorization()->skip(fn () => $dbForPlatform->getDocument('projects', $projectId));
|
||||
|
||||
if ($project->isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$presenceIds = \array_keys($presencesById);
|
||||
$presences = \array_values($presencesById);
|
||||
$dbForProject = getProjectDB($project);
|
||||
|
||||
$user = new User([]);
|
||||
if (!empty($userId)) {
|
||||
try {
|
||||
$fetched = $dbForProject->getAuthorization()->skip(
|
||||
fn () => $dbForProject->getDocument('users', $userId)
|
||||
);
|
||||
if (!$fetched->isEmpty()) {
|
||||
$user = new User($fetched->getArrayCopy());
|
||||
}
|
||||
} catch (Throwable) {
|
||||
// Fall back to empty User if lookup fails.
|
||||
}
|
||||
}
|
||||
|
||||
/** @var UsagePublisher $publisherForUsage */
|
||||
$publisherForUsage = $container->get('publisherForUsage');
|
||||
|
||||
/** @var array<string, true> $deletedIds */
|
||||
$deletedIds = [];
|
||||
try {
|
||||
$deletionCount = $dbForProject->getAuthorization()->skip(
|
||||
function () use ($dbForProject, $presenceIds, &$deletedIds): int {
|
||||
return $dbForProject->deleteDocuments(
|
||||
'presenceLogs',
|
||||
[Query::equal('$id', $presenceIds)],
|
||||
onNext: function (Document $deleted) use (&$deletedIds): void {
|
||||
$deletedIds[$deleted->getId()] = true;
|
||||
},
|
||||
);
|
||||
}
|
||||
);
|
||||
$presenceState->triggerUsage($publisherForUsage, $project, -$deletionCount);
|
||||
} catch (Throwable $th) {
|
||||
Span::error($th);
|
||||
logError($th, 'realtimeOnClosePresenceDeletion', tags: [
|
||||
'projectId' => $projectId,
|
||||
'presences' => \count($presences)
|
||||
]);
|
||||
}
|
||||
|
||||
$queueForEvents = getQueueForEvents();
|
||||
$queueForRealtime = getQueueForRealtime();
|
||||
foreach ($presences as $presence) {
|
||||
if (!isset($deletedIds[$presence->getId()])) {
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
$presenceState->triggerEvent(
|
||||
$queueForEvents,
|
||||
$queueForRealtime,
|
||||
$project,
|
||||
$user,
|
||||
'presences.[presenceId].delete',
|
||||
$presence,
|
||||
);
|
||||
} catch (Throwable) {
|
||||
// Swallow errors to avoid breaking disconnect cleanup
|
||||
}
|
||||
}
|
||||
} catch (Throwable $th) {
|
||||
Span::error($th);
|
||||
logError($th, 'realtimeOnClosePresenceCleanup', tags: [
|
||||
'projectId' => $projectId,
|
||||
]);
|
||||
} finally {
|
||||
Span::current()?->finish();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
triggerStats([
|
||||
METRIC_REALTIME_CONNECTIONS => -1,
|
||||
|
||||
+6
-5
@@ -54,28 +54,29 @@
|
||||
"utopia-php/abuse": "1.3.*",
|
||||
"utopia-php/agents": "1.2.*",
|
||||
"utopia-php/analytics": "0.15.*",
|
||||
"utopia-php/audit": "2.3.*",
|
||||
"utopia-php/audit": "^2.4",
|
||||
"utopia-php/auth": "0.5.*",
|
||||
"utopia-php/cache": "^2.1",
|
||||
"utopia-php/cache": "^3.0",
|
||||
"utopia-php/cli": "0.23.*",
|
||||
"utopia-php/compression": "0.1.*",
|
||||
"utopia-php/config": "1.*",
|
||||
"utopia-php/console": "0.1.*",
|
||||
"utopia-php/database": "5.*",
|
||||
"utopia-php/detector": "0.2.*",
|
||||
"utopia-php/domains": "2.*",
|
||||
"utopia-php/domains": "^2.1",
|
||||
"utopia-php/emails": "0.7.*",
|
||||
"utopia-php/dns": "1.7.*",
|
||||
"utopia-php/dsn": "0.2.1",
|
||||
"utopia-php/http": "^2.0@RC",
|
||||
"utopia-php/http": "2.0.0-rc3",
|
||||
"utopia-php/fetch": "^1.1",
|
||||
"utopia-php/validators": "0.2.*",
|
||||
"utopia-php/image": "0.8.*",
|
||||
"utopia-php/locale": "0.8.*",
|
||||
"utopia-php/lock": "0.2.*",
|
||||
"utopia-php/logger": "0.8.*",
|
||||
"utopia-php/messaging": "0.22.*",
|
||||
"utopia-php/migration": "1.*",
|
||||
"utopia-php/platform": "^1.0@RC",
|
||||
"utopia-php/platform": "1.0.0-rc3",
|
||||
"utopia-php/pools": "1.*",
|
||||
"utopia-php/span": "1.1.*",
|
||||
"utopia-php/preloader": "0.2.*",
|
||||
|
||||
Generated
+224
-243
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1 @@
|
||||
Delete a presence log by its unique ID.
|
||||
@@ -0,0 +1 @@
|
||||
Get presence usage metrics, including the current total of online users and historical online user counts for the selected time range.
|
||||
@@ -0,0 +1 @@
|
||||
Get a presence log by its unique ID. Entries whose `expiresAt` is in the past are treated as not found.
|
||||
@@ -0,0 +1 @@
|
||||
List presence logs. Expired entries are filtered out automatically.
|
||||
@@ -0,0 +1 @@
|
||||
Update a presence log by its unique ID. Using the patch method you can pass only specific fields that will get updated.
|
||||
@@ -0,0 +1 @@
|
||||
Create or update a presence log by its user ID.
|
||||
@@ -1 +0,0 @@
|
||||
Create a new JWT token. This token can be used to authenticate users with custom scopes and expiration time.
|
||||
@@ -1 +0,0 @@
|
||||
Send a test email to verify SMTP configuration.
|
||||
@@ -1 +0,0 @@
|
||||
Create a new project. You can create a maximum of 100 projects per account.
|
||||
@@ -1 +0,0 @@
|
||||
Reset a custom email template to its default value. This endpoint removes any custom content and restores the template to its original state.
|
||||
@@ -1 +0,0 @@
|
||||
Delete a project by its unique ID.
|
||||
@@ -1 +0,0 @@
|
||||
Get a custom email template for the specified locale and type. This endpoint returns the template content, subject, and other configuration details.
|
||||
@@ -1 +0,0 @@
|
||||
Get a project by its unique ID. This endpoint allows you to retrieve the project's details, including its name, description, team, region, and other metadata.
|
||||
@@ -1 +0,0 @@
|
||||
Update the status of a specific authentication method. Use this endpoint to enable or disable different authentication methods such as email, magic urls or sms in your project.
|
||||
@@ -1 +0,0 @@
|
||||
Update a custom email template for the specified locale and type. Use this endpoint to modify the content of your email templates.
|
||||
@@ -1 +0,0 @@
|
||||
Update the list of mock phone numbers for testing. Use these numbers to bypass SMS verification in development.
|
||||
@@ -1 +0,0 @@
|
||||
Update the OAuth2 provider configurations. Use this endpoint to set up or update the OAuth2 provider credentials or enable/disable providers.
|
||||
@@ -1 +0,0 @@
|
||||
Update the SMTP configuration for your project. Use this endpoint to configure your project's SMTP provider with your custom settings for sending transactional emails.
|
||||
@@ -1 +0,0 @@
|
||||
Update a project by its unique ID.
|
||||
@@ -0,0 +1,116 @@
|
||||
## Getting Started
|
||||
|
||||
Before you begin, create an Appwrite project and add a Unity platform in your Appwrite Console.
|
||||
|
||||
This SDK requires the following Unity packages and libraries:
|
||||
|
||||
- [**UniTask**](https://github.com/Cysharp/UniTask): For async/await support in Unity.
|
||||
- [**NativeWebSocket**](https://github.com/endel/NativeWebSocket): For WebSocket realtime subscriptions.
|
||||
- **System.Text.Json**: For JSON serialization, provided as a DLL in the project.
|
||||
|
||||
After installing the SDK, open **Appwrite → Setup Assistant** in Unity and install the required dependencies.
|
||||
|
||||
### Configure the SDK
|
||||
|
||||
Create an Appwrite configuration using the **QuickStart** window in the **Appwrite Setup Assistant**, or through **Appwrite → Create Configuration**.
|
||||
|
||||
### Using AppwriteManager
|
||||
|
||||
```csharp
|
||||
[SerializeField] private AppwriteConfig config;
|
||||
private AppwriteManager _manager;
|
||||
|
||||
private async UniTask ExampleWithManager()
|
||||
{
|
||||
_manager = AppwriteManager.Instance ?? new GameObject("AppwriteManager").AddComponent<AppwriteManager>();
|
||||
_manager.SetConfig(config);
|
||||
|
||||
var success = await _manager.Initialize(needRealtime: true);
|
||||
if (!success)
|
||||
{
|
||||
Debug.LogError("Failed to initialize AppwriteManager");
|
||||
return;
|
||||
}
|
||||
|
||||
var client = _manager.Client;
|
||||
var pingResult = await client.Ping();
|
||||
Debug.Log($"Ping result: {pingResult}");
|
||||
|
||||
var realtime = _manager.Realtime;
|
||||
var subscription = realtime.Subscribe(
|
||||
new[] { "databases.*.collections.*.documents" },
|
||||
response =>
|
||||
{
|
||||
var eventName = response.Events != null && response.Events.Length > 0
|
||||
? response.Events[0]
|
||||
: "unknown";
|
||||
|
||||
Debug.Log($"Realtime event: {eventName}");
|
||||
}
|
||||
);
|
||||
|
||||
// Keep a reference to close the subscription when your MonoBehaviour is destroyed.
|
||||
// subscription.Close();
|
||||
}
|
||||
```
|
||||
|
||||
### Using Client directly
|
||||
|
||||
```csharp
|
||||
private async UniTask ExampleWithDirectClient()
|
||||
{
|
||||
var client = Client.From(
|
||||
projectId: "<PROJECT_ID>",
|
||||
endpoint: "https://<REGION>.cloud.appwrite.io/v1",
|
||||
endpointRealtime: "wss://<REGION>.cloud.appwrite.io/v1");
|
||||
|
||||
var pingResult = await client.Ping();
|
||||
Debug.Log($"Direct client ping: {pingResult}");
|
||||
}
|
||||
```
|
||||
|
||||
You can also create authenticated clients with `Client.FromSession`, `Client.FromDevKey`, or `Client.FromImpersonation` when those authentication flows are needed.
|
||||
|
||||
### Error handling
|
||||
|
||||
```csharp
|
||||
try
|
||||
{
|
||||
var result = await client.Ping();
|
||||
}
|
||||
catch (AppwriteException ex)
|
||||
{
|
||||
Debug.LogError($"Appwrite Error: {ex.Message}");
|
||||
Debug.LogError($"Status Code: {ex.Code}");
|
||||
Debug.LogError($"Response: {ex.Response}");
|
||||
}
|
||||
```
|
||||
|
||||
## Preparing Models for Databases API
|
||||
|
||||
When working with the Databases API in Unity, models should be prepared for serialization using the System.Text.Json library. System.Text.Json uses CLR property names by default unless a naming policy is configured. If your project or SDK configuration serializes property names differently from your Appwrite collection attributes, this can cause errors due to mismatches between serialized property names and actual attribute names in your collection.
|
||||
|
||||
To avoid this, add the `JsonPropertyName` attribute to each property in your model class to match the attribute name in Appwrite:
|
||||
|
||||
```csharp
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
public class TestModel
|
||||
{
|
||||
[JsonPropertyName("name")]
|
||||
public string Name { get; set; }
|
||||
|
||||
[JsonPropertyName("release_date")]
|
||||
public System.DateTime ReleaseDate { get; set; }
|
||||
}
|
||||
```
|
||||
|
||||
The `JsonPropertyName` attribute ensures your data object is serialized with the correct attribute names for Appwrite databases.
|
||||
|
||||
### Learn more
|
||||
You can use the following resources to learn more and get help:
|
||||
|
||||
- 🚀 [Getting Started Tutorial](https://appwrite.io/docs/getting-started-for-client)
|
||||
- 📜 [Appwrite Docs](https://appwrite.io/docs)
|
||||
- 💬 [Discord Community](https://appwrite.io/discord)
|
||||
- 🧰 [Appwrite SDK Generator](https://github.com/appwrite/sdk-generator)
|
||||
@@ -122,9 +122,9 @@ class Key
|
||||
$secret = $key;
|
||||
}
|
||||
|
||||
$role = User::ROLE_APPS;
|
||||
$role = User::ROLE_KEYS;
|
||||
$roles = Config::getParam('roles', []);
|
||||
$scopes = $roles[User::ROLE_APPS]['scopes'] ?? [];
|
||||
$scopes = $roles[User::ROLE_KEYS]['scopes'] ?? [];
|
||||
$expired = false;
|
||||
|
||||
$guestKey = new Key(
|
||||
@@ -270,7 +270,7 @@ class Key
|
||||
|
||||
$name = $key->getAttribute('name', 'UNKNOWN');
|
||||
|
||||
$role = User::ROLE_APPS;
|
||||
$role = User::ROLE_KEYS;
|
||||
|
||||
$scopes = $key->getAttribute('scopes', []);
|
||||
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Event\Message;
|
||||
|
||||
use Utopia\Database\Document;
|
||||
|
||||
final class Database extends Base
|
||||
{
|
||||
public function __construct(
|
||||
public readonly ?Document $project = null,
|
||||
public readonly ?Document $user = null,
|
||||
public readonly string $type = '',
|
||||
public readonly ?Document $table = null,
|
||||
public readonly ?Document $row = null,
|
||||
public readonly ?Document $collection = null,
|
||||
public readonly ?Document $document = null,
|
||||
public readonly ?Document $database = null,
|
||||
public readonly array $events = [],
|
||||
) {
|
||||
}
|
||||
|
||||
public function toArray(): array
|
||||
{
|
||||
return [
|
||||
'project' => $this->project?->getArrayCopy(),
|
||||
'user' => $this->user?->getArrayCopy(),
|
||||
'type' => $this->type,
|
||||
'table' => $this->table?->getArrayCopy(),
|
||||
'row' => $this->row?->getArrayCopy(),
|
||||
'collection' => $this->collection?->getArrayCopy(),
|
||||
'document' => $this->document?->getArrayCopy(),
|
||||
'database' => $this->database?->getArrayCopy(),
|
||||
'events' => $this->events,
|
||||
];
|
||||
}
|
||||
|
||||
public static function fromArray(array $data): static
|
||||
{
|
||||
return new self(
|
||||
project: !empty($data['project']) ? new Document($data['project']) : null,
|
||||
user: !empty($data['user']) ? new Document($data['user']) : null,
|
||||
type: $data['type'] ?? '',
|
||||
table: !empty($data['table']) ? new Document($data['table']) : null,
|
||||
row: !empty($data['row']) ? new Document($data['row']) : null,
|
||||
collection: !empty($data['collection']) ? new Document($data['collection']) : null,
|
||||
document: !empty($data['document']) ? new Document($data['document']) : null,
|
||||
database: !empty($data['database']) ? new Document($data['database']) : null,
|
||||
events: $data['events'] ?? [],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -6,8 +6,13 @@ use Utopia\Database\Document;
|
||||
|
||||
final class StatsResources extends Base
|
||||
{
|
||||
/**
|
||||
* @param Document $project
|
||||
* @param array<int, array{metric: string, value: int}> $gauges
|
||||
*/
|
||||
public function __construct(
|
||||
public readonly Document $project,
|
||||
public readonly array $gauges = [],
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -15,6 +20,7 @@ final class StatsResources extends Base
|
||||
{
|
||||
return [
|
||||
'project' => $this->project->getArrayCopy(),
|
||||
'gauges' => $this->gauges,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -22,6 +28,7 @@ final class StatsResources extends Base
|
||||
{
|
||||
return new self(
|
||||
project: new Document($data['project'] ?? []),
|
||||
gauges: $data['gauges'] ?? [],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Event\Publisher;
|
||||
|
||||
use Appwrite\Event\Message\Database as DatabaseMessage;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\DSN\DSN;
|
||||
use Utopia\Queue\Publisher;
|
||||
use Utopia\Queue\Queue;
|
||||
|
||||
readonly class Database extends Base
|
||||
{
|
||||
public function __construct(
|
||||
Publisher $publisher,
|
||||
protected Queue $queue,
|
||||
) {
|
||||
parent::__construct($publisher);
|
||||
}
|
||||
|
||||
public function enqueue(DatabaseMessage $message, ?Queue $queue = null): string|bool
|
||||
{
|
||||
return $this->publish($queue ?? $this->getQueueFromProject($message->project), $message);
|
||||
}
|
||||
|
||||
public function getSize(bool $failed = false, ?Queue $queue = null): int
|
||||
{
|
||||
return $this->getQueueSize($queue ?? $this->queue, $failed);
|
||||
}
|
||||
|
||||
private function getQueueFromProject(?Document $project): Queue
|
||||
{
|
||||
$database = $project?->getAttribute('database', '');
|
||||
if (empty($database)) {
|
||||
return $this->queue;
|
||||
}
|
||||
|
||||
try {
|
||||
$dsn = new DSN($database);
|
||||
} catch (\InvalidArgumentException) {
|
||||
$dsn = new DSN('mysql://' . $database);
|
||||
}
|
||||
|
||||
return new Queue($dsn->getHost());
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,18 @@ class StatsResources extends Event
|
||||
{
|
||||
protected bool $critical = false;
|
||||
|
||||
/**
|
||||
* Pre-computed gauge metric snapshots to write to the stats collection. When non-empty,
|
||||
* the StatsResources worker takes the fast path: it writes these directly via
|
||||
* upsertDocuments (replace semantics) and skips the standard counting work.
|
||||
*
|
||||
* Each entry is a tuple of (metric key, value). The worker writes one stats document per
|
||||
* (metric, period) tuple using the project's region.
|
||||
*
|
||||
* @var array<int, array{metric: string, value: int}>
|
||||
*/
|
||||
protected array $gauges = [];
|
||||
|
||||
public function __construct(protected Publisher $publisher)
|
||||
{
|
||||
parent::__construct($publisher);
|
||||
@@ -18,6 +30,35 @@ class StatsResources extends Event
|
||||
->setClass(System::getEnv('_APP_STATS_RESOURCES_CLASS_NAME', Event::STATS_RESOURCES_CLASS_NAME));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the full set of pre-computed gauge metrics for this message. Replaces any
|
||||
* previously-set gauges.
|
||||
*
|
||||
* @param array<int, array{metric: string, value: int}> $gauges
|
||||
*/
|
||||
public function setGauges(array $gauges): self
|
||||
{
|
||||
$this->gauges = $gauges;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Append a single pre-computed gauge metric to this message.
|
||||
*/
|
||||
public function addGauge(string $metric, int $value): self
|
||||
{
|
||||
$this->gauges[] = ['metric' => $metric, 'value' => $value];
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int, array{metric: string, value: int}>
|
||||
*/
|
||||
public function getGauges(): array
|
||||
{
|
||||
return $this->gauges;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the payload for the usage event.
|
||||
*
|
||||
@@ -26,7 +67,8 @@ class StatsResources extends Event
|
||||
protected function preparePayload(): array
|
||||
{
|
||||
return [
|
||||
'project' => $this->project
|
||||
'project' => $this->project,
|
||||
'gauges' => $this->gauges,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -202,6 +202,10 @@ class Exception extends \Exception
|
||||
/** Log */
|
||||
public const string LOG_NOT_FOUND = 'log_not_found';
|
||||
|
||||
/** Presence */
|
||||
public const string PRESENCE_NOT_FOUND = 'presence_not_found';
|
||||
public const string PRESENCE_ALREADY_EXISTS = 'presence_already_exists';
|
||||
|
||||
/** Databases */
|
||||
public const string DATABASE_NOT_FOUND = 'database_not_found';
|
||||
public const string DATABASE_ALREADY_EXISTS = 'database_already_exists';
|
||||
|
||||
@@ -342,9 +342,9 @@ class Resolvers
|
||||
|
||||
$lock->acquire();
|
||||
|
||||
$original = $utopia->getRoute();
|
||||
try {
|
||||
$request = clone $request;
|
||||
$request->addHeader('x-appwrite-source', 'graphql');
|
||||
|
||||
// Drop json content type so post args are used directly.
|
||||
if (\str_starts_with($request->getHeader('content-type'), 'application/json')) {
|
||||
@@ -362,10 +362,9 @@ class Resolvers
|
||||
$resolverResponse->setContentType(Response::CONTENT_TYPE_NULL);
|
||||
$resolverResponse->setSent(false);
|
||||
|
||||
$route = $utopia->match($request, fresh: true);
|
||||
$request->setRoute($route);
|
||||
$request->setRoute($utopia->match($request)?->route);
|
||||
|
||||
$utopia->execute($route, $request, $resolverResponse);
|
||||
$utopia->execute($request, $resolverResponse);
|
||||
|
||||
self::mergeResponseSideEffects($resolverResponse, $response);
|
||||
|
||||
@@ -384,10 +383,6 @@ class Resolvers
|
||||
$reject($e);
|
||||
return;
|
||||
} finally {
|
||||
if ($original !== null) {
|
||||
$utopia->setRoute($original);
|
||||
}
|
||||
|
||||
$lock->release();
|
||||
unset(self::$locks[\spl_object_hash($utopia)]);
|
||||
}
|
||||
|
||||
@@ -34,6 +34,7 @@ class Realtime extends MessagingAdapter
|
||||
'account',
|
||||
'teams',
|
||||
'memberships',
|
||||
'presences'
|
||||
];
|
||||
|
||||
/**
|
||||
@@ -44,6 +45,7 @@ class Realtime extends MessagingAdapter
|
||||
* 'roles' -> [ROLE_x, ROLE_Y]
|
||||
* 'userId' -> [USER_ID]
|
||||
* 'channels' -> [CHANNEL_NAME_X, CHANNEL_NAME_Y, CHANNEL_NAME_Z]
|
||||
* 'presences' -> [PRESENCE_ID_1, PRESENCE_ID_2, ...]
|
||||
*/
|
||||
public array $connections = [];
|
||||
|
||||
@@ -146,6 +148,7 @@ class Realtime extends MessagingAdapter
|
||||
'roles' => \array_values(\array_unique(\array_merge($existingRoles, $roles))),
|
||||
'userId' => $userId ?? ($existing['userId'] ?? ''),
|
||||
'channels' => \array_values(\array_unique(\array_merge($existingChannels, $channels))),
|
||||
'presences' => $this->connections[$identifier]['presences'] ?? []
|
||||
];
|
||||
|
||||
if (\array_key_exists('authorization', $existing)) {
|
||||
@@ -202,6 +205,74 @@ class Realtime extends MessagingAdapter
|
||||
return $subscriptions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Dedup delete presence triggers.
|
||||
* Scenario: when client is connected to realtime and a delete call is made throught rest.
|
||||
* If not dedupe then two delete events will get triggered. So remove the presenceIds
|
||||
*
|
||||
* @param string $projectId
|
||||
* @param string $presenceId
|
||||
* @return int Number of connections whose presences map was updated.
|
||||
*/
|
||||
public function removePresenceFromConnections(string $projectId, string $presenceId): int
|
||||
{
|
||||
if ($projectId === '' || $presenceId === '') {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$removed = 0;
|
||||
foreach ($this->connections as $connectionId => $connection) {
|
||||
if (($connection['projectId'] ?? null) !== $projectId) {
|
||||
continue;
|
||||
}
|
||||
if (!isset($connection['presences'][$presenceId])) {
|
||||
continue;
|
||||
}
|
||||
unset($this->connections[$connectionId]['presences'][$presenceId]);
|
||||
$removed++;
|
||||
}
|
||||
|
||||
return $removed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the presence ID carried by a `presences.{id}.delete` event payload,
|
||||
* or null when the event is not a presence delete.
|
||||
*
|
||||
* @param array $event Decoded pubsub payload produced by self::send().
|
||||
* @return string|null
|
||||
*/
|
||||
public static function extractDeletedPresenceId(array $event): ?string
|
||||
{
|
||||
$events = $event['data']['events'] ?? [];
|
||||
if (!\is_array($events)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$isPresenceDelete = false;
|
||||
foreach ($events as $eventName) {
|
||||
if (
|
||||
\is_string($eventName)
|
||||
&& \str_starts_with($eventName, 'presences.')
|
||||
&& \str_ends_with($eventName, '.delete')
|
||||
) {
|
||||
$isPresenceDelete = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$isPresenceDelete) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$presenceId = $event['data']['payload']['$id'] ?? null;
|
||||
if (!\is_string($presenceId) || $presenceId === '') {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $presenceId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all subscriptions for a connection.
|
||||
*
|
||||
@@ -789,6 +860,11 @@ class Realtime extends MessagingAdapter
|
||||
}
|
||||
$roles = [Role::team($project->getAttribute('teamId'))->toString()];
|
||||
break;
|
||||
case 'presences':
|
||||
$channels[] = 'presences';
|
||||
$channels[] = 'presences.' . $parts[1];
|
||||
$roles = $payload->getRead();
|
||||
break;
|
||||
}
|
||||
|
||||
// Action is the last segment for plain CRUD events (e.g. `documents.X.create`),
|
||||
|
||||
@@ -55,6 +55,9 @@ class V24 extends Migration
|
||||
if ($this->project->getSequence() != 'console') {
|
||||
Console::info('Migrating Databases');
|
||||
$this->migrateDatabases();
|
||||
|
||||
Console::info('Creating presence logs collection');
|
||||
$this->createPresenceLogsCollection();
|
||||
}
|
||||
|
||||
Console::info('Migrating Buckets');
|
||||
@@ -330,6 +333,30 @@ class V24 extends Migration
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure the presenceLogs collection exists for project databases.
|
||||
*
|
||||
* @return void
|
||||
* @throws Throwable
|
||||
*/
|
||||
private function createPresenceLogsCollection(): void
|
||||
{
|
||||
$collectionId = 'presenceLogs';
|
||||
|
||||
try {
|
||||
Console::info("Ensuring collection \"{$collectionId}\" exists for project \"{$this->project->getId()}\".");
|
||||
$this->dbForProject->purgeCachedCollection($collectionId);
|
||||
$this->dbForProject->purgeCachedDocument(Database::METADATA, $collectionId);
|
||||
|
||||
$this->createCollection($collectionId);
|
||||
} catch (Throwable $th) {
|
||||
Console::warning("Failed to create collection \"{$collectionId}\": {$th->getMessage()}");
|
||||
|
||||
// Re-throw so the migration fails fast and doesn't leave the system in a partially migrated state.
|
||||
throw $th;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Migrate all Bucket tables
|
||||
*
|
||||
|
||||
@@ -11,6 +11,8 @@ use Appwrite\Platform\Modules\Databases;
|
||||
use Appwrite\Platform\Modules\Functions;
|
||||
use Appwrite\Platform\Modules\Health;
|
||||
use Appwrite\Platform\Modules\Migrations;
|
||||
use Appwrite\Platform\Modules\Organization;
|
||||
use Appwrite\Platform\Modules\Presences;
|
||||
use Appwrite\Platform\Modules\Project;
|
||||
use Appwrite\Platform\Modules\Projects;
|
||||
use Appwrite\Platform\Modules\Proxy;
|
||||
@@ -31,6 +33,7 @@ class Appwrite extends Platform
|
||||
$this->addModule(new Avatars\Module());
|
||||
$this->addModule(new Databases\Module());
|
||||
$this->addModule(new Projects\Module());
|
||||
$this->addModule(new Presences\Module());
|
||||
$this->addModule(new Functions\Module());
|
||||
$this->addModule(new Health\Module());
|
||||
$this->addModule(new Sites\Module());
|
||||
@@ -42,6 +45,7 @@ class Appwrite extends Platform
|
||||
$this->addModule(new VCS\Module());
|
||||
$this->addModule(new Webhooks\Module());
|
||||
$this->addModule(new Migrations\Module());
|
||||
$this->addModule(new Organization\Module());
|
||||
$this->addModule(new Project\Module());
|
||||
$this->addModule(new Advisor\Module());
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ use Appwrite\Auth\MFA\Type;
|
||||
use Appwrite\Auth\MFA\Type\TOTP;
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Platform\Action;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\ContentType;
|
||||
use Appwrite\SDK\Deprecated;
|
||||
@@ -17,7 +18,7 @@ use Utopia\Database\Document;
|
||||
use Utopia\Database\Helpers\ID;
|
||||
use Utopia\Database\Helpers\Permission;
|
||||
use Utopia\Database\Helpers\Role;
|
||||
use Utopia\Platform\Action;
|
||||
use Utopia\Platform\Enum;
|
||||
use Utopia\Platform\Scope\HTTP;
|
||||
use Utopia\Validator\WhiteList;
|
||||
|
||||
@@ -77,7 +78,7 @@ class Create extends Action
|
||||
contentType: ContentType::JSON
|
||||
)
|
||||
])
|
||||
->param('type', null, new WhiteList([Type::TOTP]), 'Type of authenticator. Must be `' . Type::TOTP . '`')
|
||||
->param('type', null, new WhiteList([Type::TOTP]), 'Type of authenticator. Must be `' . Type::TOTP . '`', enum: new Enum(name: 'AuthenticatorType'))
|
||||
->inject('response')
|
||||
->inject('project')
|
||||
->inject('user')
|
||||
|
||||
@@ -6,6 +6,7 @@ use Appwrite\Auth\MFA\Type;
|
||||
use Appwrite\Auth\MFA\Type\TOTP;
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Platform\Action;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\ContentType;
|
||||
use Appwrite\SDK\Deprecated;
|
||||
@@ -14,7 +15,7 @@ use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Platform\Action;
|
||||
use Utopia\Platform\Enum;
|
||||
use Utopia\Platform\Scope\HTTP;
|
||||
use Utopia\Validator\WhiteList;
|
||||
|
||||
@@ -74,7 +75,7 @@ class Delete extends Action
|
||||
contentType: ContentType::NONE
|
||||
)
|
||||
])
|
||||
->param('type', null, new WhiteList([Type::TOTP]), 'Type of authenticator.')
|
||||
->param('type', null, new WhiteList([Type::TOTP]), 'Type of authenticator.', enum: new Enum(name: 'AuthenticatorType'))
|
||||
->inject('response')
|
||||
->inject('user')
|
||||
->inject('dbForProject')
|
||||
|
||||
@@ -7,6 +7,7 @@ use Appwrite\Auth\MFA\Type;
|
||||
use Appwrite\Auth\MFA\Type\TOTP;
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Platform\Action;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\ContentType;
|
||||
use Appwrite\SDK\Deprecated;
|
||||
@@ -15,7 +16,7 @@ use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Platform\Action;
|
||||
use Utopia\Platform\Enum;
|
||||
use Utopia\Platform\Scope\HTTP;
|
||||
use Utopia\Validator\Text;
|
||||
use Utopia\Validator\WhiteList;
|
||||
@@ -76,7 +77,7 @@ class Update extends Action
|
||||
contentType: ContentType::JSON
|
||||
)
|
||||
])
|
||||
->param('type', null, new WhiteList([Type::TOTP]), 'Type of authenticator.')
|
||||
->param('type', null, new WhiteList([Type::TOTP]), 'Type of authenticator.', enum: new Enum(name: 'AuthenticatorType'))
|
||||
->param('otp', '', new Text(256), 'Valid verification token.')
|
||||
->inject('response')
|
||||
->inject('user')
|
||||
|
||||
@@ -10,6 +10,7 @@ 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;
|
||||
use Appwrite\SDK\ContentType;
|
||||
use Appwrite\SDK\Deprecated;
|
||||
@@ -29,7 +30,7 @@ use Utopia\Database\Document;
|
||||
use Utopia\Database\Helpers\Permission;
|
||||
use Utopia\Database\Helpers\Role;
|
||||
use Utopia\Locale\Locale;
|
||||
use Utopia\Platform\Action;
|
||||
use Utopia\Platform\Enum;
|
||||
use Utopia\Platform\Scope\HTTP;
|
||||
use Utopia\Storage\Validator\FileName;
|
||||
use Utopia\System\System;
|
||||
@@ -94,7 +95,7 @@ class Create extends Action
|
||||
])
|
||||
->label('abuse-limit', 10)
|
||||
->label('abuse-key', 'url:{url},userId:{userId}')
|
||||
->param('factor', '', new WhiteList([Type::EMAIL, Type::PHONE, Type::TOTP, Type::RECOVERY_CODE]), 'Factor used for verification. Must be one of following: `' . Type::EMAIL . '`, `' . Type::PHONE . '`, `' . Type::TOTP . '`, `' . Type::RECOVERY_CODE . '`.')
|
||||
->param('factor', '', new WhiteList([Type::EMAIL, Type::PHONE, Type::TOTP, Type::RECOVERY_CODE]), 'Factor used for verification. Must be one of following: `' . Type::EMAIL . '`, `' . Type::PHONE . '`, `' . Type::TOTP . '`, `' . Type::RECOVERY_CODE . '`.', enum: new Enum(name: 'AuthenticationFactor'))
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('user')
|
||||
|
||||
@@ -36,7 +36,7 @@ class Get extends Action
|
||||
group: 'insights',
|
||||
name: 'getInsight',
|
||||
description: '/docs/references/advisor/get-insight.md',
|
||||
auth: [AuthType::ADMIN, AuthType::SESSION, AuthType::KEY, AuthType::JWT],
|
||||
auth: [AuthType::ADMIN, AuthType::KEY],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
|
||||
@@ -42,7 +42,7 @@ class XList extends Action
|
||||
group: 'insights',
|
||||
name: 'listInsights',
|
||||
description: '/docs/references/advisor/list-insights.md',
|
||||
auth: [AuthType::ADMIN, AuthType::SESSION, AuthType::KEY, AuthType::JWT],
|
||||
auth: [AuthType::ADMIN, AuthType::KEY],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
|
||||
@@ -37,7 +37,7 @@ class Get extends Action
|
||||
group: 'reports',
|
||||
name: 'getReport',
|
||||
description: '/docs/references/advisor/get-report.md',
|
||||
auth: [AuthType::ADMIN, AuthType::SESSION, AuthType::KEY, AuthType::JWT],
|
||||
auth: [AuthType::ADMIN, AuthType::KEY],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
|
||||
@@ -41,7 +41,7 @@ class XList extends Action
|
||||
group: 'reports',
|
||||
name: 'listReports',
|
||||
description: '/docs/references/advisor/list-reports.md',
|
||||
auth: [AuthType::ADMIN, AuthType::SESSION, AuthType::KEY, AuthType::JWT],
|
||||
auth: [AuthType::ADMIN, AuthType::KEY],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
|
||||
@@ -11,6 +11,7 @@ use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\Platform\Action as UtopiaAction;
|
||||
use Utopia\Platform\Enum;
|
||||
use Utopia\Platform\Scope\HTTP;
|
||||
use Utopia\Validator\Range;
|
||||
use Utopia\Validator\WhiteList;
|
||||
@@ -49,7 +50,10 @@ class Get extends Action
|
||||
],
|
||||
contentType: ContentType::IMAGE_PNG
|
||||
))
|
||||
->param('code', '', new WhiteList(\array_keys(Config::getParam('avatar-browsers'))), 'Browser Code.')
|
||||
->param('code', '', new WhiteList(\array_keys(Config::getParam('avatar-browsers'))), 'Browser Code.', enum: new Enum(
|
||||
name: 'Browser',
|
||||
map: \array_map(fn (array $browser) => $browser['name'], Config::getParam('avatar-browsers')),
|
||||
))
|
||||
->param('width', 100, new Range(0, 2000), 'Image width. Pass an integer between 0 to 2000. Defaults to 100.', true)
|
||||
->param('height', 100, new Range(0, 2000), 'Image height. Pass an integer between 0 to 2000. Defaults to 100.', true)
|
||||
->param('quality', -1, new Range(-1, 100), 'Image quality. Pass an integer between 0 to 100. Defaults to keep existing image quality.', true)
|
||||
|
||||
@@ -11,6 +11,7 @@ use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\Platform\Action as UtopiaAction;
|
||||
use Utopia\Platform\Enum;
|
||||
use Utopia\Platform\Scope\HTTP;
|
||||
use Utopia\Validator\Range;
|
||||
use Utopia\Validator\WhiteList;
|
||||
@@ -49,7 +50,10 @@ class Get extends Action
|
||||
],
|
||||
contentType: ContentType::IMAGE_PNG
|
||||
))
|
||||
->param('code', '', new WhiteList(\array_keys(Config::getParam('avatar-credit-cards'))), 'Credit Card Code. Possible values: ' . \implode(', ', \array_keys(Config::getParam('avatar-credit-cards'))) . '.')
|
||||
->param('code', '', new WhiteList(\array_keys(Config::getParam('avatar-credit-cards'))), 'Credit Card Code. Possible values: ' . \implode(', ', \array_keys(Config::getParam('avatar-credit-cards'))) . '.', enum: new Enum(
|
||||
name: 'CreditCard',
|
||||
map: \array_map(fn (array $creditCard) => $creditCard['name'], Config::getParam('avatar-credit-cards')),
|
||||
))
|
||||
->param('width', 100, new Range(0, 2000), 'Image width. Pass an integer between 0 to 2000. Defaults to 100.', true)
|
||||
->param('height', 100, new Range(0, 2000), 'Image height. Pass an integer between 0 to 2000. Defaults to 100.', true)
|
||||
->param('quality', -1, new Range(-1, 100), 'Image quality. Pass an integer between 0 to 100. Defaults to keep existing image quality.', true)
|
||||
|
||||
@@ -11,6 +11,7 @@ use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\Platform\Action as UtopiaAction;
|
||||
use Utopia\Platform\Enum;
|
||||
use Utopia\Platform\Scope\HTTP;
|
||||
use Utopia\Validator\Range;
|
||||
use Utopia\Validator\WhiteList;
|
||||
@@ -49,7 +50,10 @@ class Get extends Action
|
||||
],
|
||||
contentType: ContentType::IMAGE_PNG
|
||||
))
|
||||
->param('code', '', new WhiteList(\array_keys(Config::getParam('avatar-flags'))), 'Country Code. ISO Alpha-2 country code format.')
|
||||
->param('code', '', new WhiteList(\array_keys(Config::getParam('avatar-flags'))), 'Country Code. ISO Alpha-2 country code format.', enum: new Enum(
|
||||
name: 'Flag',
|
||||
map: \array_map(fn (array $flag) => $flag['name'], Config::getParam('avatar-flags')),
|
||||
))
|
||||
->param('width', 100, new Range(0, 2000), 'Image width. Pass an integer between 0 to 2000. Defaults to 100.', true)
|
||||
->param('height', 100, new Range(0, 2000), 'Image height. Pass an integer between 0 to 2000. Defaults to 100.', true)
|
||||
->param('quality', -1, new Range(-1, 100), 'Image quality. Pass an integer between 0 to 100. Defaults to keep existing image quality.', true)
|
||||
|
||||
@@ -16,6 +16,7 @@ use Utopia\Domains\Domain;
|
||||
use Utopia\Fetch\Client;
|
||||
use Utopia\Image\Image;
|
||||
use Utopia\Platform\Action as UtopiaAction;
|
||||
use Utopia\Platform\Enum;
|
||||
use Utopia\Platform\Scope\HTTP;
|
||||
use Utopia\System\System;
|
||||
use Utopia\Validator\ArrayList;
|
||||
@@ -68,21 +69,21 @@ class Get extends Action
|
||||
->param('viewportWidth', 1280, new Range(1, 1920), 'Browser viewport width. Pass an integer between 1 to 1920. Defaults to 1280.', true, example: '1920')
|
||||
->param('viewportHeight', 720, new Range(1, 1080), 'Browser viewport height. Pass an integer between 1 to 1080. Defaults to 720.', true, example: '1080')
|
||||
->param('scale', 1, new Range(0.1, 3, Range::TYPE_FLOAT), 'Browser scale factor. Pass a number between 0.1 to 3. Defaults to 1.', true, example: '2')
|
||||
->param('theme', 'light', new WhiteList(['light', 'dark']), 'Browser theme. Pass "light" or "dark". Defaults to "light".', true, example: 'dark')
|
||||
->param('theme', 'light', new WhiteList(['light', 'dark']), 'Browser theme. Pass "light" or "dark". Defaults to "light".', true, example: 'dark', enum: new Enum(name: 'BrowserTheme'))
|
||||
->param('userAgent', '', new Text(512), 'Custom user agent string. Defaults to browser default.', true, example: 'Mozilla/5.0 (iPhone; CPU iPhone OS 14_0 like Mac OS X) AppleWebKit/605.1.15')
|
||||
->param('fullpage', false, new Boolean(true), 'Capture full page scroll. Pass 0 for viewport only, or 1 for full page. Defaults to 0.', true, example: 'true')
|
||||
->param('locale', '', new Text(10), 'Browser locale (e.g., "en-US", "fr-FR"). Defaults to browser default.', true, example: 'en-US')
|
||||
->param('timezone', '', new WhiteList(timezone_identifiers_list()), 'IANA timezone identifier (e.g., "America/New_York", "Europe/London"). Defaults to browser default.', true, example: 'america/new_york')
|
||||
->param('timezone', '', new WhiteList(timezone_identifiers_list()), 'IANA timezone identifier (e.g., "America/New_York", "Europe/London"). Defaults to browser default.', true, example: 'America/New_York', enum: new Enum(name: 'Timezone'))
|
||||
->param('latitude', 0, new Range(-90, 90, Range::TYPE_FLOAT), 'Geolocation latitude. Pass a number between -90 to 90. Defaults to 0.', true, example: '37.7749')
|
||||
->param('longitude', 0, new Range(-180, 180, Range::TYPE_FLOAT), 'Geolocation longitude. Pass a number between -180 to 180. Defaults to 0.', true, example: '-122.4194')
|
||||
->param('accuracy', 0, new Range(0, 100000, Range::TYPE_FLOAT), 'Geolocation accuracy in meters. Pass a number between 0 to 100000. Defaults to 0.', true, example: '100')
|
||||
->param('touch', false, new Boolean(true), 'Enable touch support. Pass 0 for no touch, or 1 for touch enabled. Defaults to 0.', true, example: 'true')
|
||||
->param('permissions', [], new ArrayList(new WhiteList(['geolocation', 'camera', 'microphone', 'notifications', 'midi', 'push', 'clipboard-read', 'clipboard-write', 'payment-handler', 'usb', 'bluetooth', 'accelerometer', 'gyroscope', 'magnetometer', 'ambient-light-sensor', 'background-sync', 'persistent-storage', 'screen-wake-lock', 'web-share', 'xr-spatial-tracking'])), 'Browser permissions to grant. Pass an array of permission names like ["geolocation", "camera", "microphone"]. Defaults to empty.', true, example: '["geolocation","notifications"]')
|
||||
->param('permissions', [], new ArrayList(new WhiteList(['geolocation', 'camera', 'microphone', 'notifications', 'midi', 'push', 'clipboard-read', 'clipboard-write', 'payment-handler', 'usb', 'bluetooth', 'accelerometer', 'gyroscope', 'magnetometer', 'ambient-light-sensor', 'background-sync', 'persistent-storage', 'screen-wake-lock', 'web-share', 'xr-spatial-tracking'])), 'Browser permissions to grant. Pass an array of permission names like ["geolocation", "camera", "microphone"]. Defaults to empty.', true, example: '["geolocation","notifications"]', enum: new Enum(name: 'BrowserPermission'))
|
||||
->param('sleep', 0, new Range(0, 10), 'Wait time in seconds before taking the screenshot. Pass an integer between 0 to 10. Defaults to 0.', true, example: '3')
|
||||
->param('width', 0, new Range(0, 2000), 'Output image width. Pass 0 to use original width, or an integer between 1 to 2000. Defaults to 0 (original width).', true, example: '800')
|
||||
->param('height', 0, new Range(0, 2000), 'Output image height. Pass 0 to use original height, or an integer between 1 to 2000. Defaults to 0 (original height).', true, example: '600')
|
||||
->param('quality', -1, new Range(-1, 100), 'Screenshot quality. Pass an integer between 0 to 100. Defaults to keep existing image quality.', true, example: '85')
|
||||
->param('output', '', new WhiteList(\array_keys(Config::getParam('storage-outputs')), true), 'Output format type (jpeg, jpg, png, gif and webp).', true, example: 'jpeg')
|
||||
->param('output', '', new WhiteList(\array_keys(Config::getParam('storage-outputs')), true), 'Output format type (jpeg, jpg, png, gif and webp).', true, example: 'jpeg', enum: new Enum(name: 'ImageFormat'))
|
||||
->inject('response')
|
||||
->inject('usage')
|
||||
->callback($this->action(...));
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace Appwrite\Platform\Modules\Console\Http\Resources;
|
||||
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Platform\Action;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\ContentType;
|
||||
use Appwrite\SDK\Method;
|
||||
@@ -12,7 +13,7 @@ use Utopia\Database\Database;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Domains\Domain as Domain;
|
||||
use Utopia\Platform\Action;
|
||||
use Utopia\Platform\Enum;
|
||||
use Utopia\Platform\Scope\HTTP;
|
||||
use Utopia\System\System;
|
||||
use Utopia\Validator\Domain as DomainValidator;
|
||||
@@ -56,7 +57,7 @@ class Get extends Action
|
||||
->label('abuse-key', 'userId:{userId}, url:{url}')
|
||||
->label('abuse-time', 60)
|
||||
->param('value', '', new Text(256), 'Resource value.')
|
||||
->param('type', '', new WhiteList(['rules']), 'Resource type.')
|
||||
->param('type', '', new WhiteList(['rules']), 'Resource type.', enum: new Enum(name: 'ConsoleResourceType'))
|
||||
->inject('response')
|
||||
->inject('dbForPlatform')
|
||||
->inject('platform')
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Modules\Console\Http\Scopes\Organization;
|
||||
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\ContentType;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Platform\Action;
|
||||
use Utopia\Platform\Scope\HTTP;
|
||||
|
||||
class XList extends Action
|
||||
{
|
||||
use HTTP;
|
||||
|
||||
public static function getName(): string
|
||||
{
|
||||
return 'listConsoleOrganizationScopes';
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->setHttpMethod(Action::HTTP_REQUEST_METHOD_GET)
|
||||
->setHttpPath('/v1/console/scopes/organization')
|
||||
->desc('List organization scopes')
|
||||
->groups(['api'])
|
||||
->label('scope', 'public')
|
||||
->label('sdk', new Method(
|
||||
namespace: 'console',
|
||||
group: 'console',
|
||||
name: 'listOrganizationScopes',
|
||||
description: 'List all scopes available for organization API keys, along with a description for each scope.',
|
||||
auth: [AuthType::ADMIN],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
model: Response::MODEL_CONSOLE_KEY_SCOPE_LIST,
|
||||
)
|
||||
],
|
||||
contentType: ContentType::JSON
|
||||
))
|
||||
->inject('response')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(Response $response): void
|
||||
{
|
||||
$scopesConfig = Config::getParam('organizationScopes', []);
|
||||
|
||||
$scopes = [];
|
||||
foreach ($scopesConfig as $scopeId => $scope) {
|
||||
$scopes[] = new Document([
|
||||
'$id' => $scopeId,
|
||||
'description' => $scope['description'] ?? '',
|
||||
'category' => $scope['category'] ?? '',
|
||||
'deprecated' => $scope['deprecated'] ?? false,
|
||||
]);
|
||||
}
|
||||
|
||||
$response->dynamic(new Document([
|
||||
'total' => \count($scopes),
|
||||
'scopes' => $scopes,
|
||||
]), Response::MODEL_CONSOLE_KEY_SCOPE_LIST);
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Modules\Console\Http\Scopes\Key;
|
||||
namespace Appwrite\Platform\Modules\Console\Http\Scopes\Project;
|
||||
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\ContentType;
|
||||
@@ -0,0 +1,124 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Modules\Console\Http\Templates\Email;
|
||||
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Template\Template;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Locale\Locale;
|
||||
use Utopia\Platform\Action;
|
||||
use Utopia\Platform\Enum;
|
||||
use Utopia\Platform\Scope\HTTP;
|
||||
use Utopia\System\System;
|
||||
use Utopia\Validator\WhiteList;
|
||||
|
||||
class Get extends Action
|
||||
{
|
||||
use HTTP;
|
||||
|
||||
public static function getName(): string
|
||||
{
|
||||
return 'getConsoleEmailTemplate';
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->setHttpMethod(Action::HTTP_REQUEST_METHOD_GET)
|
||||
->setHttpPath('/v1/console/templates/email/:templateId')
|
||||
->desc('Get email template')
|
||||
->groups(['api'])
|
||||
->label('scope', 'public')
|
||||
->label('sdk', new Method(
|
||||
namespace: 'console',
|
||||
group: null,
|
||||
name: 'getEmailTemplate',
|
||||
description: <<<EOT
|
||||
Get the Appwrite built-in default email template for the specified type and locale. Always returns the unmodified default, ignoring any custom project overrides.
|
||||
EOT,
|
||||
auth: [AuthType::ADMIN],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
model: Response::MODEL_EMAIL_TEMPLATE,
|
||||
)
|
||||
]
|
||||
))
|
||||
->param('templateId', '', new WhiteList(Config::getParam('locale-templates')['email'] ?? [], true), 'Email template type. Can be one of: ' . \implode(', ', Config::getParam('locale-templates')['email'] ?? []), enum: new Enum(name: 'ProjectEmailTemplateId'))
|
||||
->param('locale', '', fn ($localeCodes) => new WhiteList($localeCodes), 'Template locale. If left empty, the fallback locale (en) will be used.', optional: true, injections: ['localeCodes'], enum: new Enum(name: 'ProjectEmailTemplateLocale'))
|
||||
->inject('response')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(
|
||||
string $templateId,
|
||||
string $locale,
|
||||
Response $response,
|
||||
): void {
|
||||
$locale = $locale ?: System::getEnv('_APP_LOCALE', 'en');
|
||||
|
||||
$localeObj = new Locale($locale);
|
||||
$localeObj->setFallback(System::getEnv('_APP_LOCALE', 'en'));
|
||||
|
||||
$response->dynamic(new Document([
|
||||
'templateId' => $templateId,
|
||||
'locale' => $locale,
|
||||
'subject' => $localeObj->getText('emails.' . $templateId . '.subject'),
|
||||
'message' => $this->getDefaultMessage($templateId, $localeObj),
|
||||
'senderName' => '',
|
||||
'senderEmail' => '',
|
||||
'replyToEmail' => '',
|
||||
'replyToName' => '',
|
||||
]), Response::MODEL_EMAIL_TEMPLATE);
|
||||
}
|
||||
|
||||
private function getDefaultMessage(string $templateId, Locale $localeObj): string
|
||||
{
|
||||
$templateConfigs = [
|
||||
'magicSession' => [
|
||||
'file' => 'email-magic-url.tpl',
|
||||
'placeholders' => ['optionButton', 'buttonText', 'optionUrl', 'clientInfo', 'securityPhrase']
|
||||
],
|
||||
'mfaChallenge' => [
|
||||
'file' => 'email-mfa-challenge.tpl',
|
||||
'placeholders' => ['description', 'clientInfo']
|
||||
],
|
||||
'otpSession' => [
|
||||
'file' => 'email-otp.tpl',
|
||||
'placeholders' => ['description', 'clientInfo', 'securityPhrase']
|
||||
],
|
||||
'sessionAlert' => [
|
||||
'file' => 'email-session-alert.tpl',
|
||||
'placeholders' => ['body', 'listDevice', 'listIpAddress', 'listCountry', 'footer']
|
||||
],
|
||||
];
|
||||
|
||||
$config = $templateConfigs[$templateId] ?? [
|
||||
'file' => 'email-inner-base.tpl',
|
||||
'placeholders' => ['buttonText', 'body', 'footer']
|
||||
];
|
||||
|
||||
$templateString = file_get_contents(APP_CE_CONFIG_DIR . '/locale/templates/' . $config['file']);
|
||||
$message = Template::fromString($templateString);
|
||||
|
||||
foreach ($config['placeholders'] as $param) {
|
||||
$escapeHtml = !in_array($param, ['clientInfo', 'body', 'footer', 'description']);
|
||||
if ($templateId === 'magicSession' && $param === 'securityPhrase') {
|
||||
$message->setParam('{{securityPhrase}}', '');
|
||||
continue;
|
||||
}
|
||||
|
||||
$message->setParam("{{{$param}}}", $localeObj->getText("emails.{$templateId}.{$param}"), escapeHtml: $escapeHtml);
|
||||
}
|
||||
|
||||
$message
|
||||
->setParam('{{hello}}', $localeObj->getText("emails.{$templateId}.hello"))
|
||||
->setParam('{{thanks}}', $localeObj->getText("emails.{$templateId}.thanks"))
|
||||
->setParam('{{signature}}', $localeObj->getText("emails.{$templateId}.signature"));
|
||||
|
||||
return $message->render(useContent: true);
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,9 @@ use Appwrite\Platform\Modules\Console\Http\Redirects\Recover\Get as RedirectReco
|
||||
use Appwrite\Platform\Modules\Console\Http\Redirects\Register\Get as RedirectRegister;
|
||||
use Appwrite\Platform\Modules\Console\Http\Redirects\Root\Get as RedirectRoot;
|
||||
use Appwrite\Platform\Modules\Console\Http\Resources\Get as GetResourceAvailability;
|
||||
use Appwrite\Platform\Modules\Console\Http\Scopes\Key\XList as ListKeyScopes;
|
||||
use Appwrite\Platform\Modules\Console\Http\Scopes\Organization\XList as ListOrganizationScopes;
|
||||
use Appwrite\Platform\Modules\Console\Http\Scopes\Project\XList as ListKeyScopes;
|
||||
use Appwrite\Platform\Modules\Console\Http\Templates\Email\Get as GetEmailTemplate;
|
||||
use Appwrite\Platform\Modules\Console\Http\Variables\Get as GetVariables;
|
||||
use Utopia\Platform\Service;
|
||||
|
||||
@@ -30,8 +32,10 @@ class Http extends Service
|
||||
$this->addAction(Web::getName(), new Web());
|
||||
|
||||
$this->addAction(GetVariables::getName(), new GetVariables());
|
||||
$this->addAction(GetEmailTemplate::getName(), new GetEmailTemplate());
|
||||
$this->addAction(ListOAuth2Providers::getName(), new ListOAuth2Providers());
|
||||
$this->addAction(ListKeyScopes::getName(), new ListKeyScopes());
|
||||
$this->addAction(ListOrganizationScopes::getName(), new ListOrganizationScopes());
|
||||
$this->addAction(CreateAssistantQuery::getName(), new CreateAssistantQuery());
|
||||
$this->addAction(GetResourceAvailability::getName(), new GetResourceAvailability());
|
||||
|
||||
|
||||
+15
-16
@@ -2,8 +2,9 @@
|
||||
|
||||
namespace Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Attributes;
|
||||
|
||||
use Appwrite\Event\Database as EventDatabase;
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Event\Message\Database as DatabaseMessage;
|
||||
use Appwrite\Event\Publisher\Database as DatabasePublisher;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Appwrite\Utopia\Response as UtopiaResponse;
|
||||
@@ -312,7 +313,7 @@ abstract class Action extends UtopiaAction
|
||||
};
|
||||
}
|
||||
|
||||
protected function createAttribute(string $databaseId, string $collectionId, Document $attribute, Response $response, Database $dbForProject, EventDatabase $queueForDatabase, Event $queueForEvents, Authorization $authorization): Document
|
||||
protected function createAttribute(string $databaseId, string $collectionId, Document $attribute, Response $response, Database $dbForProject, DatabasePublisher $publisherForDatabase, Event $queueForEvents, Authorization $authorization): Document
|
||||
{
|
||||
$key = $attribute->getAttribute('key');
|
||||
$type = $attribute->getAttribute('type', '');
|
||||
@@ -464,20 +465,6 @@ abstract class Action extends UtopiaAction
|
||||
$dbForProject->purgeCachedCollection('database_' . $db->getSequence() . '_collection_' . $relatedCollection->getSequence());
|
||||
}
|
||||
|
||||
$queueForDatabase
|
||||
->setType(DATABASE_TYPE_CREATE_ATTRIBUTE)
|
||||
->setDatabase($db);
|
||||
|
||||
if ($this->isCollectionsAPI()) {
|
||||
$queueForDatabase
|
||||
->setDocument($attribute)
|
||||
->setCollection($collection);
|
||||
} else {
|
||||
$queueForDatabase
|
||||
->setRow($attribute)
|
||||
->setTable($collection);
|
||||
}
|
||||
|
||||
$queueForEvents
|
||||
->setContext('database', $db)
|
||||
->setParam('databaseId', $databaseId)
|
||||
@@ -487,6 +474,18 @@ abstract class Action extends UtopiaAction
|
||||
->setParam('columnId', $attribute->getId())
|
||||
->setContext($this->getCollectionsEventsContext(), $collection);
|
||||
|
||||
$publisherForDatabase->enqueue(new DatabaseMessage(
|
||||
project: $queueForEvents->getProject(),
|
||||
user: $queueForEvents->getUser(),
|
||||
type: DATABASE_TYPE_CREATE_ATTRIBUTE,
|
||||
database: $db,
|
||||
collection: $this->isCollectionsAPI() ? $collection : null,
|
||||
document: $this->isCollectionsAPI() ? $attribute : null,
|
||||
table: $this->isCollectionsAPI() ? null : $collection,
|
||||
row: $this->isCollectionsAPI() ? null : $attribute,
|
||||
events: Event::generateEvents($queueForEvents->getEvent(), $queueForEvents->getParams()),
|
||||
));
|
||||
|
||||
$response->setStatusCode(SwooleResponse::STATUS_CODE_CREATED);
|
||||
|
||||
return $attribute;
|
||||
|
||||
+4
-4
@@ -2,8 +2,8 @@
|
||||
|
||||
namespace Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Attributes\BigInt;
|
||||
|
||||
use Appwrite\Event\Database as EventDatabase;
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Event\Publisher\Database as DatabasePublisher;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Attributes\Action;
|
||||
use Appwrite\SDK\AuthType;
|
||||
@@ -73,13 +73,13 @@ class Create extends Action
|
||||
->param('array', false, new Boolean(), 'Is attribute an array?', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('queueForDatabase')
|
||||
->inject('publisherForDatabase')
|
||||
->inject('queueForEvents')
|
||||
->inject('authorization')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(string $databaseId, string $collectionId, string $key, ?bool $required, ?int $min, ?int $max, ?int $default, bool $array, UtopiaResponse $response, Database $dbForProject, EventDatabase $queueForDatabase, Event $queueForEvents, Authorization $authorization): void
|
||||
public function action(string $databaseId, string $collectionId, string $key, ?bool $required, ?int $min, ?int $max, ?int $default, bool $array, UtopiaResponse $response, Database $dbForProject, DatabasePublisher $publisherForDatabase, Event $queueForEvents, Authorization $authorization): void
|
||||
{
|
||||
$min ??= \PHP_INT_MIN;
|
||||
$max ??= \PHP_INT_MAX;
|
||||
@@ -102,7 +102,7 @@ class Create extends Action
|
||||
'array' => $array,
|
||||
'format' => APP_DATABASE_ATTRIBUTE_BIGINT_RANGE,
|
||||
'formatOptions' => ['min' => $min, 'max' => $max],
|
||||
]), $response, $dbForProject, $queueForDatabase, $queueForEvents, $authorization);
|
||||
]), $response, $dbForProject, $publisherForDatabase, $queueForEvents, $authorization);
|
||||
|
||||
$formatOptions = $attribute->getAttribute('formatOptions', []);
|
||||
if (!empty($formatOptions)) {
|
||||
|
||||
+4
-4
@@ -2,8 +2,8 @@
|
||||
|
||||
namespace Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Attributes\Boolean;
|
||||
|
||||
use Appwrite\Event\Database as EventDatabase;
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Event\Publisher\Database as DatabasePublisher;
|
||||
use Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Attributes\Action;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\Deprecated;
|
||||
@@ -68,13 +68,13 @@ class Create extends Action
|
||||
->param('array', false, new Boolean(), 'Is attribute an array?', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('queueForDatabase')
|
||||
->inject('publisherForDatabase')
|
||||
->inject('queueForEvents')
|
||||
->inject('authorization')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(string $databaseId, string $collectionId, string $key, ?bool $required, ?bool $default, bool $array, UtopiaResponse $response, Database $dbForProject, EventDatabase $queueForDatabase, Event $queueForEvents, Authorization $authorization): void
|
||||
public function action(string $databaseId, string $collectionId, string $key, ?bool $required, ?bool $default, bool $array, UtopiaResponse $response, Database $dbForProject, DatabasePublisher $publisherForDatabase, Event $queueForEvents, Authorization $authorization): void
|
||||
{
|
||||
$attribute = $this->createAttribute($databaseId, $collectionId, new Document([
|
||||
'key' => $key,
|
||||
@@ -83,7 +83,7 @@ class Create extends Action
|
||||
'required' => $required,
|
||||
'default' => $default,
|
||||
'array' => $array,
|
||||
]), $response, $dbForProject, $queueForDatabase, $queueForEvents, $authorization);
|
||||
]), $response, $dbForProject, $publisherForDatabase, $queueForEvents, $authorization);
|
||||
|
||||
$response
|
||||
->setStatusCode(SwooleResponse::STATUS_CODE_ACCEPTED)
|
||||
|
||||
+4
-4
@@ -2,8 +2,8 @@
|
||||
|
||||
namespace Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Attributes\Datetime;
|
||||
|
||||
use Appwrite\Event\Database as EventDatabase;
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Event\Publisher\Database as DatabasePublisher;
|
||||
use Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Attributes\Action;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\Deprecated;
|
||||
@@ -69,13 +69,13 @@ class Create extends Action
|
||||
->param('array', false, new Boolean(), 'Is attribute an array?', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('queueForDatabase')
|
||||
->inject('publisherForDatabase')
|
||||
->inject('queueForEvents')
|
||||
->inject('authorization')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(string $databaseId, string $collectionId, string $key, ?bool $required, ?string $default, bool $array, UtopiaResponse $response, Database $dbForProject, EventDatabase $queueForDatabase, Event $queueForEvents, Authorization $authorization): void
|
||||
public function action(string $databaseId, string $collectionId, string $key, ?bool $required, ?string $default, bool $array, UtopiaResponse $response, Database $dbForProject, DatabasePublisher $publisherForDatabase, Event $queueForEvents, Authorization $authorization): void
|
||||
{
|
||||
$attribute = $this->createAttribute(
|
||||
$databaseId,
|
||||
@@ -91,7 +91,7 @@ class Create extends Action
|
||||
]),
|
||||
$response,
|
||||
$dbForProject,
|
||||
$queueForDatabase,
|
||||
$publisherForDatabase,
|
||||
$queueForEvents,
|
||||
$authorization
|
||||
);
|
||||
|
||||
+16
-17
@@ -2,8 +2,9 @@
|
||||
|
||||
namespace Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Attributes;
|
||||
|
||||
use Appwrite\Event\Database as EventDatabase;
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Event\Message\Database as DatabaseMessage;
|
||||
use Appwrite\Event\Publisher\Database as DatabasePublisher;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\ContentType;
|
||||
@@ -66,13 +67,13 @@ class Delete extends Action
|
||||
->param('key', '', fn (Database $dbForProject) => new Key(false, $dbForProject->getAdapter()->getMaxUIDLength()), 'Attribute Key.', false, ['dbForProject'])
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('queueForDatabase')
|
||||
->inject('publisherForDatabase')
|
||||
->inject('queueForEvents')
|
||||
->inject('authorization')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(string $databaseId, string $collectionId, string $key, UtopiaResponse $response, Database $dbForProject, EventDatabase $queueForDatabase, Event $queueForEvents, Authorization $authorization): void
|
||||
public function action(string $databaseId, string $collectionId, string $key, UtopiaResponse $response, Database $dbForProject, DatabasePublisher $publisherForDatabase, Event $queueForEvents, Authorization $authorization): void
|
||||
{
|
||||
$db = $authorization->skip(fn () => $dbForProject->getDocument('databases', $databaseId));
|
||||
if ($db->isEmpty()) {
|
||||
@@ -129,20 +130,6 @@ class Delete extends Action
|
||||
}
|
||||
}
|
||||
|
||||
$queueForDatabase
|
||||
->setDatabase($db)
|
||||
->setType(DATABASE_TYPE_DELETE_ATTRIBUTE);
|
||||
|
||||
if ($this->isCollectionsAPI()) {
|
||||
$queueForDatabase
|
||||
->setRow($attribute)
|
||||
->setTable($collection);
|
||||
} else {
|
||||
$queueForDatabase
|
||||
->setDocument($attribute)
|
||||
->setCollection($collection);
|
||||
}
|
||||
|
||||
$type = $attribute->getAttribute('type');
|
||||
$format = $attribute->getAttribute('format');
|
||||
|
||||
@@ -158,6 +145,18 @@ class Delete extends Action
|
||||
->setPayload($response->output($attribute, $model))
|
||||
->setContext($this->getCollectionsEventsContext(), $collection);
|
||||
|
||||
$publisherForDatabase->enqueue(new DatabaseMessage(
|
||||
project: $queueForEvents->getProject(),
|
||||
user: $queueForEvents->getUser(),
|
||||
type: DATABASE_TYPE_DELETE_ATTRIBUTE,
|
||||
database: $db,
|
||||
collection: $this->isCollectionsAPI() ? null : $collection,
|
||||
document: $this->isCollectionsAPI() ? null : $attribute,
|
||||
table: $this->isCollectionsAPI() ? $collection : null,
|
||||
row: $this->isCollectionsAPI() ? $attribute : null,
|
||||
events: Event::generateEvents($queueForEvents->getEvent(), $queueForEvents->getParams()),
|
||||
));
|
||||
|
||||
$response->noContent();
|
||||
}
|
||||
}
|
||||
|
||||
+4
-4
@@ -2,8 +2,8 @@
|
||||
|
||||
namespace Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Attributes\Email;
|
||||
|
||||
use Appwrite\Event\Database as EventDatabase;
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Event\Publisher\Database as DatabasePublisher;
|
||||
use Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Attributes\Action;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\Deprecated;
|
||||
@@ -69,13 +69,13 @@ class Create extends Action
|
||||
->param('array', false, new Boolean(), 'Is attribute an array?', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('queueForDatabase')
|
||||
->inject('publisherForDatabase')
|
||||
->inject('queueForEvents')
|
||||
->inject('authorization')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(string $databaseId, string $collectionId, string $key, ?bool $required, ?string $default, bool $array, UtopiaResponse $response, Database $dbForProject, EventDatabase $queueForDatabase, Event $queueForEvents, Authorization $authorization): void
|
||||
public function action(string $databaseId, string $collectionId, string $key, ?bool $required, ?string $default, bool $array, UtopiaResponse $response, Database $dbForProject, DatabasePublisher $publisherForDatabase, Event $queueForEvents, Authorization $authorization): void
|
||||
{
|
||||
$attribute = $this->createAttribute(
|
||||
$databaseId,
|
||||
@@ -91,7 +91,7 @@ class Create extends Action
|
||||
]),
|
||||
$response,
|
||||
$dbForProject,
|
||||
$queueForDatabase,
|
||||
$publisherForDatabase,
|
||||
$queueForEvents,
|
||||
$authorization
|
||||
);
|
||||
|
||||
+4
-4
@@ -2,8 +2,8 @@
|
||||
|
||||
namespace Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Attributes\Enum;
|
||||
|
||||
use Appwrite\Event\Database as EventDatabase;
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Event\Publisher\Database as DatabasePublisher;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Attributes\Action;
|
||||
use Appwrite\SDK\AuthType;
|
||||
@@ -72,13 +72,13 @@ class Create extends Action
|
||||
->param('array', false, new Boolean(), 'Is attribute an array?', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('queueForDatabase')
|
||||
->inject('publisherForDatabase')
|
||||
->inject('queueForEvents')
|
||||
->inject('authorization')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(string $databaseId, string $collectionId, string $key, array $elements, ?bool $required, ?string $default, bool $array, UtopiaResponse $response, Database $dbForProject, EventDatabase $queueForDatabase, Event $queueForEvents, Authorization $authorization): void
|
||||
public function action(string $databaseId, string $collectionId, string $key, array $elements, ?bool $required, ?string $default, bool $array, UtopiaResponse $response, Database $dbForProject, DatabasePublisher $publisherForDatabase, Event $queueForEvents, Authorization $authorization): void
|
||||
{
|
||||
if (!is_null($default) && !\in_array($default, $elements, true)) {
|
||||
throw new Exception($this->getInvalidValueException(), 'Default value not found in elements');
|
||||
@@ -99,7 +99,7 @@ class Create extends Action
|
||||
]),
|
||||
$response,
|
||||
$dbForProject,
|
||||
$queueForDatabase,
|
||||
$publisherForDatabase,
|
||||
$queueForEvents,
|
||||
$authorization
|
||||
);
|
||||
|
||||
+4
-4
@@ -2,8 +2,8 @@
|
||||
|
||||
namespace Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Attributes\Float;
|
||||
|
||||
use Appwrite\Event\Database as EventDatabase;
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Event\Publisher\Database as DatabasePublisher;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Attributes\Action;
|
||||
use Appwrite\SDK\AuthType;
|
||||
@@ -73,13 +73,13 @@ class Create extends Action
|
||||
->param('array', false, new Boolean(), 'Is attribute an array?', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('queueForDatabase')
|
||||
->inject('publisherForDatabase')
|
||||
->inject('queueForEvents')
|
||||
->inject('authorization')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(string $databaseId, string $collectionId, string $key, ?bool $required, ?float $min, ?float $max, ?float $default, bool $array, UtopiaResponse $response, Database $dbForProject, EventDatabase $queueForDatabase, Event $queueForEvents, Authorization $authorization): void
|
||||
public function action(string $databaseId, string $collectionId, string $key, ?bool $required, ?float $min, ?float $max, ?float $default, bool $array, UtopiaResponse $response, Database $dbForProject, DatabasePublisher $publisherForDatabase, Event $queueForEvents, Authorization $authorization): void
|
||||
{
|
||||
$min ??= -PHP_FLOAT_MAX;
|
||||
$max ??= PHP_FLOAT_MAX;
|
||||
@@ -102,7 +102,7 @@ class Create extends Action
|
||||
'array' => $array,
|
||||
'format' => APP_DATABASE_ATTRIBUTE_FLOAT_RANGE,
|
||||
'formatOptions' => ['min' => $min, 'max' => $max],
|
||||
]), $response, $dbForProject, $queueForDatabase, $queueForEvents, $authorization);
|
||||
]), $response, $dbForProject, $publisherForDatabase, $queueForEvents, $authorization);
|
||||
|
||||
$formatOptions = $attribute->getAttribute('formatOptions', []);
|
||||
if (!empty($formatOptions)) {
|
||||
|
||||
+4
-4
@@ -2,8 +2,8 @@
|
||||
|
||||
namespace Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Attributes\IP;
|
||||
|
||||
use Appwrite\Event\Database as EventDatabase;
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Event\Publisher\Database as DatabasePublisher;
|
||||
use Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Attributes\Action;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\Deprecated;
|
||||
@@ -69,13 +69,13 @@ class Create extends Action
|
||||
->param('array', false, new Boolean(), 'Is attribute an array?', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('queueForDatabase')
|
||||
->inject('publisherForDatabase')
|
||||
->inject('queueForEvents')
|
||||
->inject('authorization')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(string $databaseId, string $collectionId, string $key, ?bool $required, ?string $default, bool $array, UtopiaResponse $response, Database $dbForProject, EventDatabase $queueForDatabase, Event $queueForEvents, Authorization $authorization): void
|
||||
public function action(string $databaseId, string $collectionId, string $key, ?bool $required, ?string $default, bool $array, UtopiaResponse $response, Database $dbForProject, DatabasePublisher $publisherForDatabase, Event $queueForEvents, Authorization $authorization): void
|
||||
{
|
||||
$attribute = $this->createAttribute(
|
||||
$databaseId,
|
||||
@@ -91,7 +91,7 @@ class Create extends Action
|
||||
]),
|
||||
$response,
|
||||
$dbForProject,
|
||||
$queueForDatabase,
|
||||
$publisherForDatabase,
|
||||
$queueForEvents,
|
||||
$authorization
|
||||
);
|
||||
|
||||
+4
-4
@@ -2,8 +2,8 @@
|
||||
|
||||
namespace Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Attributes\Integer;
|
||||
|
||||
use Appwrite\Event\Database as EventDatabase;
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Event\Publisher\Database as DatabasePublisher;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Attributes\Action;
|
||||
use Appwrite\SDK\AuthType;
|
||||
@@ -73,13 +73,13 @@ class Create extends Action
|
||||
->param('array', false, new Boolean(), 'Is attribute an array?', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('queueForDatabase')
|
||||
->inject('publisherForDatabase')
|
||||
->inject('queueForEvents')
|
||||
->inject('authorization')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(string $databaseId, string $collectionId, string $key, ?bool $required, ?int $min, ?int $max, ?int $default, bool $array, UtopiaResponse $response, Database $dbForProject, EventDatabase $queueForDatabase, Event $queueForEvents, Authorization $authorization): void
|
||||
public function action(string $databaseId, string $collectionId, string $key, ?bool $required, ?int $min, ?int $max, ?int $default, bool $array, UtopiaResponse $response, Database $dbForProject, DatabasePublisher $publisherForDatabase, Event $queueForEvents, Authorization $authorization): void
|
||||
{
|
||||
$min ??= \PHP_INT_MIN;
|
||||
$max ??= \PHP_INT_MAX;
|
||||
@@ -104,7 +104,7 @@ class Create extends Action
|
||||
'array' => $array,
|
||||
'format' => APP_DATABASE_ATTRIBUTE_INT_RANGE,
|
||||
'formatOptions' => ['min' => $min, 'max' => $max],
|
||||
]), $response, $dbForProject, $queueForDatabase, $queueForEvents, $authorization);
|
||||
]), $response, $dbForProject, $publisherForDatabase, $queueForEvents, $authorization);
|
||||
|
||||
$formatOptions = $attribute->getAttribute('formatOptions', []);
|
||||
if (!empty($formatOptions)) {
|
||||
|
||||
+4
-4
@@ -2,8 +2,8 @@
|
||||
|
||||
namespace Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Attributes\Line;
|
||||
|
||||
use Appwrite\Event\Database as EventDatabase;
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Event\Publisher\Database as DatabasePublisher;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Attributes\Action;
|
||||
use Appwrite\SDK\AuthType;
|
||||
@@ -69,13 +69,13 @@ class Create extends Action
|
||||
->param('default', null, new Nullable(new Spatial(Database::VAR_LINESTRING)), 'Default value for attribute when not provided, two-dimensional array of coordinate pairs, [[longitude, latitude], [longitude, latitude], …], listing the vertices of the line in order. Cannot be set when attribute is required.', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('queueForDatabase')
|
||||
->inject('publisherForDatabase')
|
||||
->inject('queueForEvents')
|
||||
->inject('authorization')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(string $databaseId, string $collectionId, string $key, ?bool $required, ?array $default, UtopiaResponse $response, Database $dbForProject, EventDatabase $queueForDatabase, Event $queueForEvents, Authorization $authorization): void
|
||||
public function action(string $databaseId, string $collectionId, string $key, ?bool $required, ?array $default, UtopiaResponse $response, Database $dbForProject, DatabasePublisher $publisherForDatabase, Event $queueForEvents, Authorization $authorization): void
|
||||
{
|
||||
if (!$dbForProject->getAdapter()->getSupportForSpatialAttributes()) {
|
||||
throw new Exception(Exception::GENERAL_FEATURE_UNSUPPORTED, 'Spatial columns are not supported by this database.');
|
||||
@@ -86,7 +86,7 @@ class Create extends Action
|
||||
'type' => Database::VAR_LINESTRING,
|
||||
'required' => $required,
|
||||
'default' => $default
|
||||
]), $response, $dbForProject, $queueForDatabase, $queueForEvents, $authorization);
|
||||
]), $response, $dbForProject, $publisherForDatabase, $queueForEvents, $authorization);
|
||||
|
||||
$response
|
||||
->setStatusCode(SwooleResponse::STATUS_CODE_ACCEPTED)
|
||||
|
||||
+4
-4
@@ -2,8 +2,8 @@
|
||||
|
||||
namespace Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Attributes\Longtext;
|
||||
|
||||
use Appwrite\Event\Database as EventDatabase;
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Event\Publisher\Database as DatabasePublisher;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Attributes\Action;
|
||||
use Appwrite\SDK\AuthType;
|
||||
@@ -67,7 +67,7 @@ class Create extends Action
|
||||
->param('encrypt', false, new Boolean(), 'Toggle encryption for the attribute. Encryption enhances security by not storing any plain text values in the database. However, encrypted attributes cannot be queried.', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('queueForDatabase')
|
||||
->inject('publisherForDatabase')
|
||||
->inject('queueForEvents')
|
||||
->inject('plan')
|
||||
->inject('authorization')
|
||||
@@ -84,7 +84,7 @@ class Create extends Action
|
||||
bool $encrypt,
|
||||
UtopiaResponse $response,
|
||||
Database $dbForProject,
|
||||
EventDatabase $queueForDatabase,
|
||||
DatabasePublisher $publisherForDatabase,
|
||||
Event $queueForEvents,
|
||||
array $plan,
|
||||
Authorization $authorization
|
||||
@@ -112,7 +112,7 @@ class Create extends Action
|
||||
]),
|
||||
$response,
|
||||
$dbForProject,
|
||||
$queueForDatabase,
|
||||
$publisherForDatabase,
|
||||
$queueForEvents,
|
||||
$authorization
|
||||
);
|
||||
|
||||
+4
-4
@@ -2,8 +2,8 @@
|
||||
|
||||
namespace Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Attributes\Mediumtext;
|
||||
|
||||
use Appwrite\Event\Database as EventDatabase;
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Event\Publisher\Database as DatabasePublisher;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Attributes\Action;
|
||||
use Appwrite\SDK\AuthType;
|
||||
@@ -67,7 +67,7 @@ class Create extends Action
|
||||
->param('encrypt', false, new Boolean(), 'Toggle encryption for the attribute. Encryption enhances security by not storing any plain text values in the database. However, encrypted attributes cannot be queried.', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('queueForDatabase')
|
||||
->inject('publisherForDatabase')
|
||||
->inject('queueForEvents')
|
||||
->inject('plan')
|
||||
->inject('authorization')
|
||||
@@ -84,7 +84,7 @@ class Create extends Action
|
||||
bool $encrypt,
|
||||
UtopiaResponse $response,
|
||||
Database $dbForProject,
|
||||
EventDatabase $queueForDatabase,
|
||||
DatabasePublisher $publisherForDatabase,
|
||||
Event $queueForEvents,
|
||||
array $plan,
|
||||
Authorization $authorization
|
||||
@@ -112,7 +112,7 @@ class Create extends Action
|
||||
]),
|
||||
$response,
|
||||
$dbForProject,
|
||||
$queueForDatabase,
|
||||
$publisherForDatabase,
|
||||
$queueForEvents,
|
||||
$authorization
|
||||
);
|
||||
|
||||
+4
-4
@@ -2,8 +2,8 @@
|
||||
|
||||
namespace Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Attributes\Point;
|
||||
|
||||
use Appwrite\Event\Database as EventDatabase;
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Event\Publisher\Database as DatabasePublisher;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Attributes\Action;
|
||||
use Appwrite\SDK\AuthType;
|
||||
@@ -69,13 +69,13 @@ class Create extends Action
|
||||
->param('default', null, new Nullable(new Spatial(Database::VAR_POINT)), 'Default value for attribute when not provided, array of two numbers [longitude, latitude], representing a single coordinate. Cannot be set when attribute is required.', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('queueForDatabase')
|
||||
->inject('publisherForDatabase')
|
||||
->inject('queueForEvents')
|
||||
->inject('authorization')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(string $databaseId, string $collectionId, string $key, ?bool $required, ?array $default, UtopiaResponse $response, Database $dbForProject, EventDatabase $queueForDatabase, Event $queueForEvents, Authorization $authorization): void
|
||||
public function action(string $databaseId, string $collectionId, string $key, ?bool $required, ?array $default, UtopiaResponse $response, Database $dbForProject, DatabasePublisher $publisherForDatabase, Event $queueForEvents, Authorization $authorization): void
|
||||
{
|
||||
if (!$dbForProject->getAdapter()->getSupportForSpatialAttributes()) {
|
||||
throw new Exception(Exception::GENERAL_FEATURE_UNSUPPORTED, 'Spatial columns are not supported by this database.');
|
||||
@@ -86,7 +86,7 @@ class Create extends Action
|
||||
'type' => Database::VAR_POINT,
|
||||
'required' => $required,
|
||||
'default' => $default,
|
||||
]), $response, $dbForProject, $queueForDatabase, $queueForEvents, $authorization);
|
||||
]), $response, $dbForProject, $publisherForDatabase, $queueForEvents, $authorization);
|
||||
|
||||
$response
|
||||
->setStatusCode(SwooleResponse::STATUS_CODE_ACCEPTED)
|
||||
|
||||
+4
-4
@@ -2,8 +2,8 @@
|
||||
|
||||
namespace Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Attributes\Polygon;
|
||||
|
||||
use Appwrite\Event\Database as EventDatabase;
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Event\Publisher\Database as DatabasePublisher;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Attributes\Action;
|
||||
use Appwrite\SDK\AuthType;
|
||||
@@ -69,13 +69,13 @@ class Create extends Action
|
||||
->param('default', null, new Nullable(new Spatial(Database::VAR_POLYGON)), 'Default value for attribute when not provided, three-dimensional array where the outer array holds one or more linear rings, [[[longitude, latitude], …], …], the first ring is the exterior boundary, any additional rings are interior holes, and each ring must start and end with the same coordinate pair. Cannot be set when attribute is required.', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('queueForDatabase')
|
||||
->inject('publisherForDatabase')
|
||||
->inject('queueForEvents')
|
||||
->inject('authorization')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(string $databaseId, string $collectionId, string $key, ?bool $required, ?array $default, UtopiaResponse $response, Database $dbForProject, EventDatabase $queueForDatabase, Event $queueForEvents, Authorization $authorization): void
|
||||
public function action(string $databaseId, string $collectionId, string $key, ?bool $required, ?array $default, UtopiaResponse $response, Database $dbForProject, DatabasePublisher $publisherForDatabase, Event $queueForEvents, Authorization $authorization): void
|
||||
{
|
||||
if (!$dbForProject->getAdapter()->getSupportForSpatialAttributes()) {
|
||||
throw new Exception(Exception::GENERAL_FEATURE_UNSUPPORTED, 'Spatial columns are not supported by this database.');
|
||||
@@ -86,7 +86,7 @@ class Create extends Action
|
||||
'type' => Database::VAR_POLYGON,
|
||||
'required' => $required,
|
||||
'default' => $default,
|
||||
]), $response, $dbForProject, $queueForDatabase, $queueForEvents, $authorization);
|
||||
]), $response, $dbForProject, $publisherForDatabase, $queueForEvents, $authorization);
|
||||
|
||||
$response
|
||||
->setStatusCode(SwooleResponse::STATUS_CODE_ACCEPTED)
|
||||
|
||||
+7
-6
@@ -2,8 +2,8 @@
|
||||
|
||||
namespace Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Attributes\Relationship;
|
||||
|
||||
use Appwrite\Event\Database as EventDatabase;
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Event\Publisher\Database as DatabasePublisher;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Attributes\Action;
|
||||
use Appwrite\SDK\AuthType;
|
||||
@@ -17,6 +17,7 @@ use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Database\Validator\Key;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Http\Adapter\Swoole\Response as SwooleResponse;
|
||||
use Utopia\Platform\Enum;
|
||||
use Utopia\Validator\Boolean;
|
||||
use Utopia\Validator\Nullable;
|
||||
use Utopia\Validator\WhiteList;
|
||||
@@ -70,7 +71,7 @@ class Create extends Action
|
||||
Database::RELATION_MANY_TO_ONE,
|
||||
Database::RELATION_MANY_TO_MANY,
|
||||
Database::RELATION_ONE_TO_MANY
|
||||
], true), 'Relation type')
|
||||
], true), 'Relation type', enum: new Enum(name: 'RelationshipType'))
|
||||
->param('twoWay', false, new Boolean(), 'Is Two Way?', true)
|
||||
->param('key', null, fn (Database $dbForProject) => new Nullable(new Key(false, $dbForProject->getAdapter()->getMaxUIDLength())), 'Attribute Key.', true, ['dbForProject'])
|
||||
->param('twoWayKey', null, fn (Database $dbForProject) => new Nullable(new Key(false, $dbForProject->getAdapter()->getMaxUIDLength())), 'Two Way Attribute Key.', true, ['dbForProject'])
|
||||
@@ -78,16 +79,16 @@ class Create extends Action
|
||||
Database::RELATION_MUTATE_CASCADE,
|
||||
Database::RELATION_MUTATE_RESTRICT,
|
||||
Database::RELATION_MUTATE_SET_NULL
|
||||
], true), 'Constraints option', true)
|
||||
], true), 'Constraints option', true, enum: new Enum(name: 'RelationMutate'))
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('queueForDatabase')
|
||||
->inject('publisherForDatabase')
|
||||
->inject('queueForEvents')
|
||||
->inject('authorization')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(string $databaseId, string $collectionId, string $relatedCollectionId, string $type, bool $twoWay, ?string $key, ?string $twoWayKey, string $onDelete, UtopiaResponse $response, Database $dbForProject, EventDatabase $queueForDatabase, Event $queueForEvents, Authorization $authorization): void
|
||||
public function action(string $databaseId, string $collectionId, string $relatedCollectionId, string $type, bool $twoWay, ?string $key, ?string $twoWayKey, string $onDelete, UtopiaResponse $response, Database $dbForProject, DatabasePublisher $publisherForDatabase, Event $queueForEvents, Authorization $authorization): void
|
||||
{
|
||||
if (!$dbForProject->getAdapter()->getSupportForRelationships()) {
|
||||
throw new Exception(Exception::GENERAL_FEATURE_UNSUPPORTED, 'Relationships are not supported by this database.');
|
||||
@@ -159,7 +160,7 @@ class Create extends Action
|
||||
'twoWayKey' => $twoWayKey,
|
||||
'onDelete' => $onDelete,
|
||||
]
|
||||
]), $response, $dbForProject, $queueForDatabase, $queueForEvents, $authorization);
|
||||
]), $response, $dbForProject, $publisherForDatabase, $queueForEvents, $authorization);
|
||||
|
||||
foreach ($attribute->getAttribute('options', []) as $k => $option) {
|
||||
$attribute->setAttribute($k, $option);
|
||||
|
||||
+2
-1
@@ -16,6 +16,7 @@ use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Database\Validator\Key;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Http\Adapter\Swoole\Response as SwooleResponse;
|
||||
use Utopia\Platform\Enum;
|
||||
use Utopia\Validator\Nullable;
|
||||
use Utopia\Validator\WhiteList;
|
||||
|
||||
@@ -69,7 +70,7 @@ class Update extends Action
|
||||
Database::RELATION_MUTATE_CASCADE,
|
||||
Database::RELATION_MUTATE_RESTRICT,
|
||||
Database::RELATION_MUTATE_SET_NULL
|
||||
], true), 'Constraints option', true)
|
||||
], true), 'Constraints option', true, enum: new Enum(name: 'RelationMutate'))
|
||||
->param('newKey', null, fn (Database $dbForProject) => new Nullable(new Key(false, $dbForProject->getAdapter()->getMaxUIDLength())), 'New Attribute Key.', true, ['dbForProject'])
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
|
||||
+4
-4
@@ -2,8 +2,8 @@
|
||||
|
||||
namespace Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Attributes\String;
|
||||
|
||||
use Appwrite\Event\Database as EventDatabase;
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Event\Publisher\Database as DatabasePublisher;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Attributes\Action;
|
||||
use Appwrite\SDK\AuthType;
|
||||
@@ -75,7 +75,7 @@ class Create extends Action
|
||||
->param('encrypt', false, new Boolean(), 'Toggle encryption for the attribute. Encryption enhances security by not storing any plain text values in the database. However, encrypted attributes cannot be queried.', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('queueForDatabase')
|
||||
->inject('publisherForDatabase')
|
||||
->inject('queueForEvents')
|
||||
->inject('plan')
|
||||
->inject('authorization')
|
||||
@@ -93,7 +93,7 @@ class Create extends Action
|
||||
bool $encrypt,
|
||||
UtopiaResponse $response,
|
||||
Database $dbForProject,
|
||||
EventDatabase $queueForDatabase,
|
||||
DatabasePublisher $publisherForDatabase,
|
||||
Event $queueForEvents,
|
||||
array $plan,
|
||||
Authorization $authorization
|
||||
@@ -134,7 +134,7 @@ class Create extends Action
|
||||
]),
|
||||
$response,
|
||||
$dbForProject,
|
||||
$queueForDatabase,
|
||||
$publisherForDatabase,
|
||||
$queueForEvents,
|
||||
$authorization
|
||||
);
|
||||
|
||||
+4
-4
@@ -2,8 +2,8 @@
|
||||
|
||||
namespace Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Attributes\Text;
|
||||
|
||||
use Appwrite\Event\Database as EventDatabase;
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Event\Publisher\Database as DatabasePublisher;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Attributes\Action;
|
||||
use Appwrite\SDK\AuthType;
|
||||
@@ -67,7 +67,7 @@ class Create extends Action
|
||||
->param('encrypt', false, new Boolean(), 'Toggle encryption for the attribute. Encryption enhances security by not storing any plain text values in the database. However, encrypted attributes cannot be queried.', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('queueForDatabase')
|
||||
->inject('publisherForDatabase')
|
||||
->inject('queueForEvents')
|
||||
->inject('plan')
|
||||
->inject('authorization')
|
||||
@@ -84,7 +84,7 @@ class Create extends Action
|
||||
bool $encrypt,
|
||||
UtopiaResponse $response,
|
||||
Database $dbForProject,
|
||||
EventDatabase $queueForDatabase,
|
||||
DatabasePublisher $publisherForDatabase,
|
||||
Event $queueForEvents,
|
||||
array $plan,
|
||||
Authorization $authorization
|
||||
@@ -112,7 +112,7 @@ class Create extends Action
|
||||
]),
|
||||
$response,
|
||||
$dbForProject,
|
||||
$queueForDatabase,
|
||||
$publisherForDatabase,
|
||||
$queueForEvents,
|
||||
$authorization
|
||||
);
|
||||
|
||||
+4
-4
@@ -2,8 +2,8 @@
|
||||
|
||||
namespace Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Attributes\URL;
|
||||
|
||||
use Appwrite\Event\Database as EventDatabase;
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Event\Publisher\Database as DatabasePublisher;
|
||||
use Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Attributes\Action;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\Deprecated;
|
||||
@@ -69,7 +69,7 @@ class Create extends Action
|
||||
->param('array', false, new Boolean(), 'Is attribute an array?', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('queueForDatabase')
|
||||
->inject('publisherForDatabase')
|
||||
->inject('queueForEvents')
|
||||
->inject('authorization')
|
||||
->callback($this->action(...));
|
||||
@@ -84,7 +84,7 @@ class Create extends Action
|
||||
bool $array,
|
||||
UtopiaResponse $response,
|
||||
Database $dbForProject,
|
||||
EventDatabase $queueForDatabase,
|
||||
DatabasePublisher $publisherForDatabase,
|
||||
Event $queueForEvents,
|
||||
Authorization $authorization
|
||||
): void {
|
||||
@@ -96,7 +96,7 @@ class Create extends Action
|
||||
'default' => $default,
|
||||
'array' => $array,
|
||||
'format' => APP_DATABASE_ATTRIBUTE_URL,
|
||||
]), $response, $dbForProject, $queueForDatabase, $queueForEvents, $authorization);
|
||||
]), $response, $dbForProject, $publisherForDatabase, $queueForEvents, $authorization);
|
||||
|
||||
$response
|
||||
->setStatusCode(SwooleResponse::STATUS_CODE_ACCEPTED)
|
||||
|
||||
+4
-4
@@ -2,8 +2,8 @@
|
||||
|
||||
namespace Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Attributes\Varchar;
|
||||
|
||||
use Appwrite\Event\Database as EventDatabase;
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Event\Publisher\Database as DatabasePublisher;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Attributes\Action;
|
||||
use Appwrite\SDK\AuthType;
|
||||
@@ -70,7 +70,7 @@ class Create extends Action
|
||||
->param('encrypt', false, new Boolean(), 'Toggle encryption for the attribute. Encryption enhances security by not storing any plain text values in the database. However, encrypted attributes cannot be queried.', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('queueForDatabase')
|
||||
->inject('publisherForDatabase')
|
||||
->inject('queueForEvents')
|
||||
->inject('plan')
|
||||
->inject('authorization')
|
||||
@@ -88,7 +88,7 @@ class Create extends Action
|
||||
bool $encrypt,
|
||||
UtopiaResponse $response,
|
||||
Database $dbForProject,
|
||||
EventDatabase $queueForDatabase,
|
||||
DatabasePublisher $publisherForDatabase,
|
||||
Event $queueForEvents,
|
||||
array $plan,
|
||||
Authorization $authorization
|
||||
@@ -129,7 +129,7 @@ class Create extends Action
|
||||
]),
|
||||
$response,
|
||||
$dbForProject,
|
||||
$queueForDatabase,
|
||||
$publisherForDatabase,
|
||||
$queueForEvents,
|
||||
$authorization
|
||||
);
|
||||
|
||||
@@ -2,8 +2,9 @@
|
||||
|
||||
namespace Appwrite\Platform\Modules\Databases\Http\Databases\Collections;
|
||||
|
||||
use Appwrite\Event\Database as EventDatabase;
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Event\Message\Database as DatabaseMessage;
|
||||
use Appwrite\Event\Publisher\Database as DatabasePublisher;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\ContentType;
|
||||
@@ -63,13 +64,13 @@ class Delete extends Action
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('getDatabasesDB')
|
||||
->inject('queueForDatabase')
|
||||
->inject('publisherForDatabase')
|
||||
->inject('queueForEvents')
|
||||
->inject('authorization')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(string $databaseId, string $collectionId, UtopiaResponse $response, Database $dbForProject, callable $getDatabasesDB, EventDatabase $queueForDatabase, Event $queueForEvents, Authorization $authorization): void
|
||||
public function action(string $databaseId, string $collectionId, UtopiaResponse $response, Database $dbForProject, callable $getDatabasesDB, DatabasePublisher $publisherForDatabase, Event $queueForEvents, Authorization $authorization): void
|
||||
{
|
||||
$database = $authorization->skip(fn () => $dbForProject->getDocument('databases', $databaseId));
|
||||
if ($database->isEmpty()) {
|
||||
@@ -89,22 +90,22 @@ class Delete extends Action
|
||||
$dbForDatabases = $getDatabasesDB($database);
|
||||
$dbForDatabases->purgeCachedCollection('database_' . $database->getSequence() . '_collection_' . $collection->getSequence());
|
||||
|
||||
$queueForDatabase
|
||||
->setType(DATABASE_TYPE_DELETE_COLLECTION)
|
||||
->setDatabase($database);
|
||||
|
||||
if ($this->isCollectionsAPI()) {
|
||||
$queueForDatabase->setCollection($collection);
|
||||
} else {
|
||||
$queueForDatabase->setTable($collection);
|
||||
}
|
||||
|
||||
$queueForEvents
|
||||
->setParam('databaseId', $databaseId)
|
||||
->setContext('database', $database)
|
||||
->setParam($this->getEventsParamKey(), $collection->getId())
|
||||
->setPayload($response->output($collection, $this->getResponseModel()));
|
||||
|
||||
$publisherForDatabase->enqueue(new DatabaseMessage(
|
||||
project: $queueForEvents->getProject(),
|
||||
user: $queueForEvents->getUser(),
|
||||
type: DATABASE_TYPE_DELETE_COLLECTION,
|
||||
database: $database,
|
||||
collection: $this->isCollectionsAPI() ? $collection : null,
|
||||
table: $this->isCollectionsAPI() ? null : $collection,
|
||||
events: Event::generateEvents($queueForEvents->getEvent(), $queueForEvents->getParams()),
|
||||
));
|
||||
|
||||
$response->noContent();
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -93,7 +93,7 @@ class Decrement extends Action
|
||||
|
||||
public function action(string $databaseId, string $collectionId, string $documentId, string $attribute, int|float $value, int|float|null $min, ?string $transactionId, UtopiaResponse $response, Database $dbForProject, callable $getDatabasesDB, Event $queueForEvents, Context $usage, array $plan, Authorization $authorization, User $user): void
|
||||
{
|
||||
$isAPIKey = $user->isApp($authorization->getRoles());
|
||||
$isAPIKey = $user->isKey($authorization->getRoles());
|
||||
$isPrivilegedUser = $user->isPrivileged($authorization->getRoles());
|
||||
|
||||
$database = $authorization->skip(fn () => $dbForProject->getDocument('databases', $databaseId));
|
||||
|
||||
+1
-1
@@ -93,7 +93,7 @@ class Increment extends Action
|
||||
|
||||
public function action(string $databaseId, string $collectionId, string $documentId, string $attribute, int|float $value, int|float|null $max, ?string $transactionId, UtopiaResponse $response, Database $dbForProject, callable $getDatabasesDB, Event $queueForEvents, Context $usage, array $plan, Authorization $authorization, User $user): void
|
||||
{
|
||||
$isAPIKey = $user->isApp($authorization->getRoles());
|
||||
$isAPIKey = $user->isKey($authorization->getRoles());
|
||||
$isPrivilegedUser = $user->isPrivileged($authorization->getRoles());
|
||||
|
||||
$database = $authorization->skip(fn () => $dbForProject->getDocument('databases', $databaseId));
|
||||
|
||||
+1
-1
@@ -201,7 +201,7 @@ class Create extends Action
|
||||
$documents = [$data];
|
||||
}
|
||||
|
||||
$isAPIKey = $user->isApp($authorization->getRoles());
|
||||
$isAPIKey = $user->isKey($authorization->getRoles());
|
||||
$isPrivilegedUser = $user->isPrivileged($authorization->getRoles());
|
||||
|
||||
if ($isBulk && !$isAPIKey && !$isPrivilegedUser) {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user